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}