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