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