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}