001    package com.nimbusds.oauth2.sdk;
002    
003    
004    import java.util.HashSet;
005    import java.util.StringTokenizer;
006    
007    import net.jcip.annotations.Immutable;
008    import net.jcip.annotations.NotThreadSafe;
009    
010    import com.nimbusds.oauth2.sdk.id.Identifier;
011    
012    
013    /**
014     * Authorisation scope. This class is not thread-safe.
015     *
016     * <p>Example scope from OpenID Connect indicating access to the user's email
017     * and profile details:
018     *
019     * <pre>
020     * Scope scope = new Scope();
021     * scope.add(OIDCScopeValue.OPENID);
022     * scope.add(OIDCScopeValue.EMAIL);
023     * scope.add(OIDCScopeValue.PROFILE);
024     * </pre>
025     *
026     * <p>Related specifications:
027     *
028     * <ul>
029     * <li>OAuth 2.0 (RFC 6749), section 3.3.
030     * </ul>
031     *
032     * @author Vladimir Dzhuvinov
033     */
034    @NotThreadSafe
035    public class Scope extends HashSet<Scope.Value> {
036    
037            
038            /**
039             * Authorisation scope value. This class is immutable.
040             */
041            @Immutable
042            public static class Value extends Identifier {
043    
044                    /**
045                     * Enumeration of the scope value requirements for 
046                     * application-specific authorisation requests.
047                     */
048                    public static enum Requirement {
049    
050                            /**
051                             * The value must be present in the {@link Scope}
052                             * parameter.
053                             */
054                            REQUIRED,
055                            /**
056                             * The value may be optionally included in the
057                             * {@link Scope} parameter.
058                             */
059                            OPTIONAL
060                    }
061                    
062                    
063                    /**
064                     * Optional requirement.
065                     */
066                    private final Value.Requirement requirement;
067    
068                    /**
069                     * Creates a new scope value. The requirement is not specified.
070                     *
071                     * @param value The scope value. Must not be {@code null} or
072                     *              empty string.
073                     */
074                    public Value(final String value) {
075    
076                            this(value, null);
077                    }
078    
079                    /**
080                     * Creates a new scope value with an optional requirement.
081                     *
082                     * @param value       The scope value. Must not be {@code null} 
083                     *                    or empty string.
084                     * @param requirement The requirement, {@code null} if not
085                     *                    specified.
086                     */
087                    public Value(final String value, final Requirement requirement) {
088    
089                            super(value);
090    
091                            this.requirement = requirement;
092                    }
093    
094                    /**
095                     * Gets the requirement of this scope value.
096                     *
097                     * @return The requirement, {@code null} if not specified.
098                     */
099                    public Requirement getRequirement() {
100    
101                            return requirement;
102                    }
103    
104                    @Override
105                    public boolean equals(final Object object) {
106    
107                            return object != null
108                                    && object instanceof Value
109                                    && this.toString().equals(object.toString());
110                    }
111            }
112    
113            
114            /**
115             * Creates a new empty authorisation scope.
116             */
117            public Scope() {
118                    // Nothing to do
119            }
120    
121            
122            /**
123             * Returns the string representation of this scope. The scope values 
124             * may be serialised in any order.
125             *
126             * @return The string representation.
127             */
128            @Override
129            public String toString() {
130    
131                    StringBuilder sb = new StringBuilder();
132    
133                    for (Scope.Value token : this) {
134    
135                            if (sb.length() > 0) {
136                                    sb.append(' ');
137                            }
138    
139                            sb.append(token.toString());
140                    }
141    
142                    return sb.toString();
143            }
144    
145            
146            /**
147             * Parses a scope from the specified string representation.
148             *
149             * @param s The scope string, {@code null} if not specified.
150             *
151             * @return The scope, {@code null} if not specified.
152             */
153            public static Scope parse(final String s) {
154    
155                    if (s == null) {
156                            return null;
157                    }
158    
159                    Scope scope = new Scope();
160    
161                    if (s.trim().isEmpty()) {
162                            return scope;
163                    }
164    
165                    StringTokenizer st = new StringTokenizer(s, " ");
166    
167                    while(st.hasMoreTokens())
168                            scope.add(new Scope.Value(st.nextToken()));
169    
170                    return scope;
171            }
172    }