added PKCE editing capabilities to UI

pull/1192/merge
Justin Richer 2017-02-20 15:40:16 -05:00
parent 650429a2de
commit 141f4da7f1
9 changed files with 109 additions and 44 deletions

View File

@ -56,5 +56,6 @@ public interface RegisteredClientFields {
public String REDIRECT_URIS = "redirect_uris"; public String REDIRECT_URIS = "redirect_uris";
public String CLIENT_SECRET = "client_secret"; public String CLIENT_SECRET = "client_secret";
public String CLIENT_ID = "client_id"; public String CLIENT_ID = "client_id";
public String CODE_CHALLENGE_METHOD = "code_challenge_method";
} }

View File

@ -20,6 +20,36 @@
package org.mitre.openid.connect; package org.mitre.openid.connect;
import static org.mitre.util.JsonUtils.getAsArray;
import static org.mitre.util.JsonUtils.getAsDate;
import static org.mitre.util.JsonUtils.getAsJweAlgorithm;
import static org.mitre.util.JsonUtils.getAsJweEncryptionMethod;
import static org.mitre.util.JsonUtils.getAsJwsAlgorithm;
import static org.mitre.util.JsonUtils.getAsPkceAlgorithm;
import static org.mitre.util.JsonUtils.getAsString;
import static org.mitre.util.JsonUtils.getAsStringSet;
import java.text.ParseException;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
import org.mitre.oauth2.model.RegisteredClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE; import static org.mitre.oauth2.model.RegisteredClientFields.APPLICATION_TYPE;
import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS; import static org.mitre.oauth2.model.RegisteredClientFields.CLAIMS_REDIRECT_URIS;
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID; import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_ID;
@ -28,6 +58,7 @@ import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_NAME;
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET; import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET;
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT; import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_SECRET_EXPIRES_AT;
import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI; import static org.mitre.oauth2.model.RegisteredClientFields.CLIENT_URI;
import static org.mitre.oauth2.model.RegisteredClientFields.CODE_CHALLENGE_METHOD;
import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS; import static org.mitre.oauth2.model.RegisteredClientFields.CONTACTS;
import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES; import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_ACR_VALUES;
import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE; import static org.mitre.oauth2.model.RegisteredClientFields.DEFAULT_MAX_AGE;
@ -59,37 +90,6 @@ import static org.mitre.oauth2.model.RegisteredClientFields.TOS_URI;
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG; import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ALG;
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC; import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_ENCRYPTED_RESPONSE_ENC;
import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG; import static org.mitre.oauth2.model.RegisteredClientFields.USERINFO_SIGNED_RESPONSE_ALG;
import static org.mitre.util.JsonUtils.getAsArray;
import static org.mitre.util.JsonUtils.getAsDate;
import static org.mitre.util.JsonUtils.getAsJweAlgorithm;
import static org.mitre.util.JsonUtils.getAsJweEncryptionMethod;
import static org.mitre.util.JsonUtils.getAsJwsAlgorithm;
import static org.mitre.util.JsonUtils.getAsString;
import static org.mitre.util.JsonUtils.getAsStringSet;
import java.text.ParseException;
import org.mitre.jwt.assertion.AssertionValidator;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
import org.mitre.oauth2.model.RegisteredClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Sets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
/** /**
* Utility class to handle the parsing and serialization of ClientDetails objects. * Utility class to handle the parsing and serialization of ClientDetails objects.
@ -204,6 +204,9 @@ public class ClientDetailsEntityJsonProcessor {
c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS)); c.setClaimsRedirectUris(getAsStringSet(o, CLAIMS_REDIRECT_URIS));
c.setCodeChallengeMethod(getAsPkceAlgorithm(o, CODE_CHALLENGE_METHOD));
// note that this does not process or validate the software statement, that's handled in other components
String softwareStatement = getAsString(o, SOFTWARE_STATEMENT); String softwareStatement = getAsString(o, SOFTWARE_STATEMENT);
if (!Strings.isNullOrEmpty(softwareStatement)) { if (!Strings.isNullOrEmpty(softwareStatement)) {
try { try {
@ -216,6 +219,7 @@ public class ClientDetailsEntityJsonProcessor {
} }
return c; return c;
} else { } else {
return null; return null;
@ -339,6 +343,8 @@ public class ClientDetailsEntityJsonProcessor {
o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris())); o.add(CLAIMS_REDIRECT_URIS, getAsArray(c.getClaimsRedirectUris()));
o.addProperty(CODE_CHALLENGE_METHOD, c.getCodeChallengeMethod() != null ? c.getCodeChallengeMethod().getName() : null);
if (c.getSoftwareStatement() != null) { if (c.getSoftwareStatement() != null) {
o.addProperty(SOFTWARE_STATEMENT, c.getSoftwareStatement().serialize()); o.addProperty(SOFTWARE_STATEMENT, c.getSoftwareStatement().serialize());
} }

View File

@ -28,6 +28,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.mitre.oauth2.model.PKCEAlgorithm;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -138,6 +139,21 @@ public class JsonUtils {
} }
} }
/**
* Gets the value of the given member as a PKCE Algorithm, null if it doesn't exist
* @param o
* @param member
* @return
*/
public static PKCEAlgorithm getAsPkceAlgorithm(JsonObject o, String member) {
String s = getAsString(o, member);
if (s != null) {
return PKCEAlgorithm.parse(s);
} else {
return null;
}
}
/** /**
* Gets the value of the given member as a string, null if it doesn't exist * Gets the value of the given member as a string, null if it doesn't exist
*/ */

