001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2016, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.openid.connect.sdk.federation.entities; 019 020 021import java.util.Date; 022import java.util.LinkedList; 023import java.util.List; 024 025import net.minidev.json.JSONObject; 026 027import com.nimbusds.jose.jwk.JWKSet; 028import com.nimbusds.jwt.JWTClaimsSet; 029import com.nimbusds.oauth2.sdk.ParseException; 030import com.nimbusds.oauth2.sdk.as.AuthorizationServerMetadata; 031import com.nimbusds.oauth2.sdk.client.ClientMetadata; 032import com.nimbusds.oauth2.sdk.id.Identifier; 033import com.nimbusds.oauth2.sdk.id.Issuer; 034import com.nimbusds.oauth2.sdk.id.Subject; 035import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 036import com.nimbusds.oauth2.sdk.util.MapUtils; 037import com.nimbusds.openid.connect.sdk.claims.CommonClaimsSet; 038import com.nimbusds.openid.connect.sdk.federation.trust.constraints.TrustChainConstraints; 039import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata; 040import com.nimbusds.openid.connect.sdk.rp.OIDCClientMetadata; 041 042 043/** 044 * Federation entity statement claims set, serialisable to a JSON object. 045 * 046 * <p>Example claims set: 047 * 048 * <pre> 049 * { 050 * "iss": "https://feide.no", 051 * "sub": "https://ntnu.no", 052 * "iat": 1516239022, 053 * "exp": 1516298022, 054 * "crit": ["jti"], 055 * "jti": "7l2lncFdY6SlhNia", 056 * "policy_language_crit": ["regexp"], 057 * "metadata_policy": { 058 * "openid_provider": { 059 * "issuer": {"value": "https://ntnu.no"}, 060 * "organization_name": {"value": "NTNU"}, 061 * "id_token_signing_alg_values_supported": 062 * {"subset_of": ["RS256", "RS384", "RS512"]}, 063 * "op_policy_uri": { 064 * "regexp": "^https:\/\/[\w-]+\.example\.com\/[\w-]+\.html"} 065 * }, 066 * "openid_relying_party": { 067 * "organization_name": {"value": "NTNU"}, 068 * "grant_types_supported": { 069 * "subset_of": ["authorization_code", "implicit"]}, 070 * "scopes": { 071 * "subset_of": ["openid", "profile", "email", "phone"]} 072 * } 073 * }, 074 * "constraints": { 075 * "max_path_length": 2 076 * } 077 * "jwks": { 078 * "keys": [ 079 * { 080 * "alg": "RS256", 081 * "e": "AQAB", 082 * "ext": true, 083 * "key_ops": ["verify"], 084 * "kid": "key1", 085 * "kty": "RSA", 086 * "n": "pnXBOusEANuug6ewezb9J_...", 087 * "use": "sig" 088 * } 089 * ] 090 * }, 091 * "authority_hints": [ 092 * "https://edugain.org/federation" 093 * ] 094 * } 095 * </pre> 096 * 097 * <p>Related specifications: 098 * 099 * <ul> 100 * <li>OpenID Connect Federation 1.0, section 2.1. 101 * </ul> 102 */ 103public class EntityStatementClaimsSet extends CommonClaimsSet { 104 105 106 /** 107 * The expiration time claim name. 108 */ 109 public static final String EXP_CLAIM_NAME = "exp"; 110 111 112 /** 113 * The JWK set claim name. 114 */ 115 public static final String JWKS_CLAIM_NAME = "jwks"; 116 117 118 /** 119 * The authority hints claim name. 120 */ 121 public static final String AUTHORITY_HINTS_CLAIM_NAME = "authority_hints"; 122 123 124 /** 125 * The metadata claim name. 126 */ 127 public static final String METADATA_CLAIM_NAME = "metadata"; 128 129 130 /** 131 * The metadata policy claim name. 132 */ 133 public static final String METADATA_POLICY_CLAIM_NAME = "metadata_policy"; 134 135 136 /** 137 * The constraints claim name. 138 */ 139 public static final String CONSTRAINTS_CLAIM_NAME = "constraints"; 140 141 142 /** 143 * The critical claim name. 144 */ 145 public static final String CRITICAL_CLAIM_NAME = "crit"; 146 147 148 /** 149 * The policy critical claim name. 150 */ 151 public static final String POLICY_LANGUAGE_CRITICAL_CLAIM_NAME = "policy_language_crit"; 152 153 154 /** 155 * Creates a new federation entity statement claims set with the 156 * minimum required claims. 157 * 158 * @param iss The issuer. Must not be {@code null}. 159 * @param sub The subject. Must not be {@code null}. 160 * @param iat The issue time. Must not be {@code null}. 161 * @param exp The expiration time. Must not be {@code null}. 162 * @param jwks The entity public JWK set. Must not be {@code null}. 163 */ 164 public EntityStatementClaimsSet(final Issuer iss, 165 final Subject sub, 166 final Date iat, 167 final Date exp, 168 final JWKSet jwks) { 169 170 this(new EntityID(iss.getValue()), new EntityID(sub.getValue()), iat, exp, jwks); 171 } 172 173 174 /** 175 * Creates a new federation entity statement claims set with the 176 * minimum required claims. 177 * 178 * @param iss The issuer. Must not be {@code null}. 179 * @param sub The subject. Must not be {@code null}. 180 * @param iat The issue time. Must not be {@code null}. 181 * @param exp The expiration time. Must not be {@code null}. 182 * @param jwks The entity public JWK set. Must not be {@code null}. 183 */ 184 public EntityStatementClaimsSet(final EntityID iss, 185 final EntityID sub, 186 final Date iat, 187 final Date exp, 188 final JWKSet jwks) { 189 190 setClaim(ISS_CLAIM_NAME, iss.getValue()); 191 setClaim(SUB_CLAIM_NAME, sub.getValue()); 192 setDateClaim(IAT_CLAIM_NAME, iat); 193 setDateClaim(EXP_CLAIM_NAME, exp); 194 setClaim(JWKS_CLAIM_NAME, jwks.toJSONObject(true)); // public JWKs only 195 } 196 197 198 /** 199 * Creates a new federation entity statement claims set from the 200 * specified JWT claims set. 201 * 202 * @param jwtClaimsSet The JWT claims set. Must not be {@code null}. 203 * 204 * @throws ParseException If the JWT claims set doesn't represent a 205 * valid federation entity statement claims set. 206 */ 207 public EntityStatementClaimsSet(final JWTClaimsSet jwtClaimsSet) 208 throws ParseException { 209 210 super(jwtClaimsSet.toJSONObject()); 211 212 validateRequiredClaimsPresence(); 213 } 214 215 216 /** 217 * Validates this claims set for having all minimum required claims for 218 * an entity statement. If a {@link #isSelfStatement() selt-statement} 219 * check for the {@link #hasMetadata() presence of metadata}. If 220 * {@link #getCriticalExtensionClaims() critical extension claims} are 221 * listed their presence is also checked. 222 * 223 * @throws ParseException If the validation failed and a required claim 224 * is missing. 225 */ 226 public void validateRequiredClaimsPresence() 227 throws ParseException { 228 229 if (getIssuer() == null) { 230 throw new ParseException("Missing iss (issuer) claim"); 231 } 232 233 EntityID.parse(getIssuer()); // ensure URI 234 235 if (getSubject() == null) { 236 throw new ParseException("Missing sub (subject) claim"); 237 } 238 239 EntityID.parse(getSubject()); // ensure URI 240 241 if (getIssueTime() == null) { 242 throw new ParseException("Missing iat (issued-at) claim"); 243 } 244 245 if (getExpirationTime() == null) { 246 throw new ParseException("Missing exp (expiration) claim"); 247 } 248 249 if (getJWKSet() == null) { 250 throw new ParseException("Missing jwks (JWK set) claim"); 251 } 252 253 if (isSelfStatement() && ! hasMetadata()) { 254 throw new ParseException("Missing required metadata claim for self-statement"); 255 } 256 257 List<String> crit = getCriticalExtensionClaims(); 258 259 if (crit != null) { 260 for (String claimName: crit) { 261 if (getClaim(claimName) == null) { 262 throw new ParseException("Missing critical " + claimName + " claim"); 263 } 264 } 265 } 266 } 267 268 269 /** 270 * Returns {@code true} if this is a self-statement (issuer and subject 271 * match). 272 * 273 * @return {@code true} for a self-statement, {@code false} if not. 274 */ 275 public boolean isSelfStatement() { 276 277 Issuer issuer = getIssuer(); 278 Subject subject = getSubject(); 279 280 return issuer != null && subject != null && issuer.getValue().equals(subject.getValue()); 281 } 282 283 284 /** 285 * Returns the issuer as entity ID. 286 * 287 * @return The issuer as entity ID. 288 */ 289 public EntityID getIssuerEntityID() { 290 291 return new EntityID(getIssuer().getValue()); 292 } 293 294 295 /** 296 * Returns the subject as entity ID. 297 * 298 * @return The subject as entity ID. 299 */ 300 public EntityID getSubjectEntityID() { 301 302 return new EntityID(getSubject().getValue()); 303 } 304 305 306 /** 307 * Gets the entity statement expiration time. Corresponds to the 308 * {@code exp} claim. 309 * 310 * @return The expiration time, {@code null} if not specified or 311 * parsing failed. 312 */ 313 public Date getExpirationTime() { 314 315 return getDateClaim(EXP_CLAIM_NAME); 316 } 317 318 319 /** 320 * Gets the entity JWK set. 321 * 322 * @return The entity JWK set, {@code null} if not specified or parsing 323 * failed. 324 */ 325 public JWKSet getJWKSet() { 326 327 try { 328 return JWKSet.parse(getJSONObjectClaim(JWKS_CLAIM_NAME)); 329 } catch (java.text.ParseException e) { 330 return null; 331 } 332 } 333 334 335 /** 336 * Gets the entity IDs of the intermediate entities or trust anchors. 337 * 338 * @return The entity IDs, {@code null} or empty list for a trust 339 * anchor, or if parsing failed. 340 */ 341 public List<EntityID> getAuthorityHints() { 342 343 List<String> strings = getStringListClaim(AUTHORITY_HINTS_CLAIM_NAME); 344 345 if (strings == null) { 346 return null; 347 } 348 349 List<EntityID> trustChain = new LinkedList<>(); 350 for (String s: strings) { 351 trustChain.add(new EntityID(s)); 352 } 353 return trustChain; 354 } 355 356 357 /** 358 * Sets the entity IDs of the intermediate entities or trust anchors. 359 * 360 * @param trustChain The entity IDs, {@code null} or empty list for a 361 * trust anchor. 362 */ 363 public void setAuthorityHints(final List<EntityID> trustChain) { 364 365 if (trustChain != null) { 366 setClaim(AUTHORITY_HINTS_CLAIM_NAME, Identifier.toStringList(trustChain)); 367 } else { 368 setClaim(AUTHORITY_HINTS_CLAIM_NAME, null); 369 } 370 } 371 372 373 /** 374 * Returns {@code true} if a metadata field is present. 375 * 376 * @return {@code true} if for a metadata field for an OpenID relying 377 * party, OpenID provider, OAuth authorisation server, OAuth 378 * client, OAuth protected resource or a federation entity is 379 * present. 380 */ 381 public boolean hasMetadata() { 382 383 JSONObject metadataObject = getJSONObjectClaim(METADATA_CLAIM_NAME); 384 385 if (MapUtils.isEmpty(metadataObject)) { 386 return false; 387 } 388 389 if (metadataObject.get(FederationMetadataType.OPENID_RELYING_PARTY.getValue()) != null) return true; 390 if (metadataObject.get(FederationMetadataType.OPENID_PROVIDER.getValue()) != null) return true; 391 if (metadataObject.get(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER.getValue()) != null) return true; 392 if (metadataObject.get(FederationMetadataType.OAUTH_CLIENT.getValue()) != null) return true; 393 if (metadataObject.get(FederationMetadataType.OAUTH_RESOURCE.getValue()) != null) return true; 394 if (metadataObject.get(FederationMetadataType.FEDERATION_ENTITY.getValue()) != null) return true; 395 396 return false; 397 } 398 399 400 /** 401 * Gets the OpenID relying party metadata if present for this entity. 402 * 403 * @return The RP metadata, {@code null} if not specified or if parsing 404 * failed. 405 */ 406 public OIDCClientMetadata getRPMetadata() { 407 408 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 409 410 if (o == null) { 411 return null; 412 } 413 414 try { 415 JSONObject rpo = JSONObjectUtils.getJSONObject(o, FederationMetadataType.OPENID_RELYING_PARTY.getValue(), null); 416 if (rpo == null) { 417 return null; 418 } 419 return OIDCClientMetadata.parse(rpo); 420 421 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 422 return null; 423 } 424 } 425 426 427 /** 428 * Sets the OpenID relying party metadata if present for this entity. 429 * 430 * @param rpMetadata The RP metadata, {@code null} if not specified. 431 */ 432 public void setRPMetadata(final OIDCClientMetadata rpMetadata) { 433 434 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 435 436 if (o == null) { 437 if (rpMetadata == null) { 438 return; // nothing to clear 439 } 440 o = new JSONObject(); 441 } 442 443 if (rpMetadata != null) { 444 o.put(FederationMetadataType.OPENID_RELYING_PARTY.getValue(), rpMetadata.toJSONObject()); 445 } else { 446 o.put(FederationMetadataType.OPENID_RELYING_PARTY.getValue(), null); 447 } 448 449 setClaim(METADATA_CLAIM_NAME, o); 450 } 451 452 453 /** 454 * Gets the OpenID provider metadata if present for this entity. 455 * 456 * @return The OP metadata, {@code null} if not specified or if parsing 457 * failed. 458 */ 459 public OIDCProviderMetadata getOPMetadata() { 460 461 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 462 463 if (o == null) { 464 return null; 465 } 466 467 try { 468 JSONObject opo = JSONObjectUtils.getJSONObject(o, FederationMetadataType.OPENID_PROVIDER.getValue(), null); 469 if (opo == null) { 470 return null; 471 } 472 return OIDCProviderMetadata.parse(opo); 473 474 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 475 return null; 476 } 477 } 478 479 480 /** 481 * Gets the OpenID provider metadata if present for this entity. 482 * 483 * @param opMetadata The OP metadata, {@code null} if not specified. 484 */ 485 public void setOPMetadata(final OIDCProviderMetadata opMetadata) { 486 487 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 488 489 if (o == null) { 490 if (opMetadata == null) { 491 return; // nothing to clear 492 } 493 o = new JSONObject(); 494 } 495 496 if (opMetadata != null) { 497 o.put(FederationMetadataType.OPENID_PROVIDER.getValue(), opMetadata.toJSONObject()); 498 } else { 499 o.put(FederationMetadataType.OPENID_PROVIDER.getValue(), null); 500 } 501 502 setClaim(METADATA_CLAIM_NAME, o); 503 } 504 505 506 /** 507 * Gets the OAuth 2.0 client metadata if present for this entity. 508 * 509 * @return The client metadata, {@code null} if not specified or if 510 * parsing failed. 511 */ 512 public ClientMetadata getOAuthClientMetadata() { 513 514 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 515 516 if (o == null) { 517 return null; 518 } 519 520 try { 521 JSONObject aco = JSONObjectUtils.getJSONObject(o, FederationMetadataType.OAUTH_CLIENT.getValue(), null); 522 if (aco == null) { 523 return null; 524 } 525 return ClientMetadata.parse(aco); 526 527 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 528 return null; 529 } 530 } 531 532 533 /** 534 * Sets the OAuth 2.0 client metadata if present for this entity. 535 * 536 * @param clientMetadata The client metadata, {@code null} if not 537 * specified. 538 */ 539 public void setOAuthClientMetadata(final ClientMetadata clientMetadata) { 540 541 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 542 543 if (o == null) { 544 if (clientMetadata == null) { 545 return; // nothing to clear 546 } 547 o = new JSONObject(); 548 } 549 550 if (clientMetadata != null) { 551 o.put(FederationMetadataType.OAUTH_CLIENT.getValue(), clientMetadata.toJSONObject()); 552 } else { 553 o.put(FederationMetadataType.OAUTH_CLIENT.getValue(), null); 554 } 555 556 setClaim(METADATA_CLAIM_NAME, o); 557 } 558 559 560 /** 561 * Gets the OAuth 2.0 authorisation server metadata if present for this 562 * entity. 563 * 564 * @return The AS metadata, {@code null} if not specified or if parsing 565 * failed. 566 */ 567 public AuthorizationServerMetadata getASMetadata() { 568 569 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 570 571 if (o == null) { 572 return null; 573 } 574 575 try { 576 JSONObject opo = JSONObjectUtils.getJSONObject(o, FederationMetadataType.OAUTH_AUTHORIZATION_SERVER.getValue(), null); 577 if (opo == null) { 578 return null; 579 } 580 return AuthorizationServerMetadata.parse(opo); 581 582 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 583 return null; 584 } 585 } 586 587 588 /** 589 * Sets the OAuth 2.0 authorisation server metadata if present for this 590 * entity. 591 * 592 * @param asMetadata The AS metadata, {@code null} if not specified. 593 */ 594 public void setASMetadata(final AuthorizationServerMetadata asMetadata) { 595 596 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 597 598 if (o == null) { 599 if (asMetadata == null) { 600 return; // nothing to clear 601 } 602 o = new JSONObject(); 603 } 604 605 if (asMetadata != null) { 606 o.put(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER.getValue(), asMetadata.toJSONObject()); 607 } else { 608 o.put(FederationMetadataType.OAUTH_AUTHORIZATION_SERVER.getValue(), null); 609 } 610 611 setClaim(METADATA_CLAIM_NAME, o); 612 } 613 614 615 /** 616 * Gets the federation entity metadata if present for this entity. 617 * 618 * @return The federation entity metadata, {@code null} if not 619 * specified or if parsing failed. 620 */ 621 public FederationEntityMetadata getFederationEntityMetadata() { 622 623 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 624 625 if (o == null) { 626 return null; 627 } 628 629 try { 630 JSONObject feo = JSONObjectUtils.getJSONObject(o, FederationMetadataType.FEDERATION_ENTITY.getValue(), null); 631 if (feo == null) { 632 return null; 633 } 634 return FederationEntityMetadata.parse(feo); 635 636 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 637 return null; 638 } 639 } 640 641 642 /** 643 * Sets the federation entity metadata if present for this entity. 644 * 645 * @param entityMetadata The federation entity metadata, {@code null} 646 * if not specified. 647 */ 648 public void setFederationEntityMetadata(final FederationEntityMetadata entityMetadata) { 649 650 JSONObject o = getJSONObjectClaim(METADATA_CLAIM_NAME); 651 652 if (o == null) { 653 if (entityMetadata == null) { 654 return; // nothing to clear 655 } 656 o = new JSONObject(); 657 } 658 659 if (entityMetadata != null) { 660 o.put(FederationMetadataType.FEDERATION_ENTITY.getValue(), entityMetadata.toJSONObject()); 661 } else { 662 o.put(FederationMetadataType.FEDERATION_ENTITY.getValue(), null); 663 } 664 665 setClaim(METADATA_CLAIM_NAME, o); 666 } 667 668 669 /** 670 * Gets the metadata policy JSON object. 671 * 672 * @return The metadata policy JSON object, {@code null} if not 673 * specified or if parsing failed. 674 */ 675 public JSONObject getMetadataPolicyJSONObject() { 676 677 return getJSONObjectClaim(METADATA_POLICY_CLAIM_NAME); 678 } 679 680 681 /** 682 * Sets the metadata policy JSON object. 683 * 684 * @param metadataPolicy The metadata policy JSON object, {@code null} 685 * if not specified. 686 */ 687 public void setMetadataPolicyJSONObject(final JSONObject metadataPolicy) { 688 689 setClaim(METADATA_POLICY_CLAIM_NAME, metadataPolicy); 690 } 691 692 693 /** 694 * Gets the trust chain constraints for subordinate entities. 695 * 696 * @return The trust chain constraints, {@code null} if not specified 697 * or if parsing failed. 698 */ 699 public TrustChainConstraints getConstraints() { 700 701 JSONObject o = getJSONObjectClaim(CONSTRAINTS_CLAIM_NAME); 702 703 if (o == null) { 704 return null; 705 } 706 707 try { 708 return TrustChainConstraints.parse(o); 709 } catch (com.nimbusds.oauth2.sdk.ParseException e) { 710 return null; 711 } 712 } 713 714 715 /** 716 * Sets the trust chain constraints for subordinate entities. 717 * 718 * @param constraints The trust chain constraints, {@code null} if not 719 * specified. 720 */ 721 public void setConstraints(final TrustChainConstraints constraints) { 722 723 if (constraints != null) { 724 setClaim(CONSTRAINTS_CLAIM_NAME, constraints.toJSONObject()); 725 } else { 726 setClaim(CONSTRAINTS_CLAIM_NAME, null); 727 } 728 } 729 730 731 /** 732 * Gets the names of the critical extension claims. 733 * 734 * @return The names of the critical extension claims, {@code null} if 735 * not specified or if parsing failed. 736 */ 737 public List<String> getCriticalExtensionClaims() { 738 739 return getStringListClaim(CRITICAL_CLAIM_NAME); 740 } 741 742 743 /** 744 * Sets the names of the critical extension claims. 745 * 746 * @param claimNames The names of the critical extension claims, 747 * {@code null} if not specified. Must not be an 748 * empty list. 749 */ 750 public void setCriticalExtensionClaims(final List<String> claimNames) { 751 752 if (claimNames != null && claimNames.isEmpty()) { 753 throw new IllegalArgumentException("The critical extension claim names must not be empty"); 754 } 755 756 setClaim(CRITICAL_CLAIM_NAME, claimNames); 757 } 758 759 760 /** 761 * Gets the names of the critical policy extensions. 762 * 763 * @return The names of the critical policy extensions or if parsing 764 * failed. 765 */ 766 public List<String> getCriticalPolicyExtensions() { 767 768 return getStringListClaim(POLICY_LANGUAGE_CRITICAL_CLAIM_NAME); 769 } 770 771 772 /** 773 * Sets the names of the critical policy extensions. 774 * 775 * @param extNames The names of the critical policy extensions, 776 * {@code null} if not specified. Must not be an empty 777 * list. 778 */ 779 public void setCriticalPolicyExtensions(final List<String> extNames) { 780 781 if (extNames != null && extNames.isEmpty()) { 782 throw new IllegalArgumentException("The critical policy extension names must not be empty"); 783 } 784 785 setClaim(POLICY_LANGUAGE_CRITICAL_CLAIM_NAME, extNames); 786 } 787}