001/******************************************************************************* 002 * Copyright 2017 The MIT Internet Trust Consortium 003 * 004 * Portions copyright 2011-2013 The MITRE Corporation 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the 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 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 *******************************************************************************/ 018/** 019 * 020 */ 021package org.mitre.openid.connect; 022 023 024import static org.mitre.util.JsonUtils.getAsArray; 025import static org.mitre.util.JsonUtils.getAsDate; 026import static org.mitre.util.JsonUtils.getAsJweAlgorithm; 027import static org.mitre.util.JsonUtils.getAsJweEncryptionMethod; 028import static org.mitre.util.JsonUtils.getAsJwsAlgorithm; 029import static org.mitre.util.JsonUtils.getAsPkceAlgorithm; 030import static org.mitre.util.JsonUtils.getAsString; 031import static org.mitre.util.JsonUtils.getAsStringSet; 032 033import java.text.ParseException; 034 035import org.mitre.oauth2.model.ClientDetailsEntity; 036import org.mitre.oauth2.model.ClientDetailsEntity.AppType; 037import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; 038import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType; 039import org.mitre.oauth2.model.RegisteredClient; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043import com.google.common.base.Joiner; 044import com.google.common.base.Splitter; 045import com.google.common.base.Strings; 046import com.google.common.collect.Sets; 047import com.google.gson.JsonElement; 048import com.google.gson.JsonObject; 049import com.google.gson.JsonParser; 050import com.nimbusds.jose.jwk.JWKSet; 051import com.nimbusds.jwt.JWT; 052import com.nimbusds.jwt.JWTParser; 053 054import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE; 055import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS; 056import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID; 057import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID_ISSUED_AT; 058import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME; 059import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET; 060import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT; 061import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI; 062import static org.mitre.oauth2.model.RegisteredClientFields.CODE_CHALLENGE_METHOD; 063import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS; 064import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES; 065import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE; 066import static org.mitre.oauth2.model.RegisteredClientFields.GRANT_TYPES; 067import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ALG; 068import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_ENCRYPTED_RESPONSE_ENC; 069import static org.mitre.oauth2.model.RegisteredClientFields.ID_TOKEN_SIGNED_RESPONSE_ALG; 070import static org.mitre.oauth2.model.RegisteredClientFields.INITIATE_LOGIN_URI; 071import static org.mitre.oauth2.model.RegisteredClientFields.JWKS; 072import static org.mitre.oauth2.model.RegisteredClientFields.JWKS_URI; 073import static org.mitre.oauth2.model.RegisteredClientFields.LOGO_URI; 074import static org.mitre.oauth2.model.RegisteredClientFields.POLICY_URI; 075import static org.mitre.oauth2.model.RegisteredClientFields.POST_LOGOUT_REDIRECT_URIS; 076import static org.mitre.oauth2.model.RegisteredClientFields.REDIRECT_URIS; 077import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_ACCESS_TOKEN; 078import static org.mitre.oauth2.model.RegisteredClientFields.REGISTRATION_CLIENT_URI; 079import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_OBJECT_SIGNING_ALG; 080import static org.mitre.oauth2.model.RegisteredClientFields.REQUEST_URIS; 081import static org.mitre.oauth2.model.RegisteredClientFields.REQUIRE_AUTH_TIME; 082import static org.mitre.oauth2.model.RegisteredClientFields.RESPONSE_TYPES; 083import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE; 084import static org.mitre.oauth2.model.RegisteredClientFields.SCOPE_SEPARATOR; 085import static org.mitre.oauth2.model.RegisteredClientFields.SECTOR_IDENTIFIER_URI; 086import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_ID; 087import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_STATEMENT; 088import static org.mitre.oauth2.model.RegisteredClientFields.SOFTWARE_VERSION; 089import static org.mitre.oauth2.model.RegisteredClientFields.SUBJECT_TYPE; 090import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_METHOD; 091import static org.mitre.oauth2.model.RegisteredClientFields.TOKEN_ENDPOINT_AUTH_SIGNING_ALG; 092import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI; 093import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG; 094import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC; 095import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG; 096 097/** 098 * Utility class to handle the parsing and serialization of ClientDetails objects. 099 * 100 * @author jricher 101 * 102 */ 103public class ClientDetailsEntityJsonProcessor { 104 105 private static Logger logger = LoggerFactory.getLogger(ClientDetailsEntityJsonProcessor.class); 106 107 private static JsonParser parser = new JsonParser(); 108 109 /** 110 * 111 * Create an unbound ClientDetailsEntity from the given JSON string. 112 * 113 * @param jsonString 114 * @return the entity if successful, null otherwise 115 */ 116 public static ClientDetailsEntity parse(String jsonString) { 117 JsonElement jsonEl = parser.parse(jsonString); 118 return parse(jsonEl); 119 } 120 121 public static ClientDetailsEntity parse(JsonElement jsonEl) { 122 if (jsonEl.isJsonObject()) { 123 124 JsonObject o = jsonEl.getAsJsonObject(); 125 ClientDetailsEntity c = new ClientDetailsEntity(); 126 127 // these two fields should only be sent in the update request, and MUST match existing values 128 c.setClientId(getAsString(o, CLIENT_ID)); 129 c.setClientSecret(getAsString(o, CLIENT_SECRET)); 130 131 // OAuth DynReg 132 c.setRedirectUris(getAsStringSet(o, REDIRECT_URIS)); 133 c.setClientName(getAsString(o, CLIENT_NAME)); 134 c.setClientUri(getAsString(o, CLIENT_URI)); 135 c.setLogoUri(getAsString(o, LOGO_URI)); 136 c.setContacts(getAsStringSet(o, CONTACTS)); 137 c.setTosUri(getAsString(o, TOS_URI)); 138 139 String authMethod = getAsString(o, TOKEN_ENDPOINT_AUTH_METHOD); 140 if (authMethod != null) { 141 c.setTokenEndpointAuthMethod(AuthMethod.getByValue(authMethod)); 142 } 143 144 // scope is a space-separated string 145 String scope = getAsString(o, SCOPE); 146 if (scope != null) { 147 c.setScope(Sets.newHashSet(Splitter.on(SCOPE_SEPARATOR).split(scope))); 148 } 149 150 c.setGrantTypes(getAsStringSet(o, GRANT_TYPES)); 151 c.setResponseTypes(getAsStringSet(o, RESPONSE_TYPES)); 152 c.setPolicyUri(getAsString(o, POLICY_URI)); 153 c.setJwksUri(getAsString(o, JWKS_URI)); 154 155 JsonElement jwksEl = o.get(JWKS); 156 if (jwksEl != null && jwksEl.isJsonObject()) { 157 try { 158 JWKSet jwks = JWKSet.parse(jwksEl.toString()); // we have to pass this through Nimbus's parser as a string 159 c.setJwks(jwks); 160 } catch (ParseException e) { 161 logger.error("Unable to parse JWK Set for client", e); 162 return null; 163 } 164 } 165 166 // OIDC Additions 167 String appType = getAsString(o, APPLICATION_TYPE); 168 if (appType != null) { 169 c.setApplicationType(AppType.getByValue(appType)); 170 } 171 172 c.setSectorIdentifierUri(getAsString(o, SECTOR_IDENTIFIER_URI)); 173 174 String subjectType = getAsString(o, SUBJECT_TYPE); 175 if (subjectType != null) { 176 c.setSubjectType(SubjectType.getByValue(subjectType)); 177 } 178 179 c.setRequestObjectSigningAlg(getAsJwsAlgorithm(o, REQUEST_OBJECT_SIGNING_ALG)); 180 181 c.setUserInfoSignedResponseAlg(getAsJwsAlgorithm(o, USERINFO_SIGNED_RESPONSE_ALG)); 182 c.setUserInfoEncryptedResponseAlg(getAsJweAlgorithm(o, USERINFO_ENCRYPTED_RESPONSE_ALG)); 183 c.setUserInfoEncryptedResponseEnc(getAsJweEncryptionMethod(o, USERINFO_ENCRYPTED_RESPONSE_ENC)); 184 185 c.setIdTokenSignedResponseAlg(getAsJwsAlgorithm(o, ID_TOKEN_SIGNED_RESPONSE_ALG)); 186 c.setIdTokenEncryptedResponseAlg(getAsJweAlgorithm(o, ID_TOKEN_ENCRYPTED_RESPONSE_ALG)); 187 c.setIdTokenEncryptedResponseEnc(getAsJweEncryptionMethod(o, ID_TOKEN_ENCRYPTED_RESPONSE_ENC)); 188 189 c.setTokenEndpointAuthSigningAlg(getAsJwsAlgorithm(o, TOKEN_ENDPOINT_AUTH_SIGNING_ALG)); 190 191 if (o.has(DEFAULT_MAX_AGE)) { 192 if (o.get(DEFAULT_MAX_AGE).isJsonPrimitive()) { 193 c.setDefaultMaxAge(o.get(DEFAULT_MAX_AGE).getAsInt()); 194 } 195 } 196 197 if (o.has(REQUIRE_AUTH_TIME)) { 198 if (o.get(REQUIRE_AUTH_TIME).isJsonPrimitive()) { 199 c.setRequireAuthTime(o.get(REQUIRE_AUTH_TIME).getAsBoolean()); 200 } 201 } 202 203 c.setDefaultACRvalues(getAsStringSet(o, DEFAULT_ACR_VALUES)); 204 c.setInitiateLoginUri(getAsString(o, INITIATE_LOGIN_URI)); 205 c.setPostLogoutRedirectUris(getAsStringSet(o, POST_LOGOUT_REDIRECT_URIS)); 206 c.setRequestUris(getAsStringSet(o, REQUEST_URIS)); 207 208 c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS)); 209 210 c.setCodeChallengeMethod(getAsPkceAlgorithm(o, CODE_CHALLENGE_METHOD)); 211 212 c.setSoftwareId(getAsString(o, SOFTWARE_ID)); 213 c.setSoftwareVersion(getAsString(o, SOFTWARE_VERSION)); 214 215 // note that this does not process or validate the software statement, that's handled in other components 216 String softwareStatement = getAsString(o, SOFTWARE_STATEMENT); 217 if (!Strings.isNullOrEmpty(softwareStatement)) { 218 try { 219 JWT softwareStatementJwt = JWTParser.parse(softwareStatement); 220 c.setSoftwareStatement(softwareStatementJwt); 221 } catch (ParseException e) { 222 logger.warn("Error parsing software statement", e); 223 return null; 224 } 225 } 226 227 228 229 return c; 230 } else { 231 return null; 232 } 233 } 234 235 /** 236 * Parse the JSON as a RegisteredClient (useful in the dynamic client filter) 237 */ 238 public static RegisteredClient parseRegistered(String jsonString) { 239 240 241 JsonElement jsonEl = parser.parse(jsonString); 242 return parseRegistered(jsonEl); 243 } 244 245 public static RegisteredClient parseRegistered(JsonElement jsonEl) { 246 if (jsonEl.isJsonObject()) { 247 248 JsonObject o = jsonEl.getAsJsonObject(); 249 ClientDetailsEntity c = parse(jsonEl); 250 251 RegisteredClient rc = new RegisteredClient(c); 252 // get any fields from the registration 253 rc.setRegistrationAccessToken(getAsString(o, REGISTRATION_ACCESS_TOKEN)); 254 rc.setRegistrationClientUri(getAsString(o, REGISTRATION_CLIENT_URI)); 255 rc.setClientIdIssuedAt(getAsDate(o, CLIENT_ID_ISSUED_AT)); 256 rc.setClientSecretExpiresAt(getAsDate(o, CLIENT_SECRET_EXPIRES_AT)); 257 258 rc.setSource(o); 259 260 return rc; 261 } else { 262 return null; 263 } 264 } 265 266 /** 267 * @param c 268 * @param token 269 * @param registrationUri 270 * @return 271 */ 272 public static JsonObject serialize(RegisteredClient c) { 273 274 if (c.getSource() != null) { 275 // if we have the original object, just use that 276 return c.getSource(); 277 } else { 278 279 JsonObject o = new JsonObject(); 280 281 o.addProperty(CLIENT_ID, c.getClientId()); 282 if (c.getClientSecret() != null) { 283 o.addProperty(CLIENT_SECRET, c.getClientSecret()); 284 285 if (c.getClientSecretExpiresAt() == null) { 286 o.addProperty(CLIENT_SECRET_EXPIRES_AT, 0); // TODO: do we want to let secrets expire? 287 } else { 288 o.addProperty(CLIENT_SECRET_EXPIRES_AT, c.getClientSecretExpiresAt().getTime() / 1000L); 289 } 290 } 291 292 if (c.getClientIdIssuedAt() != null) { 293 o.addProperty(CLIENT_ID_ISSUED_AT, c.getClientIdIssuedAt().getTime() / 1000L); 294 } else if (c.getCreatedAt() != null) { 295 o.addProperty(CLIENT_ID_ISSUED_AT, c.getCreatedAt().getTime() / 1000L); 296 } 297 if (c.getRegistrationAccessToken() != null) { 298 o.addProperty(REGISTRATION_ACCESS_TOKEN, c.getRegistrationAccessToken()); 299 } 300 301 if (c.getRegistrationClientUri() != null) { 302 o.addProperty(REGISTRATION_CLIENT_URI, c.getRegistrationClientUri()); 303 } 304 305 306 // add in all other client properties 307 308 // OAuth DynReg 309 o.add(REDIRECT_URIS, getAsArray(c.getRedirectUris())); 310 o.addProperty(CLIENT_NAME, c.getClientName()); 311 o.addProperty(CLIENT_URI, c.getClientUri()); 312 o.addProperty(LOGO_URI, c.getLogoUri()); 313 o.add(CONTACTS, getAsArray(c.getContacts())); 314 o.addProperty(TOS_URI, c.getTosUri()); 315 o.addProperty(TOKEN_ENDPOINT_AUTH_METHOD, c.getTokenEndpointAuthMethod() != null ? c.getTokenEndpointAuthMethod().getValue() : null); 316 o.addProperty(SCOPE, c.getScope() != null ? Joiner.on(SCOPE_SEPARATOR).join(c.getScope()) : null); 317 o.add(GRANT_TYPES, getAsArray(c.getGrantTypes())); 318 o.add(RESPONSE_TYPES, getAsArray(c.getResponseTypes())); 319 o.addProperty(POLICY_URI, c.getPolicyUri()); 320 o.addProperty(JWKS_URI, c.getJwksUri()); 321 322 // get the JWKS sub-object 323 if (c.getJwks() != null) { 324 // We have to re-parse it into GSON because Nimbus uses a different parser 325 JsonElement jwks = parser.parse(c.getJwks().toString()); 326 o.add(JWKS, jwks); 327 } else { 328 o.add(JWKS, null); 329 } 330 331 // OIDC Registration 332 o.addProperty(APPLICATION_TYPE, c.getApplicationType() != null ? c.getApplicationType().getValue() : null); 333 o.addProperty(SECTOR_IDENTIFIER_URI, c.getSectorIdentifierUri()); 334 o.addProperty(SUBJECT_TYPE, c.getSubjectType() != null ? c.getSubjectType().getValue() : null); 335 o.addProperty(REQUEST_OBJECT_SIGNING_ALG, c.getRequestObjectSigningAlg() != null ? c.getRequestObjectSigningAlg().getName() : null); 336 o.addProperty(USERINFO_SIGNED_RESPONSE_ALG, c.getUserInfoSignedResponseAlg() != null ? c.getUserInfoSignedResponseAlg().getName() : null); 337 o.addProperty(USERINFO_ENCRYPTED_RESPONSE_ALG, c.getUserInfoEncryptedResponseAlg() != null ? c.getUserInfoEncryptedResponseAlg().getName() : null); 338 o.addProperty(USERINFO_ENCRYPTED_RESPONSE_ENC, c.getUserInfoEncryptedResponseEnc() != null ? c.getUserInfoEncryptedResponseEnc().getName() : null); 339 o.addProperty(ID_TOKEN_SIGNED_RESPONSE_ALG, c.getIdTokenSignedResponseAlg() != null ? c.getIdTokenSignedResponseAlg().getName() : null); 340 o.addProperty(ID_TOKEN_ENCRYPTED_RESPONSE_ALG, c.getIdTokenEncryptedResponseAlg() != null ? c.getIdTokenEncryptedResponseAlg().getName() : null); 341 o.addProperty(ID_TOKEN_ENCRYPTED_RESPONSE_ENC, c.getIdTokenEncryptedResponseEnc() != null ? c.getIdTokenEncryptedResponseEnc().getName() : null); 342 o.addProperty(TOKEN_ENDPOINT_AUTH_SIGNING_ALG, c.getTokenEndpointAuthSigningAlg() != null ? c.getTokenEndpointAuthSigningAlg().getName() : null); 343 o.addProperty(DEFAULT_MAX_AGE, c.getDefaultMaxAge()); 344 o.addProperty(REQUIRE_AUTH_TIME, c.getRequireAuthTime()); 345 o.add(DEFAULT_ACR_VALUES, getAsArray(c.getDefaultACRvalues())); 346 o.addProperty(INITIATE_LOGIN_URI, c.getInitiateLoginUri()); 347 o.add(POST_LOGOUT_REDIRECT_URIS, getAsArray(c.getPostLogoutRedirectUris())); 348 o.add(REQUEST_URIS, getAsArray(c.getRequestUris())); 349 350 o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris())); 351 352 o.addProperty(CODE_CHALLENGE_METHOD, c.getCodeChallengeMethod() != null ? c.getCodeChallengeMethod().getName() : null); 353 354 o.addProperty(SOFTWARE_ID, c.getSoftwareId()); 355 o.addProperty(SOFTWARE_VERSION, c.getSoftwareVersion()); 356 357 if (c.getSoftwareStatement() != null) { 358 o.addProperty(SOFTWARE_STATEMENT, c.getSoftwareStatement().serialize()); 359 } 360 361 return o; 362 } 363 364 } 365}