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