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}