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.oauth2.sdk;
019
020
021import java.util.*;
022
023import net.jcip.annotations.Immutable;
024import net.jcip.annotations.NotThreadSafe;
025
026import com.nimbusds.oauth2.sdk.id.Identifier;
027
028
029/**
030 * Authorisation scope.
031 *
032 * <p>Example scope from OpenID Connect indicating access to the user's email
033 * and profile details:
034 *
035 * <pre>
036 * Scope scope = new Scope();
037 * scope.add(OIDCScopeValue.OPENID);
038 * scope.add(OIDCScopeValue.EMAIL);
039 * scope.add(OIDCScopeValue.PROFILE);
040 * </pre>
041 *
042 * <p>Related specifications:
043 *
044 * <ul>
045 *     <li>OAuth 2.0 (RFC 6749), section 3.3.
046 * </ul>
047 */
048@NotThreadSafe
049public class Scope extends LinkedHashSet<Scope.Value> {
050        
051        
052        private static final long serialVersionUID = -553103514038936007L;
053        
054        
055        /**
056         * Authorisation scope value.
057         */
058        @Immutable
059        public static class Value extends Identifier {
060                
061                
062                private static final long serialVersionUID = -1885648673808651565L;
063                
064                
065                /**
066                 * Enumeration of the scope value requirements for 
067                 * application-specific authorisation requests.
068                 */
069                public enum Requirement {
070
071                        
072                        /**
073                         * The value must be present in the {@link Scope}
074                         * parameter.
075                         */
076                        REQUIRED,
077                        
078                        
079                        /**
080                         * The value may be optionally included in the
081                         * {@link Scope} parameter.
082                         */
083                        OPTIONAL
084                }
085                
086                
087                /**
088                 * Optional requirement.
089                 */
090                private final Value.Requirement requirement;
091                
092
093                /**
094                 * Creates a new scope value. The requirement is not specified.
095                 *
096                 * @param value The scope value. Must not be {@code null} or
097                 *              empty string.
098                 */
099                public Value(final String value) {
100
101                        this(value, null);
102                }
103
104                /**
105                 * Creates a new scope value with an optional requirement.
106                 *
107                 * @param value       The scope value. Must not be {@code null} 
108                 *                    or empty string.
109                 * @param requirement The requirement, {@code null} if not
110                 *                    specified.
111                 */
112                public Value(final String value, final Requirement requirement) {
113
114                        super(value);
115
116                        this.requirement = requirement;
117                }
118
119                
120                /**
121                 * Gets the requirement of this scope value.
122                 *
123                 * @return The requirement, {@code null} if not specified.
124                 */
125                public Requirement getRequirement() {
126
127                        return requirement;
128                }
129
130                
131                @Override
132                public boolean equals(final Object object) {
133
134                        return object instanceof Value &&
135                               this.toString().equals(object.toString());
136                }
137        }
138
139        
140        /**
141         * Creates a new empty authorisation scope.
142         */
143        public Scope() {
144                // Nothing to do
145        }
146
147
148        /**
149         * Creates a new scope from the specified scope.
150         *
151         * @param scope The scope. May be {@code null}.
152         */
153        public Scope(final Scope scope) {
154
155                if (scope == null) {
156                        return;
157                }
158
159                addAll(scope);
160        }
161
162
163        /**
164         * Creates a new authorisation scope with the specified string values.
165         *
166         * @param values The string values.
167         */
168        public Scope(final String ... values) {
169
170                for (String v: values)
171                        add(new Value(v));
172        }
173
174
175        /**
176         * Creates a new authorisation scope with the specified values.
177         *
178         * @param values The values.
179         */
180        public Scope(final Value ... values) {
181
182                addAll(Arrays.asList(values));
183        }
184
185
186        /**
187         * Adds the specified string value to this scope.
188         *
189         * @param value The string value. Must not be {@code null}.
190         *
191         * @return {@code true} if this scope did not already contain the
192         *         specified value.
193         */
194        public boolean add(final String value) {
195
196                return add(new Value(value));
197        }
198
199
200        /**
201         * Checks if this scope contains the specified string value.
202         *
203         * @param value The string value. Must not be {@code null}.
204         *
205         * @return {@code true} if the value is contained, else {@code false}.
206         */
207        public boolean contains(final String value) {
208
209                return contains(new Value(value));
210        }
211
212        
213        /**
214         * Returns the string representation of this scope. The scope values 
215         * will be serialised in the order they were added.
216         *
217         * @return The string representation.
218         */
219        @Override
220        public String toString() {
221
222                StringBuilder sb = new StringBuilder();
223
224                for (Scope.Value v : this) {
225
226                        if (sb.length() > 0) {
227                                sb.append(' ');
228                        }
229
230                        sb.append(v.toString());
231                }
232
233                return sb.toString();
234        }
235
236
237        /**
238         * Returns the string list representation of this scope. The scope
239         * values will be serialised in the order they were added.
240         *
241         * @return The string list representation.
242         */
243        public List<String> toStringList() {
244
245                List<String> list = new ArrayList<>(this.size());
246
247                for (Scope.Value v: this)
248                        list.add(v.getValue());
249
250                return list;
251        }
252        
253        
254        /**
255         * Parses a scope from the specified string collection representation.
256         * 
257         * @param collection The string collection, {@code null} if not 
258         *                   specified.
259         * 
260         * @return The scope, {@code null} if not specified.
261         */
262        public static Scope parse(final Collection<String> collection) {
263                
264                if (collection == null)
265                        return null;
266                
267                Scope scope = new Scope();
268                
269                for (String v: collection)
270                        scope.add(new Scope.Value(v));
271                
272                return scope;
273        }
274
275        
276        /**
277         * Parses a scope from the specified string representation.
278         *
279         * @param s The scope string, {@code null} if not specified.
280         *
281         * @return The scope, {@code null} if not specified.
282         */
283        public static Scope parse(final String s) {
284
285                if (s == null)
286                        return null;
287
288                Scope scope = new Scope();
289
290                if (s.trim().isEmpty())
291                        return scope;
292
293                // OAuth specifies space as delimiter, also support comma (old draft)
294                StringTokenizer st = new StringTokenizer(s, " ,");
295
296                while(st.hasMoreTokens())
297                        scope.add(new Scope.Value(st.nextToken()));
298
299                return scope;
300        }
301}