KEYCLOAK-2152 KEYCLOAK-2061 Client switches changes. Support for response_types and grant_types in OIDC Client registration
This commit is contained in:
parent
0492d84b59
commit
ec327c99f4
28 changed files with 241 additions and 61 deletions
|
@ -23,4 +23,19 @@ public class CollectionUtil {
|
|||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Return true if all items from col1 are in col2 and viceversa. Order is not taken into account
|
||||
public static <T> boolean collectionEquals(Collection<T> col1, Collection<T> col2) {
|
||||
if (col1.size() != col2.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (T item : col1) {
|
||||
if (!col2.contains(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
|
|
|
@ -27,6 +27,8 @@ public interface OAuth2Constants {
|
|||
|
||||
String AUTHORIZATION_CODE = "authorization_code";
|
||||
|
||||
String IMPLICIT = "implicit";
|
||||
|
||||
String PASSWORD = "password";
|
||||
|
||||
String CLIENT_CREDENTIALS = "client_credentials";
|
||||
|
|
|
@ -30,6 +30,7 @@ public class ClientRepresentation {
|
|||
protected Boolean implicitFlowEnabled;
|
||||
protected Boolean directAccessGrantsEnabled;
|
||||
protected Boolean serviceAccountsEnabled;
|
||||
@Deprecated
|
||||
protected Boolean directGrantsOnly;
|
||||
protected Boolean publicClient;
|
||||
protected Boolean frontchannelLogout;
|
||||
|
@ -216,6 +217,7 @@ public class ClientRepresentation {
|
|||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Boolean isDirectGrantsOnly() {
|
||||
return directGrantsOnly;
|
||||
}
|
||||
|
|
|
@ -14,9 +14,9 @@ public class OIDCClientRepresentation {
|
|||
|
||||
private String token_endpoint_auth_method;
|
||||
|
||||
private String grant_types;
|
||||
private List<String> grant_types;
|
||||
|
||||
private String response_types;
|
||||
private List<String> response_types;
|
||||
|
||||
private String client_id;
|
||||
|
||||
|
@ -68,19 +68,19 @@ public class OIDCClientRepresentation {
|
|||
this.token_endpoint_auth_method = token_endpoint_auth_method;
|
||||
}
|
||||
|
||||
public String getGrantTypes() {
|
||||
public List<String> getGrantTypes() {
|
||||
return grant_types;
|
||||
}
|
||||
|
||||
public void setGrantTypes(String grantTypes) {
|
||||
public void setGrantTypes(List<String> grantTypes) {
|
||||
this.grant_types = grantTypes;
|
||||
}
|
||||
|
||||
public String getResponseTypes() {
|
||||
public List<String> getResponseTypes() {
|
||||
return response_types;
|
||||
}
|
||||
|
||||
public void setResponseTypes(String responseTypes) {
|
||||
public void setResponseTypes(List<String> responseTypes) {
|
||||
this.response_types = responseTypes;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ public interface Details {
|
|||
String REDIRECT_URI = "redirect_uri";
|
||||
String RESPONSE_TYPE = "response_type";
|
||||
String RESPONSE_MODE = "response_mode";
|
||||
String GRANT_TYPE = "grant_type";
|
||||
String AUTH_TYPE = "auth_type";
|
||||
String AUTH_METHOD = "auth_method";
|
||||
String IDENTITY_PROVIDER = "identity_provider";
|
||||
|
|
|
@ -865,7 +865,6 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
|||
$scope.client = {
|
||||
enabled: true,
|
||||
standardFlowEnabled: true,
|
||||
directAccessGrantsEnabled: true,
|
||||
attributes: {}
|
||||
};
|
||||
$scope.client.attributes['saml_signature_canonicalization_method'] = $scope.canonicalization[0].value;
|
||||
|
|
|
@ -34,7 +34,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
private boolean implicitFlowEnabled;
|
||||
private boolean directAccessGrantsEnabled;
|
||||
private boolean serviceAccountsEnabled;
|
||||
private boolean directGrantsOnly;
|
||||
private int nodeReRegistrationTimeout;
|
||||
|
||||
// We are using names of defaultRoles (not ids)
|
||||
|
@ -278,14 +277,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
|||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||
}
|
||||
|
||||
public boolean isDirectGrantsOnly() {
|
||||
return directGrantsOnly;
|
||||
}
|
||||
|
||||
public void setDirectGrantsOnly(boolean directGrantsOnly) {
|
||||
this.directGrantsOnly = directGrantsOnly;
|
||||
}
|
||||
|
||||
public List<String> getDefaultRoles() {
|
||||
return defaultRoles;
|
||||
}
|
||||
|
|
|
@ -776,17 +776,19 @@ public class RepresentationToModel {
|
|||
if (resourceRep.getBaseUrl() != null) client.setBaseUrl(resourceRep.getBaseUrl());
|
||||
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
||||
if (resourceRep.isStandardFlowEnabled() != null) client.setStandardFlowEnabled(resourceRep.isStandardFlowEnabled());
|
||||
if (resourceRep.isImplicitFlowEnabled() != null) client.setImplicitFlowEnabled(resourceRep.isImplicitFlowEnabled());
|
||||
if (resourceRep.isDirectAccessGrantsEnabled() != null) client.setDirectAccessGrantsEnabled(resourceRep.isDirectAccessGrantsEnabled());
|
||||
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
|
||||
|
||||
// Backwards compatibility only
|
||||
if (resourceRep.isDirectGrantsOnly() != null) {
|
||||
logger.warn("Using deprecated 'directGrantsOnly' configuration in JSON representation. It will be removed in future versions");
|
||||
client.setStandardFlowEnabled(!resourceRep.isDirectGrantsOnly());
|
||||
client.setDirectAccessGrantsEnabled(resourceRep.isDirectGrantsOnly());
|
||||
}
|
||||
|
||||
if (resourceRep.isStandardFlowEnabled() != null) client.setStandardFlowEnabled(resourceRep.isStandardFlowEnabled());
|
||||
if (resourceRep.isImplicitFlowEnabled() != null) client.setImplicitFlowEnabled(resourceRep.isImplicitFlowEnabled());
|
||||
if (resourceRep.isDirectAccessGrantsEnabled() != null) client.setDirectAccessGrantsEnabled(resourceRep.isDirectAccessGrantsEnabled());
|
||||
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
|
||||
|
||||
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
||||
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
|
||||
if (resourceRep.getProtocol() != null) client.setProtocol(resourceRep.getProtocol());
|
||||
|
|
|
@ -726,7 +726,6 @@ public class RealmAdapter implements RealmModel {
|
|||
entity.setClientId(clientId);
|
||||
entity.setEnabled(true);
|
||||
entity.setStandardFlowEnabled(true);
|
||||
entity.setDirectAccessGrantsEnabled(true);
|
||||
entity.setRealm(realm);
|
||||
realm.getClients().add(entity);
|
||||
em.persist(entity);
|
||||
|
|
|
@ -811,7 +811,6 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
clientEntity.setRealmId(getId());
|
||||
clientEntity.setEnabled(true);
|
||||
clientEntity.setStandardFlowEnabled(true);
|
||||
clientEntity.setDirectAccessGrantsEnabled(true);
|
||||
getMongoStore().insertEntity(clientEntity, invocationContext);
|
||||
|
||||
final ClientModel model = new ClientAdapter(session, this, clientEntity, invocationContext);
|
||||
|
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
|
|
|
@ -4,6 +4,7 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationService;
|
||||
import org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory;
|
||||
import org.keycloak.services.resources.RealmsResource;
|
||||
|
@ -22,13 +23,13 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
|
||||
public static final List<String> DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list("RS256");
|
||||
|
||||
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||
public static final List<String> DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||
|
||||
public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE);
|
||||
public static final List<String> DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token");
|
||||
|
||||
public static final List<String> DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public");
|
||||
|
||||
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query");
|
||||
public static final List<String> DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post");
|
||||
|
||||
private KeycloakSession session;
|
||||
|
||||
|
|
|
@ -178,6 +178,8 @@ public class TokenEndpoint {
|
|||
} else {
|
||||
throw new ErrorResponseException(Errors.INVALID_REQUEST, "Invalid " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
event.detail(Details.GRANT_TYPE, grantType);
|
||||
}
|
||||
|
||||
public Response buildAuthorizationCodeAccessTokenResponse() {
|
||||
|
@ -327,7 +329,7 @@ public class TokenEndpoint {
|
|||
}
|
||||
|
||||
public Response buildResourceOwnerPasswordCredentialsGrant() {
|
||||
event.detail(Details.AUTH_METHOD, "oauth_credentials").detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD);
|
||||
event.detail(Details.AUTH_METHOD, "oauth_credentials");
|
||||
|
||||
if (client.isConsentRequired()) {
|
||||
event.error(Errors.CONSENT_DENIED);
|
||||
|
@ -393,8 +395,6 @@ public class TokenEndpoint {
|
|||
throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
event.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS);
|
||||
|
||||
UserModel clientUser = session.users().getUserByServiceAccountClient(client);
|
||||
|
||||
if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) {
|
||||
|
|
|
@ -42,7 +42,7 @@ public abstract class OIDCRedirectUriBuilder {
|
|||
|
||||
|
||||
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
||||
public static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
private static class QueryRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
|
||||
protected QueryRedirectUriBuilder(KeycloakUriBuilder uriBuilder) {
|
||||
super(uriBuilder);
|
||||
|
@ -64,7 +64,7 @@ public abstract class OIDCRedirectUriBuilder {
|
|||
|
||||
|
||||
// http://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
|
||||
public static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
private static class FragmentRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
|
||||
private StringBuilder fragment;
|
||||
|
||||
|
@ -98,7 +98,7 @@ public abstract class OIDCRedirectUriBuilder {
|
|||
|
||||
|
||||
// http://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html
|
||||
public static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
private static class FormPostRedirectUriBuilder extends OIDCRedirectUriBuilder {
|
||||
|
||||
private Map<String, String> params = new HashMap<>();
|
||||
|
||||
|
|
|
@ -46,6 +46,16 @@ public class OIDCResponseType {
|
|||
return new OIDCResponseType(allowedTypes);
|
||||
}
|
||||
|
||||
public static OIDCResponseType parse(List<String> responseTypes) {
|
||||
OIDCResponseType result = new OIDCResponseType(new ArrayList<String>());
|
||||
for (String respType : responseTypes) {
|
||||
OIDCResponseType responseType = parse(respType);
|
||||
result.responseTypes.addAll(responseType.responseTypes);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void validateAllowedTypes(List<String> responseTypes) {
|
||||
if (responseTypes.size() == 0) {
|
||||
throw new IllegalStateException("No responseType provided");
|
||||
|
@ -53,9 +63,6 @@ public class OIDCResponseType {
|
|||
if (responseTypes.contains(NONE) && responseTypes.size() > 1) {
|
||||
throw new IllegalArgumentException("None not allowed with some other response_type");
|
||||
}
|
||||
if (responseTypes.contains(ID_TOKEN) && responseTypes.size() == 1) {
|
||||
throw new IllegalArgumentException("Not supported to use response_type=id_token alone");
|
||||
}
|
||||
if (responseTypes.contains(TOKEN) && responseTypes.size() == 1) {
|
||||
throw new IllegalArgumentException("Not supported to use response_type=token alone");
|
||||
}
|
||||
|
@ -72,7 +79,7 @@ public class OIDCResponseType {
|
|||
}
|
||||
|
||||
public boolean isImplicitFlow() {
|
||||
return hasResponseType(TOKEN) && hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
|
||||
return hasResponseType(ID_TOKEN) && !hasResponseType(CODE);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package org.keycloak.services.clientregistration;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class ClientRegistrationException extends RuntimeException {
|
||||
|
||||
public ClientRegistrationException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public ClientRegistrationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public ClientRegistrationException(Throwable throwable) {
|
||||
super(throwable);
|
||||
}
|
||||
|
||||
public ClientRegistrationException(String message, Throwable throwable) {
|
||||
super(message, throwable);
|
||||
}
|
||||
}
|
|
@ -1,21 +1,48 @@
|
|||
package org.keycloak.services.clientregistration.oidc;
|
||||
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
public class DescriptionConverter {
|
||||
|
||||
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
|
||||
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) throws ClientRegistrationException {
|
||||
ClientRepresentation client = new ClientRepresentation();
|
||||
client.setClientId(clientOIDC.getClientId());
|
||||
client.setName(clientOIDC.getClientName());
|
||||
client.setRedirectUris(clientOIDC.getRedirectUris());
|
||||
client.setBaseUrl(clientOIDC.getClientUri());
|
||||
|
||||
List<String> oidcResponseTypes = clientOIDC.getResponseTypes();
|
||||
if (oidcResponseTypes == null || oidcResponseTypes.isEmpty()) {
|
||||
oidcResponseTypes = Collections.singletonList(OIDCResponseType.CODE);
|
||||
}
|
||||
List<String> oidcGrantTypes = clientOIDC.getGrantTypes();
|
||||
|
||||
try {
|
||||
OIDCResponseType responseType = OIDCResponseType.parse(oidcResponseTypes);
|
||||
client.setStandardFlowEnabled(responseType.hasResponseType(OIDCResponseType.CODE));
|
||||
client.setImplicitFlowEnabled(responseType.isImplicitOrHybridFlow());
|
||||
if (oidcGrantTypes != null) {
|
||||
client.setDirectAccessGrantsEnabled(oidcGrantTypes.contains(OAuth2Constants.PASSWORD));
|
||||
client.setServiceAccountsEnabled(oidcGrantTypes.contains(OAuth2Constants.CLIENT_CREDENTIALS));
|
||||
}
|
||||
} catch (IllegalArgumentException iae) {
|
||||
throw new ClientRegistrationException(iae.getMessage(), iae);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
|
@ -28,7 +55,45 @@ public class DescriptionConverter {
|
|||
response.setRedirectUris(client.getRedirectUris());
|
||||
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
||||
response.setRegistrationClientUri(uri.toString());
|
||||
response.setResponseTypes(getOIDCResponseTypes(client));
|
||||
response.setGrantTypes(getOIDCGrantTypes(client));
|
||||
return response;
|
||||
}
|
||||
|
||||
private static List<String> getOIDCResponseTypes(ClientRepresentation client) {
|
||||
List<String> responseTypes = new ArrayList<>();
|
||||
if (client.isStandardFlowEnabled()) {
|
||||
responseTypes.add(OAuth2Constants.CODE);
|
||||
responseTypes.add(OIDCResponseType.NONE);
|
||||
}
|
||||
if (client.isImplicitFlowEnabled()) {
|
||||
responseTypes.add(OIDCResponseType.ID_TOKEN);
|
||||
responseTypes.add("id_token token");
|
||||
}
|
||||
if (client.isStandardFlowEnabled() && client.isImplicitFlowEnabled()) {
|
||||
responseTypes.add("code id_token");
|
||||
responseTypes.add("code token");
|
||||
responseTypes.add("code id_token token");
|
||||
}
|
||||
return responseTypes;
|
||||
}
|
||||
|
||||
private static List<String> getOIDCGrantTypes(ClientRepresentation client) {
|
||||
List<String> grantTypes = new ArrayList<>();
|
||||
if (client.isStandardFlowEnabled()) {
|
||||
grantTypes.add(OAuth2Constants.AUTHORIZATION_CODE);
|
||||
}
|
||||
if (client.isImplicitFlowEnabled()) {
|
||||
grantTypes.add(OAuth2Constants.IMPLICIT);
|
||||
}
|
||||
if (client.isDirectAccessGrantsEnabled()) {
|
||||
grantTypes.add(OAuth2Constants.PASSWORD);
|
||||
}
|
||||
if (client.isServiceAccountsEnabled()) {
|
||||
grantTypes.add(OAuth2Constants.CLIENT_CREDENTIALS);
|
||||
}
|
||||
grantTypes.add(OAuth2Constants.REFRESH_TOKEN);
|
||||
return grantTypes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.keycloak.services.clientregistration.oidc;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -9,6 +10,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.services.ErrorResponseException;
|
||||
import org.keycloak.services.clientregistration.AbstractClientRegistrationProvider;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationAuth;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||
import org.keycloak.services.clientregistration.ErrorCodes;
|
||||
|
||||
import javax.ws.rs.*;
|
||||
|
@ -21,6 +23,8 @@ import java.net.URI;
|
|||
*/
|
||||
public class OIDCClientRegistrationProvider extends AbstractClientRegistrationProvider {
|
||||
|
||||
private static final Logger log = Logger.getLogger(OIDCClientRegistrationProvider.class);
|
||||
|
||||
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
@ -33,12 +37,17 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
|||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client Identifier included", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
try {
|
||||
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||
client = create(client);
|
||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
||||
clientOIDC.setClientIdIssuedAt(Time.currentTime());
|
||||
return Response.created(uri).entity(clientOIDC).build();
|
||||
} catch (ClientRegistrationException cre) {
|
||||
log.error(cre.getMessage());
|
||||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client metadata invalid", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -54,11 +63,16 @@ public class OIDCClientRegistrationProvider extends AbstractClientRegistrationPr
|
|||
@Path("{clientId}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public Response updateOIDC(@PathParam("clientId") String clientId, OIDCClientRepresentation clientOIDC) {
|
||||
try {
|
||||
ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||
client = update(clientId, client);
|
||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(client.getClientId()).build();
|
||||
clientOIDC = DescriptionConverter.toExternalResponse(client, uri);
|
||||
return Response.ok(clientOIDC).build();
|
||||
} catch (ClientRegistrationException cre) {
|
||||
log.error(cre.getMessage());
|
||||
throw new ErrorResponseException(ErrorCodes.INVALID_CLIENT_METADATA, "Client metadata invalid", Response.Status.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
|||
import org.keycloak.representations.idm.GroupRepresentation;
|
||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.clientregistration.ClientRegistrationException;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.managers.LDAPConnectionTestManager;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package org.keycloak.test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
|
@ -16,7 +19,7 @@ public class ResponseTypeTest {
|
|||
assertFail("foo");
|
||||
assertSuccess("code");
|
||||
assertSuccess("none");
|
||||
assertFail("id_token");
|
||||
assertSuccess("id_token");
|
||||
assertFail("token");
|
||||
assertFail("refresh_token");
|
||||
assertSuccess("id_token token");
|
||||
|
@ -27,6 +30,38 @@ public class ResponseTypeTest {
|
|||
assertFail("code refresh_token");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleResponseTypes() {
|
||||
try {
|
||||
OIDCResponseType.parse(Arrays.asList("code", "token"));
|
||||
Assert.fail("Not expected to parse with success");
|
||||
} catch (IllegalArgumentException iae) {
|
||||
}
|
||||
|
||||
OIDCResponseType responseType = OIDCResponseType.parse(Collections.singletonList("code"));
|
||||
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
||||
|
||||
responseType = OIDCResponseType.parse(Arrays.asList("code", "none"));
|
||||
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||
Assert.assertTrue(responseType.hasResponseType("none"));
|
||||
Assert.assertFalse(responseType.isImplicitOrHybridFlow());
|
||||
|
||||
responseType = OIDCResponseType.parse(Arrays.asList("code", "code token"));
|
||||
Assert.assertTrue(responseType.hasResponseType("code"));
|
||||
Assert.assertFalse(responseType.hasResponseType("none"));
|
||||
Assert.assertTrue(responseType.hasResponseType("token"));
|
||||
Assert.assertFalse(responseType.hasResponseType("id_token"));
|
||||
Assert.assertTrue(responseType.isImplicitOrHybridFlow());
|
||||
Assert.assertFalse(responseType.isImplicitFlow());
|
||||
|
||||
responseType = OIDCResponseType.parse(Arrays.asList("id_token", "id_token token"));
|
||||
Assert.assertFalse(responseType.hasResponseType("code"));
|
||||
Assert.assertTrue(responseType.isImplicitOrHybridFlow());
|
||||
Assert.assertTrue(responseType.isImplicitFlow());
|
||||
}
|
||||
|
||||
private void assertSuccess(String responseType) {
|
||||
OIDCResponseType.parse(responseType);
|
||||
}
|
||||
|
|
|
@ -2,12 +2,16 @@ package org.keycloak.testsuite.client;
|
|||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.common.util.CollectionUtil;
|
||||
import org.keycloak.protocol.oidc.utils.OIDCResponseType;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessCreatePresentation;
|
||||
import org.keycloak.representations.idm.ClientInitialAccessPresentation;
|
||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
@ -49,6 +53,8 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
assertEquals("http://root", response.getClientUri());
|
||||
assertEquals(1, response.getRedirectUris().size());
|
||||
assertEquals("http://redirect", response.getRedirectUris().get(0));
|
||||
assertEquals(Arrays.asList("code", "none"), response.getResponseTypes());
|
||||
assertEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -59,6 +65,8 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
OIDCClientRepresentation rep = reg.oidc().get(response.getClientId());
|
||||
assertNotNull(rep);
|
||||
assertNotEquals(response.getRegistrationAccessToken(), rep.getRegistrationAccessToken());
|
||||
assertTrue(CollectionUtil.collectionEquals(Arrays.asList("code", "none"), response.getResponseTypes()));
|
||||
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN), response.getGrantTypes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -67,11 +75,26 @@ public class OIDCClientRegistrationTest extends AbstractClientRegistrationTest {
|
|||
reg.auth(Auth.token(response));
|
||||
|
||||
response.setRedirectUris(Collections.singletonList("http://newredirect"));
|
||||
response.setResponseTypes(Arrays.asList("code", "id_token token", "code id_token token"));
|
||||
response.setGrantTypes(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD));
|
||||
|
||||
OIDCClientRepresentation updated = reg.oidc().update(response);
|
||||
|
||||
assertEquals(1, updated.getRedirectUris().size());
|
||||
assertEquals("http://newredirect", updated.getRedirectUris().get(0));
|
||||
assertTrue(CollectionUtil.collectionEquals(Collections.singletonList("http://newredirect"), updated.getRedirectUris()));
|
||||
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD), updated.getGrantTypes()));
|
||||
assertTrue(CollectionUtil.collectionEquals(Arrays.asList(OAuth2Constants.CODE, OIDCResponseType.NONE, OIDCResponseType.ID_TOKEN, "id_token token", "code id_token", "code token", "code id_token token"), updated.getResponseTypes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateClientError() throws ClientRegistrationException {
|
||||
try {
|
||||
OIDCClientRepresentation response = create();
|
||||
reg.auth(Auth.token(response));
|
||||
response.setResponseTypes(Arrays.asList("code", "token"));
|
||||
reg.oidc().update(response);
|
||||
fail("Not expected to end with success");
|
||||
} catch (ClientRegistrationException cre) {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -135,7 +135,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
|
|||
return expect(EventType.CLIENT_LOGIN)
|
||||
.detail(Details.CODE_ID, isCodeId())
|
||||
.detail(Details.CLIENT_AUTH_METHOD, ClientIdAndSecretAuthenticator.PROVIDER_ID)
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.CLIENT_CREDENTIALS)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.session(isUUID());
|
||||
}
|
||||
|
|
|
@ -218,7 +218,7 @@ public class CustomFlowTest {
|
|||
.client(clientId)
|
||||
.user(userId)
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, login)
|
||||
|
|
|
@ -257,7 +257,7 @@ public class GroupTest {
|
|||
.client(clientId)
|
||||
.user(userId)
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, login)
|
||||
|
|
|
@ -189,7 +189,7 @@ public class ClientAuthSignedJWTTest {
|
|||
events.expectLogin()
|
||||
.client("client2")
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, "test-user@localhost")
|
||||
|
|
|
@ -319,7 +319,7 @@ public class OfflineTokenTest {
|
|||
.client("offline-client")
|
||||
.user(userId)
|
||||
.session(token.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
|
@ -361,7 +361,7 @@ public class OfflineTokenTest {
|
|||
.client("offline-client")
|
||||
.user(userId)
|
||||
.session(token.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, token.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, offlineToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_TYPE, TokenUtil.TOKEN_TYPE_OFFLINE)
|
||||
|
|
|
@ -94,7 +94,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.client(clientId)
|
||||
.user(userId)
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.detail(Details.USERNAME, login)
|
||||
|
@ -130,7 +130,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session(accessToken.getSessionState())
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.TOKEN_ID, accessToken.getId())
|
||||
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
|
||||
.removeDetail(Details.CODE_ID)
|
||||
|
@ -286,7 +286,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
events.expectLogin()
|
||||
.client("resource-owner")
|
||||
.session((String) null)
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
.removeDetail(Details.CONSENT)
|
||||
|
@ -308,7 +308,7 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
|
|||
.client("resource-owner")
|
||||
.user((String) null)
|
||||
.session((String) null)
|
||||
.detail(Details.RESPONSE_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
|
||||
.detail(Details.USERNAME, "invalid")
|
||||
.removeDetail(Details.CODE_ID)
|
||||
.removeDetail(Details.REDIRECT_URI)
|
||||
|
|
Loading…
Reference in a new issue