001package com.nimbusds.openid.connect.provider.spi.grants; 002 003 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.List; 007 008import net.jcip.annotations.Immutable; 009import net.minidev.json.JSONObject; 010import org.checkerframework.checker.nullness.qual.Nullable; 011 012import com.nimbusds.oauth2.sdk.ParseException; 013import com.nimbusds.oauth2.sdk.Scope; 014import com.nimbusds.oauth2.sdk.id.Audience; 015import com.nimbusds.oauth2.sdk.util.CollectionUtils; 016import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 017import com.nimbusds.oauth2.sdk.util.MapUtils; 018 019 020/** 021 * Basic OAuth 2.0 authorisation produced by a {@link GrantHandler}. 022 * 023 * <p>Required authorisation details: 024 * 025 * <ul> 026 * <li>The authorised scope. 027 * </ul> 028 * 029 * <p>All other parameters are optional or have suitable defaults. 030 */ 031@Immutable 032public class GrantAuthorization { 033 034 035 /** 036 * The authorised scope. 037 */ 038 private final Scope scope; 039 040 041 /** 042 * The access token specification. 043 */ 044 private final AccessTokenSpec accessTokenSpec; 045 046 047 /** 048 * The OpenID Connect claims spec. 049 */ 050 private final ClaimsSpec claimsSpec; 051 052 053 /** 054 * Optional authorisation data as a JSON object, {@code null} if not 055 * specified. 056 */ 057 private final @Nullable JSONObject data; 058 059 060 /** 061 * Creates a new basic authorisation. 062 * 063 * @param scope The authorised scope values. Must not be {@code null}. 064 */ 065 public GrantAuthorization(final Scope scope) { 066 067 this(scope, AccessTokenSpec.DEFAULT, new ClaimsSpec(), null); 068 } 069 070 071 /** 072 * Creates a new basic authorisation. 073 * 074 * @param scope The authorised scope. Must not be 075 * {@code null}. 076 * @param accessTokenSpec The access token specification. Must not be 077 * {@code null}. 078 * @param data Additional data as a JSON object, 079 * {@code null} if not specified. 080 */ 081 public GrantAuthorization(final Scope scope, 082 final AccessTokenSpec accessTokenSpec, 083 final @Nullable JSONObject data) { 084 085 this(scope, accessTokenSpec, new ClaimsSpec(), data); 086 } 087 088 089 /** 090 * Creates a new basic authorisation. 091 * 092 * @param scope The authorised scope. Must not be 093 * {@code null}. 094 * @param accessTokenSpec The access token specification. Must not be 095 * {@code null}. 096 * @param claimsSpec The OpenID claims specification. Must not be 097 * {@code null}. 098 * @param data Additional data as a JSON object, 099 * {@code null} if not specified. 100 */ 101 public GrantAuthorization(final Scope scope, 102 final AccessTokenSpec accessTokenSpec, 103 final ClaimsSpec claimsSpec, 104 final @Nullable JSONObject data) { 105 106 if (scope == null) { 107 throw new IllegalArgumentException("The scope must not be null"); 108 } 109 110 this.scope = scope; 111 112 if (accessTokenSpec == null) { 113 throw new IllegalArgumentException("The access token specification must not be null"); 114 } 115 116 this.accessTokenSpec = accessTokenSpec; 117 118 if (claimsSpec == null) { 119 throw new IllegalArgumentException("The claim specification must not be null"); 120 } 121 122 this.claimsSpec = claimsSpec; 123 124 this.data = data; 125 } 126 127 128 /** 129 * Creates a new basic authorisation. 130 * 131 * @param scope The authorised scope. Must not be 132 * {@code null}. 133 * @param audList Explicit audience of the access token, 134 * {@code null} if not specified. 135 * @param accessTokenSpec The access token specification. Must not be 136 * {@code null}. 137 * @param data Additional data as a JSON object, 138 * {@code null} if not specified. 139 */ 140 @Deprecated 141 public GrantAuthorization(final Scope scope, 142 final @Nullable List<Audience> audList, 143 final AccessTokenSpec accessTokenSpec, 144 final @Nullable JSONObject data) { 145 146 this(scope, 147 new AccessTokenSpec( 148 accessTokenSpec.getLifetime(), 149 audList, // override with top-level parameter, backward compat API 150 accessTokenSpec.getEncoding(), 151 accessTokenSpec.getImpersonatedSubject(), 152 accessTokenSpec.encrypt(), 153 accessTokenSpec.getSubjectType()), 154 data); 155 } 156 157 158 /** 159 * Returns the authorised scope. 160 * 161 * @return The authorised scope. 162 */ 163 public Scope getScope() { 164 165 return scope; 166 } 167 168 169 /** 170 * Returns the explicit audience of the access token. 171 * 172 * @return The audience of the access token, {@code null} if not 173 * specified. 174 */ 175 @Deprecated 176 public @Nullable List<Audience> getAudience() { 177 178 return getAccessTokenSpec().getAudience(); 179 } 180 181 182 /** 183 * Returns the access token specification. 184 * 185 * @return The access token specification. 186 */ 187 public AccessTokenSpec getAccessTokenSpec() { 188 189 return accessTokenSpec; 190 } 191 192 193 /** 194 * Returns the OpenID claims specification. 195 * 196 * @return The OpenID claims specification. 197 */ 198 public ClaimsSpec getClaimsSpec() { 199 200 return claimsSpec; 201 } 202 203 204 /** 205 * Returns the additional data as a JSON object. 206 * 207 * @return The additional data, {@code null} if not specified. 208 */ 209 public @Nullable JSONObject getData() { 210 211 return data; 212 } 213 214 215 /** 216 * Returns a JSON object representation of this authorisation. 217 * 218 * @return The JSON object representation. 219 */ 220 public JSONObject toJSONObject() { 221 222 JSONObject o = new JSONObject(); 223 224 o.put("scope", scope.toStringList()); 225 226 JSONObject accessTokenSpecJSONObject = accessTokenSpec.toJSONObject(); 227 228 // Backward API compat 229 // TODO remove in future major server version, deprecated in server v12.0 230 if (CollectionUtils.isNotEmpty(getAccessTokenSpec().getAudience())) { 231 o.put("audience", accessTokenSpecJSONObject.get("audience")); 232 } 233 234 o.put("access_token", accessTokenSpecJSONObject); 235 236 o.putAll(claimsSpec.toJSONObject()); 237 238 if (MapUtils.isNotEmpty(data)) { 239 o.put("data", data); 240 } 241 242 return o; 243 } 244 245 246 /** 247 * Parses a basic authorisation from the specified JSON object. 248 * 249 * @param jsonObject The JSON object to parse. Must not be 250 * {@code null}. 251 * 252 * @return The basic authorisation. 253 * 254 * @throws ParseException If parsing failed. 255 */ 256 public static GrantAuthorization parse(final JSONObject jsonObject) 257 throws ParseException { 258 259 Scope scope = Scope.parse(Arrays.asList(JSONObjectUtils.getStringArray(jsonObject, "scope"))); 260 261 // Backward API compat 262 List<Audience> topLevelAudList = null; 263 if (jsonObject.containsKey("audience")) { 264 String[] sa = JSONObjectUtils.getStringArray(jsonObject, "audience"); 265 topLevelAudList = new ArrayList<>(sa.length); 266 for (String s: sa) { 267 topLevelAudList.add(new Audience(s)); 268 } 269 } 270 271 AccessTokenSpec accessTokenSpec; 272 273 if (jsonObject.get("access_token") != null) { 274 // Parse 275 JSONObject accessTokenJSONObject = JSONObjectUtils.getJSONObject(jsonObject, "access_token"); 276 if (topLevelAudList != null) { 277 accessTokenJSONObject.put("audience", Audience.toStringList(topLevelAudList)); 278 } 279 accessTokenSpec = AccessTokenSpec.parse(accessTokenJSONObject); 280 if (topLevelAudList != null) { 281 accessTokenSpec = new AccessTokenSpec( 282 accessTokenSpec.getLifetime(), 283 topLevelAudList, // Backward API compat 284 accessTokenSpec.getEncoding(), 285 accessTokenSpec.getImpersonatedSubject(), 286 accessTokenSpec.encrypt(), 287 accessTokenSpec.getSubjectType()); 288 } 289 } else { 290 // Apply default settings 291 accessTokenSpec = new AccessTokenSpec(); 292 } 293 294 ClaimsSpec claimsSpec = ClaimsSpec.parse(jsonObject); 295 296 JSONObject data = null; 297 298 if (jsonObject.containsKey("data")) { 299 data = JSONObjectUtils.getJSONObject(jsonObject, "data"); 300 } 301 302 return new GrantAuthorization(scope, accessTokenSpec, claimsSpec, data); 303 } 304 305 306 /** 307 * Parses a basic authorisation from the specified JSON object string. 308 * 309 * @param json The JSON object string to parse. Must not be 310 * {@code null}. 311 * 312 * @return The basic authorisation. 313 * 314 * @throws ParseException If parsing failed. 315 */ 316 public static GrantAuthorization parse(final String json) 317 throws ParseException { 318 319 return parse(JSONObjectUtils.parse(json)); 320 } 321}