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}