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}