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.openid.connect.sdk.token;
019
020
021import java.util.Collections;
022import java.util.HashSet;
023import java.util.Set;
024
025import net.minidev.json.JSONObject;
026
027import com.nimbusds.jwt.JWT;
028import com.nimbusds.jwt.JWTParser;
029import com.nimbusds.oauth2.sdk.ParseException;
030import com.nimbusds.oauth2.sdk.token.AccessToken;
031import com.nimbusds.oauth2.sdk.token.RefreshToken;
032import com.nimbusds.oauth2.sdk.token.Tokens;
033import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
034
035
036/**
037 * ID token, access token and optional refresh token.
038 */
039public final class OIDCTokens extends Tokens {
040
041
042        /**
043         * The ID Token serialised to a JWT, {@code null} if not specified.
044         */
045        private final JWT idToken;
046
047
048        /**
049         * The ID Token as raw string (for more efficient serialisation),
050         * {@code null} if not specified.
051         */
052        private final String idTokenString;
053        
054        
055        /**
056         * Creates a new OpenID Connect tokens instance.
057         *
058         * @param idToken      The ID token. Must not be {@code null}.
059         * @param accessToken  The access token. Must not be {@code null}.
060         * @param refreshToken The refresh token. If none {@code null}.
061         */
062        public OIDCTokens(final JWT idToken, final AccessToken accessToken, final RefreshToken refreshToken) {
063
064                super(accessToken, refreshToken);
065
066                if (idToken == null) {
067                        throw new IllegalArgumentException("The ID token must not be null");
068                }
069
070                this.idToken = idToken;
071                idTokenString = null;
072        }
073        
074        
075        /**
076         * Creates a new OpenID Connect tokens instance.
077         *
078         * @param idTokenString The ID token string. Must not be {@code null}.
079         * @param accessToken   The access token. Must not be {@code null}.
080         * @param refreshToken  The refresh token. If none {@code null}.
081         */
082        public OIDCTokens(final String idTokenString, final AccessToken accessToken, final RefreshToken refreshToken) {
083
084                super(accessToken, refreshToken);
085
086                if (idTokenString == null) {
087                        throw new IllegalArgumentException("The ID token string must not be null");
088                }
089
090                this.idTokenString = idTokenString;
091                idToken = null;
092        }
093        
094        
095        /**
096         * Creates a new OpenID Connect tokens instance without an ID token.
097         * Intended for token responses from a refresh token grant where the ID
098         * token is optional.
099         *
100         * @param accessToken  The access token. Must not be {@code null}.
101         * @param refreshToken The refresh token. If none {@code null}.
102         */
103        public OIDCTokens(final AccessToken accessToken, final RefreshToken refreshToken) {
104                
105                super(accessToken, refreshToken);
106                this.idToken = null;
107                this.idTokenString = null;
108        }
109
110
111        /**
112         * Gets the ID token.
113         *
114         * @return The ID token, {@code null} if none or if parsing to a JWT
115         *         failed.
116         */
117        public JWT getIDToken() {
118
119                if (idToken != null)
120                        return idToken;
121
122                if (idTokenString != null) {
123
124                        try {
125                                return JWTParser.parse(idTokenString);
126
127                        } catch (java.text.ParseException e) {
128
129                                return null;
130                        }
131                }
132
133                return null;
134        }
135
136
137        /**
138         * Gets the ID token string.
139         *
140         * @return The ID token string, {@code null} if none or if
141         *         serialisation to a string failed.
142         */
143        public String getIDTokenString() {
144
145                if (idTokenString != null)
146                        return idTokenString;
147
148                if (idToken != null) {
149
150                        // Reproduce originally parsed string if any
151                        if (idToken.getParsedString() != null)
152                                return idToken.getParsedString();
153
154                        try {
155                                return idToken.serialize();
156
157                        } catch(IllegalStateException e) {
158
159                                return null;
160                        }
161                }
162
163                return null;
164        }
165
166
167        @Override
168        public Set<String> getParameterNames() {
169
170                Set<String> paramNames = new HashSet<>(super.getParameterNames());
171                if (idToken != null || idTokenString != null) {
172                        paramNames.add("id_token");
173                }
174                return Collections.unmodifiableSet(paramNames);
175        }
176
177
178        @Override
179        public JSONObject toJSONObject() {
180
181                JSONObject o = super.toJSONObject();
182                if (getIDTokenString() != null) {
183                        o.put("id_token", getIDTokenString());
184                }
185                return o;
186        }
187
188
189        /**
190         * Parses an OpenID Connect tokens instance from the specified JSON
191         * object.
192         *
193         * @param jsonObject The JSON object to parse. Must not be {@code null}.
194         *
195         * @return The OpenID Connect tokens.
196         *
197         * @throws ParseException If the JSON object couldn't be parsed to an
198         *                        OpenID Connect tokens instance.
199         */
200        public static OIDCTokens parse(final JSONObject jsonObject)
201                throws ParseException {
202                
203                AccessToken accessToken = AccessToken.parse(jsonObject);
204                
205                RefreshToken refreshToken = RefreshToken.parse(jsonObject);
206                
207                if (jsonObject.get("id_token") != null) {
208                        
209                        JWT idToken;
210                        try {
211                                idToken = JWTParser.parse(JSONObjectUtils.getString(jsonObject, "id_token"));
212                        } catch (java.text.ParseException e) {
213                                throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e);
214                        }
215                        
216                        return new OIDCTokens(idToken, accessToken, refreshToken);
217                        
218                } else {
219                
220                        // Likely a token response from a refresh token grant without an ID token
221                        return new OIDCTokens(accessToken, refreshToken);
222                }
223        }
224}