001/* 002 * oauth2-oidc-sdk 003 * 004 * Copyright 2012-2021, Connect2id Ltd and contributors. 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use 007 * this file except in compliance with the License. You may obtain a copy of the 008 * License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software distributed 013 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 014 * CONDITIONS OF ANY KIND, either express or implied. See the License for the 015 * specific language governing permissions and limitations under the License. 016 */ 017 018package com.nimbusds.oauth2.sdk.rar; 019 020import com.nimbusds.oauth2.sdk.ParseException; 021import com.nimbusds.oauth2.sdk.id.Identifier; 022import com.nimbusds.oauth2.sdk.util.JSONArrayUtils; 023import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; 024import com.nimbusds.oauth2.sdk.util.ListUtils; 025import net.minidev.json.JSONArray; 026import net.minidev.json.JSONObject; 027 028import java.util.Collections; 029import java.util.LinkedList; 030import java.util.List; 031import java.util.Objects; 032 033/** 034 * Authorisation detail. 035 * 036 * <p>Related specifications: 037 * 038 * <ul> 039 * <li>OAuth 2.0 Rich Authorization Requests (RFC 9396), section 2. 040 * </ul> 041 */ 042public class AuthorizationDetail { 043 044 045 /** 046 * Builder for constructing authorisation details. 047 */ 048 public static class Builder { 049 050 051 /** 052 * The authorisation details JSON object. 053 */ 054 private final JSONObject jsonObject = new JSONObject(); 055 056 057 /** 058 * Creates a new authorisation detail builder. 059 * 060 * @param type The authorisation type. Must not be 061 * {@code null}. 062 */ 063 public Builder(final AuthorizationType type) { 064 jsonObject.put("type", type.getValue()); 065 } 066 067 068 /** 069 * Sets the locations. 070 * 071 * @param locations The locations, {@code null} if not 072 * specified. 073 * 074 * @return This builder. 075 */ 076 public Builder locations(final List<Location> locations) { 077 if (locations != null) { 078 jsonObject.put("locations", Identifier.toStringList(locations)); 079 } else { 080 jsonObject.remove("locations"); 081 } 082 return this; 083 } 084 085 086 /** 087 * Sets the actions. 088 * 089 * @param actions The actions, {@code null} if not specified. 090 * 091 * @return This builder. 092 */ 093 public Builder actions(final List<Action> actions) { 094 if (actions != null) { 095 jsonObject.put("actions", Identifier.toStringList(actions)); 096 } else { 097 jsonObject.remove("actions"); 098 } 099 return this; 100 } 101 102 103 /** 104 * Sets the data types. 105 * 106 * @param dataTypes The data types, {@code null} if not 107 * specified. 108 * 109 * @return This builder. 110 */ 111 public Builder dataTypes(final List<DataType> dataTypes) { 112 if (dataTypes != null) { 113 jsonObject.put("datatypes", Identifier.toStringList(dataTypes)); 114 } else { 115 jsonObject.remove("datatypes"); 116 } 117 return this; 118 } 119 120 121 /** 122 * Sets the identifier. 123 * 124 * @param identifier The identifier, {@code null} if not 125 * specified. 126 * 127 * @return This builder. 128 */ 129 public Builder identifier(final Identifier identifier) { 130 if (identifier != null) { 131 jsonObject.put("identifier", identifier.getValue()); 132 } else { 133 jsonObject.remove("identifier"); 134 } 135 return this; 136 } 137 138 139 /** 140 * Sets the privileges. 141 * 142 * @param privileges The privileges, {@code null} if not 143 * specified. 144 * 145 * @return This builder. 146 */ 147 public Builder privileges(final List<Privilege> privileges) { 148 if (privileges != null) { 149 jsonObject.put("privileges", Identifier.toStringList(privileges)); 150 } else { 151 jsonObject.remove("privileges"); 152 } 153 return this; 154 } 155 156 157 /** 158 * Sets the specified authorisation detail field. 159 * 160 * @param name The field name. Must not be {@code null}. 161 * @param value The field value, {@code null} if not specified. 162 * 163 * @return This builder. 164 */ 165 public Builder field(final String name, final Object value) { 166 if (value != null) { 167 jsonObject.put(name, value); 168 } else { 169 jsonObject.remove(name); 170 } 171 return this; 172 } 173 174 175 /** 176 * Builds a new authorisation detail. 177 * 178 * @return The authorisation detail. 179 */ 180 public AuthorizationDetail build() { 181 return new AuthorizationDetail(jsonObject); 182 } 183 } 184 185 186 /** 187 * The authorisation details JSON object. 188 */ 189 private final JSONObject jsonObject; 190 191 192 /** 193 * Creates a new authorisation detail from the specified JSON object. 194 * 195 * @param jsonObject The JSON object. Must not be {@code null}. 196 */ 197 private AuthorizationDetail(final JSONObject jsonObject) { 198 Objects.requireNonNull(jsonObject); 199 this.jsonObject = jsonObject; 200 } 201 202 203 /** 204 * Returns the type. 205 * 206 * @return The type. 207 */ 208 public AuthorizationType getType() { 209 try { 210 return new AuthorizationType(JSONObjectUtils.getString(jsonObject, "type")); 211 } catch (Exception e) { 212 throw new RuntimeException(e); 213 } 214 } 215 216 217 /** 218 * Returns the locations. 219 * 220 * @return The locations as an unmodifiable list, {@code null} if not 221 * specified. 222 */ 223 public List<Location> getLocations() { 224 List<String> values = getStringListField("locations"); 225 if (values == null) { 226 return null; 227 } 228 List<Location> locations = new LinkedList<>(); 229 for (String v: ListUtils.removeNullItems(values)) { 230 locations.add(new Location(v)); 231 } 232 return Collections.unmodifiableList(locations); 233 } 234 235 236 /** 237 * Returns the actions. 238 * 239 * @return The actions as an unmodifiable list, {@code null} if not 240 * specified. 241 */ 242 public List<Action> getActions() { 243 List<String> values = getStringListField("actions"); 244 if (values == null) { 245 return null; 246 } 247 List<Action> actions = new LinkedList<>(); 248 for (String v: ListUtils.removeNullItems(values)) { 249 actions.add(new Action(v)); 250 } 251 return Collections.unmodifiableList(actions); 252 } 253 254 255 /** 256 * Returns the data types. 257 * 258 * @return The data type as an unmodifiable list, {@code null} if not 259 * specified. 260 */ 261 public List<DataType> getDataTypes() { 262 List<String> values = getStringListField("datatypes"); 263 if (values == null) { 264 return null; 265 } 266 List<DataType> dataTypes = new LinkedList<>(); 267 for (String v: ListUtils.removeNullItems(values)) { 268 dataTypes.add(new DataType(v)); 269 } 270 return Collections.unmodifiableList(dataTypes); 271 } 272 273 274 /** 275 * Returns the identifier. 276 * 277 * @return The identifier, {@code null} if not specified. 278 */ 279 public Identifier getIdentifier() { 280 String value; 281 try { 282 value = JSONObjectUtils.getString(jsonObject, "identifier"); 283 } catch (ParseException e) { 284 return null; 285 } 286 if (value.trim().isEmpty()) { 287 return null; 288 } 289 return new Identifier(value); 290 } 291 292 293 /** 294 * Returns the privileges. 295 * 296 * @return The privileges as an unmodifiable list, {@code null} if not 297 * specified. 298 */ 299 public List<Privilege> getPrivileges() { 300 List<String> values = getStringListField("privileges"); 301 if (values == null) { 302 return null; 303 } 304 List<Privilege> privileges = new LinkedList<>(); 305 for (String v: ListUtils.removeNullItems(values)) { 306 privileges.add(new Privilege(v)); 307 } 308 return Collections.unmodifiableList(privileges); 309 } 310 311 312 /** 313 * Returns the field with the specified name. 314 * 315 * @param name The field name. 316 * 317 * @return The field value, {@code null} if not specified. 318 */ 319 public Object getField(final String name) { 320 return jsonObject.get(name); 321 } 322 323 324 /** 325 * Returns the string field with the specified name. 326 * 327 * @param name The field name. 328 * 329 * @return The field value, {@code null} if not specified or parsing 330 * failed. 331 */ 332 public String getStringField(final String name) { 333 try { 334 return JSONObjectUtils.getString(jsonObject, name); 335 } catch (ParseException e) { 336 return null; 337 } 338 } 339 340 341 /** 342 * Returns the string list field with the specified name. 343 * 344 * @param name The field name. 345 * 346 * @return The field value, {@code null} if not specified or parsing 347 * failed. 348 */ 349 public List<String> getStringListField(final String name) { 350 try { 351 return JSONObjectUtils.getStringList(jsonObject, name); 352 } catch (ParseException e) { 353 return null; 354 } 355 } 356 357 358 /** 359 * Returns the JSON object field with the specified name. 360 * 361 * @param name The field name. 362 * 363 * @return The field value, {@code null} if not specified or parsing 364 * failed. 365 */ 366 public JSONObject getJSONObjectField(final String name) { 367 try { 368 return JSONObjectUtils.getJSONObject(jsonObject, name); 369 } catch (ParseException e) { 370 return null; 371 } 372 } 373 374 375 /** 376 * Returns a JSON object representation of this authorisation detail. 377 * 378 * @return The JSON object. 379 */ 380 public JSONObject toJSONObject() { 381 JSONObject o = new JSONObject(); 382 o.putAll(jsonObject); 383 return o; 384 } 385 386 387 @Override 388 public boolean equals(Object o) { 389 if (this == o) return true; 390 if (!(o instanceof AuthorizationDetail)) return false; 391 AuthorizationDetail detail = (AuthorizationDetail) o; 392 return Objects.equals(jsonObject, detail.jsonObject); 393 } 394 395 396 @Override 397 public int hashCode() { 398 return Objects.hash(jsonObject); 399 } 400 401 402 /** 403 * Returns the JSON array representation of the specified authorisation 404 * details. 405 * 406 * @param details The authorisation details. Must not be {@code null}. 407 * 408 * @return The JSON array. 409 */ 410 public static JSONArray toJSONArray(final List<AuthorizationDetail> details) { 411 JSONArray jsonArray = new JSONArray(); 412 for (AuthorizationDetail detail : details) { 413 jsonArray.add(detail.toJSONObject()); 414 } 415 return jsonArray; 416 } 417 418 419 /** 420 * Returns the JSON array string representation of the specified 421 * authorisation details. 422 * 423 * @param details The authorisation details. Must not be {@code null}. 424 * 425 * @return The JSON string. 426 */ 427 public static String toJSONString(final List<AuthorizationDetail> details) { 428 return toJSONArray(details).toJSONString(); 429 } 430 431 432 /** 433 * Parses an authorisation detail from the specified JSON object. 434 * 435 * @param jsonObject The JSON object. Must not be {@code null}. 436 * 437 * @return The authorisation detail. 438 * 439 * @throws ParseException If parsing failed. 440 */ 441 public static AuthorizationDetail parse(final JSONObject jsonObject) 442 throws ParseException { 443 444 AuthorizationDetail detail = new AuthorizationDetail(jsonObject); 445 446 // Verify a type is present 447 try { 448 detail.getType(); 449 } catch (Exception e) { 450 throw new ParseException("Illegal or missing type"); 451 } 452 453 return detail; 454 } 455 456 457 /** 458 * Parses an authorisation details list from the specified JSON objects 459 * list. 460 * 461 * @param jsonObjects The JSON objects list. Must not be {@code null}. 462 * 463 * @return The authorisation details, as unmodifiable list. 464 * 465 * @throws ParseException If parsing failed. 466 */ 467 public static List<AuthorizationDetail> parseList(final List<JSONObject> jsonObjects) 468 throws ParseException { 469 470 List<AuthorizationDetail> details = new LinkedList<>(); 471 472 int i=0; 473 for (JSONObject jsonObject: ListUtils.removeNullItems(jsonObjects)) { 474 475 AuthorizationDetail detail; 476 try { 477 detail = parse(jsonObject); 478 } catch (ParseException e) { 479 throw new ParseException("Invalid authorization detail at position " + i + ": " + e.getMessage()); 480 } 481 details.add(detail); 482 } 483 484 return Collections.unmodifiableList(details); 485 } 486 487 488 /** 489 * Parses an authorisation details list from the specified JSON array 490 * string. 491 * 492 * @param json The JSON string. Must not be {@code null}. 493 * 494 * @return The authorisation details, as unmodifiable list. 495 * 496 * @throws ParseException If parsing failed. 497 */ 498 public static List<AuthorizationDetail> parseList(final String json) 499 throws ParseException { 500 501 try { 502 JSONArray jsonArray = JSONArrayUtils.parse(json); 503 List<JSONObject> jsonObjects = JSONArrayUtils.toJSONObjectList(jsonArray); 504 return parseList(jsonObjects); 505 } catch (ParseException e) { 506 throw new ParseException("Invalid authorization details: " + e.getMessage()); 507 } 508 } 509}