001package com.nimbusds.openid.connect.sdk;
002
003
004import java.util.Arrays;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.LinkedHashSet;
008import java.util.Set;
009
010import net.minidev.json.JSONObject;
011
012import com.nimbusds.oauth2.sdk.Scope;
013
014import com.nimbusds.openid.connect.sdk.claims.ClaimRequirement;
015
016
017/**
018 * Standard OpenID Connect scope value.
019 * 
020 * <p>Related specifications:
021 *
022 * <ul>
023 *     <li>OpenID Connect Messages 1.0, section 2.4.
024 * </ul>
025 *
026 * @author Vladimir Dzhuvinov
027 */
028public class OIDCScopeValue extends Scope.Value {
029
030
031        /**
032         * Informs the authorisation server that the client is making an OpenID 
033         * Connect request (REQUIRED). This scope value requests access to the
034         * {@code sub} claim. 
035         */
036        public static final OIDCScopeValue OPENID =
037                new OIDCScopeValue("openid", Scope.Value.Requirement.REQUIRED, new String[]{"sub"});
038        
039        
040        /**
041         * Requests that access to the end-user's default profile claims at the 
042         * UserInfo endpoint be granted by the issued access token. These 
043         * claims are: {@code name}, {@code family_name}, {@code given_name}, 
044         * {@code middle_name}, {@code nickname}, {@code preferred_username}, 
045         * {@code profile}, {@code picture}, {@code website}, {@code gender}, 
046         * {@code birthdate}, {@code zoneinfo}, {@code locale}, and 
047         * {@code updated_at}. 
048         */
049        public static final OIDCScopeValue PROFILE =
050                new OIDCScopeValue("profile", new String[]{"name",
051                                                           "family_name",
052                                                           "given_name",
053                                                           "middle_name",
054                                                           "nickname",
055                                                           "preferred_username",
056                                                           "profile",
057                                                           "picture",
058                                                           "website",
059                                                           "gender",
060                                                           "birthdate",
061                                                           "zoneinfo",
062                                                           "locale",
063                                                           "updated_at"});
064        
065        
066        /**
067         * Requests that access to the {@code email} and {@code email_verified}
068         * claims at the UserInfo endpoint be granted by the issued access 
069         * token.
070         */
071        public static final OIDCScopeValue EMAIL =
072                new OIDCScopeValue("email", new String[]{"email", "email_verified"});
073        
074        
075        /**
076         * Requests that access to {@code address} claim at the UserInfo 
077         * endpoint be granted by the issued access token. 
078         */
079        public static final OIDCScopeValue ADDRESS =
080                new OIDCScopeValue("address", new String[]{"address"});
081        
082        
083        /**
084         * Requests that access to the {@code phone_number} and
085         * {@code phone_number_verified} claims at the UserInfo endpoint be 
086         * granted by the issued access token. 
087         */
088        public static final OIDCScopeValue PHONE =
089                new OIDCScopeValue("phone", new String[]{"phone_number",
090                                                         "phone_number_verified"});
091
092
093        /**
094         * Requests that an OAuth 2.0 refresh token be issued that can be used
095         * to obtain an access token that grants access the end-user's UserInfo
096         * endpoint even when the user is not present (not logged in).
097         */
098        public static final OIDCScopeValue OFFLINE_ACCESS =
099                new OIDCScopeValue("offline_access", null);
100
101
102        /**
103         * The names of the associated claims, {@code null} if not applicable.
104         */
105        private final Set<String> claims;
106
107
108        /**
109         * Creates a new OpenID Connect scope value.
110         *
111         * @param value       The scope value. Must not be {@code null}.
112         * @param requirement The requirement. Must not be {@code null}.
113         * @param claims      The names of the associated claims, {@code null} 
114         *                    if not applicable.
115         */
116        private OIDCScopeValue(final String value, 
117                               final Scope.Value.Requirement requirement,
118                               final String[] claims) {
119        
120                super(value, requirement);
121                
122                if (claims != null)
123                        this.claims = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(claims)));
124                else
125                        this.claims = null;
126        }
127
128
129        /**
130         * Creates a new OpenID Connect scope value. The requirement is set to
131         * {@link OIDCScopeValue.Requirement#OPTIONAL optional}.
132         *
133         * @param value  The scope value. Must not be {@code null}.
134         * @param claims The names of the associated claims. Must not be
135         *               {@code null}.
136         */
137        private OIDCScopeValue(final String value, 
138                               final String[] claims) {
139        
140                this(value, Scope.Value.Requirement.OPTIONAL, claims);
141        }
142
143
144        /**
145         * Returns the names of the associated claims.
146         *
147         * @return The names of the associated claims, {@code null} if not
148         *         applicable.
149         */
150        public Set<String> getClaimNames() {
151
152                return claims;
153        }
154        
155        
156        /**
157         * Gets the claims request JSON object for this OpenID Connect scope 
158         * value.
159         * 
160         * <p>See OpenID Connect Messages 1.0, section 2.6.1.
161         * 
162         * <p>Example JSON object for "openid" scope value:
163         * 
164         * <pre>
165         * {
166         *   "openid" : { "essential" : true }
167         * }
168         * </pre>
169         * 
170         * <p>Example JSON object for "email" scope value:
171         * 
172         * <pre>
173         * {
174         *   "email"          : null,
175         *   "email_verified" : null
176         * }
177         * </pre>
178         *
179         * @return The claims request JSON object, {@code null} if not
180         *         applicable.
181         */
182        public JSONObject toClaimsRequestJSONObject() {
183
184                JSONObject req = new JSONObject();
185                
186                for (String claim: claims) {
187                
188                        if (getRequirement() == Scope.Value.Requirement.REQUIRED) {
189                        
190                                // Essential (applies to OPENID - sub only)
191                                JSONObject details = new JSONObject();
192                                details.put("essential", true);
193                                req.put(claim, details);
194                                
195                        } else {
196                                // Voluntary
197                                req.put(claim, null);
198                        }
199                }
200                
201                return req;
202        }
203        
204        
205        /**
206         * Gets the claims request entries for this OpenID Connect scope value.
207         * 
208         * <p>See OpenID Connect Messages 1.0, section 2.6.1.
209         * 
210         * @return The claims request entries, {@code null} if not applicable 
211         *         (for scope values {@link #OPENID} and 
212         *         {@link #OFFLINE_ACCESS}).
213         */
214        public Set<ClaimsRequest.Entry> toClaimsRequestEntries() {
215                
216                Set<ClaimsRequest.Entry> entries = new HashSet<ClaimsRequest.Entry>();
217                
218                if (this == OPENID || this == OFFLINE_ACCESS)
219                        return Collections.unmodifiableSet(entries);
220                
221                for (String claimName: getClaimNames())
222                        entries.add(new ClaimsRequest.Entry(claimName, ClaimRequirement.VOLUNTARY));
223                
224                return Collections.unmodifiableSet(entries);
225        }
226}