| @@ -41,8 +41,19 @@ | ||
| 41 | 41 | */ |
| 42 | 42 | private $metadata; |
| 43 | 43 | |
| 44 | 44 | /** |
| 45 | + * The redirect URIs that have completed the authorization-code flow on this site. | |
| 46 | + * | |
| 47 | + * Stored alongside the client (rather than in $metadata, which is reserved for standardized | |
| 48 | + * authorization-server metadata) so the validation state is invalidated automatically whenever | |
| 49 | + * the registration is removed or replaced. | |
| 50 | + * | |
| 51 | + * @var string[] | |
| 52 | + */ | |
| 53 | + private $validated_uris; | |
| 54 | + | |
| 55 | + /** | |
| 45 | 56 | * Registered_Client constructor. |
| 46 | 57 | * |
| 47 | 58 | * @param string $client_id The registered client ID. |
| 48 | 59 | * @param string $registration_access_token The registration access token. |
| @@ -47,8 +58,9 @@ | ||
| 47 | 58 | * @param string $client_id The registered client ID. |
| 48 | 59 | * @param string $registration_access_token The registration access token. |
| 49 | 60 | * @param string $registration_client_uri The management endpoint URL. |
| 50 | 61 | * @param array<string, string|array<string>> $metadata Additional metadata from the registration response. |
| 62 | + * @param string[] $validated_uris Redirect URIs that have completed the auth-code flow. | |
| 51 | 63 | * |
| 52 | 64 | * @throws InvalidArgumentException If client_id is empty. |
| 53 | 65 | */ |
| 54 | 66 | public function __construct( |
| @@ -56,9 +68,10 @@ | ||
| 56 | 68 | // phpcs:ignore PHPCompatibility.Attributes.NewAttributes.PHPNativeAttributeFound -- No-op on PHP < 8.2; redacts parameter from stack traces on PHP 8.2+. |
| 57 | 69 | #[SensitiveParameter] |
| 58 | 70 | string $registration_access_token, |
| 59 | 71 | string $registration_client_uri, |
| 60 | - array $metadata = [] | |
| 72 | + array $metadata = [], | |
| 73 | + array $validated_uris = [] | |
| 61 | 74 | ) { |
| 62 | 75 | if ( $client_id === '' ) { |
| 63 | 76 | throw new InvalidArgumentException( 'Registered_Client requires a non-empty client_id.' ); |
| 64 | 77 | } |
| @@ -66,8 +79,9 @@ | ||
| 66 | 79 | $this->client_id = $client_id; |
| 67 | 80 | $this->registration_access_token = $registration_access_token; |
| 68 | 81 | $this->registration_client_uri = $registration_client_uri; |
| 69 | 82 | $this->metadata = $metadata; |
| 83 | + $this->validated_uris = \array_values( $validated_uris ); | |
| 70 | 84 | } |
| 71 | 85 | |
| 72 | 86 | /** |
| 73 | 87 | * Returns the registered client ID. |
| @@ -105,8 +119,73 @@ | ||
| 105 | 119 | return $this->metadata; |
| 106 | 120 | } |
| 107 | 121 | |
| 108 | 122 | /** |
| 123 | + * Returns the redirect URIs this client is registered with. | |
| 124 | + * | |
| 125 | + * @return string[] | |
| 126 | + */ | |
| 127 | + public function get_redirect_uris(): array { | |
| 128 | + $redirect_uris = ( $this->metadata['redirect_uris'] ?? [] ); | |
| 129 | + | |
| 130 | + return ( \is_array( $redirect_uris ) ) ? \array_values( $redirect_uris ) : []; | |
| 131 | + } | |
| 132 | + | |
| 133 | + /** | |
| 134 | + * Whether this client's registered redirect URIs exactly match the given set (order-insensitive), | |
| 135 | + * so both additions and removals count as a mismatch. | |
| 136 | + * | |
| 137 | + * @param string[] $redirect_uris The redirect-URI set to compare against. | |
| 138 | + * | |
| 139 | + * @return bool | |
| 140 | + */ | |
| 141 | + public function has_redirect_uris( array $redirect_uris ): bool { | |
| 142 | + $wanted = \array_unique( $redirect_uris ); | |
| 143 | + $stored = \array_unique( $this->get_redirect_uris() ); | |
| 144 | + \sort( $wanted ); | |
| 145 | + \sort( $stored ); | |
| 146 | + | |
| 147 | + return $wanted === $stored; | |
| 148 | + } | |
| 149 | + | |
| 150 | + /** | |
| 151 | + * Returns the redirect URIs that have completed the authorization-code flow on this site. | |
| 152 | + * | |
| 153 | + * @return string[] | |
| 154 | + */ | |
| 155 | + public function get_validated_uris(): array { | |
| 156 | + return $this->validated_uris; | |
| 157 | + } | |
| 158 | + | |
| 159 | + /** | |
| 160 | + * Whether the given redirect URI has completed the authorization-code flow on this site. | |
| 161 | + * | |
| 162 | + * @param string $redirect_uri The redirect URI to check. | |
| 163 | + * | |
| 164 | + * @return bool | |
| 165 | + */ | |
| 166 | + public function is_uri_validated( string $redirect_uri ): bool { | |
| 167 | + return \in_array( $redirect_uri, $this->validated_uris, true ); | |
| 168 | + } | |
| 169 | + | |
| 170 | + /** | |
| 171 | + * Returns a copy of this client with its validated redirect URIs replaced. | |
| 172 | + * | |
| 173 | + * @param string[] $validated_uris The redirect URIs that have completed the auth-code flow. | |
| 174 | + * | |
| 175 | + * @return self | |
| 176 | + */ | |
| 177 | + public function with_validated_uris( array $validated_uris ): self { | |
| 178 | + return new self( | |
| 179 | + $this->client_id, | |
| 180 | + $this->registration_access_token, | |
| 181 | + $this->registration_client_uri, | |
| 182 | + $this->metadata, | |
| 183 | + $validated_uris, | |
| 184 | + ); | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 109 | 188 | * Converts the DTO to an associative array for storage. |
| 110 | 189 | * |
| 111 | 190 | * @return array<string, string|array<string>> |
| 112 | 191 | */ |
| @@ -115,7 +194,8 @@ | ||
| 115 | 194 | 'client_id' => $this->client_id, |
| 116 | 195 | 'registration_access_token' => $this->registration_access_token, |
| 117 | 196 | 'registration_client_uri' => $this->registration_client_uri, |
| 118 | 197 | 'metadata' => $this->metadata, |
| 198 | + 'validated_uris' => $this->validated_uris, | |
| 119 | 199 | ]; |
| 120 | 200 | } |
| 121 | 201 | } |