Merge pull request #1904 from mposolda/master

Fixes
This commit is contained in:
Marek Posolda 2015-11-30 20:05:46 +01:00
commit 07e12f3b5f
71 changed files with 476 additions and 89 deletions

View file

@ -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;
}
}

View file

@ -48,7 +48,7 @@
<addColumn tableName="IDENTITY_PROVIDER">
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
<constraints nullable="true"/>
</column>
</addColumn>
@ -56,6 +56,7 @@
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
</addColumn>
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD" />
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
@ -83,13 +84,14 @@
<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>
<update tableName="CLIENT">
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
<column name="DIRECT_ACCESS_GRANTS_ENABLED" valueBoolean="true"/>
<where>DIRECT_GRANTS_ONLY = :value</where>
<whereParams>
<param valueBoolean="true" />

View file

@ -27,7 +27,7 @@ public class Update1_7_0 extends Update {
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
client.append("standardFlowEnabled", !directGrantsOnly);
client.append("implicitFlowEnabled", false);
client.append("directAccessGrantsEnabled", true);
client.append("directAccessGrantsEnabled", directGrantsOnly);
client.removeField("directGrantsOnly");
clients.save(client);

View file

@ -27,6 +27,8 @@ public interface OAuth2Constants {
String AUTHORIZATION_CODE = "authorization_code";
String IMPLICIT = "implicit";
String PASSWORD = "password";
String CLIENT_CREDENTIALS = "client_credentials";

View file

@ -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;
}

View file

@ -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;
}

View file

@ -82,11 +82,24 @@
<section>
<title>Migrating to 1.7.0.CR1</title>
<simplesect>
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
<title>Direct access grants disabled by default for clients</title>
<para>
form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for
the the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions.
In order to add more compliance with OpenID Connect specification, we added flags into admin console to Client Settings page,
where you can enable/disable various kinds of OpenID Connect/OAuth2 flows (Standard flow, Implicit flow, Direct Access Grants, Service Accounts).
As part of this, we have <literal>Direct Access Grants</literal> (corresponds to OAuth2 <literal>Resource Owner Password Credentials Grant</literal>) disabled by default for new clients.
</para>
<para>
Clients migrated from previous version have <literal>Direct Access Grants</literal> enabled just if they had flag <literal>Direct Grants Only</literal> on. The
<literal>Direct Grants Only</literal> flag was removed as if you enable Direct Access Grants and disable both Standard+Implicit flow, you will achieve same effect.
</para>
<para>
We also added builtin client <literal>admin-cli</literal> to each realm. This client has <literal>Direct Access Grants</literal> enabled.
So if you're using Admin REST API or Keycloak admin-client, you should update your configuration to use <literal>admin-cli</literal> instead
of <literal>security-admin-console</literal> as the latter one doesn't have direct access grants enabled anymore by default.
</para>
</simplesect>
<simplesect>
<title>Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator</title>
<para>
In this version, we added <literal>First Broker Login</literal>, which allows you to specify what exactly should be done
when new user is logged through Identity provider (or Social provider), but there is no existing Keycloak user
@ -100,6 +113,13 @@
and then you configure the option under <literal>Review Profile</literal> authenticator.
</para>
</simplesect>
<simplesect>
<title>Element 'form-error-page' in web.xml not supported anymore</title>
<para>
form-error-page in web.xml will no longer work for client adapter authentication errors. You must define an error-page for
the various HTTP error codes. See documentation for more details if you want to catch and handle adapter error conditions.
</para>
</simplesect>
</section>
<section>
<title>Migrating to 1.6.0.Final</title>

View file

@ -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";

View file

