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}