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 static 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 authorisation scope with the specified string values.
127         *
128         * @param values The string values.
129         */
130        public Scope(final String ... values) {
131
132                for (String v: values)
133                        add(new Value(v));
134        }
135
136
137        /**
138         * Creates a new authorisation scope with the specified values.
139         *
140         * @param values The values.
141         */
142        public Scope(final Value ... values) {
143
144                addAll(Arrays.asList(values));
145        }
146
147
148        /**
149         * Adds the specified string value to this scope.
150         *
151         * @param value The string value. Must not be {@code null}.
152         *
153         * @return {@code true} if this scope did not already contain the
154         *         specified value.
155         */
156        public boolean add(final String value) {
157
158                return add(new Value(value));
159        }
160
161
162        /**
163         * Checks if this scope contains the specified string value.
164         *
165         * @param value The string value. Must not be {@code null}.
166         *
167         * @return {@code true} if the value is contained, else {@code false}.
168         */
169        public boolean contains(final String value) {
170
171                return contains(new Value(value));
172        }
173
174        
175        /**
176         * Returns the string representation of this scope. The scope values 
177         * will be serialised in the order they were added.
178         *
179         * @return The string representation.
180         */
181        @Override
182        public String toString() {
183
184                StringBuilder sb = new StringBuilder();
185
186                for (Scope.Value v : this) {
187
188                        if (sb.length() > 0) {
189                                sb.append(' ');
190                        }
191
192                        sb.append(v.toString());
193                }
194
195                return sb.toString();
196        }
197
198
199        /**
200         * Returns the string list representation of this scope. The scope
201         * values will be serialised in the order they were added.
202         *
203         * @return The string list representation.
204         */
205        public List<String> toStringList() {
206
207                List<String> list = new ArrayList<>(this.size());
208
209                for (Scope.Value v: this)
210                        list.add(v.getValue());
211
212                return list;
213        }
214        
215        
216        /**
217         * Parses a scope from the specified string collection representation.
218         * 
219         * @param collection The string collection, {@code null} if not 
220         *                   specified.
221         * 
222         * @return The scope, {@code null} if not specified.
223         */
224        public static Scope parse(final Collection<String> collection) {
225                
226                if (collection == null)
227                        return null;
228                
229                Scope scope = new Scope();
230                
231                for (String v: collection)
232                        scope.add(new Scope.Value(v));
233                
234                return scope;
235        }
236
237        
238        /**
239         * Parses a scope from the specified string representation.
240         *
241         * @param s The scope string, {@code null} if not specified.
242         *
243         * @return The scope, {@code null} if not specified.
244         */
245        public static Scope parse(final String s) {
246
247                if (s == null)
248                        return null;
249
250                Scope scope = new Scope();
251
252                if (s.trim().isEmpty())
253                        return scope;
254
255                // OAuth specifies space as delimiter, also support comma (old draft)
256                StringTokenizer st = new StringTokenizer(s, " ,");
257
258                while(st.hasMoreTokens())
259                        scope.add(new Scope.Value(st.nextToken()));
260
261                return scope;
262        }
263}