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 com.nimbusds.jwt.JWT;
022import com.nimbusds.jwt.JWTParser;
023import com.nimbusds.oauth2.sdk.ParseException;
024import com.nimbusds.oauth2.sdk.token.AccessToken;
025import com.nimbusds.oauth2.sdk.token.RefreshToken;
026import com.nimbusds.oauth2.sdk.token.Tokens;
027import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
028import com.nimbusds.openid.connect.sdk.nativesso.DeviceSecret;
029import net.minidev.json.JSONObject;
030
031import java.util.Collections;
032import java.util.HashSet;
033import java.util.Objects;
034import java.util.Set;
035
036
037/**
038 * ID token, access token and optional refresh token.
039 */
040public final class OIDCTokens extends Tokens {
041
042
043        /**
044         * The ID token serialised to a JWT, {@code null} if not specified.
045         */
046        private final JWT idToken;
047
048
049        /**
050         * The ID token as raw string (for more efficient serialisation),
051         * {@code null} if not specified.
052         */
053        private final String idTokenString;
054
055
056        /**
057         * Device secret for OpenID Connect native SSO, {@code null} if not
058         * specified.
059         */
060        private final DeviceSecret deviceSecret;
061        
062        
063        /**
064         * Creates a new OpenID Connect tokens instance.
065         *
066         * @param idToken      The ID token. Must not be {@code null}.
067         * @param accessToken  The access token. Must not be {@code null}.
068         * @param refreshToken The refresh token, {@code null} if none.
069         */
070        public OIDCTokens(final JWT idToken, final AccessToken accessToken, final RefreshToken refreshToken) {
071                this(idToken, accessToken, refreshToken, null);
072        }
073
074
075        /**
076         * Creates a new OpenID Connect tokens instance.
077         *
078         * @param idToken      The ID token. Must not be {@code null}.
079         * @param accessToken  The access token. Must not be {@code null}.
080         * @param refreshToken The refresh token, {@code null} if none.
081         * @param deviceSecret The device secret for OpenID Connect native SSO,
082         *                     {@code null} if not specified.
083         */
084        public OIDCTokens(final JWT idToken,
085                          final AccessToken accessToken,
086                          final RefreshToken refreshToken,
087                          final DeviceSecret deviceSecret) {
088                super(accessToken, refreshToken);
089                this.idToken = Objects.requireNonNull(idToken);
090                idTokenString = null;
091                this.deviceSecret = deviceSecret;
092        }
093        
094        
095        /**
096         * Creates a new OpenID Connect tokens instance.
097         *
098         * @param idTokenString The ID token string. Must not be {@code null}.
099         * @param accessToken   The access token. Must not be {@code null}.
100         * @param refreshToken  The refresh token, {@code null} if none.
101         */
102        public OIDCTokens(final String idTokenString, final AccessToken accessToken, final RefreshToken refreshToken) {
103                this(idTokenString, accessToken, refreshToken, null);
104        }
105
106
107        /**
108         * Creates a new OpenID Connect tokens instance.
109         *
110         * @param idTokenString The ID token string. Must not be {@code null}.
111         * @param accessToken   The access token. Must not be {@code null}.
112         * @param refreshToken  The refresh token, {@code null} if none.
113         * @param deviceSecret  The device secret for OpenID Connect native
114         *                      SSO, {@code null} if not specified.
115         */
116        public OIDCTokens(final String idTokenString,
117                          final AccessToken accessToken,
118                          final RefreshToken refreshToken,
119                          final DeviceSecret deviceSecret) {
120                super(accessToken, refreshToken);
121                this.idTokenString = Objects.requireNonNull(idTokenString);
122                idToken = null;
123                this.deviceSecret = deviceSecret;
124        }
125        
126        
127        /**
128         * Creates a new OpenID Connect tokens instance without an ID token.
129         * Intended for token responses from a refresh token grant where the ID
130         * token is optional.
131         *
132         * @param accessToken  The access token. Must not be {@code null}.
133         * @param refreshToken The refresh token, {@code null} if none.
134         */
135        public OIDCTokens(final AccessToken accessToken, final RefreshToken refreshToken) {
136                this(accessToken, refreshToken, null);
137        }
138
139
140        /**
141         * Creates a new OpenID Connect tokens instance without an ID token.
142         * Intended for token responses from a refresh token grant where the ID
143         * token is optional.
144         *
145         * @param accessToken  The access token. Must not be {@code null}.
146         * @param refreshToken The refresh token, {@code null} if none.
147         * @param deviceSecret The device secret for OpenID Connect native SSO,
148         *                     {@code null} if not specified.
149         */
150        public OIDCTokens(final AccessToken accessToken,
151                          final RefreshToken refreshToken,
152                          final DeviceSecret deviceSecret) {
153
154                super(accessToken, refreshToken);
155                this.idToken = null;
156                this.idTokenString = null;
157                this.deviceSecret = deviceSecret;
158        }
159
160
161        /**
162         * Gets the ID token.
163         *
164         * @return The ID token, {@code null} if none or if parsing to a JWT
165         *         failed.
166         */
167        public JWT getIDToken() {
168
169                if (idToken != null)
170                        return idToken;
171
172                if (idTokenString != null) {
173
174                        try {
175                                return JWTParser.parse(idTokenString);
176
177                        } catch (java.text.ParseException e) {
178
179                                return null;
180                        }
181                }
182
183                return null;
184        }
185
186
187        /**
188         * Gets the ID token string.
189         *
190         * @return The ID token string, {@code null} if none or if
191         *         serialisation to a string failed.
192         */
193        public String getIDTokenString() {
194
195                if (idTokenString != null)
196                        return idTokenString;
197
198                if (idToken != null) {
199
200                        // Reproduce originally parsed string if any
201                        if (idToken.getParsedString() != null)
202                                return idToken.getParsedString();
203
204                        try {
205                                return idToken.serialize();
206
207                        } catch(IllegalStateException e) {
208
209                                return null;
210                        }
211                }
212
213                return null;
214        }
215
216
217        /**
218         * Returns the device secret for native SSO.
219         *
220         * @return The device secret, {@code null} if not specified.
221         */
222        public DeviceSecret getDeviceSecret() {
223
224                return deviceSecret;
225        }
226
227
228        @Override
229        public Set<String> getParameterNames() {
230
231                Set<String> paramNames = new HashSet<>(super.getParameterNames());
232                if (idToken != null || idTokenString != null) {
233                        paramNames.add("id_token");
234                }
235                if (deviceSecret != null) {
236                        paramNames.add("device_secret");
237                }
238                return Collections.unmodifiableSet(paramNames);
239        }
240
241
242        @Override
243        public JSONObject toJSONObject() {
244
245                JSONObject o = super.toJSONObject();
246                if (getIDTokenString() != null) {
247                        o.put("id_token", getIDTokenString());
248                }
249                if (deviceSecret != null) {
250                        o.put("device_secret", deviceSecret.getValue());
251                }
252                return o;
253        }
254
255
256        /**
257         * Parses an OpenID Connect tokens instance from the specified JSON
258         * object.
259         *
260         * @param jsonObject The JSON object to parse. Must not be {@code null}.
261         *
262         * @return The OpenID Connect tokens.
263         *
264         * @throws ParseException If the JSON object couldn't be parsed to an
265         *                        OpenID Connect tokens instance.
266         */
267        public static OIDCTokens parse(final JSONObject jsonObject)
268                throws ParseException {
269                
270                AccessToken accessToken = AccessToken.parse(jsonObject);
271                
272                RefreshToken refreshToken = RefreshToken.parse(jsonObject);
273
274                DeviceSecret deviceSecret = null;
275                if (jsonObject.get("device_secret") != null) {
276                        deviceSecret = DeviceSecret.parse(JSONObjectUtils.getNonBlankString(jsonObject, "device_secret"));
277                }
278                
279                if (jsonObject.get("id_token") != null) {
280                        JWT idToken;
281                        try {
282                                idToken = JWTParser.parse(JSONObjectUtils.getNonBlankString(jsonObject, "id_token"));
283                        } catch (java.text.ParseException e) {
284                                throw new ParseException("Couldn't parse ID token: " + e.getMessage(), e);
285                        }
286
287                        return new OIDCTokens(idToken, accessToken, refreshToken, deviceSecret);
288                        
289                } else {
290                        // Likely a token response from a refresh token grant without an ID token
291                        return new OIDCTokens(accessToken, refreshToken, deviceSecret);
292                }
293        }
294}