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) 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 this.jsonObject = Objects.requireNonNull(jsonObject); 199 } 200 201 202 /** 203 * Returns the type. 204 * 205 * @return The type. 206 */ 207 public AuthorizationType getType() { 208 try { 209 return new AuthorizationType(JSONObjectUtils.getNonBlankString(jsonObject, "type")); 210 } catch (Exception e) { 211 throw new RuntimeException(e); 212 } 213 } 214 215 216 /** 217 * Returns the locations. 218 * 219 * @return The locations as an unmodifiable list, {@code null} if not 220 * specified. 221 */ 222 public List<Location> getLocations() { 223 List<String> values = getStringListField("locations"); 224 if (values == null) { 225 return null; 226 } 227 List<Location> locations = new LinkedList<>(); 228 for (String v: ListUtils.removeNullItems(values)) { 229 locations.add(new Location(v)); 230 } 231 return Collections.unmodifiableList(locations); 232 } 233 234 235 /** 236 * Returns the actions. 237 * 238 * @return The actions as an unmodifiable list, {@code null} if not 239 * specified. 240 */ 241 public List<Action> getActions() { 242 List<String> values = getStringListField("actions"); 243 if (values == null) { 244 return null; 245 } 246 List<Action> actions = new LinkedList<>(); 247 for (String v: ListUtils.removeNullItems(values)) { 248 actions.add(new Action(v)); 249 } 250 return Collections.unmodifiableList(actions); 251 } 252 253 254 /** 255 * Returns the data types. 256 * 257 * @return The data type as an unmodifiable list, {@code null} if not 258 * specified. 259 */ 260 public List<DataType> getDataTypes() { 261 List<String> values = getStringListField("datatypes"); 262 if (values == null) { 263 return null; 264 } 265 List<DataType> dataTypes = new LinkedList<>(); 266 for (String v: ListUtils.removeNullItems(values)) { 267 dataTypes.add(new DataType(v)); 268 } 269 return Collections.unmodifiableList(dataTypes); 270 } 271 272 273 /** 274 * Returns the identifier. 275 * 276 * @return The identifier, {@code null} if not specified. 277 */ 278 public Identifier getIdentifier() { 279 String value; 280 try { 281 value = JSONObjectUtils.getNonBlankString(jsonObject, "identifier"); 282 } catch (ParseException e) { 283 return null; 284 } 285 if (value.trim().isEmpty()) { 286 return null; 287 } 288 return new Identifier(value); 289 } 290 291 292 /** 293 * Returns the privileges. 294 * 295 * @return The privileges as an unmodifiable list, {@code null} if not 296 * specified. 297 */ 298 public List<Privilege> getPrivileges() { 299 List<String> values = getStringListField("privileges"); 300 if (values == null) { 301 return null; 302 } 303 List<Privilege> privileges = new LinkedList<>(); 304 for (String v: ListUtils.removeNullItems(values)) { 305 privileges.add(new Privilege(v)); 306 } 307 return Collections.unmodifiableList(privileges); 308 } 309 310 311 /** 312 * Returns the field with the specified name. 313 * 314 * @param name The field name. 315 * 316 * @return The field value, {@code null} if not specified. 317 */ 318 public Object getField(final String name) { 319 return jsonObject.get(name); 320 } 321 322 323 /** 324 * Returns the string field with the specified name. 325 * 326 * @param name The field name. 327 * 328 * @return The field value, {@code null} if not specified or parsing 329 * failed. 330 */ 331 public String getStringField(final String name) { 332 try { 333 return JSONObjectUtils.getNonBlankString(jsonObject, name); 334 } catch (ParseException e) { 335 return null; 336 } 337 } 338 339 340 /** 341 * Returns the string list field with the specified name. 342 * 343 * @param name The field name. 344 * 345 * @return The field value, {@code null} if not specified or parsing 346 * failed. 347 */ 348 public List<String> getStringListField(final String name) { 349 try { 350 return JSONObjectUtils.getStringList(jsonObject, name); 351 } catch (ParseException e) { 352 return null; 353 } 354 } 355 356 357 /** 358 * Returns the JSON object field with the specified name. 359 * 360 * @param name The field name. 361 * 362 * @return The field value, {@code null} if not specified or parsing 363 * failed. 364 */ 365 public JSONObject getJSONObjectField(final String name) { 366 try { 367 return JSONObjectUtils.getJSONObject(jsonObject, name); 368 } catch (ParseException e) { 369 return null; 370 } 371 } 372 373 374 /** 375 * Returns a JSON object representation of this authorisation detail. 376 * 377 * @return The JSON object. 378 */ 379 public JSONObject toJSONObject() { 380 JSONObject o = new JSONObject(); 381 o.putAll(jsonObject); 382 return o; 383 } 384 385 386 @Override 387 public boolean equals(Object o) { 388 if (this == o) return true; 389 if (!(o instanceof AuthorizationDetail)) return false; 390 AuthorizationDetail detail = (AuthorizationDetail) o; 391 return Objects.equals(jsonObject, detail.jsonObject); 392 } 393 394 395 @Override 396 public int hashCode() { 397 return Objects.hash(jsonObject); 398 } 399 400 401 /** 402 * Returns the JSON array representation of the specified authorisation 403 * details. 404 * 405 * @param details The authorisation details. Must not be {@code null}. 406 * 407 * @return The JSON array. 408 */ 409 public static JSONArray toJSONArray(final List<AuthorizationDetail> details) { 410 JSONArray jsonArray = new JSONArray(); 411 for (AuthorizationDetail detail : details) { 412 jsonArray.add(detail.toJSONObject()); 413 } 414 return jsonArray; 415 } 416 417 418 /** 419 * Returns the JSON array string representation of the specified 420 * authorisation details. 421 * 422 * @param details The authorisation details. Must not be {@code null}. 423 * 424 * @return The JSON string. 425 */ 426 public static String toJSONString(final List<AuthorizationDetail> details) { 427 return toJSONArray(details).toJSONString(); 428 } 429 430 431 /** 432 * Parses an authorisation detail from the specified JSON object. 433 * 434 * @param jsonObject The JSON object. Must not be {@code null}. 435 * 436 * @return The authorisation detail. 437 * 438 * @throws ParseException If parsing failed. 439 */ 440 public static AuthorizationDetail parse(final JSONObject jsonObject) 441 throws ParseException { 442 443 AuthorizationDetail detail = new AuthorizationDetail(jsonObject); 444 445 // Verify a type is present 446 try { 447 detail.getType(); 448 } catch (Exception e) { 449 throw new ParseException("Illegal or missing type"); 450 } 451 452 return detail; 453 } 454 455 456 /** 457 * Parses an authorisation details list from the specified JSON objects 458 * list. 459 * 460 * @param jsonObjects The JSON objects list. Must not be {@code null}. 461 * 462 * @return The authorisation details, as unmodifiable list. 463 * 464 * @throws ParseException If parsing failed. 465 */ 466 public static List<AuthorizationDetail> parseList(final List<JSONObject> jsonObjects) 467 throws ParseException { 468 469 List<AuthorizationDetail> details = new LinkedList<>(); 470 471 int i=0; 472 for (JSONObject jsonObject: ListUtils.removeNullItems(jsonObjects)) { 473 474 AuthorizationDetail detail; 475 try { 476 detail = parse(jsonObject); 477 } catch (ParseException e) { 478 throw new ParseException("Invalid authorization detail at position " + i + ": " + e.getMessage()); 479 } 480 details.add(detail); 481 } 482 483 return Collections.unmodifiableList(details); 484 } 485 486 487 /** 488 * Parses an authorisation details list from the specified JSON array 489 * string. 490 * 491 * @param json The JSON string. Must not be {@code null}. 492 * 493 * @return The authorisation details, as unmodifiable list. 494 * 495 * @throws ParseException If parsing failed. 496 */ 497 public static List<AuthorizationDetail> parseList(final String json) 498 throws ParseException { 499 500 try { 501 JSONArray jsonArray = JSONArrayUtils.parse(json); 502 List<JSONObject> jsonObjects = JSONArrayUtils.toJSONObjectList(jsonArray); 503 return parseList(jsonObjects); 504 } catch (ParseException e) { 505 throw new ParseException("Invalid authorization details: " + e.getMessage()); 506 } 507 } 508}