View File

@ -77,6 +77,8 @@ var ClientModel = Backbone.Model.extend({
requestUris:[], requestUris:[],
codeChallengeMethod:null,
authorities:[], authorities:[],
accessTokenValiditySeconds: null, accessTokenValiditySeconds: null,
refreshTokenValiditySeconds: null, refreshTokenValiditySeconds: null,
@ -957,7 +959,8 @@ var ClientFormView = Backbone.View.extend({
idTokenSignedResponseAlg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()), idTokenSignedResponseAlg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()),
idTokenEncryptedResponseAlg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()), idTokenEncryptedResponseAlg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()),
idTokenEncryptedResponseEnc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()), idTokenEncryptedResponseEnc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()),
tokenEndpointAuthSigningAlg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val()) tokenEndpointAuthSigningAlg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val()),
codeChallengeMethod: this.defaultToNull($('#codeChallengeMethod select').val())
}; };
// post-validate // post-validate
@ -1165,13 +1168,6 @@ var ClientFormView = Backbone.View.extend({
}); });
// add routes to the app
/*
"admin/clients":"listClients",
"admin/client/new":"newClient",
"admin/client/:id":"editClient",
*/
ui.routes.push({path: "admin/clients", name: "listClients", callback: ui.routes.push({path: "admin/clients", name: "listClients", callback:
function () { function () {

View File

@ -61,6 +61,8 @@ var DynRegClient = Backbone.Model.extend({
request_uris:[], request_uris:[],
code_challenge_method:null,
registration_access_token:null, registration_access_token:null,
registration_client_uri:null registration_client_uri:null
}, },
@ -435,7 +437,8 @@ var DynRegEditView = Backbone.View.extend({
id_token_signed_response_alg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()), id_token_signed_response_alg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()),
id_token_encrypted_response_alg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()), id_token_encrypted_response_alg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()),
id_token_encrypted_response_enc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()), id_token_encrypted_response_enc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()),
token_endpoint_auth_signing_alg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val()) token_endpoint_auth_signing_alg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val()),
code_challenge_method: this.defaultToNull($('#codeChallengeMethod select').val())
}; };
// set all empty strings to nulls // set all empty strings to nulls

View File

