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