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}