|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace SimpleSAML\OpenID\Core; |
| 6 | + |
| 7 | +use SimpleSAML\OpenID\Codebooks\ClaimsEnum; |
| 8 | +use SimpleSAML\OpenID\Codebooks\UriPattern; |
| 9 | +use SimpleSAML\OpenID\Exceptions\IdTokenException; |
| 10 | +use SimpleSAML\OpenID\Jws\ParsedJws; |
| 11 | + |
| 12 | +/** |
| 13 | + * ID Token abstraction from |
| 14 | + * https://openid.net/specs/openid-connect-core-1_0.html#IDToken |
| 15 | + */ |
| 16 | +class IdToken extends ParsedJws |
| 17 | +{ |
| 18 | + public function getIssuer(): string |
| 19 | + { |
| 20 | + // REQUIRED. Issuer Identifier for the Issuer of the response. The iss |
| 21 | + // value is a case-sensitive URL using the https scheme that contains |
| 22 | + // scheme, host, and optionally, port number and path components and |
| 23 | + // no query or fragment components. |
| 24 | + $iss = parent::getIssuer() ?? throw new IdTokenException('No Issuer claim found.'); |
| 25 | + |
| 26 | + // We will leave the possibility of http usage for local testing purposes. |
| 27 | + return $this->helpers->type()->enforceUri($iss, 'Issuer claim', UriPattern::HttpNoQueryNoFragment->value); |
| 28 | + } |
| 29 | + |
| 30 | + |
| 31 | + public function getSubject(): string |
| 32 | + { |
| 33 | + // REQUIRED. Subject Identifier. A locally unique and never reassigned |
| 34 | + // identifier within the Issuer for the End-User, which is intended to |
| 35 | + // be consumed by the Client, e.g., 24400320 or |
| 36 | + // AItOawmwtWwcT0k51BayewNvutrJUqsvl6qs7A4. It MUST NOT exceed 255 |
| 37 | + // ASCII [RFC20] characters in length. The sub value is a |
| 38 | + // case-sensitive string. |
| 39 | + $sub = parent::getSubject() ?? throw new IdTokenException('No Subject claim found.'); |
| 40 | + |
| 41 | + if (!mb_check_encoding($sub, 'ASCII')) { |
| 42 | + throw new IdTokenException('Subject claim contains non-ASCII characters.'); |
| 43 | + } |
| 44 | + |
| 45 | + if (strlen($sub) > 255) { |
| 46 | + throw new IdTokenException('Subject claim exceeds 255 ASCII characters limit.'); |
| 47 | + } |
| 48 | + |
| 49 | + return $sub; |
| 50 | + } |
| 51 | + |
| 52 | + |
| 53 | + public function getAudience(): array |
| 54 | + { |
| 55 | + // REQUIRED. Audience(s) that this ID Token is intended for. It MUST |
| 56 | + // contain the OAuth 2.0 client_id of the Relying Party as an audience |
| 57 | + // value. It MAY also contain identifiers for other audiences. In the |
| 58 | + // general case, the aud value is an array of case-sensitive strings. |
| 59 | + // In the common special case when there is one audience, the aud value |
| 60 | + // MAY be a single case-sensitive string. |
| 61 | + return parent::getAudience() ?? throw new IdTokenException('No Audience claim found.'); |
| 62 | + } |
| 63 | + |
| 64 | + |
| 65 | + public function getExpirationTime(): int |
| 66 | + { |
| 67 | + return parent::getExpirationTime() ?? throw new IdTokenException('No Expiration Time claim found.'); |
| 68 | + } |
| 69 | + |
| 70 | + |
| 71 | + public function getIssuedAt(): int |
| 72 | + { |
| 73 | + return parent::getIssuedAt() ?? throw new IdTokenException('No Issued At claim found.'); |
| 74 | + } |
| 75 | + |
| 76 | + |
| 77 | + /** |
| 78 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 79 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 80 | + */ |
| 81 | + public function getAuthTime(): ?int |
| 82 | + { |
| 83 | + // Time when the End-User authentication occurred. Its value is a JSON |
| 84 | + // number representing the number of seconds from 1970-01-01T00:00:00Z |
| 85 | + // as measured in UTC until the date/time. When a max_age request is |
| 86 | + // made or when auth_time is requested as an Essential Claim, then |
| 87 | + // this Claim is REQUIRED; otherwise, its inclusion is OPTIONAL. |
| 88 | + // (The auth_time Claim semantically corresponds to the OpenID 2.0 PAPE |
| 89 | + // [OpenID.PAPE] auth_time response parameter.) |
| 90 | + |
| 91 | + $authTime = $this->getPayloadClaim(ClaimsEnum::AuthTime->value); |
| 92 | + |
| 93 | + if (is_null($authTime)) { |
| 94 | + return null; |
| 95 | + } |
| 96 | + |
| 97 | + return $this->helpers->type()->ensureInt($authTime, ClaimsEnum::AuthTime->value); |
| 98 | + } |
| 99 | + |
| 100 | + |
| 101 | + /** |
| 102 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 103 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 104 | + */ |
| 105 | + public function getNonce(): ?string |
| 106 | + { |
| 107 | + // String value used to associate a Client session with an ID Token, |
| 108 | + // and to mitigate replay attacks. The value is passed through |
| 109 | + // unmodified from the Authentication Request to the ID Token. If |
| 110 | + // present in the ID Token, Clients MUST verify that the nonce Claim |
| 111 | + // Value is equal to the value of the nonce parameter sent in the |
| 112 | + // Authentication Request. If present in the Authentication Request, |
| 113 | + // Authorization Servers MUST include a nonce Claim in the ID Token |
| 114 | + // with the Claim Value being the nonce value sent in the Authentication |
| 115 | + // Request. Authorization Servers SHOULD perform no other processing |
| 116 | + // on nonce values used. The nonce value is a case-sensitive string. |
| 117 | + $nonce = $this->getPayloadClaim(ClaimsEnum::Nonce->value); |
| 118 | + |
| 119 | + if (is_null($nonce)) { |
| 120 | + return null; |
| 121 | + } |
| 122 | + |
| 123 | + return $this->helpers->type()->ensureNonEmptyString($nonce, ClaimsEnum::Nonce->value); |
| 124 | + } |
| 125 | + |
| 126 | + |
| 127 | + /** |
| 128 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 129 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 130 | + */ |
| 131 | + public function getAuthenticationContextClassReference(): ?string |
| 132 | + { |
| 133 | + // OPTIONAL. Authentication Context Class Reference. String specifying |
| 134 | + // an Authentication Context Class Reference value that identifies the |
| 135 | + // Authentication Context Class that the authentication performed |
| 136 | + // satisfied. The value "0" indicates the End-User authentication did |
| 137 | + // not meet the requirements of ISO/IEC 29115 [ISO29115] level 1. |
| 138 | + // For historic reasons, the value "0" is used to indicate that there |
| 139 | + // is no confidence that the same person is actually there. |
| 140 | + // Authentications with level 0 SHOULD NOT be used to authorize access |
| 141 | + // to any resource of any monetary value. (This corresponds to the |
| 142 | + // OpenID 2.0 PAPE [OpenID.PAPE] nist_auth_level 0.) An absolute URI |
| 143 | + // or an RFC 6711 [RFC6711] registered name SHOULD be used as the acr |
| 144 | + // value; registered names MUST NOT be used with a different meaning |
| 145 | + // than that which is registered. Parties using this claim will need to |
| 146 | + // agree upon the meanings of the values used, which may be context - |
| 147 | + // specific. The acr value is a case-sensitive string. |
| 148 | + |
| 149 | + $acr = $this->getPayloadClaim(ClaimsEnum::Acr->value); |
| 150 | + |
| 151 | + if (is_null($acr)) { |
| 152 | + return null; |
| 153 | + } |
| 154 | + |
| 155 | + return $this->helpers->type()->ensureNonEmptyString($acr, ClaimsEnum::Acr->value); |
| 156 | + } |
| 157 | + |
| 158 | + |
| 159 | + /** |
| 160 | + * @return ?string[] |
| 161 | + * |
| 162 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 163 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 164 | + */ |
| 165 | + public function getAuthenticationMethodsReferences(): ?array |
| 166 | + { |
| 167 | + // OPTIONAL. Authentication Methods References. JSON array of strings |
| 168 | + // that are identifiers for authentication methods used in the |
| 169 | + // authentication. For instance, values might indicate that both |
| 170 | + // password and OTP authentication methods were used. The amr value is |
| 171 | + // an array of case-sensitive strings. Values used in the amr Claim |
| 172 | + // SHOULD be from those registered in the IANA Authentication Method |
| 173 | + // Reference Values registry [IANA.AMR] established by [RFC8176]; |
| 174 | + // parties using this claim will need to agree upon the meanings of any |
| 175 | + // unregistered values used, which may be context-specific. |
| 176 | + |
| 177 | + $amr = $this->getPayloadClaim(ClaimsEnum::Amr->value); |
| 178 | + |
| 179 | + if (is_null($amr)) { |
| 180 | + return null; |
| 181 | + } |
| 182 | + |
| 183 | + return $this->helpers->type()->ensureArrayWithValuesAsNonEmptyStrings($amr, ClaimsEnum::Amr->value); |
| 184 | + } |
| 185 | + |
| 186 | + |
| 187 | + /** |
| 188 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 189 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 190 | + */ |
| 191 | + public function getAuthorizedParty(): ?string |
| 192 | + { |
| 193 | + // OPTIONAL. Authorized party - the party to which the ID Token was |
| 194 | + // issued. If present, it MUST contain the OAuth 2.0 Client ID of this |
| 195 | + // party. The azp value is a case-sensitive string containing a |
| 196 | + // StringOrURI value. Note that in practice, the azp Claim only occurs |
| 197 | + // when extensions beyond the scope of this specification are used; |
| 198 | + // therefore, implementations not using such extensions are encouraged |
| 199 | + // to not use azp and to ignore it when it does occur. |
| 200 | + |
| 201 | + $azp = $this->getPayloadClaim(ClaimsEnum::Azp->value); |
| 202 | + |
| 203 | + if (is_null($azp)) { |
| 204 | + return null; |
| 205 | + } |
| 206 | + |
| 207 | + return $this->helpers->type()->ensureNonEmptyString($azp, ClaimsEnum::Azp->value); |
| 208 | + } |
| 209 | + |
| 210 | + |
| 211 | + /** |
| 212 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 213 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 214 | + */ |
| 215 | + public function getAccessTokenHash(): ?string |
| 216 | + { |
| 217 | + // OPTIONAL. Access Token hash value. Its value is the base64url |
| 218 | + // encoding of the left-most half of the hash of the octets of the ASCII |
| 219 | + // representation of the access_token value, where the hash algorithm |
| 220 | + // used is the hash algorithm used in the alg Header Parameter of the |
| 221 | + // ID Token's JOSE Header. For instance, if the alg is RS256, hash the |
| 222 | + // access_token value with SHA-256, then take the left-most 128 bits |
| 223 | + // and base64url-encode them. The at_hash value is a case-sensitive |
| 224 | + // string. |
| 225 | + $aTHash = $this->getPayloadClaim(ClaimsEnum::ATHash->value); |
| 226 | + |
| 227 | + if (is_null($aTHash)) { |
| 228 | + return null; |
| 229 | + } |
| 230 | + |
| 231 | + return $this->helpers->type()->ensureNonEmptyString($aTHash, ClaimsEnum::ATHash->value); |
| 232 | + } |
| 233 | + |
| 234 | + |
| 235 | + /** |
| 236 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 237 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 238 | + */ |
| 239 | + public function getCodeHash(): ?string |
| 240 | + { |
| 241 | + // Code hash value. Its value is the base64url encoding of the left-most |
| 242 | + // half of the hash of the octets of the ASCII representation of the |
| 243 | + // code value, where the hash algorithm used is the hash algorithm used |
| 244 | + // in the alg Header Parameter of the ID Token's JOSE Header. For |
| 245 | + // instance, if the alg is HS512, hash the code value with SHA-512, |
| 246 | + // then take the left-most 256 bits and base64url-encode them. The |
| 247 | + // c_hash value is a case-sensitive string. |
| 248 | + //If the ID Token is issued from the Authorization Endpoint with a code, |
| 249 | + // which is the case for the response_type values code id_token and |
| 250 | + // code id_token token, this is REQUIRED; otherwise, its inclusion is |
| 251 | + // OPTIONAL. |
| 252 | + |
| 253 | + $cHash = $this->getPayloadClaim(ClaimsEnum::CHash->value); |
| 254 | + |
| 255 | + if (is_null($cHash)) { |
| 256 | + return null; |
| 257 | + } |
| 258 | + |
| 259 | + return $this->helpers->type()->ensureNonEmptyString($cHash, ClaimsEnum::CHash->value); |
| 260 | + } |
| 261 | + |
| 262 | + |
| 263 | + /** |
| 264 | + * @return ?array<string, string> |
| 265 | + * |
| 266 | + * @throws \SimpleSAML\OpenID\Exceptions\JwsException |
| 267 | + * @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException |
| 268 | + */ |
| 269 | + public function getSubJwk(): ?array |
| 270 | + { |
| 271 | + // Public key used to check the signature of an ID Token issued by a |
| 272 | + // Self-Issued OpenID Provider, as specified in Section 7. The key is a |
| 273 | + // bare key in JWK [JWK] format (not an X.509 certificate value). |
| 274 | + // The sub_jwk value is a JSON object. Use of the sub_jwk Claim is NOT |
| 275 | + // RECOMMENDED when the OP is not Self-Issued. |
| 276 | + $subJwk = $this->getPayloadClaim(ClaimsEnum::SubJwk->value); |
| 277 | + |
| 278 | + if (is_null($subJwk)) { |
| 279 | + return null; |
| 280 | + } |
| 281 | + |
| 282 | + return $this->helpers->type() |
| 283 | + ->ensureArrayWithKeysAndValuesAsNonEmptyStrings($subJwk, ClaimsEnum::SubJwk->value); |
| 284 | + } |
| 285 | +} |
0 commit comments