| @@ -39,8 +39,31 @@ | ||
| 39 | 39 | */ |
| 40 | 40 | public const USER_HANDLE_MISMATCH_KEY = 'user_handle_match_failed'; |
| 41 | 41 | |
| 42 | 42 | /** |
| 43 | + * User ID that findOneByCredentialId() lookups are currently scoped to. | |
| 44 | + * Must be set only via set_expected_user_id() from trusted server-side | |
| 45 | + * context (e.g. the WP_User passed down the 2FA login chain) — never | |
| 46 | + * derived from request input inside this class. | |
| 47 | + * | |
| 48 | + * @var int|null | |
| 49 | + */ | |
| 50 | + private $expected_user_id = null; | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * Restrict findOneByCredentialId() to a specific user's credential set. | |
| 54 | + * Call this before delegating to Webauthn\Server so the library's internal | |
| 55 | + * repository lookups stay scoped to the expected 2FA target user. | |
| 56 | + * | |
| 57 | + * @param int|null $user_id Trusted user ID, or null to clear the scope. | |
| 58 | + * | |
| 59 | + * @return void | |
| 60 | + */ | |
| 61 | + public function set_expected_user_id( ?int $user_id ): void { | |
| 62 | + $this->expected_user_id = $user_id; | |
| 63 | + } | |
| 64 | + | |
| 65 | + /** | |
| 43 | 66 | * Get user credentials. |
| 44 | 67 | * |
| 45 | 68 | * @param int $user_id The user ID. |
| 46 | 69 | * |
| @@ -63,8 +86,12 @@ | ||
| 63 | 86 | } |
| 64 | 87 | |
| 65 | 88 | /** |
| 66 | 89 | * Get one credential by credential ID. |
| 90 | + * {@inheritDoc} | |
| 91 | + * Interface-mandated single-parameter signature. Scoping to a specific | |
| 92 | + * user is done via $this->expected_user_id, set beforehand through | |
| 93 | + * set_expected_user_id() by trusted calling code — never via $_POST. | |
| 67 | 94 | * |
| 68 | 95 | * @param string $public_key_credential_id The public key credential ID. |
| 69 | 96 | * |
| 70 | 97 | * @return PublicKeyCredentialSource|null |
| @@ -69,23 +96,18 @@ | ||
| 69 | 96 | * |
| 70 | 97 | * @return PublicKeyCredentialSource|null |
| 71 | 98 | */ |
| 72 | 99 | public function findOneByCredentialId( string $public_key_credential_id ): ?PublicKeyCredentialSource { |
| 73 | - $username = defender_get_data_from_request( 'username', 'p' ); | |
| 74 | - if ( isset( $username ) ) { | |
| 75 | - $user = get_user_by( 'login', $username ); | |
| 100 | + if ( null === $this->expected_user_id ) { | |
| 101 | + // No trusted scope set — deny rather than search globally. | |
| 102 | + // Never needs an unscoped, cross-account lookup. | |
| 103 | + return null; | |
| 104 | + } | |
| 76 | 105 | |
| 77 | - if ( is_object( $user ) ) { | |
| 78 | - $user_id = $user->ID; | |
| 79 | - } else { | |
| 80 | - $user_id = 0; | |
| 81 | - } | |
| 82 | - } else { | |
| 83 | - $user_id = get_current_user_id(); | |
| 84 | - } | |
| 85 | - $data = $this->getCredentials( $user_id ); | |
| 86 | - if ( isset( $data[ base64_encode( $public_key_credential_id ) ]['credential_source'] ) ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode | |
| 87 | - $credential_source = $data[ base64_encode( $public_key_credential_id ) ]['credential_source']; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode | |
| 106 | + $data = $this->getCredentials( $this->expected_user_id ); | |
| 107 | + $key = base64_encode( $public_key_credential_id ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode | |
| 108 | + if ( isset( $data[ $key ]['credential_source'] ) ) { | |
| 109 | + $credential_source = $data[ $key ]['credential_source']; // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode | |
| 88 | 110 | |
| 89 | 111 | return PublicKeyCredentialSource::createFromArray( $this->normalize_legacy_trust_path( $credential_source ) ); |
| 90 | 112 | } |
| 91 | 113 | |