@ -24,6 +24,7 @@
"clients": [
{
"clientId": "examples-admin-client",
"directAccessGrantsEnabled": true,
"enabled": true,
"fullScopeAllowed": true,
"baseUrl": "/examples-admin-client",

View file

@ -43,6 +43,8 @@
"clients": [
{
"clientId": "basic-auth-service",
"standardFlowEnabled": false,
"directAccessGrantsEnabled": true,
"enabled": true,
"adminUrl": "/basicauth",
"baseUrl": "/basicauth",

View file

@ -49,6 +49,7 @@
"enabled": true,
"fullScopeAllowed": true,
"baseUrl": "/admin-client",
"directAccessGrantsEnabled": true,
"redirectUris": [
"/admin-client/*"
],

View file

@ -178,7 +178,8 @@
"clientId": "admin-client",
"enabled": true,
"publicClient": true,
"directGrantsOnly": true
"standardFlowEnabled": false,
"directAccessGrantsEnabled": true
},
{
"clientId": "product-sa-client",

View file

@ -182,7 +182,8 @@
"clientId": "ssh-jmx-admin-client",
"enabled": true,
"publicClient": false,
"directGrantsOnly": true,
"standardFlowEnabled": false,
"directAccessGrantsEnabled": true,
"secret": "password"
}
],

View file

@ -55,6 +55,7 @@ role_read-token=Read token
role_offline-access=Offline access
client_account=Account
client_security-admin-console=Security Admin Console
client_admin-cli=Admin CLI
client_realm-management=Realm Management
client_broker=Broker

View file

@ -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;

View file

@ -113,6 +113,7 @@ role_read-token=Read token
role_offline-access=Offline access
client_account=Account
client_security-admin-console=Security Admin Console
client_admin-cli=Admin CLI
client_realm-management=Realm Management
client_broker=Broker

View file

@ -1,6 +1,7 @@
package org.keycloak.migration;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.provider.Provider;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@ -21,4 +22,6 @@ public interface MigrationProvider extends Provider {
List<ProtocolMapperModel> getBuiltinMappers(String protocol);
void setupAdminCli(RealmModel realm);
}

View file

@ -2,10 +2,14 @@ package org.keycloak.migration.migrators;
import java.util.List;
import org.keycloak.migration.MigrationProvider;
import org.keycloak.migration.ModelVersion;
import org.keycloak.models.AuthenticationFlowModel;
import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.DefaultAuthenticationFlows;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -17,7 +21,24 @@ public class MigrateTo1_7_0 {
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
// Set default accessToken timeout for implicit flow
realm.setAccessTokenLifespanForImplicitFlow(Constants.DEFAULT_ACCESS_TOKEN_LIFESPAN_FOR_IMPLICIT_FLOW_TIMEOUT);
// Add 'admin-cli' builtin client
MigrationProvider migrationProvider = session.getProvider(MigrationProvider.class);
migrationProvider.setupAdminCli(realm);
// add firstBrokerLogin flow and set it to all identityProviders
DefaultAuthenticationFlows.migrateFlows(realm);
AuthenticationFlowModel firstBrokerLoginFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW);
List<IdentityProviderModel> identityProviders = realm.getIdentityProviders();
for (IdentityProviderModel identityProvider : identityProviders) {
if (identityProvider.getFirstBrokerLoginFlowId() == null) {
identityProvider.setFirstBrokerLoginFlowId(firstBrokerLoginFlow.getId());
realm.updateIdentityProvider(identityProvider);
}
}
}
}
}

View file

@ -8,6 +8,7 @@ import org.keycloak.OAuth2Constants;
*/
public interface Constants {
String ADMIN_CONSOLE_CLIENT_ID = "security-admin-console";
String ADMIN_CLI_CLIENT_ID = "admin-cli";
String ACCOUNT_MANAGEMENT_CLIENT_ID = "account";
String IMPERSONATION_SERVICE_CLIENT_ID = "impersonation";

View file

@ -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;
}

View file

