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