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}