@ -460,6 +460,10 @@ public class RepresentationToModel {
newRealm.setClientAuthenticationFlow(newRealm.getFlowByAlias(rep.getClientAuthenticationFlow()));
}
// Added in 1.7
if (newRealm.getFlowByAlias(DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW) == null) {
DefaultAuthenticationFlows.firstBrokerLoginFlow(newRealm, true);
}
}
private static void convertDeprecatedSocialProviders(RealmRepresentation rep) {
@ -776,17 +780,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());

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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() {
@ -223,6 +225,11 @@ public class TokenEndpoint {
throw new ErrorResponseException("invalid_grant", "Auth error", Response.Status.BAD_REQUEST);
}
if (!client.isStandardFlowEnabled()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException("invalid_grant", "Client not allowed to exchange code", Response.Status.BAD_REQUEST);
}
UserModel user = session.users().getUserById(userSession.getUser().getId(), realm);
if (user == null) {
event.error(Errors.USER_NOT_FOUND);
@ -327,7 +334,12 @@ 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.isDirectAccessGrantsEnabled()) {
event.error(Errors.NOT_ALLOWED);
throw new ErrorResponseException("invalid_grant", "Client not allowed for direct access grants", Response.Status.BAD_REQUEST);
}
if (client.isConsentRequired()) {
event.error(Errors.CONSENT_DENIED);
@ -393,8 +405,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) {

View file

@ -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<>();

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -113,6 +113,7 @@ public class RealmManager implements RealmImporter {
setupAccountManagement(realm);
setupBrokerService(realm);
setupAdminConsole(realm);
setupAdminCli(realm);
setupImpersonationService(realm);
setupAuthenticationFlows(realm);
setupRequiredActions(realm);
@ -158,6 +159,30 @@ public class RealmManager implements RealmImporter {
adminConsole.addScopeMapping(adminRole);
}
public void setupAdminCli(RealmModel realm) {
ClientModel adminCli = realm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
if (adminCli == null) {
adminCli = new ClientManager(this).createClient(realm, Constants.ADMIN_CLI_CLIENT_ID);
adminCli.setName("${client_" + Constants.ADMIN_CLI_CLIENT_ID + "}");
adminCli.setEnabled(true);
adminCli.setPublicClient(true);
adminCli.setFullScopeAllowed(false);
adminCli.setStandardFlowEnabled(false);
adminCli.setDirectAccessGrantsEnabled(true);
RoleModel adminRole;
if (realm.getName().equals(Config.getAdminRealm())) {
adminRole = realm.getRole(AdminRoles.ADMIN);
} else {
String realmAdminApplicationClientId = getRealmAdminClientId(realm);
ClientModel realmAdminApp = realm.getClientByClientId(realmAdminApplicationClientId);
adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
}
adminCli.addScopeMapping(adminRole);
}
}
public String getRealmAdminClientId(RealmModel realm) {
return Constants.REALM_MANAGEMENT_CLIENT_ID;
}
@ -375,6 +400,16 @@ public class RealmManager implements RealmImporter {
if (!hasBrokerClient(rep)) setupBrokerService(realm);
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
boolean postponeAdminCliSetup = false;
if (!hasAdminCliClient(rep)) {
if (hasRealmAdminManagementClient(rep)) {
postponeAdminCliSetup = true;
} else {
setupAdminCli(realm);
}
}
if (!hasRealmRole(rep, Constants.OFFLINE_ACCESS_ROLE)) setupOfflineTokens(realm);
RepresentationToModel.importRealm(session, rep, realm);
@ -389,6 +424,10 @@ public class RealmManager implements RealmImporter {
setupImpersonationService(realm);
}
if (postponeAdminCliSetup) {
setupAdminCli(realm);
}
setupAuthenticationFlows(realm);
setupRequiredActions(realm);
@ -428,6 +467,10 @@ public class RealmManager implements RealmImporter {
return hasClient(rep, Constants.ADMIN_CONSOLE_CLIENT_ID);
}
private boolean hasAdminCliClient(RealmRepresentation rep) {
return hasClient(rep, Constants.ADMIN_CLI_CLIENT_ID);
}
private boolean hasClient(RealmRepresentation rep, String clientId) {
if (rep.getClients() != null) {
for (ClientRepresentation clientRep : rep.getClients()) {

View file

@ -10,12 +10,14 @@ import org.keycloak.migration.MigrationProvider;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.protocol.LoginProtocol;
import org.keycloak.protocol.LoginProtocolFactory;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.services.managers.RealmManager;
/**
* Various common utils needed for migration from older version to newer
@ -59,6 +61,11 @@ public class DefaultMigrationProvider implements MigrationProvider {
return providerFactory.getBuiltinMappers();
}
@Override
public void setupAdminCli(RealmModel realm) {
new RealmManager(session).setupAdminCli(realm);
}
@Override
public void close() {
}

View file

@ -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;

View file

@ -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);
}

View file

@ -183,7 +183,7 @@ public class ContainersTestEnricher {
private void initializeAdminClient() {
adminClient.set(Keycloak.getInstance(
getAuthServerContextRootFromSystemProperty() + "/auth",
MASTER, ADMIN, ADMIN, Constants.ADMIN_CONSOLE_CLIENT_ID));
MASTER, ADMIN, ADMIN, Constants.ADMIN_CLI_CLIENT_ID));
}
private void initializeOAuthClient() {

View file

@ -104,7 +104,7 @@ public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTes
}
private String getToken(String username, String password) {
return oauthClient.getToken(REALM_NAME, "security-admin-console", null, username, password).getToken();
return oauthClient.getToken(REALM_NAME, Constants.ADMIN_CLI_CLIENT_ID, null, username, password).getToken();
}
}

View file

@ -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

View file

@ -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());
}

View file

@ -187,7 +187,7 @@ public class AdapterTestStrategy extends ExternalResource {
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
// View stats
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats();
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats();
Map<String, String> customerPortalStats = null;
Map<String, String> productPortalStats = null;
for (Map<String, String> s : stats) {
@ -594,7 +594,7 @@ public class AdapterTestStrategy extends ExternalResource {
loginAndCheckSession(driver, loginPage);
// logout mposolda with admin client
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
Keycloak keycloakAdmin = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
ApiUtil.findClientByClientId(keycloakAdmin.realm("demo"), "session-portal").logoutUser("mposolda");
// bburke should be still logged with original httpSession in our browser window

View file

@ -131,7 +131,7 @@ public class RelativeUriAdapterTest {
Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad"));
// View stats
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats();
List<Map<String, String>> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats();
Map<String, String> customerPortalStats = null;
Map<String, String> productPortalStats = null;
for (Map<String, String> s : stats) {

View file

@ -43,7 +43,7 @@ public class AddUserTest {
try {
server.start();
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CONSOLE_CLIENT_ID);
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "addusertest-admin", "password", Constants.ADMIN_CLI_CLIENT_ID);
keycloak.realms().findAll();
RealmRepresentation testRealm = new RealmRepresentation();

View file

@ -45,10 +45,12 @@ public abstract class AbstractClientTest {
testRealm.setEnabled(true);
testRealm.setAccessCodeLifespanUserAction(600);
KeycloakModelUtils.generateRealmKeys(testRealm);
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
realm = keycloak.realm(REALM_NAME);
}

View file

@ -79,7 +79,7 @@ public class AdminAPITest {
RealmManager manager = new RealmManager(session);
RealmModel adminRealm = manager.getRealm(Config.getAdminRealm());
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername("admin", adminRealm);
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);

View file

@ -4,6 +4,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.keycloak.admin.client.resource.ClientResource;
import org.keycloak.admin.client.resource.ProtocolMappersResource;
import org.keycloak.models.Constants;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.ProtocolMapperRepresentation;
@ -42,7 +43,7 @@ public class ClientTest extends AbstractClientTest {
@Test
public void getClients() {
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker");
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", Constants.ADMIN_CLI_CLIENT_ID);
}
private String createClient() {
@ -60,7 +61,7 @@ public class ClientTest extends AbstractClientTest {
String id = createClient();
assertNotNull(realm.clients().get(id));
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app");
assertNames(realm.clients().findAll(), "account", "realm-management", "security-admin-console", "broker", "my-app", Constants.ADMIN_CLI_CLIENT_ID);
}
@Test

View file

@ -117,7 +117,7 @@ public class ImpersonationTest {
RealmManager manager = new RealmManager(session);
RealmModel adminRealm = manager.getRealm(realm);
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
ClientModel adminConsole = adminRealm.getClientByClientId(Constants.ADMIN_CLI_CLIENT_ID);
TokenManager tm = new TokenManager();
UserModel admin = session.users().getUserByUsername(username, adminRealm);
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);

View file

@ -77,6 +77,8 @@ public class FederationProvidersIntegrationTest {
ldapFedProvider.getLdapIdentityStore().updatePassword(john, "Password1");
LDAPObject existing = FederationTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678");
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});

View file

@ -77,6 +77,8 @@ public class BruteForceTest {
appRealm.setBruteForceProtected(true);
appRealm.setFailureFactor(2);
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@ -116,7 +118,7 @@ public class BruteForceTest {
}
public String getAdminToken() throws Exception {
String clientId = Constants.ADMIN_CONSOLE_CLIENT_ID;
String clientId = Constants.ADMIN_CLI_CLIENT_ID;
return oauth.doGrantAccessTokenRequest("master", "admin", "admin", null, clientId, null).getAccessToken();
}

View file

@ -127,7 +127,11 @@ public class CustomFlowTest {
// Set passthrough clientAuthenticator for our clients
ClientModel dummyClient = new ClientManager().createClient(appRealm, "dummy-client");
dummyClient.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
appRealm.getClientByClientId("test-app").setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
dummyClient.setDirectAccessGrantsEnabled(true);
ClientModel testApp = appRealm.getClientByClientId("test-app");
testApp.setClientAuthenticatorType(PassThroughClientAuthenticator.PROVIDER_ID);
testApp.setDirectAccessGrantsEnabled(true);
}
});
@ -218,7 +222,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)

View file

@ -48,6 +48,7 @@ public class JaxrsBasicAuthTest {
app.setEnabled(true);
app.setSecret("password");
app.setFullScopeAllowed(true);
app.setDirectAccessGrantsEnabled(true);
JaxrsBasicAuthTest.appRealm = appRealm;
}

View file

@ -456,7 +456,7 @@ public class SamlAdapterTestStrategy extends ExternalResource {
public static void uploadSP(String AUTH_SERVER_URL) {
try {
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
Keycloak keycloak = Keycloak.getInstance(AUTH_SERVER_URL, "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID, null);
RealmResource admin = keycloak.realm("demo");
admin.toRepresentation();

View file

@ -62,15 +62,19 @@ public class GroupTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
app.setDirectAccessGrantsEnabled(true);
app.setSecret("secret");
app = appRealm.getClientByClientId("test-app");
app.setDirectAccessGrantsEnabled(true);
UserModel user = session.users().addUser(appRealm, "direct-login");
user.setEmail("direct-login@localhost");
user.setEnabled(true);
session.users().updateCredential(appRealm, user, UserCredentialModel.password("password"));
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID);
keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID);
}
});
@ -257,7 +261,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)

View file

@ -90,7 +90,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size());
List<ClientModel> resources = realm.getClients();
Assert.assertEquals(7, resources.size());
Assert.assertEquals(8, resources.size());
// Test applications imported
ClientModel application = realm.getClientByClientId("Application");
@ -101,7 +101,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertNotNull(otherApp);
Assert.assertNull(nonExisting);
Map<String, ClientModel> clients = realm.getClientNameMap();
Assert.assertEquals(7, clients.size());
Assert.assertEquals(8, clients.size());
Assert.assertTrue(clients.values().contains(application));
Assert.assertTrue(clients.values().contains(otherApp));
Assert.assertTrue(clients.values().contains(accountApp));

View file

@ -92,7 +92,14 @@ import static org.junit.Assert.*;
public class AccessTokenTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@Rule
public WebRule webRule = new WebRule(this);

View file

@ -61,6 +61,7 @@ public class ClientAuthSignedJWTTest {
app1.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
ClientModel app2 = appRealm.addClient("client2");
app2.setDirectAccessGrantsEnabled(true);
new ClientManager(manager).enableServiceAccount(app2);
app2.setClientAuthenticatorType(JWTClientAuthenticator.PROVIDER_ID);
@ -189,7 +190,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")

View file

@ -67,6 +67,7 @@ public class OfflineTokenTest {
appRealm.setSsoSessionIdleTimeout(30);
ClientModel app = new ClientManager(manager).createClient(appRealm, "offline-client");
app.setDirectAccessGrantsEnabled(true);
app.setSecret("secret1");
String testAppRedirectUri = appRealm.getClientByClientId("test-app").getRedirectUris().iterator().next();
offlineClientAppUri = UriUtils.getOrigin(testAppRedirectUri) + "/offline-client";
@ -319,7 +320,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 +362,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)

View file

@ -71,7 +71,14 @@ import static org.junit.Assert.assertNull;
public class RefreshTokenTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@Rule
public WebRule webRule = new WebRule(this);

View file

@ -35,9 +35,11 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel app = new ClientManager(manager).createClient(appRealm, "resource-owner");
app.setDirectAccessGrantsEnabled(true);
app.setSecret("secret");
ClientModel app2 = new ClientManager(manager).createClient(appRealm, "resource-owner-public");
app2.setDirectAccessGrantsEnabled(true);
app2.setPublicClient(true);
UserModel user = session.users().addUser(appRealm, "direct-login");
@ -94,7 +96,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 +132,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)
@ -191,6 +193,41 @@ public class ResourceOwnerPasswordCredentialsGrantTest {
.assertEvent();
}
@Test
public void grantAccessTokenClientNotAllowed() throws Exception {
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel client = appRealm.getClientByClientId("resource-owner");
client.setDirectAccessGrantsEnabled(false);
}
});
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password");
assertEquals(400, response.getStatusCode());
assertEquals("invalid_grant", response.getError());
events.expectLogin()
.client("resource-owner")
.session((String) null)
.clearDetails()
.error(Errors.NOT_ALLOWED)
.user((String) null)
.assertEvent();
keycloakRule.update(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
ClientModel client = appRealm.getClientByClientId("resource-owner");
client.setDirectAccessGrantsEnabled(true);
}
});
}
@Test
public void grantAccessTokenVerifyEmail() throws Exception {
@ -286,7 +323,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 +345,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)

View file

@ -30,6 +30,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.UserInfo;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@ -56,7 +57,14 @@ import static org.junit.Assert.assertNotNull;
public class UserInfoTest {
@ClassRule
public static KeycloakRule keycloakRule = new KeycloakRule();
public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
@Override
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true);
}
});
@Rule
public WebRule webRule = new WebRule(this);

View file

@ -457,7 +457,7 @@ public class SamlBindingTest {
public static void uploadSP() {
try {
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CONSOLE_CLIENT_ID, null);
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID, null);
RealmResource admin = keycloak.realm("demo");
admin.toRepresentation();

View file

@ -120,6 +120,7 @@
{
"name": "customer-portal",
"enabled": true,
"directAccessGrantsEnabled": true,
"adminUrl": "http://localhost:8081/customer-portal",
"baseUrl": "http://localhost:8081/customer-portal",
"redirectUris": [

View file

@ -156,6 +156,7 @@
"name": "Applicationn",
"enabled": true,
"implicitFlowEnabled": true,
"directAccessGrantsEnabled": true,
"nodeReRegistrationTimeout": 50,
"registeredNodes": {
"node1": 10,

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -46,6 +46,7 @@
"fullScopeAllowed": true,
"adminUrl": "http://localhost:8080/customer-portal",
"baseUrl": "http://localhost:8080/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8080/customer-portal/*"
],

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -46,6 +46,7 @@
"fullScopeAllowed": true,
"adminUrl": "http://localhost:8080/customer-portal",
"baseUrl": "http://localhost:8080/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8080/customer-portal/*"
],

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -75,6 +75,7 @@
"enabled": true,
"adminUrl": "http://localhost:8082/customer-portal",
"baseUrl": "http://localhost:8082/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8082/customer-portal/*"
],

View file

@ -46,6 +46,7 @@
"fullScopeAllowed": true,
"adminUrl": "http://localhost:8080/customer-portal",
"baseUrl": "http://localhost:8080/customer-portal",
"directAccessGrantsEnabled": true,
"redirectUris": [
"http://localhost:8080/customer-portal/*"
],