@ -744,6 +744,17 @@
</div> </div>
</div> </div>
<div class="control-group" id="codeChallengeMethod">
<label class="control-label" data-i18n="client.client-form.code-challenge-method">Proof Key for Code Exchange (PKCE) Code Challenge Method</label>
<div class="controls">
<select>
<option value="default" <%-client.codeChallengeMethod == null ? 'selected ' : ''%> data-i18n="client.client-form.code-challenge-none">No code challenge</option>
<option value="plain" <%-client.codeChallengeMethod == "plain" ? 'selected' : ''%> data-i18n="client.client-form.code-challenge-plain">Plain code challenge</option>
<option value="S256" <%-client.codeChallengeMethod == "S256" ? 'selected' : ''%> data-i18n="client.client-form.code-challenge-s256">SHA-256 hash algorithm</option>
</select>
</div>
</div>
</div> </div>
<div class="tab-pane" id="client-other-tab"> <div class="tab-pane" id="client-other-tab">

View File

@ -512,6 +512,17 @@
</select> </select>
</div> </div>
</div> </div>
<div class="control-group" id="codeChallengeMethod">
<label class="control-label" data-i18n="client.client-form.code-challenge-method">Proof Key for Code Exchange (PKCE) Code Challenge Method</label>
<div class="controls">
<select>
<option value="default" <%-client.code_challenge_method == null ? 'selected ' : ''%> data-i18n="client.client-form.code-challenge-none">No code challenge</option>
<option value="plain" <%-client.code_challenge_method == "plain" ? 'selected' : ''%> data-i18n="client.client-form.code-challenge-plain">Plain code challenge</option>
<option value="S256" <%-client.code_challenge_method == "S256" ? 'selected' : ''%> data-i18n="client.client-form.code-challenge-s256">SHA-256 hash algorithm</option>
</select>
</div>
</div>
</div> </div>
<div class="tab-pane" id="client-other-tab"> <div class="tab-pane" id="client-other-tab">

View File

@ -27,6 +27,7 @@ import java.util.Map;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.mitre.oauth2.model.PKCEAlgorithm;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -36,11 +37,15 @@ import org.springframework.web.servlet.view.AbstractView;
import com.google.gson.ExclusionStrategy; import com.google.gson.ExclusionStrategy;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive; import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer; import com.google.gson.JsonSerializer;
import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.EncryptionMethod;
import com.nimbusds.jose.JWEAlgorithm; import com.nimbusds.jose.JWEAlgorithm;
import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSAlgorithm;
@ -118,6 +123,15 @@ public abstract class AbstractClientEntityView extends AbstractView {
} }
}) })
.registerTypeAdapter(PKCEAlgorithm.class, new JsonSerializer<PKCEAlgorithm>() {
public JsonPrimitive serialize(PKCEAlgorithm src, Type typeOfSrc, JsonSerializationContext context) {
if (src != null) {
return new JsonPrimitive(src.getName());
} else {
return null;
}
}
})
.serializeNulls() .serializeNulls()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create(); .create();

View File

@ -29,6 +29,7 @@ import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientDetailsEntity.AppType; import org.mitre.oauth2.model.ClientDetailsEntity.AppType;
import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod;
import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType; import org.mitre.oauth2.model.ClientDetailsEntity.SubjectType;
import org.mitre.oauth2.model.PKCEAlgorithm;
import org.mitre.oauth2.service.ClientDetailsEntityService; import org.mitre.oauth2.service.ClientDetailsEntityService;
import org.mitre.oauth2.web.AuthenticationUtilities; import org.mitre.oauth2.web.AuthenticationUtilities;
import org.mitre.openid.connect.exception.ValidationException; import org.mitre.openid.connect.exception.ValidationException;
@ -68,9 +69,6 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParseException; import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import com.nimbusds.jose.Algorithm; import com.nimbusds.jose.Algorithm;
import com.nimbusds.jose.EncryptionMethod; import com.nimbusds.jose.EncryptionMethod;
@ -203,6 +201,15 @@ public class ClientAPI {
} }
} }
}) })
.registerTypeAdapter(PKCEAlgorithm.class, new JsonDeserializer<Algorithm>() {
public PKCEAlgorithm deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
if (json.isJsonPrimitive()) {
return PKCEAlgorithm.parse(json.getAsString());
} else {
return null;
}
}
})
.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ")
.create(); .create();