Security fixes: CSV injection, policy body output, invite hashing, slot datetimes
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 1m11s
CI / PHPStan (pull_request) Successful in 1m20s
CI / Build Plugin Zip (pull_request) Has been skipped
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 49s
CI / Tests (PHP 8.2) (pull_request) Successful in 59s
CI / Coding Standards (pull_request) Successful in 1m11s
CI / PHPStan (pull_request) Successful in 1m20s
CI / Build Plugin Zip (pull_request) Has been skipped
Four fixes from a security review pass: - Neutralise CSV formula injection in the payments export: fields with a leading =, +, -, @, tab, or CR (e.g. a hostile student display name) are apostrophe-prefixed in PaymentReport::csvLine() so they open as text in Excel/Google Sheets. Fixes #39. - Sanitise policy bodies with wp_kses_post at output in PolicyEndpoint::index() (the booking JS renders that HTML raw), so a future write path that forgets kses can never become stored XSS. Fixes #40. - Store invite tokens hashed (SHA-256) at rest: a database leak can no longer redeem pending invites. The registration link is shown once, at creation; the pending list shows email/invited date; lookups hash the submitted token. Existing plaintext pending invites must be re-issued. Fixes #41. - Validate availability slot datetimes on both creation paths (REST and admin form) via AvailabilitySlot::normalizeDateTime(): canonical and datetime-local forms normalise to Y-m-d H:i:s, garbage and end <= start are rejected (REST 400) instead of reaching the DATETIME column or throwing inside the weekly-series date arithmetic. Fixes #42. composer test (204 tests, 594 assertions), PHPStan L6, and PHPCS all green. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
@@ -17,8 +17,9 @@ class RegistrationController {
|
||||
wp_die( esc_html__( 'You do not have permission to manage invites.', 'unsupervised-schedular' ) );
|
||||
}
|
||||
|
||||
$newInviteUrl = '';
|
||||
if ( isset( $_POST['usc_action'] ) && check_admin_referer( 'usc_invite_action' ) ) {
|
||||
$this->handleFormAction();
|
||||
$newInviteUrl = $this->handleFormAction();
|
||||
}
|
||||
|
||||
$pendingInvites = $this->invites->findPending();
|
||||
@@ -28,7 +29,12 @@ class RegistrationController {
|
||||
include USC_PLUGIN_DIR . 'templates/admin/invites.php';
|
||||
}
|
||||
|
||||
private function handleFormAction(): void {
|
||||
/**
|
||||
* Handle a posted admin action. Returns the registration link for a freshly
|
||||
* created invite — the only time it can be shown, since just the token's hash
|
||||
* is stored — or an empty string for every other action.
|
||||
*/
|
||||
private function handleFormAction(): string {
|
||||
// Nonce is verified by the caller (renderPage) before this method runs.
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Missing
|
||||
$action = sanitize_key( wp_unslash( $_POST['usc_action'] ?? '' ) );
|
||||
@@ -45,13 +51,17 @@ class RegistrationController {
|
||||
&& false === email_exists( $email )
|
||||
&& null === $this->invites->findPendingByEmail( $email )
|
||||
) {
|
||||
$rawToken = wp_generate_password( 32, false );
|
||||
|
||||
$this->invites->insert(
|
||||
new Invite(
|
||||
email: $email,
|
||||
token: wp_generate_password( 32, false ),
|
||||
token: Invite::hashToken( $rawToken ),
|
||||
invitedBy: get_current_user_id(),
|
||||
)
|
||||
);
|
||||
|
||||
return $this->registrationLink( $rawToken );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,5 +72,17 @@ class RegistrationController {
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the registration URL for a raw invite token.
|
||||
*/
|
||||
private function registrationLink( string $rawToken ): string {
|
||||
$pageId = (int) get_option( self::OPTION_PAGE, 0 );
|
||||
$linkBase = $pageId > 0 ? (string) get_permalink( $pageId ) : '';
|
||||
|
||||
return add_query_arg( 'us_invite', rawurlencode( $rawToken ), '' !== $linkBase ? $linkBase : home_url( '/' ) );
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user