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 }