001package com.nimbusds.oauth2.sdk;
002
003
004import java.util.Date;
005import java.util.List;
006
007import com.nimbusds.jwt.util.DateUtils;
008import com.nimbusds.oauth2.sdk.http.CommonContentTypes;
009import com.nimbusds.oauth2.sdk.http.HTTPResponse;
010import com.nimbusds.oauth2.sdk.id.*;
011import com.nimbusds.oauth2.sdk.token.AccessTokenType;
012import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
013import net.jcip.annotations.Immutable;
014import net.minidev.json.JSONObject;
015
016
017/**
018 * Token introspection success response.
019 *
020 * <p>Related specifications:
021 *
022 * <ul>
023 *     <li>OAuth 2.0 Token Introspection (RFC 7662).
024 * </ul>
025 */
026@Immutable
027public class TokenIntrospectionSuccessResponse extends TokenIntrospectionResponse implements SuccessResponse {
028
029
030        /**
031         * Builder for constructing token introspection success responses.
032         */
033        public static class Builder {
034
035
036                /**
037                 * Determines whether the token is active.
038                 */
039                private final boolean active;
040
041
042                /**
043                 * The optional token scope.
044                 */
045                private Scope scope;
046
047
048                /**
049                 * The optional client ID for the token.
050                 */
051                private ClientID clientID;
052
053
054                /**
055                 * The optional username for the token.
056                 */
057                private String username;
058
059
060                /**
061                 * The optional token type.
062                 */
063                private AccessTokenType tokenType;
064
065
066                /**
067                 * The optional token expiration date.
068                 */
069                private Date exp;
070
071
072                /**
073                 * The optional token issue date.
074                 */
075                private Date iat;
076
077
078                /**
079                 * The optional token not-before date.
080                 */
081                private Date nbf;
082
083
084                /**
085                 * The optional token subject.
086                 */
087                private Subject sub;
088
089
090                /**
091                 * The optional token audience.
092                 */
093                private List<Audience> audList;
094
095
096                /**
097                 * The optional token issuer.
098                 */
099                private Issuer iss;
100
101
102                /**
103                 * The optional token identifier.
104                 */
105                private JWTID jti;
106
107
108                /**
109                 * Optional custom parameters.
110                 */
111                private final JSONObject customParams = new JSONObject();
112
113
114                /**
115                 * Creates a new token introspection success response builder.
116                 *
117                 * @param active {@code true} if the token is active, else
118                 *               {@code false}.
119                 */
120                public Builder(final boolean active) {
121
122                        this.active = active;
123                }
124
125
126                /**
127                 * Sets the token scope.
128                 *
129                 * @param scope The token scope, {@code null} if not specified.
130                 *
131                 * @return This builder.
132                 */
133                public Builder scope(final Scope scope) {
134                        this.scope = scope;
135                        return this;
136                }
137
138
139                /**
140                 * Sets the identifier for the OAuth 2.0 client that requested
141                 * the token.
142                 *
143                 * @param clientID The client identifier, {@code null} if not
144                 *                 specified.
145                 *
146                 * @return This builder.
147                 */
148                public Builder clientID(final ClientID clientID) {
149                        this.clientID = clientID;
150                        return this;
151                }
152
153
154                /**
155                 * Sets the username of the resource owner who authorised the
156                 * token.
157                 *
158                 * @param username The username, {@code null} if not specified.
159                 *
160                 * @return This builder.
161                 */
162                public Builder username(final String username) {
163                        this.username = username;
164                        return this;
165                }
166
167
168                /**
169                 * Sets the token type.
170                 *
171                 * @param tokenType The token type, {@code null} if not
172                 *                  specified.
173                 *
174                 * @return This builder.
175                 */
176                public Builder tokenType(final AccessTokenType tokenType) {
177                        this.tokenType = tokenType;
178                        return this;
179                }
180
181
182                /**
183                 * Sets the token expiration time.
184                 *
185                 * @param exp The token expiration time, {@code null} if not
186                 *            specified.
187                 *
188                 * @return This builder.
189                 */
190                public Builder expirationTime(final Date exp) {
191                        this.exp = exp;
192                        return this;
193                }
194
195
196                /**
197                 * Sets the token issue time.
198                 *
199                 * @param iat The token issue time, {@code null} if not
200                 *            specified.
201                 *
202                 * @return This builder.
203                 */
204                public Builder issueTime(final Date iat) {
205                        this.iat = iat;
206                        return this;
207                }
208
209
210                /**
211                 * Sets the token not-before time.
212                 *
213                 * @param nbf The token not-before time, {@code null} if not
214                 *            specified.
215                 *
216                 * @return This builder.
217                 */
218                public Builder notBeforeTime(final Date nbf) {
219                        this.nbf = nbf;
220                        return this;
221                }
222
223
224                /**
225                 * Sets the token subject.
226                 *
227                 * @param sub The token subject, {@code null} if not specified.
228                 *
229                 * @return This builder.
230                 */
231                public Builder subject(final Subject sub) {
232                        this.sub = sub;
233                        return this;
234                }
235
236
237                /**
238                 * Sets the token audience.
239                 *
240                 * @param audList The token audience, {@code null} if not
241                 *                specified.
242                 *
243                 * @return This builder.
244                 */
245                public Builder audience(final List<Audience> audList) {
246                        this.audList = audList;
247                        return this;
248                }
249
250
251                /**
252                 * Sets the token issuer.
253                 *
254                 * @param iss The token issuer, {@code null} if not specified.
255                 *
256                 * @return This builder.
257                 */
258                public Builder issuer(final Issuer iss) {
259                        this.iss = iss;
260                        return this;
261                }
262
263
264                /**
265                 * Sets the token identifier.
266                 *
267                 * @param jti The token identifier, {@code null} if not
268                 *            specified.
269                 *
270                 * @return This builder.
271                 */
272                public Builder jwtID(final JWTID jti) {
273                        this.jti = jti;
274                        return this;
275                }
276
277
278                /**
279                 * Sets a custom parameter.
280                 *
281                 * @param name  The parameter name. Must not be {@code null}.
282                 * @param value The parameter value. Should map to a JSON type.
283                 *              If {@code null} not specified.
284                 *
285                 * @return This builder.
286                 */
287                public Builder parameter(final String name, final Object value) {
288                        if (value != null) {
289                                customParams.put(name, value);
290                        }
291                        return this;
292                }
293
294
295                /**
296                 * Builds a new token introspection success response.
297                 *
298                 * @return The token introspection success response.
299                 */
300                public TokenIntrospectionSuccessResponse build() {
301
302                        JSONObject o = new JSONObject();
303                        o.put("active", active);
304                        if (scope != null) o.put("scope", scope.toString());
305                        if (clientID != null) o.put("client_id", clientID.getValue());
306                        if (username != null) o.put("username", username);
307                        if (tokenType != null) o.put("token_type", tokenType.getValue());
308                        if (exp != null) o.put("exp", DateUtils.toSecondsSinceEpoch(exp));
309                        if (iat != null) o.put("iat", DateUtils.toSecondsSinceEpoch(iat));
310                        if (nbf != null) o.put("nbf", DateUtils.toSecondsSinceEpoch(nbf));
311                        if (sub != null) o.put("sub", sub.getValue());
312                        if (audList != null) o.put("aud", Audience.toStringList(audList));
313                        if (iss != null) o.put("iss", iss.getValue());
314                        if (jti != null) o.put("jti", jti.getValue());
315                        o.putAll(customParams);
316                        return new TokenIntrospectionSuccessResponse(o);
317                }
318        }
319
320
321        /**
322         * The parameters.
323         */
324        private final JSONObject params;
325
326
327        /**
328         * Creates a new token introspection success response.
329         *
330         * @param params The response parameters. Must contain at least the
331         *               required {@code active} parameter and not be
332         *               {@code null}.
333         */
334        public TokenIntrospectionSuccessResponse(final JSONObject params) {
335
336                if (! (params.get("active") instanceof Boolean)) {
337                        throw new IllegalArgumentException("Missing / invalid boolean active parameter");
338                }
339
340                this.params = params;
341        }
342
343
344        /**
345         * Returns the active status for the token. Corresponds to the
346         * {@code active} claim.
347         *
348         * @return {@code true} if the token is active, else {@code false}.
349         */
350        public boolean isActive() {
351
352                try {
353                        return JSONObjectUtils.getBoolean(params, "active");
354                } catch (ParseException e) {
355                        return false; // always false on error
356                }
357        }
358
359
360        /**
361         * Returns the scope of the token. Corresponds to the {@code scope}
362         * claim.
363         *
364         * @return The token scope, {@code null} if not specified.
365         */
366        public Scope getScope() {
367
368                try {
369                        return Scope.parse(JSONObjectUtils.getString(params, "scope"));
370                } catch (ParseException e) {
371                        return null;
372                }
373        }
374
375
376        /**
377         * Returns the identifier of the OAuth 2.0 client that requested the
378         * token. Corresponds to the {@code client_id} claim.
379         *
380         * @return The client identifier, {@code null} if not specified.
381         */
382        public ClientID getClientID() {
383
384                try {
385                        return new ClientID(JSONObjectUtils.getString(params, "client_id"));
386                } catch (ParseException e) {
387                        return null;
388                }
389        }
390
391
392        /**
393         * Returns the username of the resource owner who authorised the token.
394         * Corresponds to the {@code username} claim.
395         *
396         * @return The username, {@code null} if not specified.
397         */
398        public String getUsername() {
399
400                try {
401                        return JSONObjectUtils.getString(params, "username");
402                } catch (ParseException e) {
403                        return null;
404                }
405        }
406
407
408        /**
409         * Returns the access token type. Corresponds to the {@code token_type}
410         * claim.
411         *
412         * @return The token type, {@code null} if not specified.
413         */
414        public AccessTokenType getTokenType() {
415
416                try {
417                        return new AccessTokenType(JSONObjectUtils.getString(params, "token_type"));
418                } catch (ParseException e) {
419                        return null;
420                }
421        }
422
423
424        /**
425         * Returns the token expiration time. Corresponds to the {@code exp}
426         * claim.
427         *
428         * @return The token expiration time, {@code null} if not specified.
429         */
430        public Date getExpirationTime() {
431
432                try {
433                        return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "exp"));
434                } catch (ParseException e) {
435                        return null;
436                }
437        }
438
439
440        /**
441         * Returns the token issue time. Corresponds to the {@code iat} claim.
442         *
443         * @return The token issue time, {@code null} if not specified.
444         */
445        public Date getIssueTime() {
446
447                try {
448                        return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "iat"));
449                } catch (ParseException e) {
450                        return null;
451                }
452        }
453
454
455        /**
456         * Returns the token not-before time. Corresponds to the {@code nbf}
457         * claim.
458         *
459         * @return The token not-before time, {@code null} if not specified.
460         */
461        public Date getNotBeforeTime() {
462
463                try {
464                        return DateUtils.fromSecondsSinceEpoch(JSONObjectUtils.getLong(params, "nbf"));
465                } catch (ParseException e) {
466                        return null;
467                }
468        }
469
470
471        /**
472         * Returns the subject of the token, usually a machine-readable
473         * identifier of the resource owner who authorised the token.
474         * Corresponds to the {@code sub} claim.
475         *
476         * @return The token subject, {@code null} if not specified.
477         */
478        public Subject getSubject() {
479
480                try {
481                        return new Subject(JSONObjectUtils.getString(params, "sub"));
482                } catch (ParseException e) {
483                        return null;
484                }
485        }
486
487
488        /**
489         * Returns the intended audience for the token. Corresponds to the
490         * {@code aud} claim.
491         *
492         * @return The token audience, {@code null} if not specified.
493         */
494        public List<Audience> getAudience() {
495                // Try string array first, then string
496                try {
497                        return Audience.create(JSONObjectUtils.getStringList(params, "aud"));
498                } catch (ParseException e) {
499                        try {
500                                return new Audience(JSONObjectUtils.getString(params, "aud")).toSingleAudienceList();
501                        } catch (ParseException e2) {
502                                return null;
503                        }
504                }
505        }
506
507
508        /**
509         * Returns the token issuer. Corresponds to the {@code iss} claim.
510         *
511         * @return The token issuer, {@code null} if not specified.
512         */
513        public Issuer getIssuer() {
514
515                try {
516                        return new Issuer(JSONObjectUtils.getString(params, "iss"));
517                } catch (ParseException e) {
518                        return null;
519                }
520        }
521
522
523        /**
524         * Returns the token identifier. Corresponds to the {@code jti}
525         * claim.
526         *
527         * @return The token identifier, {@code null} if not specified.
528         */
529        public JWTID getJWTID() {
530
531                try {
532                        return new JWTID(JSONObjectUtils.getString(params, "jti"));
533                } catch (ParseException e) {
534                        return null;
535                }
536        }
537
538
539        /**
540         * Returns a JSON object representation of this token introspection
541         * success response.
542         *
543         * <p>Example JSON object:
544         *
545         * <pre>
546         * {
547         *  "active"          : true,
548         *  "client_id"       : "l238j323ds-23ij4",
549         *  "username"        : "jdoe",
550         *  "scope"           : "read write dolphin",
551         *  "sub"             : "Z5O3upPC88QrAjx00dis",
552         *  "aud"             : "https://protected.example.net/resource",
553         *  "iss"             : "https://server.example.com/",
554         *  "exp"             : 1419356238,
555         *  "iat"             : 1419350238,
556         *  "extension_field" : "twenty-seven"
557         * }
558         * </pre>
559         *
560         * @return The JSON object.
561         */
562        public JSONObject toJSONObject() {
563
564                return new JSONObject(params);
565        }
566        
567
568        @Override
569        public boolean indicatesSuccess() {
570
571                return true;
572        }
573
574
575        @Override
576        public HTTPResponse toHTTPResponse() {
577
578                HTTPResponse httpResponse = new HTTPResponse(HTTPResponse.SC_OK);
579                httpResponse.setContentType(CommonContentTypes.APPLICATION_JSON);
580                httpResponse.setContent(params.toJSONString());
581                return httpResponse;
582        }
583
584
585        /**
586         * Parses a token introspection success response from the specified
587         * JSON object.
588         *
589         * @param jsonObject The JSON object to parse. Must not be {@code null}.
590         *
591         * @return The token introspection success response.
592         *
593         * @throws ParseException If the JSON object couldn't be parsed to a
594         *                        token introspection success response.
595         */
596        public static TokenIntrospectionSuccessResponse parse(final JSONObject jsonObject)
597                throws ParseException {
598
599                try {
600                        return new TokenIntrospectionSuccessResponse(jsonObject);
601                } catch (IllegalArgumentException e) {
602                        throw new ParseException(e.getMessage(), e);
603                }
604        }
605
606
607        /**
608         * Parses an token introspection success response from the specified
609         * HTTP response.
610         *
611         * @param httpResponse The HTTP response. Must not be {@code null}.
612         *
613         * @return The token introspection success response.
614         *
615         * @throws ParseException If the HTTP response couldn't be parsed to a
616         *                        token introspection success response.
617         */
618        public static TokenIntrospectionSuccessResponse parse(final HTTPResponse httpResponse)
619                throws ParseException {
620
621                httpResponse.ensureStatusCode(HTTPResponse.SC_OK);
622                JSONObject jsonObject = httpResponse.getContentAsJSONObject();
623                return parse(jsonObject);
624        }
625}