diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java index ee327c3740..f79e8d72d8 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java @@ -1,10 +1,10 @@ package org.keycloak.broker.provider; -import org.keycloak.broker.provider.IdentityProviderMapper; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; /** * @author Bill Burke @@ -35,4 +35,9 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { } + + @Override + public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + + } } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java b/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java index 6052f5738b..95417fd80e 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java @@ -18,9 +18,13 @@ package org.keycloak.broker.provider; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.Constants; import org.keycloak.models.IdentityProviderModel; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -152,6 +156,22 @@ public class BrokeredIdentityContext { this.contextData = contextData; } + // Set the attribute, which will be available on "Update profile" page and in authenticators + public void setUserAttribute(String attributeName, String attributeValue) { + List list = new ArrayList<>(); + list.add(attributeValue); + getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list); + } + + public String getUserAttribute(String attributeName) { + List userAttribute = (List) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName); + if (userAttribute == null || userAttribute.isEmpty()) { + return null; + } else { + return userAttribute.get(0); + } + } + public String getFirstName() { return firstName; } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java index 3f8fcf2f48..5beb0ab161 100644 --- a/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/DefaultDataMarshaller.java @@ -1,6 +1,7 @@ package org.keycloak.broker.provider; import java.io.IOException; +import java.util.List; import org.keycloak.common.util.Base64Url; import org.keycloak.util.JsonSerialization; @@ -26,15 +27,20 @@ public class DefaultDataMarshaller implements IdentityProviderDataMarshaller { @Override public T deserialize(String serialized, Class clazz) { - if (clazz.equals(String.class)) { - return clazz.cast(serialized); - } else { - byte[] bytes = Base64Url.decode(serialized); - try { - return JsonSerialization.readValue(bytes, clazz); - } catch (IOException ioe) { - throw new RuntimeException(ioe); + try { + if (clazz.equals(String.class)) { + return clazz.cast(serialized); + } else { + byte[] bytes = Base64Url.decode(serialized); + if (List.class.isAssignableFrom(clazz)) { + List list = JsonSerialization.readValue(bytes, List.class); + return clazz.cast(list); + } else { + return JsonSerialization.readValue(bytes, clazz); + } } + } catch (IOException ioe) { + throw new RuntimeException(ioe); } } } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java index d182d6f1a6..7ab99447a2 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedAttributeMapper.java @@ -69,10 +69,10 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper { } @Override - public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { String attribute = mapperModel.getConfig().get(ATTRIBUTE); String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE); - user.setSingleAttribute(attribute, attributeValue); + context.setUserAttribute(attribute, attributeValue); } @Override diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedUserSessionAttributeMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedUserSessionAttributeMapper.java index 676e6b1edf..656af4877a 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedUserSessionAttributeMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/HardcodedUserSessionAttributeMapper.java @@ -67,7 +67,7 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide } @Override - public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { String attribute = mapperModel.getConfig().get(ATTRIBUTE); String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE); context.getClientSession().setUserSessionNote(attribute, attributeValue); diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java index b76a6af777..7599fae25e 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java @@ -20,8 +20,10 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory boolean collectionEquals(Collection col1, Collection col2) { + if (col1.size() != col2.size()) { + return false; + } + + for (T item : col1) { + if (!col2.contains(item)) { + return false; + } + } + + return true; + } } diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java index bc2635c418..d1e7ac6999 100755 --- a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java +++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java @@ -69,7 +69,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon // Backwards compatibility if (cacheManager.getCacheConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME) == null) { - logger.warnf("No configuration provided for '%s' cache. Using '%s' configuration as template", + logger.debugf("No configuration provided for '%s' cache. Using '%s' configuration as template", InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, InfinispanConnectionProvider.SESSION_CACHE_NAME); Configuration sessionCacheConfig = cacheManager.getCacheConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME); diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml index eadbdcf428..7165348cad 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml @@ -48,7 +48,7 @@ - + @@ -56,6 +56,7 @@ + @@ -83,13 +84,14 @@ - + + DIRECT_GRANTS_ONLY = :value diff --git a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java index 666a3af9c9..5b3b38a7a7 100644 --- a/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java +++ b/connections/mongo-update/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update1_7_0.java @@ -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); diff --git a/core/src/main/java/org/keycloak/OAuth2Constants.java b/core/src/main/java/org/keycloak/OAuth2Constants.java index b552d1bd89..4b38a76430 100644 --- a/core/src/main/java/org/keycloak/OAuth2Constants.java +++ b/core/src/main/java/org/keycloak/OAuth2Constants.java @@ -27,6 +27,8 @@ public interface OAuth2Constants { String AUTHORIZATION_CODE = "authorization_code"; + String IMPLICIT = "implicit"; + String PASSWORD = "password"; String CLIENT_CREDENTIALS = "client_credentials"; diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index aef643c180..a648f12a22 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -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; } diff --git a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java index 1ff32fc096..c76f5c7086 100644 --- a/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/oidc/OIDCClientRepresentation.java @@ -14,9 +14,9 @@ public class OIDCClientRepresentation { private String token_endpoint_auth_method; - private String grant_types; + private List grant_types; - private String response_types; + private List 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 getGrantTypes() { return grant_types; } - public void setGrantTypes(String grantTypes) { + public void setGrantTypes(List grantTypes) { this.grant_types = grantTypes; } - public String getResponseTypes() { + public List getResponseTypes() { return response_types; } - public void setResponseTypes(String responseTypes) { + public void setResponseTypes(List responseTypes) { this.response_types = responseTypes; } diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-email-freemarker/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-email-freemarker/main/module.xml index d569144bab..f606f3daca 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-email-freemarker/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-email-freemarker/main/module.xml @@ -15,7 +15,6 @@ - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml index 340c3bff4c..927b03fdf4 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml @@ -58,6 +58,7 @@ + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-email-freemarker/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-email-freemarker/main/module.xml index a8e960ced8..f73c701cd9 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-email-freemarker/main/module.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-email-freemarker/main/module.xml @@ -15,7 +15,6 @@ - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml index aa895e8b66..56f2537006 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml @@ -59,6 +59,7 @@ + diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml index 5b96e2d1c4..d24a0bc0e1 100755 --- a/distribution/server-overlay/wf9-server-overlay/assembly.xml +++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml @@ -15,7 +15,7 @@ com/google/zxing/** org/freemarker/** - org/jboss/aesh/** + org/jboss/aesh/0.65/** org/keycloak/** org/liquibase/** org/mongodb/** diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml index 571cd6ca57..5ab29ecbc6 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -82,11 +82,24 @@
Migrating to 1.7.0.CR1 - Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator + Direct access grants disabled by default for clients - 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 Direct Access Grants (corresponds to OAuth2 Resource Owner Password Credentials Grant) disabled by default for new clients. + + Clients migrated from previous version have Direct Access Grants enabled just if they had flag Direct Grants Only on. The + Direct Grants Only flag was removed as if you enable Direct Access Grants and disable both Standard+Implicit flow, you will achieve same effect. + + + We also added builtin client admin-cli to each realm. This client has Direct Access Grants enabled. + So if you're using Admin REST API or Keycloak admin-client, you should update your configuration to use admin-cli instead + of security-admin-console as the latter one doesn't have direct access grants enabled anymore by default. + + + + Option 'Update Profile On First Login' moved from Identity provider to Review Profile authenticator In this version, we added First Broker Login, 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,23 @@ and then you configure the option under Review Profile authenticator. + + Element 'form-error-page' in web.xml not supported anymore + + 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. + + + + IdentityProviderMapper changes + + There is no change in the interface itself or method signatures. However there is some change in behaviour. We added First Broker Login flow + in this release and the method IdentityProviderMapper.importNewUser is now called after First Broker Login flow is finished. + So if you want to have any attribute available in Review Profile page, you would need to use + the method preprocessFederatedIdentity instead of importNewUser . You can set any attribute by + invoke BrokeredIdentityContext.setUserAttribute and that will be available on Review profile page. + +
Migrating to 1.6.0.Final diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml index 2036afbd8a..ab7896d7c4 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/javascript-adapter.xml @@ -96,15 +96,15 @@ keycloak.init({ onLoad: 'login-required' }) } - + ]]> The loadData() method builds an HTTP request setting the Authorization header to a bearer token. The keycloak.token points to the access token the browser obtained when it logged you in. The loadFailure() method is invoked on a failure. The reloadData() - function calls keycloak.onValidAccessToken() passing in the loadData() and - loadFailure() callbacks. The keycloak.onValidAcessToken() method checks to + function calls keycloak.updateToken() passing in the loadData() and + loadFailure() callbacks. The keycloak.updateToken() method checks to see if the access token hasn't expired. If it hasn't, and your oauth login returned a refresh token, this method will refresh the access token. Finally, if successful, it will invoke the success callback, which in this case is the loadData() method. @@ -136,6 +136,43 @@ keycloak.updateToken(30).success(function() {
+
+ Implicit and Hybrid Flow + + + By default the JavaScript adapter uses OpenID Connect standard (Authorization code) flow, which + means that after authentication will Keycloak server redirects back to your application and Javascript adapter will exchange code for access token and refresh token. + + + However Keycloak also supports OpenID Connect Implicit flow + where access token is directly sent after successful authentication from Keycloak and there is no additional request for exchange code. + This might have better performance than standard flow as there is no additional request for exchange code-to-token. However sending access token + in URL fragment might be security issue in some environments (Token can be more easily stolen from the network etc). + + To enable implicit flow, you need to enable the flag Implicit Flow Enabled for the client in Keycloak admin console. You also need to pass + the parameter flow with value implicit to init method, so that Javascript adapter will use implicit flow instead of standard flow. + The example is here: + + Note that with implicit flow, you don't have refresh token available after authentication. This makes it harder for your application to periodically update + access token in background (without browser redirection). It's recommended that you implement onTokenExpired callback method on keycloak object, so you + have possibility to do something after token is expired (For example you can call keycloak.login, which will redirect browser to Keycloak login screen and it will immediately + redirect you back if SSO session is still valid and user is still logged. However make sure to save the application state before doing redirect.) + + + Keycloak also have support for OpenID Connect Hybrid flow. This requires + that client in admin console has both flags Standard Flow Enabled and Implicit Flow Enabled enabled in admin console. + The Keycloak will send both the code and tokens to your application. Access token can be immediately used and in the meantime, code can be exchanged for access token and refresh token. + Hybrid flow is good for performance similarly like implicit flow, because access token is available immediatelly to your application. But similarly like implicit flow, the token is + sent in URL fragment, so security may not be so good. + One advantage over implicit flow is, that you have also refresh token available in your application (after code-to-token request is finished in background). + + + For hybrid flow, you need to pass the parameter flow with value hybrid to init method. + +
+
Older browsers @@ -178,6 +215,10 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' refreshToken - the base64 encoded token that can be used to retrieve a new token refreshTokenParsed - the parsed refresh token timeSkew - estimated skew between local time and Keycloak server in seconds + responseMode - responseMode passed during initialization. See below for details. Default value is fragment + flow - OpenID Connect flow passed during initialization. See Implicit flow for details. + responseType - responseType used for send to Keycloak server at login request. This is determined based on the flow value used during initialization, + but you have possibility to override it by directly set this value
@@ -195,6 +236,12 @@ new Keycloak({ url: 'http://localhost/auth', realm: 'myrealm', clientId: 'myApp' refreshToken - set an initial value for the refresh token checkLoginIframe - set to enable/disable monitoring login state (default is true) checkLoginIframeInterval - set the interval to check login state (default is 5 seconds) + responseMode - set the OpenID Connect response mode send to Keycloak server at login request. Valid values are query or fragment . + Default value is fragment, which means that after successful authentication will Keycloak redirect to javascript application + with OpenID Connect parameters added in URL fragment. This is generally safer and recommended over query. + + flow - set the OpenID Connect flow. Valid values are standard, implicit or hybrid. + See Implicit flow for details. Returns promise to set functions to be invoked on success or error. @@ -357,6 +404,7 @@ keycloak.onAuthSuccess = function() { alert('authenticated'); } onAuthRefreshSuccess - called when the token is refreshed onAuthRefreshError - called if there was an error while trying to refresh the token onAuthLogout - called if the user is logged out (will only be called if the session status iframe is enabled, or in Cordova mode) + onTokenExpired - called when access token expired. When this happens you can for example refresh token, or if refresh not available (ie. with implicit flow) you can redirect to login screen diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml index 229ee8de4a..83983b45cf 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/timeouts.xml @@ -44,9 +44,9 @@ The Access Token Lifespan For Implicit Flow is how long an access token is valid for when using OpenID Connect implicit flow. - With implicit flow, there is no refresh token available, so that's why the lifespan is usually bigger than default Access Token Lifespan mentioned above. + With implicit flow, there is no refresh token available. That's why the lifespan is usually bigger than default Access Token Lifespan mentioned above. See OpenID Connect specification for details about implicit flow and - Javascript Adapter for some additional details. + Javascript Adapter for some additional details on how to use it in Keycloak. The Client login timeout is how long an access code is valid for. An access code is obtained diff --git a/events/api/src/main/java/org/keycloak/events/Details.java b/events/api/src/main/java/org/keycloak/events/Details.java index a995d7ba3d..ef7f004c8b 100755 --- a/events/api/src/main/java/org/keycloak/events/Details.java +++ b/events/api/src/main/java/org/keycloak/events/Details.java @@ -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"; diff --git a/examples/admin-client/example-realm.json b/examples/admin-client/example-realm.json index 6b0921c367..e85bf671a5 100755 --- a/examples/admin-client/example-realm.json +++ b/examples/admin-client/example-realm.json @@ -24,6 +24,7 @@ "clients": [ { "clientId": "examples-admin-client", + "directAccessGrantsEnabled": true, "enabled": true, "fullScopeAllowed": true, "baseUrl": "/examples-admin-client", diff --git a/examples/basic-auth/basicauthrealm.json b/examples/basic-auth/basicauthrealm.json index 4ff9d42b47..13af84fe76 100644 --- a/examples/basic-auth/basicauthrealm.json +++ b/examples/basic-auth/basicauthrealm.json @@ -43,6 +43,8 @@ "clients": [ { "clientId": "basic-auth-service", + "standardFlowEnabled": false, + "directAccessGrantsEnabled": true, "enabled": true, "adminUrl": "/basicauth", "baseUrl": "/basicauth", diff --git a/examples/broker/saml-broker-authentication/saml-broker-realm.json b/examples/broker/saml-broker-authentication/saml-broker-realm.json old mode 100644 new mode 100755 index 289fa2b0a6..43c8b428e9 --- a/examples/broker/saml-broker-authentication/saml-broker-realm.json +++ b/examples/broker/saml-broker-authentication/saml-broker-realm.json @@ -40,6 +40,8 @@ "saml.signature.algorithm": "RSA_SHA256", "saml.client.signature": "true", "saml.authnstatement": "true", + "saml_assertion_consumer_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint", + "saml_single_logout_service_url_post": "http://localhost:8080/auth/realms/saml-broker-authentication-realm/broker/saml-identity-provider/endpoint", "saml.signing.private.key": "MIICWwIBAAKBgQDVG8a7xGN6ZIkDbeecySygcDfsypjUMNPE4QJjis8B316CvsZQ0hcTTLUyiRpHlHZys2k3xEhHBHymFC1AONcvzZzpb40tAhLHO1qtAnut00khjAdjR3muLVdGkM/zMC7G5s9iIwBVhwOQhy+VsGnCH91EzkjZ4SVEr55KJoyQJQIDAQABAoGADaTtoG/+foOZUiLjRWKL/OmyavK9vjgyFtThNkZY4qHOh0h3og0RdSbgIxAsIpEa1FUwU2W5yvI6mNeJ3ibFgCgcxqPk6GkAC7DWfQfdQ8cS+dCuaFTs8ObIQEvU50YzeNPiiFxRA+MnauCUXaKm/PnDfjd4tPgru7XZvlGh0wECQQDsBbN2cKkBKpr/b5oJiBcBaSZtWiMNuYBDn9x8uORj+Gy/49BUIMHF2EWyxOWz6ocP5YiynNRkPe21Zus7PEr1AkEA5yWQOkxUTIg43s4pxNSeHtL+Ebqcg54lY2xOQK0yufxUVZI8ODctAKmVBMiCKpU3mZQquOaQicuGtocpgxlScQI/YM31zZ5nsxLGf/5GL6KhzPJT0IYn2nk7IoFu7bjn9BjwgcPurpLA52TNMYWQsTqAKwT6DEhG1NaRqNWNpb4VAkBehObAYBwMm5udyHIeEc+CzUalm0iLLa0eRdiN7AUVNpCJ2V2Uo0NcxPux1AgeP5xXydXafDXYkwhINWcNO9qRAkEA58ckAC5loUGwU5dLaugsGH/a2Q8Ac8bmPglwfCstYDpl8Gp/eimb1eKyvDEELOhyImAv4/uZV9wN85V0xZXWsw==", "saml.signing.certificate": "MIIDdzCCAl+gAwIBAgIEbySuqTANBgkqhkiG9w0BAQsFADBsMRAwDgYDVQQGEwdVbmtub3duMRAwDgYDVQQIEwdVbmtub3duMRAwDgYDVQQHEwdVbmtub3duMRAwDgYDVQQKEwdVbmtub3duMRAwDgYDVQQLEwdVbmtub3duMRAwDgYDVQQDEwdVbmtub3duMB4XDTE1MDEyODIyMTYyMFoXDTE3MTAyNDIyMTYyMFowbDEQMA4GA1UEBhMHVW5rbm93bjEQMA4GA1UECBMHVW5rbm93bjEQMA4GA1UEBxMHVW5rbm93bjEQMA4GA1UEChMHVW5rbm93bjEQMA4GA1UECxMHVW5rbm93bjEQMA4GA1UEAxMHVW5rbm93bjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAII/K9NNvXi9IySl7+l2zY/kKrGTtuR4WdCI0xLW/Jn4dLY7v1/HOnV4CC4ecFOzhdNFPtJkmEhP/q62CpmOYOKApXk3tfmm2rwEz9bWprVxgFGKnbrWlz61Z/cjLAlhD3IUj2ZRBquYgSXQPsYfXo1JmSWF5pZ9uh1FVqu9f4wvRqY20ZhUN+39F+1iaBsoqsrbXypCn1HgZkW1/9D9GZug1c3vB4wg1TwZZWRNGtxwoEhdK6dPrNcZ+6PdanVilWrbQFbBjY4wz8/7IMBzssoQ7Usmo8F1Piv0FGfaVeJqBrcAvbiBMpk8pT+27u6p8VyIX6LhGvnxIwM07NByeSUCAwEAAaMhMB8wHQYDVR0OBBYEFFlcNuTYwI9W0tQ224K1gFJlMam0MA0GCSqGSIb3DQEBCwUAA4IBAQB5snl1KWOJALtAjLqD0mLPg1iElmZP82Lq1htLBt3XagwzU9CaeVeCQ7lTp+DXWzPa9nCLhsC3QyrV3/+oqNli8C6NpeqI8FqN2yQW/QMWN1m5jWDbmrWwtQzRUn/rh5KEb5m3zPB+tOC6e/2bV3QeQebxeW7lVMD0tSCviUg1MQf1l2gzuXQo60411YwqrXwk6GMkDOhFDQKDlMchO3oRbQkGbcP8UeiKAXjMeHfzbiBr+cWz8NYZEtxUEDYDjTpKrYCSMJBXpmgVJCZ00BswbksxJwaGqGMPpUKmCV671pf3m8nq3xyiHMDGuGwtbU+GE8kVx85menmp8+964nin" } diff --git a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json index caae06d518..2d9fa73354 100644 --- a/examples/broker/twitter-authentication/twitter-identity-provider-realm.json +++ b/examples/broker/twitter-authentication/twitter-identity-provider-realm.json @@ -49,6 +49,7 @@ "enabled": true, "fullScopeAllowed": true, "baseUrl": "/admin-client", + "directAccessGrantsEnabled": true, "redirectUris": [ "/admin-client/*" ], diff --git a/examples/demo-template/testrealm.json b/examples/demo-template/testrealm.json index 309afdd8a6..2542f82d24 100755 --- a/examples/demo-template/testrealm.json +++ b/examples/demo-template/testrealm.json @@ -178,7 +178,8 @@ "clientId": "admin-client", "enabled": true, "publicClient": true, - "directGrantsOnly": true + "standardFlowEnabled": false, + "directAccessGrantsEnabled": true }, { "clientId": "product-sa-client", diff --git a/examples/fuse/testrealm.json b/examples/fuse/testrealm.json index f7c3754035..de93f7d597 100644 --- a/examples/fuse/testrealm.json +++ b/examples/fuse/testrealm.json @@ -182,7 +182,8 @@ "clientId": "ssh-jmx-admin-client", "enabled": true, "publicClient": false, - "directGrantsOnly": true, + "standardFlowEnabled": false, + "directAccessGrantsEnabled": true, "secret": "password" } ], diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties index 49801041cc..f45afb9698 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -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 diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 11bb11b3b7..4e81a43035 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -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; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index eb54741514..5b81a0167b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -275,7 +275,7 @@ {{:: 'idp-sso-relay-state.tooltip' | translate}} -
+
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties index ffe8328baa..b1b5890d6e 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -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 diff --git a/forms/email-freemarker/pom.xml b/forms/email-freemarker/pom.xml index adc07dc73a..5a5b4d274e 100755 --- a/forms/email-freemarker/pom.xml +++ b/forms/email-freemarker/pom.xml @@ -54,11 +54,6 @@ freemarker provided - - javax.mail - mail - provided - diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java index b2d6b1dcf5..0783529ee5 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/model/ProfileBean.java @@ -47,8 +47,9 @@ public class ProfileBean { this.user = user; this.formData = formData; - if (user.getAttributes() != null) { - for (Map.Entry> attr : user.getAttributes().entrySet()) { + Map> modelAttrs = user.getAttributes(); + if (modelAttrs != null) { + for (Map.Entry> attr : modelAttrs.entrySet()) { List attrValue = attr.getValue(); if (attrValue != null && attrValue.size() > 0) { attributes.put(attr.getKey(), attrValue.get(0)); diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java b/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java index 9c8c67892e..f83e336f9f 100755 --- a/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java +++ b/model/api/src/main/java/org/keycloak/migration/MigrationProvider.java @@ -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 getBuiltinMappers(String protocol); + void setupAdminCli(RealmModel realm); + } diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java index e4ead4dbb2..2c5710d615 100644 --- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java +++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_7_0.java @@ -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 Marek Posolda @@ -17,7 +21,24 @@ public class MigrateTo1_7_0 { public void migrate(KeycloakSession session) { List 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 identityProviders = realm.getIdentityProviders(); + for (IdentityProviderModel identityProvider : identityProviders) { + if (identityProvider.getFirstBrokerLoginFlowId() == null) { + identityProvider.setFirstBrokerLoginFlowId(firstBrokerLoginFlow.getId()); + realm.updateIdentityProvider(identityProvider); + } + } } } } diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java index c50e4cd89b..0c529293f2 100755 --- a/model/api/src/main/java/org/keycloak/models/Constants.java +++ b/model/api/src/main/java/org/keycloak/models/Constants.java @@ -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"; @@ -27,4 +28,7 @@ public interface Constants { String VERIFY_EMAIL_KEY = "VERIFY_EMAIL_KEY"; String KEY = "key"; + + // Prefix for user attributes used in various "context"data maps + public static final String USER_ATTRIBUTES_PREFIX = "user.attributes."; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index b5edbaf9ee..46259c9198 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -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 getDefaultRoles() { return defaultRoles; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index 40a8999910..3a105c46e2 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -429,6 +429,10 @@ public class DefaultAuthenticationFlows { if (migrate) { // Try to read OTP requirement from browser flow AuthenticationFlowModel browserFlow = realm.getBrowserFlow(); + if (browserFlow == null) { + browserFlow = realm.getFlowByAlias(DefaultAuthenticationFlows.BROWSER_FLOW); + } + List browserExecutions = new LinkedList<>(); KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions); for (AuthenticationExecutionModel browserExecution : browserExecutions) { diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 8360e48731..dfa2e46190 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -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()); diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java index 87fe25b3dc..4eb0bdf8ee 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java @@ -112,21 +112,25 @@ public class InfinispanCacheUserProviderFactory implements CacheUserProviderFact @CacheEntryRemoved public void userRemoved(CacheEntryRemovedEvent event) { - CachedUser user = event.getOldValue(); - if (event.isPre() && user != null) { - removeUser(user); + if (event.isPre()) { + CachedUser user = event.getValue(); + if (user != null) { + removeUser(user); - log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername()); + log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername()); + } } } @CacheEntryInvalidated public void userInvalidated(CacheEntryInvalidatedEvent event) { - CachedUser user = event.getValue(); - if (event.isPre() && user != null) { - removeUser(user); + if (event.isPre()) { + CachedUser user = event.getValue(); + if (user != null) { + removeUser(user); - log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername()); + log.tracev("User invalidated realm={0}, id={1}, username={2}", user.getRealm(), user.getId(), user.getUsername()); + } } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index cb69f14d62..3f5817956b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -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); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index c8fc1f0b95..fc840ba534 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -811,7 +811,6 @@ public class RealmAdapter extends AbstractMongoAdapter 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); diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java index 3a30c2ee6f..0bc3edeea0 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/SamlProtocol.java @@ -522,7 +522,7 @@ public class SamlProtocol implements LoginProtocol { logger.debug("finishLogout"); String logoutBindingUri = userSession.getNote(SAML_LOGOUT_BINDING_URI); if (logoutBindingUri == null) { - logger.error("Can't finish SAML logout as there is no logout binding set"); + logger.error("Can't finish SAML logout as there is no logout binding set. Please configure the logout service url in the admin console for your client applications."); return ErrorPage.error(session, Messages.FAILED_LOGOUT); } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java index 1ee3cd2bff..10ef0191c4 100644 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java @@ -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; diff --git a/services/pom.xml b/services/pom.xml index bf2ee80b50..105a2cc976 100755 --- a/services/pom.xml +++ b/services/pom.xml @@ -50,6 +50,10 @@ org.keycloak keycloak-email-api + + javax.mail + mail + org.keycloak keycloak-login-api diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java index b4ee957e43..f4da7e97d7 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java @@ -41,13 +41,21 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator return; } - ExistingUserInfo duplication = checkExistingUser(context, serializedCtx, brokerContext); + String username = getUsername(context, serializedCtx, brokerContext); + if (username == null) { + logger.warnf("%s is null. Reset flow and enforce showing reviewProfile page", realm.isRegistrationEmailAsUsername() ? "Email" : "Username"); + context.getClientSession().setNote(ENFORCE_UPDATE_PROFILE, "true"); + context.resetFlow(); + return; + } + + ExistingUserInfo duplication = checkExistingUser(context, username, serializedCtx, brokerContext); if (duplication == null) { logger.debugf("No duplication detected. Creating account for user '%s' and linking with identity provider '%s' .", - brokerContext.getModelUsername(), brokerContext.getIdpConfig().getAlias()); + username, brokerContext.getIdpConfig().getAlias()); - UserModel federatedUser = session.users().addUser(realm, brokerContext.getModelUsername()); + UserModel federatedUser = session.users().addUser(realm, username); federatedUser.setEnabled(true); federatedUser.setEmail(brokerContext.getEmail()); federatedUser.setFirstName(brokerContext.getFirstName()); @@ -92,7 +100,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator } // Could be overriden to detect duplication based on other criterias (firstName, lastName, ...) - protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) { + protected ExistingUserInfo checkExistingUser(AuthenticationFlowContext context, String username, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) { if (brokerContext.getEmail() != null) { UserModel existingUser = context.getSession().users().getUserByEmail(brokerContext.getEmail(), context.getRealm()); @@ -101,7 +109,7 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator } } - UserModel existingUser = context.getSession().users().getUserByUsername(brokerContext.getModelUsername(), context.getRealm()); + UserModel existingUser = context.getSession().users().getUserByUsername(username, context.getRealm()); if (existingUser != null) { return new ExistingUserInfo(existingUser.getId(), UserModel.USERNAME, existingUser.getUsername()); } @@ -109,6 +117,11 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator return null; } + protected String getUsername(AuthenticationFlowContext context, SerializedBrokeredIdentityContext serializedCtx, BrokeredIdentityContext brokerContext) { + RealmModel realm = context.getRealm(); + return realm.isRegistrationEmailAsUsername() ? brokerContext.getEmail() : brokerContext.getModelUsername(); + } + @Override public boolean requiresUser() { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java index 04eb77b520..a4d0ea345c 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticator.java @@ -83,7 +83,7 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator { RealmModel realm = context.getRealm(); - List errors = Validation.validateUpdateProfileForm(true, formData); + List errors = Validation.validateUpdateProfileForm(!realm.isRegistrationEmailAsUsername(), formData); if (errors != null && !errors.isEmpty()) { Response challenge = context.form() .setErrors(errors) @@ -94,7 +94,8 @@ public class IdpReviewProfileAuthenticator extends AbstractIdpAuthenticator { return; } - userCtx.setUsername(formData.getFirst(UserModel.USERNAME)); + String username = realm.isRegistrationEmailAsUsername() ? formData.getFirst(UserModel.EMAIL) : formData.getFirst(UserModel.USERNAME); + userCtx.setUsername(username); userCtx.setFirstName(formData.getFirst(UserModel.FIRST_NAME)); userCtx.setLastName(formData.getFirst(UserModel.LAST_NAME)); diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java index e10c924c5a..fc472d0b09 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpReviewProfileAuthenticatorFactory.java @@ -94,7 +94,8 @@ public class IdpReviewProfileAuthenticatorFactory implements AuthenticatorFactor property.setDefaultValue(updateProfileValues); property.setHelpText("Define conditions under which a user has to review and update his profile after first-time login. Value 'On' means that" + " page for reviewing profile will be displayed and user can review and update his profile. Value 'off' means that page won't be displayed." - + " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one"); + + " Value 'missing' means that page is displayed just when some required attribute is missing (wasn't downloaded from identity provider). Value 'missing' is the default one." + + " WARN: In case that user clicks 'Review profile info' on link duplications page, the update page will be always displayed. You would need to disable this authenticator to never display the page."); configProperties.add(property); } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java index 8f1b026f0c..968831c1b4 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/util/SerializedBrokeredIdentityContext.java @@ -1,6 +1,8 @@ package org.keycloak.authentication.authenticators.broker.util; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -14,6 +16,7 @@ import org.keycloak.broker.provider.IdentityProviderDataMarshaller; import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.reflections.Reflections; import org.keycloak.models.ClientSessionModel; +import org.keycloak.models.Constants; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelException; @@ -37,13 +40,16 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { private String code; private String token; + @JsonIgnore + private boolean emailAsUsername; + private String identityProviderId; private Map contextData = new HashMap<>(); @JsonIgnore @Override public boolean isEditUsernameAllowed() { - return true; + return !emailAsUsername; } public String getId() { @@ -159,44 +165,52 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { this.contextData = contextData; } + @JsonIgnore @Override public Map> getAttributes() { Map> result = new HashMap<>(); for (Map.Entry entry : this.contextData.entrySet()) { - if (entry.getKey().startsWith("user.attributes.")) { - ContextDataEntry ctxEntry = entry.getValue(); - String asString = ctxEntry.getData(); - try { - List asList = JsonSerialization.readValue(asString, List.class); - result.put(entry.getKey().substring(16), asList); - } catch (IOException ioe) { - throw new RuntimeException(ioe); - } + if (entry.getKey().startsWith(Constants.USER_ATTRIBUTES_PREFIX)) { + String attrName = entry.getKey().substring(16); // length of USER_ATTRIBUTES_PREFIX + List asList = getAttribute(attrName); + result.put(attrName, asList); } } return result; } + @JsonIgnore + @Override + public void setSingleAttribute(String name, String value) { + List list = new ArrayList<>(); + list.add(value); + setAttribute(name, list); + } + + @JsonIgnore @Override public void setAttribute(String key, List value) { try { - String listStr = JsonSerialization.writeValueAsString(value); + byte[] listBytes = JsonSerialization.writeValueAsBytes(value); + String listStr = Base64Url.encode(listBytes); ContextDataEntry ctxEntry = ContextDataEntry.create(List.class.getName(), listStr); - this.contextData.put("user.attributes." + key, ctxEntry); + this.contextData.put(Constants.USER_ATTRIBUTES_PREFIX + key, ctxEntry); } catch (IOException ioe) { throw new RuntimeException(ioe); } } + @JsonIgnore @Override public List getAttribute(String key) { - ContextDataEntry ctxEntry = this.contextData.get("user.attributes." + key); + ContextDataEntry ctxEntry = this.contextData.get(Constants.USER_ATTRIBUTES_PREFIX + key); if (ctxEntry != null) { try { String asString = ctxEntry.getData(); - List asList = JsonSerialization.readValue(asString, List.class); + byte[] asBytes = Base64Url.decode(asString); + List asList = JsonSerialization.readValue(asBytes, List.class); return asList; } catch (IOException ioe) { throw new RuntimeException(ioe); @@ -206,6 +220,17 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { } } + @JsonIgnore + @Override + public String getFirstAttribute(String name) { + List attrs = getAttribute(name); + if (attrs == null || attrs.isEmpty()) { + return null; + } else { + return attrs.get(0); + } + } + public BrokeredIdentityContext deserialize(KeycloakSession session, ClientSessionModel clientSession) { BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId()); @@ -261,6 +286,8 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { ctx.setToken(context.getToken()); ctx.setIdentityProviderId(context.getIdpConfig().getAlias()); + ctx.emailAsUsername = context.getClientSession().getRealm().isRegistrationEmailAsUsername(); + IdentityProviderDataMarshaller serializer = context.getIdp().getMarshaller(); for (Map.Entry entry : context.getContextData().entrySet()) { @@ -289,7 +316,9 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext { return null; } else { try { - return JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class); + SerializedBrokeredIdentityContext serializedCtx = JsonSerialization.readValue(asString, SerializedBrokeredIdentityContext.class); + serializedCtx.emailAsUsername = clientSession.getRealm().isRegistrationEmailAsUsername(); + return serializedCtx; } catch (IOException ioe) { throw new RuntimeException(ioe); } diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java index 2acef37a0e..a88d173cac 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UpdateProfileContext.java @@ -31,8 +31,12 @@ public interface UpdateProfileContext { Map> getAttributes(); + void setSingleAttribute(String name, String value); + void setAttribute(String key, List value); + String getFirstAttribute(String name); + List getAttribute(String key); } diff --git a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java index 55d6ddae46..94a1151855 100644 --- a/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java +++ b/services/src/main/java/org/keycloak/authentication/requiredactions/util/UserUpdateProfileContext.java @@ -69,11 +69,21 @@ public class UserUpdateProfileContext implements UpdateProfileContext { return user.getAttributes(); } + @Override + public void setSingleAttribute(String name, String value) { + user.setSingleAttribute(name, value); + } + @Override public void setAttribute(String key, List value) { user.setAttribute(key, value); } + @Override + public String getFirstAttribute(String name) { + return user.getFirstAttribute(name); + } + @Override public List getAttribute(String key) { return user.getAttribute(key); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java index 60e9f9d36a..98fb49e08d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCWellKnownProvider.java @@ -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 DEFAULT_ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = list("RS256"); - public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); + public static final List DEFAULT_GRANT_TYPES_SUPPORTED = list(OAuth2Constants.AUTHORIZATION_CODE, OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS); - public static final List DEFAULT_RESPONSE_TYPES_SUPPORTED = list(OAuth2Constants.CODE); + public static final List 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 DEFAULT_SUBJECT_TYPES_SUPPORTED = list("public"); - public static final List DEFAULT_RESPONSE_MODES_SUPPORTED = list("query"); + public static final List DEFAULT_RESPONSE_MODES_SUPPORTED = list("query", "fragment", "form_post"); private KeycloakSession session; diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index fe358e0ba4..178624b63d 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -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) { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java index 06cc3cc91c..4340229231 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCRedirectUriBuilder.java @@ -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 params = new HashMap<>(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java index 6377b22a47..9313aa60bc 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/OIDCResponseType.java @@ -46,6 +46,16 @@ public class OIDCResponseType { return new OIDCResponseType(allowedTypes); } + public static OIDCResponseType parse(List responseTypes) { + OIDCResponseType result = new OIDCResponseType(new ArrayList()); + for (String respType : responseTypes) { + OIDCResponseType responseType = parse(respType); + result.responseTypes.addAll(responseType.responseTypes); + } + + return result; + } + private static void validateAllowedTypes(List 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); } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationException.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationException.java new file mode 100644 index 0000000000..71c9c3d077 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationException.java @@ -0,0 +1,23 @@ +package org.keycloak.services.clientregistration; + +/** + * @author Marek Posolda + */ +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); + } +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java index a7f9f2c82f..594a5f061f 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -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 Stian Thorgersen */ 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 oidcResponseTypes = clientOIDC.getResponseTypes(); + if (oidcResponseTypes == null || oidcResponseTypes.isEmpty()) { + oidcResponseTypes = Collections.singletonList(OIDCResponseType.CODE); + } + List 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 getOIDCResponseTypes(ClientRepresentation client) { + List 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 getOIDCGrantTypes(ClientRepresentation client) { + List 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; + } + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java index e60720bc87..82b8825eb9 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java @@ -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); } - 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(); + 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) { - 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(); + 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 diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java index d44a622828..8ab0cfc1c8 100755 --- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java +++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java @@ -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()) { diff --git a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java index ad46afe1dc..e1e6f23049 100644 --- a/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java +++ b/services/src/main/java/org/keycloak/services/migration/DefaultMigrationProvider.java @@ -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() { } diff --git a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java index 9fb60eca6d..194ad2d5a4 100755 --- a/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java +++ b/services/src/main/java/org/keycloak/services/resources/AttributeFormDataProcessor.java @@ -5,6 +5,7 @@ import java.util.List; import org.keycloak.authentication.requiredactions.util.UpdateProfileContext; import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext; +import org.keycloak.models.Constants; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; @@ -29,11 +30,12 @@ public class AttributeFormDataProcessor { public static void process(MultivaluedMap formData, RealmModel realm, UpdateProfileContext user) { for (String key : formData.keySet()) { - if (!key.startsWith("user.attributes.")) continue; - String attribute = key.substring("user.attributes.".length()); + if (!key.startsWith(Constants.USER_ATTRIBUTES_PREFIX)) continue; + String attribute = key.substring(Constants.USER_ATTRIBUTES_PREFIX.length()); // Need to handle case when attribute has multiple values, but in UI was displayed just first value - List modelValue = new ArrayList<>(user.getAttribute(attribute)); + List modelVal = user.getAttribute(attribute); + List modelValue = modelVal==null ? new ArrayList() : new ArrayList<>(modelVal); int index = 0; for (String value : formData.get(key)) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 90eab4a54e..a4d5f27f77 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -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; diff --git a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java index b3f77a7b7f..dd15aa90e9 100644 --- a/services/src/test/java/org/keycloak/test/ResponseTypeTest.java +++ b/services/src/test/java/org/keycloak/test/ResponseTypeTest.java @@ -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); } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java index 9b68c4322e..f1328a90e3 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/ContainersTestEnricher.java @@ -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() { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java index 627865353f..37b53c2787 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java @@ -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(); } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java index 9696274241..f0fbe2bedc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationTest.java @@ -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 diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java index 84e724b99e..517f6c3d96 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java @@ -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()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 31b05508d8..1e6009674a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -187,7 +187,7 @@ public class AdapterTestStrategy extends ExternalResource { Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad")); // View stats - List> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats(); + List> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats(); Map customerPortalStats = null; Map productPortalStats = null; for (Map 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 diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java index 6283718f27..eaebc821eb 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/RelativeUriAdapterTest.java @@ -131,7 +131,7 @@ public class RelativeUriAdapterTest { Assert.assertTrue(pageSource.contains("iPhone") && pageSource.contains("iPad")); // View stats - List> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", "security-admin-console").realm("demo").getClientSessionStats(); + List> stats = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", Constants.ADMIN_CLI_CLIENT_ID).realm("demo").getClientSessionStats(); Map customerPortalStats = null; Map productPortalStats = null; for (Map s : stats) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java index c3f68dbb8c..17fa17434c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adduser/AddUserTest.java @@ -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(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java index e3d6a5713a..99edd91672 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java @@ -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); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java index 8147cb3a72..e0471df8de 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java index cf2df9d9aa..dd575b1d64 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java @@ -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 diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java index 0f1510b230..263e4e2011 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java index 7d780cd72f..36bf4c7908 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java @@ -178,6 +178,48 @@ public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProvi } + /** + * Test user registers with IdentityProvider with emailAsUsername + */ + @Test + public void testRegistrationWithEmailAsUsername() { + // Require updatePassword after user registered with broker + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON); + realmWithBroker.setRegistrationEmailAsUsername(true); + } + + }, APP_REALM_ID); + + loginIDP("pedroigor"); + this.updateProfileWithUsernamePage.assertCurrent(); + + try { + this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user"); + Assert.fail("It is not expected to see username field"); + } catch (NoSuchElementException expected) { + } + + this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com"); + + // assert authenticated + assertFederatedUser("some-user@redhat.com", "some-user@redhat.com", "pedroigor"); + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING); + realmWithBroker.setRegistrationEmailAsUsername(false); + } + + }, APP_REALM_ID); + } + + /** * Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication * by create new user diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java index 1d2e5b68d4..dbf761ea77 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java @@ -266,48 +266,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent } } - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() { - - getRealm().setRegistrationEmailAsUsername(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - try { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - // check correct user is created with username from provider as email is not available - RealmModel realm = getRealm(); - UserModel federatedUser = getFederatedUser(); - assertNotNull(federatedUser); - - doAssertFederatedUserNoEmail(federatedUser); - - Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); - - assertEquals(1, federatedIdentities.size()); - - FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); - - assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); - revokeGrant(); - - driver.navigate().to("http://localhost:8081/test-app/logout"); - driver.navigate().to("http://localhost:8081/test-app"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - } finally { - getRealm().setRegistrationEmailAsUsername(false); - } - } - @Test public void testDisabled() { IdentityProviderModel identityProviderModel = getIdentityProviderModel(); @@ -395,6 +353,8 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent public void testAccountManagementLinkedIdentityAlreadyExists() { // Login as "test-user" through broker IdentityProviderModel identityProvider = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false); // Login as pedroigor to account management @@ -481,6 +441,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent authenticateWithIdentityProvider(identityProviderModel, "test-user", true); + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + UserModel federatedUser = getFederatedUser(); RealmModel realm = getRealm(); Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java index 5bfdc1d75d..63b332bc30 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java @@ -115,11 +115,6 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername(); } - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() { - super.testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided(); - } - @Test public void testTokenStorageAndRetrievalByApplication() { super.testTokenStorageAndRetrievalByApplication(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java index 11d9a37034..b67ffa3021 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/FederationProvidersIntegrationTest.java @@ -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); } }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java index 4e654aa189..4d438972f8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/BruteForceTest.java @@ -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(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java index 469030821e..31ac261e70 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/CustomFlowTest.java @@ -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) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java index 9f04201803..83e14e9184 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/jaxrs/JaxrsBasicAuthTest.java @@ -48,6 +48,7 @@ public class JaxrsBasicAuthTest { app.setEnabled(true); app.setSecret("password"); app.setFullScopeAllowed(true); + app.setDirectAccessGrantsEnabled(true); JaxrsBasicAuthTest.appRealm = appRealm; } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java index f2d8b68045..646c42b38c 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/keycloaksaml/SamlAdapterTestStrategy.java @@ -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(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java index a5f23883cb..29ab66b7d0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/GroupTest.java @@ -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) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 99e5b95649..f016ff09f1 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -90,7 +90,7 @@ public class ImportTest extends AbstractModelTest { Assert.assertEquals(0, session.users().getFederatedIdentities(user, realm).size()); List 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 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)); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java index 1b6cbd626c..3f4c53b105 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/AccessTokenTest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java index 898066aad4..8bc8be85c3 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ClientAuthSignedJWTTest.java @@ -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") diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java index a4b2855b0b..fab919f984 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java @@ -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) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java index 237348962d..a69b733ab6 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index b64a683ed9..edb1bf2baf 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -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) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java index 11ced77747..6cd84893f7 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oidc/UserInfoTest.java @@ -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); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java index 7878843e2c..446c55581c 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java @@ -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(); diff --git a/testsuite/integration/src/test/resources/adapter-test/demorealm.json b/testsuite/integration/src/test/resources/adapter-test/demorealm.json index b5cd399810..70dc85aae4 100755 --- a/testsuite/integration/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/integration/src/test/resources/adapter-test/demorealm.json @@ -120,6 +120,7 @@ { "name": "customer-portal", "enabled": true, + "directAccessGrantsEnabled": true, "adminUrl": "http://localhost:8081/customer-portal", "baseUrl": "http://localhost:8081/customer-portal", "redirectUris": [ diff --git a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json index 0b8b79e947..816aa49e02 100755 --- a/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json +++ b/testsuite/integration/src/test/resources/broker-test/test-realm-with-broker.json @@ -147,7 +147,7 @@ "clientId": "clientId", "clientSecret": "clientSecret", "prompt": "prompt", - "authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/login", + "authorizationUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/auth", "tokenUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token", "userInfoUrl": "http://localhost:8081/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo", "defaultScope": "email profile" @@ -163,10 +163,10 @@ "clientId": "broker-app", "clientSecret": "secret", "prompt": "login", - "authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/login", + "authorizationUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/auth", "tokenUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/token", "userInfoUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/userinfo", - "logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/tokens/logout", + "logoutUrl": "http://localhost:8082/auth/realms/realm-with-oidc-identity-provider/protocol/openid-connect/logout", "defaultScope": "email profile", "backchannelSupported": "true" } diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json index af86592664..a08f486a7a 100755 --- a/testsuite/integration/src/test/resources/model/testrealm.json +++ b/testsuite/integration/src/test/resources/model/testrealm.json @@ -156,6 +156,7 @@ "name": "Applicationn", "enabled": true, "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, "nodeReRegistrationTimeout": 50, "registeredNodes": { "node1": 10, diff --git a/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json index a0ddce4ec7..b0a8888c0f 100755 --- a/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty81/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/jetty/jetty81/src/test/resources/jetty-test/demorealm.json b/testsuite/jetty/jetty81/src/test/resources/jetty-test/demorealm.json index d122c197fd..86e830f984 100755 --- a/testsuite/jetty/jetty81/src/test/resources/jetty-test/demorealm.json +++ b/testsuite/jetty/jetty81/src/test/resources/jetty-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json index a0ddce4ec7..b0a8888c0f 100755 --- a/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty91/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/jetty/jetty91/src/test/resources/jetty-test/demorealm.json b/testsuite/jetty/jetty91/src/test/resources/jetty-test/demorealm.json index d122c197fd..86e830f984 100755 --- a/testsuite/jetty/jetty91/src/test/resources/jetty-test/demorealm.json +++ b/testsuite/jetty/jetty91/src/test/resources/jetty-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json b/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json index a0ddce4ec7..b0a8888c0f 100755 --- a/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/jetty/jetty92/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json index b577bfae95..e8a05f8928 100755 --- a/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat6/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json index a0ddce4ec7..b0a8888c0f 100755 --- a/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat7/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json b/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json index a0ddce4ec7..b0a8888c0f 100755 --- a/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json +++ b/testsuite/tomcat8/src/test/resources/adapter-test/demorealm.json @@ -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/*" ], diff --git a/testsuite/tomcat8/src/test/resources/tomcat-test/demorealm.json b/testsuite/tomcat8/src/test/resources/tomcat-test/demorealm.json index d122c197fd..86e830f984 100755 --- a/testsuite/tomcat8/src/test/resources/tomcat-test/demorealm.json +++ b/testsuite/tomcat8/src/test/resources/tomcat-test/demorealm.json @@ -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/*" ], diff --git a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java index 7be53c6473..ff4cf35eed 100644 --- a/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java +++ b/wildfly/adduser/src/main/java/org/keycloak/wildfly/adduser/AddUser.java @@ -154,9 +154,9 @@ public class AddUser { roles = rolesString.split(","); } else { if (realmName.equals("master")) { - roles = new String[] { "admin" }; + roles = new String[] { "admin", "account/manage-account" }; } else { - roles = new String[] { "realm-management/realm-admin" }; + roles = new String[] { "realm-management/realm-admin", "account/manage-account" }; } }