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 (oidcValue.equals(value)) {
164                                                claimNames.addAll(oidcValue.getClaimNames());
165                                        }
166                                }
167                                if (customClaims != null && customClaims.get(value) != null) {
168                                        claimNames.addAll(customClaims.get(value));
169                                }
170                        }
171                }
172
173                return Collections.unmodifiableSet(claimNames);
174        }
175
176
177        /**
178         * The names of the associated claims, {@code null} if not applicable.
179         */
180        private final Set<String> claims;
181
182
183        /**
184         * Creates a new OpenID Connect scope value.
185         *
186         * @param value       The scope value. Must not be {@code null}.
187         * @param requirement The requirement. Must not be {@code null}.
188         * @param claims      The names of the associated claims, {@code null} 
189         *                    if not applicable.
190         */
191        private OIDCScopeValue(final String value, 
192                               final Scope.Value.Requirement requirement,
193                               final String[] claims) {
194        
195                super(value, requirement);
196                
197                if (claims != null)
198                        this.claims = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(claims)));
199                else
200                        this.claims = null;
201        }
202
203
204        /**
205         * Creates a new OpenID Connect scope value. The requirement is set to
206         * {@link OIDCScopeValue.Requirement#OPTIONAL optional}.
207         *
208         * @param value  The scope value. Must not be {@code null}.
209         * @param claims The names of the associated claims. Must not be
210         *               {@code null}.
211         */
212        private OIDCScopeValue(final String value, 
213                               final String[] claims) {
214        
215                this(value, Scope.Value.Requirement.OPTIONAL, claims);
216        }
217
218
219        /**
220         * Returns the names of the associated claims.
221         *
222         * @return The names of the associated claims, {@code null} if not
223         *         applicable.
224         */
225        public Set<String> getClaimNames() {
226
227                return claims;
228        }
229        
230        
231        /**
232         * Gets the claims request JSON object for this OpenID Connect scope 
233         * value.
234         * 
235         * <p>See OpenID Connect Core 1.0
236         * 
237         * <p>Example JSON object for "openid" scope value:
238         * 
239         * <pre>
240         * {
241         *   "sub" : { "essential" : true }
242         * }
243         * </pre>
244         * 
245         * <p>Example JSON object for "email" scope value:
246         * 
247         * <pre>
248         * {
249         *   "email"          : null,
250         *   "email_verified" : null
251         * }
252         * </pre>
253         *
254         * @return The claims request JSON object, {@code null} if not
255         *         applicable.
256         */
257        public JSONObject toClaimsRequestJSONObject() {
258
259                JSONObject req = new JSONObject();
260
261                if (claims == null)
262                        return null;
263                
264                for (String claim: claims) {
265                
266                        if (getRequirement() == Scope.Value.Requirement.REQUIRED) {
267                        
268                                // Essential (applies to OPENID - sub only)
269                                JSONObject details = new JSONObject();
270                                details.put("essential", true);
271                                req.put(claim, details);
272                                
273                        } else {
274                                // Voluntary
275                                req.put(claim, null);
276                        }
277                }
278                
279                return req;
280        }
281        
282        
283        /**
284         * Gets the claims request entries for this OpenID Connect scope value.
285         * 
286         * <p>See OpenID Connect Core 1.0
287         *
288         * @see #toClaimsSetRequestEntries()
289         * 
290         * @return The claims request entries, {@code null} if not applicable 
291         *         (for scope values {@link #OPENID} and 
292         *         {@link #OFFLINE_ACCESS}).
293         */
294        @Deprecated
295        public Set<ClaimsRequest.Entry> toClaimsRequestEntries() {
296                
297                Set<ClaimsRequest.Entry> entries = new HashSet<>();
298                
299                if (this == OPENID || this == OFFLINE_ACCESS)
300                        return Collections.unmodifiableSet(entries);
301                
302                for (String claimName: getClaimNames())
303                        entries.add(new ClaimsRequest.Entry(claimName).withClaimRequirement(ClaimRequirement.VOLUNTARY));
304                
305                return Collections.unmodifiableSet(entries);
306        }
307        
308        
309        /**
310         * Gets the OpenID claims request entries for this OpenID Connect scope
311         * value.
312         *
313         * <p>See OpenID Connect Core 1.0
314         *
315         * @return The OpenID claims request entries, {@code null} if not
316         *         applicable (for scope values {@link #OPENID} and
317         *         {@link #OFFLINE_ACCESS}).
318         */
319        public List<ClaimsSetRequest.Entry> toClaimsSetRequestEntries() {
320                
321                List<ClaimsSetRequest.Entry> entries = new LinkedList<>();
322                
323                if (this == OPENID || this == OFFLINE_ACCESS)
324                        return Collections.unmodifiableList(entries);
325                
326                for (String claimName: getClaimNames())
327                        entries.add(new ClaimsSetRequest.Entry(claimName).withClaimRequirement(ClaimRequirement.VOLUNTARY));
328                
329                return Collections.unmodifiableList(entries);
330        }
331}