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