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