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; 019 020 021import com.nimbusds.oauth2.sdk.Scope; 022import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement; 023import com.nimbusds.openid.connect.sdk.claims.ClaimsSetRequest; 024import net.minidev.json.JSONObject; 025 026import java.util.*; 027 028 029/** 030 * Standard OpenID Connect scope value. 031 * 032 * <p>Related specifications: 033 * 034 * <ul> 035 * <li>OpenID Connect Core 1.0 036 * </ul> 037 */ 038public class OIDCScopeValue extends Scope.Value { 039 040 041 private static final long serialVersionUID = -652181533676125742L; 042 043 044 /** 045 * Informs the authorisation server that the client is making an OpenID 046 * Connect request (REQUIRED). This scope value requests access to the 047 * {@code sub} claim. 048 */ 049 public static final OIDCScopeValue OPENID = 050 new OIDCScopeValue("openid", Scope.Value.Requirement.REQUIRED, new String[]{"sub"}); 051 052 053 /** 054 * Requests that access to the end-user's default profile claims at the 055 * UserInfo endpoint be granted by the issued access token. These 056 * claims are: {@code name}, {@code family_name}, {@code given_name}, 057 * {@code middle_name}, {@code nickname}, {@code preferred_username}, 058 * {@code profile}, {@code picture}, {@code website}, {@code gender}, 059 * {@code birthdate}, {@code zoneinfo}, {@code locale}, and 060 * {@code updated_at}. 061 */ 062 public static final OIDCScopeValue PROFILE = 063 new OIDCScopeValue("profile", new String[]{"name", 064 "family_name", 065 "given_name", 066 "middle_name", 067 "nickname", 068 "preferred_username", 069 "profile", 070 "picture", 071 "website", 072 "gender", 073 "birthdate", 074 "zoneinfo", 075 "locale", 076 "updated_at"}); 077 078 079 /** 080 * Requests that access to the {@code email} and {@code email_verified} 081 * claims at the UserInfo endpoint be granted by the issued access 082 * token. 083 */ 084 public static final OIDCScopeValue EMAIL = 085 new OIDCScopeValue("email", new String[]{"email", "email_verified"}); 086 087 088 /** 089 * Requests that access to {@code address} claim at the UserInfo 090 * endpoint be granted by the issued access token. 091 */ 092 public static final OIDCScopeValue ADDRESS = 093 new OIDCScopeValue("address", new String[]{"address"}); 094 095 096 /** 097 * Requests that access to the {@code phone_number} and 098 * {@code phone_number_verified} claims at the UserInfo endpoint be 099 * granted by the issued access token. 100 */ 101 public static final OIDCScopeValue PHONE = 102 new OIDCScopeValue("phone", new String[]{"phone_number", 103 "phone_number_verified"}); 104 105 106 /** 107 * Requests that an OAuth 2.0 refresh token be issued that can be used 108 * to obtain an access token that grants access the end-user's UserInfo 109 * endpoint even when the user is not present (not logged in). 110 */ 111 public static final OIDCScopeValue OFFLINE_ACCESS = 112 new OIDCScopeValue("offline_access", null); 113 114 115 /** 116 * Returns the standard OpenID Connect scope values declared in this 117 * class. 118 * 119 * @return The standard OpenID Connect scope values. 120 */ 121 public static OIDCScopeValue[] values() { 122 123 return new OIDCScopeValue[]{ OPENID, PROFILE, EMAIL, ADDRESS, PHONE, OFFLINE_ACCESS }; 124 } 125 126 127 /** 128 * Resolves the claim names for all scope values that expand to claims. 129 * Recognises all standard OpenID Connect scope values as well as any 130 * that are additionally specified in the optional map. 131 * 132 * @param scope The scope, {@code null} if not specified. 133 * 134 * @return The resolved claim names, as an unmodifiable set, empty set 135 * if none. 136 */ 137 public static Set<String> resolveClaimNames(final Scope scope) { 138 139 return resolveClaimNames(scope, null); 140 } 141 142 143 /** 144 * Resolves the claim names for all scope values that expand to claims. 145 * Recognises all standard OpenID Connect scope values as well as any 146 * that are additionally specified in the optional map. 147 * 148 * @param scope The scope, {@code null} if not specified. 149 * @param customClaims Custom scope value to set of claim names map, 150 * {@code null} if not specified. 151 * 152 * @return The resolved claim names, as an unmodifiable set, empty set 153 * if none. 154 */ 155 public static Set<String> resolveClaimNames(final Scope scope, 156 final Map<Scope.Value, Set<String>> customClaims) { 157 158 Set<String> claimNames = new HashSet<>(); 159 160 if (scope != null) { 161 for (Scope.Value value: scope) { 162 for (OIDCScopeValue oidcValue: OIDCScopeValue.values()) { 163 if (OIDCScopeValue.OPENID.equals(oidcValue)) { 164 continue; // skip 165 } 166 if (oidcValue.equals(value) && oidcValue.getClaimNames() != null) { 167 claimNames.addAll(oidcValue.getClaimNames()); 168 } 169 } 170 if (customClaims != null && customClaims.get(value) != null) { 171 claimNames.addAll(customClaims.get(value)); 172 } 173 } 174 } 175 176 return Collections.unmodifiableSet(claimNames); 177 } 178 179 180 /** 181 * The names of the associated claims, {@code null} if not applicable. 182 */ 183 private final Set<String> claims; 184 185 186 /** 187 * Creates a new OpenID Connect scope value. 188 * 189 * @param value The scope value. Must not be {@code null}. 190 * @param requirement The requirement. Must not be {@code null}. 191 * @param claims The names of the associated claims, {@code null} 192 * if not applicable. 193 */ 194 private OIDCScopeValue(final String value, 195 final Scope.Value.Requirement requirement, 196 final String[] claims) { 197 198 super(value, requirement); 199 200 if (claims != null) 201 this.claims = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(claims))); 202 else 203 this.claims = null; 204 } 205 206 207 /** 208 * Creates a new OpenID Connect scope value. The requirement is set to 209 * {@link OIDCScopeValue.Requirement#OPTIONAL optional}. 210 * 211 * @param value The scope value. Must not be {@code null}. 212 * @param claims The names of the associated claims. Must not be 213 * {@code null}. 214 */ 215 private OIDCScopeValue(final String value, 216 final String[] claims) { 217 218 this(value, Scope.Value.Requirement.OPTIONAL, claims); 219 } 220 221 222 /** 223 * Returns the names of the associated claims. 224 * 225 * @return The names of the associated claims, {@code null} if not 226 * applicable. 227 */ 228 public Set<String> getClaimNames() { 229 230 return claims; 231 } 232 233 234 /** 235 * Gets the claims request JSON object for this OpenID Connect scope 236 * value. 237 * 238 * <p>See OpenID Connect Core 1.0 239 * 240 * <p>Example JSON object for "openid" scope value: 241 * 242 * <pre> 243 * { 244 * "sub" : { "essential" : true } 245 * } 246 * </pre> 247 * 248 * <p>Example JSON object for "email" scope value: 249 * 250 * <pre> 251 * { 252 * "email" : null, 253 * "email_verified" : null 254 * } 255 * </pre> 256 * 257 * @return The claims request JSON object, {@code null} if not 258 * applicable. 259 */ 260 public JSONObject toClaimsRequestJSONObject() { 261 262 JSONObject req = new JSONObject(); 263 264 if (claims == null) 265 return null; 266 267 for (String claim: claims) { 268 269 if (getRequirement() == Scope.Value.Requirement.REQUIRED) { 270 271 // Essential (applies to OPENID - sub only) 272 JSONObject details = new JSONObject(); 273 details.put("essential", true); 274 req.put(claim, details); 275 276 } else { 277 // Voluntary 278 req.put(claim, null); 279 } 280 } 281 282 return req; 283 } 284 285 286 /** 287 * Gets the claims request entries for this OpenID Connect scope value. 288 * 289 * <p>See OpenID Connect Core 1.0 290 * 291 * @see #toClaimsSetRequestEntries() 292 * 293 * @return The claims request entries, {@code null} if not applicable 294 * (for scope values {@link #OPENID} and 295 * {@link #OFFLINE_ACCESS}). 296 */ 297 @Deprecated 298 public Set<ClaimsRequest.Entry> toClaimsRequestEntries() { 299 300 Set<ClaimsRequest.Entry> entries = new HashSet<>(); 301 302 if (this == OPENID || this == OFFLINE_ACCESS) 303 return Collections.unmodifiableSet(entries); 304 305 for (String claimName: getClaimNames()) 306 entries.add(new ClaimsRequest.Entry(claimName).withClaimRequirement(ClaimRequirement.VOLUNTARY)); 307 308 return Collections.unmodifiableSet(entries); 309 } 310 311 312 /** 313 * Gets the OpenID claims request entries for this OpenID Connect scope 314 * value. 315 * 316 * <p>See OpenID Connect Core 1.0 317 * 318 * @return The OpenID claims request entries, {@code null} if not 319 * applicable (for scope values {@link #OPENID} and 320 * {@link #OFFLINE_ACCESS}). 321 */ 322 public List<ClaimsSetRequest.Entry> toClaimsSetRequestEntries() { 323 324 List<ClaimsSetRequest.Entry> entries = new LinkedList<>(); 325 326 if (this == OPENID || this == OFFLINE_ACCESS) 327 return Collections.unmodifiableList(entries); 328 329 for (String claimName: getClaimNames()) 330 entries.add(new ClaimsSetRequest.Entry(claimName).withClaimRequirement(ClaimRequirement.VOLUNTARY)); 331 332 return Collections.unmodifiableList(entries); 333 } 334}