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