keycloak-uncached
Changes
core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigRepresentation.java 10(+8 -2)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java 10(+10 -0)
integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java 11(+4 -7)
integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java 51(+29 -22)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java 13(+9 -4)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockDatabaseChangeLogGenerator.java 86(+86 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java 119(+73 -46)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java 10(+10 -0)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java 34(+17 -17)
model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java 10(+1 -9)
pom.xml 53(+38 -15)
services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java 2(+1 -1)
services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java 2(+1 -1)
services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java 41(+5 -36)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java 133(+88 -45)
services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java 15(+7 -8)
services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java 8(+7 -1)
services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java 5(+1 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java 9(+4 -5)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java 71(+69 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java 14(+10 -4)
testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java 264(+264 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/AbstractPageWithInjectedUrl.java 2(+1 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java 5(+5 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java 15(+5 -10)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java 3(+2 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java 9(+5 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java 36(+23 -13)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ServerInfoTest.java 56(+30 -26)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java 8(+4 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java 18(+14 -4)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java 6(+3 -3)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java 16(+8 -8)
testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json 23(+15 -8)
Details
diff --git a/adapters/oidc/js/src/main/resources/keycloak.js b/adapters/oidc/js/src/main/resources/keycloak.js
index 6aa116a..fa35049 100755
--- a/adapters/oidc/js/src/main/resources/keycloak.js
+++ b/adapters/oidc/js/src/main/resources/keycloak.js
@@ -192,7 +192,7 @@
redirectUri += (redirectUri.indexOf('?') == -1 ? '?' : '&') + 'prompt=' + options.prompt;
}
- sessionStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
+ localStorage.oauthState = JSON.stringify({ state: state, nonce: nonce, redirectUri: encodeURIComponent(redirectUri) });
var action = 'auth';
if (options && options.action == 'register') {
@@ -689,10 +689,10 @@
function parseCallback(url) {
var oauth = new CallbackParser(url, kc.responseMode).parseUri();
- var sessionState = sessionStorage.oauthState && JSON.parse(sessionStorage.oauthState);
+ var sessionState = localStorage.oauthState && JSON.parse(localStorage.oauthState);
if (sessionState && (oauth.code || oauth.error || oauth.access_token || oauth.id_token) && oauth.state && oauth.state == sessionState.state) {
- delete sessionStorage.oauthState;
+ delete localStorage.oauthState;
oauth.redirectUri = sessionState.redirectUri;
oauth.storedNonce = sessionState.nonce;
diff --git a/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigRepresentation.java
index dda7418..258f925 100755
--- a/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/AuthenticatorConfigRepresentation.java
@@ -27,9 +27,17 @@ import java.util.Map;
*/
public class AuthenticatorConfigRepresentation implements Serializable {
+ private String id;
private String alias;
private Map<String, String> config = new HashMap<String, String>();
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
public String getAlias() {
return alias;
@@ -39,8 +47,6 @@ public class AuthenticatorConfigRepresentation implements Serializable {
this.alias = alias;
}
-
-
public Map<String, String> getConfig() {
return config;
}
diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/logout.xml b/docbook/auth-server-docs/reference/en/en-US/modules/logout.xml
index 9f5bfa3..ea6eb4e 100755
--- a/docbook/auth-server-docs/reference/en/en-US/modules/logout.xml
+++ b/docbook/auth-server-docs/reference/en/en-US/modules/logout.xml
@@ -20,7 +20,7 @@
<para>
There are multiple ways you can logout from a web application. For Java EE servlet containers, you can call
HttpServletRequest.logout().
- For any other browser application, you can point the browser at the url <literal>http://auth-server/auth/realms/{realm-name}/tokens/logout?redirect_uri=encodedRedirectUri</literal>.
+ For any other browser application, you can point the browser at the url <literal>http://auth-server/auth/realms/{realm-name}/protocol/openid-connect/logout?redirect_uri=encodedRedirectUri</literal>.
This will log you out if you have an SSO session with your browser.
</para>
</section>
\ No newline at end of file
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java
index ae3edaa..4e628e5 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Config.java
@@ -17,6 +17,9 @@
package org.keycloak.admin.client;
+import static org.keycloak.OAuth2Constants.CLIENT_CREDENTIALS;
+import static org.keycloak.OAuth2Constants.PASSWORD;
+
/**
* @author rodrigo.sasaki@icarros.com.br
*/
@@ -28,14 +31,21 @@ public class Config {
private String password;
private String clientId;
private String clientSecret;
+ private String grantType;
public Config(String serverUrl, String realm, String username, String password, String clientId, String clientSecret) {
+ this(serverUrl, realm, username, password, clientId, clientSecret, PASSWORD);
+ }
+
+ public Config(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, String grantType) {
this.serverUrl = serverUrl;
this.realm = realm;
this.username = username;
this.password = password;
this.clientId = clientId;
this.clientSecret = clientSecret;
+ this.grantType = grantType;
+ checkGrantType(grantType);
}
public String getServerUrl() {
@@ -86,8 +96,23 @@ public class Config {
this.clientSecret = clientSecret;
}
- public boolean isPublicClient(){
+ public boolean isPublicClient() {
return clientSecret == null;
}
+ public String getGrantType() {
+ return grantType;
+ }
+
+ public void setGrantType(String grantType) {
+ this.grantType = grantType;
+ checkGrantType(grantType);
+ }
+
+ public static void checkGrantType(String grantType) {
+ if (!PASSWORD.equals(grantType) && !CLIENT_CREDENTIALS.equals(grantType)) {
+ throw new IllegalArgumentException("Unsupported grantType: " + grantType +
+ " (only " + PASSWORD + " and " + CLIENT_CREDENTIALS + " are supported)");
+ }
+ }
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java
index 52c5561..f86170e 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/Keycloak.java
@@ -28,24 +28,24 @@ import org.keycloak.admin.client.token.TokenManager;
import java.net.URI;
+import static org.keycloak.OAuth2Constants.PASSWORD;
+
/**
* Provides a Keycloak client. By default, this implementation uses a {@link ResteasyClient RESTEasy client} with the
* default {@link ResteasyClientBuilder} settings. To customize the underling client, use a {@link KeycloakBuilder} to
* create a Keycloak client.
*
- * @see KeycloakBuilder
- *
* @author rodrigo.sasaki@icarros.com.br
+ * @see KeycloakBuilder
*/
public class Keycloak {
-
private final Config config;
private final TokenManager tokenManager;
private final ResteasyWebTarget target;
private final ResteasyClient client;
- Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, ResteasyClient resteasyClient){
- config = new Config(serverUrl, realm, username, password, clientId, clientSecret);
+ Keycloak(String serverUrl, String realm, String username, String password, String clientId, String clientSecret, String grantType, ResteasyClient resteasyClient) {
+ config = new Config(serverUrl, realm, username, password, clientId, clientSecret, grantType);
client = resteasyClient != null ? resteasyClient : new ResteasyClientBuilder().connectionPoolSize(10).build();
tokenManager = new TokenManager(config, client);
@@ -55,27 +55,27 @@ public class Keycloak {
target.register(new BearerAuthFilter(tokenManager));
}
- public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret){
- return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, null);
+ public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId, String clientSecret) {
+ return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, PASSWORD, null);
}
- public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId){
- return new Keycloak(serverUrl, realm, username, password, clientId, null, null);
+ public static Keycloak getInstance(String serverUrl, String realm, String username, String password, String clientId) {
+ return new Keycloak(serverUrl, realm, username, password, clientId, null, PASSWORD, null);
}
- public RealmsResource realms(){
+ public RealmsResource realms() {
return target.proxy(RealmsResource.class);
}
- public RealmResource realm(String realmName){
+ public RealmResource realm(String realmName) {
return realms().realm(realmName);
}
- public ServerInfoResource serverInfo(){
+ public ServerInfoResource serverInfo() {
return target.proxy(ServerInfoResource.class);
}
- public TokenManager tokenManager(){
+ public TokenManager tokenManager() {
return tokenManager;
}
@@ -98,5 +98,4 @@ public class Keycloak {
public void close() {
client.close();
}
-
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java
index 5a61ffb..e192d9a 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/KeycloakBuilder.java
@@ -20,15 +20,17 @@ package org.keycloak.admin.client;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
+import static org.keycloak.OAuth2Constants.CLIENT_CREDENTIALS;
+import static org.keycloak.OAuth2Constants.PASSWORD;
+
/**
* Provides a {@link Keycloak} client builder with the ability to customize the underlying
* {@link ResteasyClient RESTEasy client} used to communicate with the Keycloak server.
- *
+ * <p>
* <p>Example usage with a connection pool size of 20:</p>
- *
* <pre>
* Keycloak keycloak = KeycloakBuilder.builder()
- * .serverUrl("https:/sso.example.com/auth")
+ * .serverUrl("https://sso.example.com/auth")
* .realm("realm")
* .username("user")
* .password("pass")
@@ -37,6 +39,16 @@ import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
* .resteasyClient(new ResteasyClientBuilder().connectionPoolSize(20).build())
* .build();
* </pre>
+ * <p>Example usage with grant_type=client_credentials</p>
+ * <pre>
+ * Keycloak keycloak = KeycloakBuilder.builder()
+ * .serverUrl("https://sso.example.com/auth")
+ * .realm("example")
+ * .grantType(OAuth2Constants.CLIENT_CREDENTIALS)
+ * .clientId("client")
+ * .clientSecret("secret")
+ * .build();
+ * </pre>
*
* @author Scott Rossillo
* @see ResteasyClientBuilder
@@ -48,6 +60,7 @@ public class KeycloakBuilder {
private String password;
private String clientId;
private String clientSecret;
+ private String grantType = PASSWORD;
private ResteasyClient resteasyClient;
public KeycloakBuilder serverUrl(String serverUrl) {
@@ -60,6 +73,12 @@ public class KeycloakBuilder {
return this;
}
+ public KeycloakBuilder grantType(String grantType) {
+ Config.checkGrantType(grantType);
+ this.grantType = grantType;
+ return this;
+ }
+
public KeycloakBuilder username(String username) {
this.username = username;
return this;
@@ -97,19 +116,25 @@ public class KeycloakBuilder {
throw new IllegalStateException("realm required");
}
- if (username == null) {
- throw new IllegalStateException("username required");
- }
-
- if (password == null) {
- throw new IllegalStateException("password required");
+ if (PASSWORD.equals(grantType)) {
+ if (username == null) {
+ throw new IllegalStateException("username required");
+ }
+
+ if (password == null) {
+ throw new IllegalStateException("password required");
+ }
+ } else if (CLIENT_CREDENTIALS.equals(grantType)) {
+ if (clientSecret == null) {
+ throw new IllegalStateException("clientSecret required with grant_type=client_credentials");
+ }
}
if (clientId == null) {
throw new IllegalStateException("clientId required");
}
- return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, resteasyClient);
+ return new Keycloak(serverUrl, realm, username, password, clientId, clientSecret, grantType, resteasyClient);
}
private KeycloakBuilder() {
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
index 51e9a45..df13c0b 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UserResource.java
@@ -35,6 +35,7 @@ import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
+import java.util.Map;
/**
* @author rodrigo.sasaki@icarros.com.br
@@ -132,4 +133,13 @@ public interface UserResource {
@Path("role-mappings")
public RoleMappingResource roles();
+
+ @GET
+ @Path("consents")
+ public List<Map<String, Object>> getConsents();
+
+ @DELETE
+ @Path("consents/{client}")
+ public void revokeConsent(@PathParam("client") String clientId);
+
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java
index 9497f3a..93a0638 100755
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/resource/UsersResource.java
@@ -19,13 +19,7 @@ package org.keycloak.admin.client.resource;
import org.keycloak.representations.idm.UserRepresentation;
-import javax.ws.rs.Consumes;
-import javax.ws.rs.GET;
-import javax.ws.rs.POST;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-import javax.ws.rs.QueryParam;
+import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
@@ -59,4 +53,7 @@ public interface UsersResource {
@Path("{id}")
UserResource get(@PathParam("id") String id);
+ @Path("{id}")
+ @DELETE
+ Response delete(@PathParam("id") String id);
}
diff --git a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
index e325681..bb32dae 100644
--- a/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
+++ b/integration/admin-client/src/main/java/org/keycloak/admin/client/token/TokenManager.java
@@ -27,11 +27,12 @@ import org.keycloak.representations.AccessTokenResponse;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.core.Form;
+import static org.keycloak.OAuth2Constants.*;
+
/**
* @author rodrigo.sasaki@icarros.com.br
*/
public class TokenManager {
-
private static final long DEFAULT_MIN_VALIDITY = 30;
private AccessTokenResponse currentToken;
@@ -39,61 +40,67 @@ public class TokenManager {
private long minTokenValidity = DEFAULT_MIN_VALIDITY;
private final Config config;
private final TokenService tokenService;
+ private final String accessTokenGrantType;
- public TokenManager(Config config, ResteasyClient client){
+ public TokenManager(Config config, ResteasyClient client) {
this.config = config;
ResteasyWebTarget target = client.target(config.getServerUrl());
- if(!config.isPublicClient()){
+ if (!config.isPublicClient()) {
target.register(new BasicAuthFilter(config.getClientId(), config.getClientSecret()));
}
- tokenService = target.proxy(TokenService.class);
+ this.tokenService = target.proxy(TokenService.class);
+ this.accessTokenGrantType = config.getGrantType();
+
+ if (CLIENT_CREDENTIALS.equals(accessTokenGrantType) && config.isPublicClient()) {
+ throw new IllegalArgumentException("Can't use " + GRANT_TYPE + "=" + CLIENT_CREDENTIALS + " with public client");
+ }
}
- public String getAccessTokenString(){
+ public String getAccessTokenString() {
return getAccessToken().getToken();
}
- public synchronized AccessTokenResponse getAccessToken(){
- if(currentToken == null){
+ public synchronized AccessTokenResponse getAccessToken() {
+ if (currentToken == null) {
grantToken();
- }else if(tokenExpired()){
+ } else if (tokenExpired()) {
refreshToken();
}
return currentToken;
}
- public AccessTokenResponse grantToken(){
- Form form = new Form()
- .param("grant_type", "password")
- .param("username", config.getUsername())
+ public AccessTokenResponse grantToken() {
+ Form form = new Form().param(GRANT_TYPE, accessTokenGrantType);
+ if (PASSWORD.equals(accessTokenGrantType)) {
+ form.param("username", config.getUsername())
.param("password", config.getPassword());
+ }
- if(config.isPublicClient()){
- form.param("client_id", config.getClientId());
+ if (config.isPublicClient()) {
+ form.param(CLIENT_ID, config.getClientId());
}
int requestTime = Time.currentTime();
synchronized (this) {
- currentToken = tokenService.grantToken( config.getRealm(), form.asMap() );
+ currentToken = tokenService.grantToken(config.getRealm(), form.asMap());
expirationTime = requestTime + currentToken.getExpiresIn();
}
return currentToken;
}
- public AccessTokenResponse refreshToken(){
- Form form = new Form()
- .param("grant_type", "refresh_token")
- .param("refresh_token", currentToken.getRefreshToken());
+ public AccessTokenResponse refreshToken() {
+ Form form = new Form().param(GRANT_TYPE, REFRESH_TOKEN)
+ .param(REFRESH_TOKEN, currentToken.getRefreshToken());
- if(config.isPublicClient()){
- form.param("client_id", config.getClientId());
+ if (config.isPublicClient()) {
+ form.param(CLIENT_ID, config.getClientId());
}
try {
int requestTime = Time.currentTime();
synchronized (this) {
- currentToken = tokenService.refreshToken( config.getRealm(), form.asMap() );
+ currentToken = tokenService.refreshToken(config.getRealm(), form.asMap());
expirationTime = requestTime + currentToken.getExpiresIn();
}
return currentToken;
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
index a011b41..b9018f8 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/conn/DefaultLiquibaseConnectionProvider.java
@@ -39,6 +39,7 @@ import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomInsertLockRecordGenerator;
+import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockDatabaseChangeLogGenerator;
import org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService;
import org.keycloak.connections.jpa.updater.liquibase.lock.DummyLockService;
import org.keycloak.models.KeycloakSession;
@@ -68,6 +69,7 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
protected void baseLiquibaseInitialization() {
ServiceLocator sl = ServiceLocator.getInstance();
+ sl.setResourceAccessor(new ClassLoaderResourceAccessor(getClass().getClassLoader()));
if (!System.getProperties().containsKey("liquibase.scan.packages")) {
if (sl.getPackages().remove("liquibase.core")) {
@@ -84,6 +86,10 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
sl.getPackages().remove("liquibase.ext");
sl.getPackages().remove("liquibase.sdk");
+
+ String lockPackageName = DummyLockService.class.getPackage().getName();
+ logger.debugf("Added package %s to liquibase", lockPackageName);
+ sl.addPackageToScan(lockPackageName);
}
LogFactory.setInstance(new LogWrapper());
@@ -93,6 +99,9 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
// Change command for creating lock and drop DELETE lock record from it
SqlGeneratorFactory.getInstance().register(new CustomInsertLockRecordGenerator());
+
+ // Use "SELECT FOR UPDATE" for locking database
+ SqlGeneratorFactory.getInstance().register(new CustomLockDatabaseChangeLogGenerator());
}
@@ -125,10 +134,6 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
String changelog = (database instanceof DB2Database) ? LiquibaseJpaUpdaterProvider.DB2_CHANGELOG : LiquibaseJpaUpdaterProvider.CHANGELOG;
logger.debugf("Using changelog file: %s", changelog);
- // We wrap liquibase update in CustomLockService provided by DBLockProvider. No need to lock inside liquibase itself.
- // NOTE: This can't be done in baseLiquibaseInitialization() as liquibase always restarts lock service
- LockServiceFactory.getInstance().register(new DummyLockService());
-
return new Liquibase(changelog, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockDatabaseChangeLogGenerator.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockDatabaseChangeLogGenerator.java
new file mode 100644
index 0000000..09d5495
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockDatabaseChangeLogGenerator.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.connections.jpa.updater.liquibase.lock;
+
+import liquibase.database.Database;
+import liquibase.database.core.DB2Database;
+import liquibase.database.core.H2Database;
+import liquibase.database.core.MSSQLDatabase;
+import liquibase.database.core.MySQLDatabase;
+import liquibase.database.core.OracleDatabase;
+import liquibase.database.core.PostgresDatabase;
+import liquibase.sql.Sql;
+import liquibase.sql.UnparsedSql;
+import liquibase.sqlgenerator.SqlGeneratorChain;
+import liquibase.sqlgenerator.core.LockDatabaseChangeLogGenerator;
+import liquibase.statement.core.LockDatabaseChangeLogStatement;
+import org.jboss.logging.Logger;
+
+/**
+ * We use "SELECT FOR UPDATE" pessimistic locking (Same algorithm like Hibernate LockMode.PESSIMISTIC_WRITE )
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class CustomLockDatabaseChangeLogGenerator extends LockDatabaseChangeLogGenerator {
+
+ private static final Logger logger = Logger.getLogger(CustomLockDatabaseChangeLogGenerator.class);
+
+ @Override
+ public int getPriority() {
+ return super.getPriority() + 1; // Ensure bigger priority than LockDatabaseChangeLogGenerator
+ }
+
+ @Override
+ public Sql[] generateSql(LockDatabaseChangeLogStatement statement, Database database, SqlGeneratorChain sqlGeneratorChain) {
+
+ Sql selectForUpdateSql = generateSelectForUpdate(database);
+
+ return new Sql[] { selectForUpdateSql };
+ }
+
+
+ private Sql generateSelectForUpdate(Database database) {
+ String catalog = database.getLiquibaseCatalogName();
+ String schema = database.getLiquibaseSchemaName();
+ String rawLockTableName = database.getDatabaseChangeLogLockTableName();
+
+ String lockTableName = database.escapeTableName(catalog, schema, rawLockTableName);
+ String idColumnName = database.escapeColumnName(catalog, schema, rawLockTableName, "ID");
+
+ String sqlBase = "SELECT " + idColumnName + " FROM " + lockTableName;
+ String sqlWhere = " WHERE " + idColumnName + "=1";
+
+ String sql;
+ if (database instanceof MySQLDatabase || database instanceof PostgresDatabase || database instanceof H2Database ||
+ database instanceof OracleDatabase) {
+ sql = sqlBase + sqlWhere + " FOR UPDATE";
+ } else if (database instanceof MSSQLDatabase) {
+ sql = sqlBase + " WITH (UPDLOCK, ROWLOCK)" + sqlWhere;
+ } else if (database instanceof DB2Database) {
+ sql = sqlBase + sqlWhere + " FOR READ ONLY WITH RS USE AND KEEP UPDATE LOCKS";
+ } else {
+ sql = sqlBase + sqlWhere;
+ logger.warnf("No direct support for database %s . Database lock may not work correctly", database.getClass().getName());
+ }
+
+ logger.debugf("SQL command for pessimistic lock: %s", sql);
+
+ return new UnparsedSql(sql);
+ }
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
index 0c246ca..3efbb23 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/CustomLockService.java
@@ -18,25 +18,16 @@
package org.keycloak.connections.jpa.updater.liquibase.lock;
import java.lang.reflect.Field;
-import java.text.DateFormat;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Date;
-import liquibase.database.Database;
import liquibase.database.core.DerbyDatabase;
import liquibase.exception.DatabaseException;
-import liquibase.exception.LockException;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
-import liquibase.lockservice.DatabaseChangeLogLock;
import liquibase.lockservice.StandardLockService;
-import liquibase.logging.LogFactory;
-import liquibase.sql.visitor.AbstractSqlVisitor;
-import liquibase.sql.visitor.SqlVisitor;
import liquibase.statement.core.CreateDatabaseChangeLogLockTableStatement;
import liquibase.statement.core.DropTableStatement;
import liquibase.statement.core.InitializeDatabaseChangeLogLockTableStatement;
+import liquibase.statement.core.LockDatabaseChangeLogStatement;
import liquibase.statement.core.RawSqlStatement;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
@@ -51,24 +42,6 @@ public class CustomLockService extends StandardLockService {
private static final Logger log = Logger.getLogger(CustomLockService.class);
- private long changeLogLocRecheckTimeMillis = -1;
-
- @Override
- public void setChangeLogLockRecheckTime(long changeLogLocRecheckTime) {
- super.setChangeLogLockRecheckTime(changeLogLocRecheckTime);
- this.changeLogLocRecheckTimeMillis = changeLogLocRecheckTime;
- }
-
- // Bug in StandardLockService.getChangeLogLockRecheckTime()
- @Override
- public Long getChangeLogLockRecheckTime() {
- if (changeLogLocRecheckTimeMillis == -1) {
- return super.getChangeLogLockRecheckTime();
- } else {
- return changeLogLocRecheckTimeMillis;
- }
- }
-
@Override
public void init() throws DatabaseException {
boolean createdTable = false;
@@ -84,8 +57,8 @@ public class CustomLockService extends StandardLockService {
database.commit();
} catch (DatabaseException de) {
log.warn("Failed to create lock table. Maybe other transaction created in the meantime. Retrying...");
- if (log.isDebugEnabled()) {
- log.debug(de.getMessage(), de); //Log details at debug level
+ if (log.isTraceEnabled()) {
+ log.trace(de.getMessage(), de); //Log details at trace level
}
database.rollback();
throw new LockRetryException(de);
@@ -115,8 +88,8 @@ public class CustomLockService extends StandardLockService {
} catch (DatabaseException de) {
log.warn("Failed to insert first record to the lock table. Maybe other transaction inserted in the meantime. Retrying...");
- if (log.isDebugEnabled()) {
- log.debug(de.getMessage(), de); // Log details at debug level
+ if (log.isTraceEnabled()) {
+ log.trace(de.getMessage(), de); // Log details at trace level
}
database.rollback();
throw new LockRetryException(de);
@@ -140,34 +113,88 @@ public class CustomLockService extends StandardLockService {
}
@Override
- public void waitForLock() throws LockException {
+ public void waitForLock() {
boolean locked = false;
long startTime = Time.toMillis(Time.currentTime());
long timeToGiveUp = startTime + (getChangeLogLockWaitTime());
+ boolean nextAttempt = true;
- while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
+ while (nextAttempt) {
locked = acquireLock();
if (!locked) {
int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
- log.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
- try {
- Thread.sleep(getChangeLogLockRecheckTime());
- } catch (InterruptedException e) {
- e.printStackTrace();
+ if (remainingTime > 0) {
+ log.debugf("Will try to acquire log another time. Remaining time: %d seconds", remainingTime);
+ } else {
+ nextAttempt = false;
}
+ } else {
+ nextAttempt = false;
}
}
if (!locked) {
- DatabaseChangeLogLock[] locks = listLocks();
- String lockedBy;
- if (locks.length > 0) {
- DatabaseChangeLogLock lock = locks[0];
- lockedBy = lock.getLockedBy() + " since " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(lock.getLockGranted());
+ int timeout = ((int)(getChangeLogLockWaitTime() / 1000));
+ throw new IllegalStateException("Could not acquire change log lock within specified timeout " + timeout + " seconds. Currently locked by other transaction");
+ }
+ }
+
+ @Override
+ public boolean acquireLock() {
+ if (hasChangeLogLock) {
+ // We already have a lock
+ return true;
+ }
+
+ Executor executor = ExecutorService.getInstance().getExecutor(database);
+
+ try {
+ database.rollback();
+
+ // Ensure table created and lock record inserted
+ this.init();
+ } catch (DatabaseException de) {
+ throw new IllegalStateException("Failed to retrieve lock", de);
+ }
+
+ try {
+ log.debug("Trying to lock database");
+ executor.execute(new LockDatabaseChangeLogStatement());
+ log.debug("Successfully acquired database lock");
+
+ hasChangeLogLock = true;
+ database.setCanCacheLiquibaseTableInfo(true);
+ return true;
+
+ } catch (DatabaseException de) {
+ log.warn("Lock didn't yet acquired. Will possibly retry to acquire lock. Details: " + de.getMessage());
+ if (log.isTraceEnabled()) {
+ log.debug(de.getMessage(), de);
+ }
+ return false;
+ }
+ }
+
+
+ @Override
+ public void releaseLock() {
+ try {
+ if (hasChangeLogLock) {
+ log.debug("Going to release database lock");
+ database.commit();
} else {
- lockedBy = "UNKNOWN";
+ log.warn("Attempt to release lock, which is not owned by current transaction");
+ }
+ } catch (Exception e) {
+ log.error("Database error during release lock", e);
+ } finally {
+ try {
+ hasChangeLogLock = false;
+ database.setCanCacheLiquibaseTableInfo(false);
+ database.rollback();
+ } catch (DatabaseException e) {
+ ;
}
- throw new LockException("Could not acquire change log lock. Currently locked by " + lockedBy);
}
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java
index 61ef19f..954822a 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/DummyLockService.java
@@ -17,6 +17,7 @@
package org.keycloak.connections.jpa.updater.liquibase.lock;
+import liquibase.exception.DatabaseException;
import liquibase.exception.LockException;
import liquibase.lockservice.StandardLockService;
@@ -28,6 +29,15 @@ import liquibase.lockservice.StandardLockService;
public class DummyLockService extends StandardLockService {
@Override
+ public int getPriority() {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public void init() throws DatabaseException {
+ }
+
+ @Override
public void waitForLock() throws LockException {
}
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
index 8769053..f44641e 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProvider.java
@@ -46,7 +46,7 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
private final LiquibaseDBLockProviderFactory factory;
private final KeycloakSession session;
- private LockService lockService;
+ private CustomLockService lockService;
private Connection dbConnection;
private int maxAttempts = DEFAULT_MAX_ATTEMPTS;
@@ -69,7 +69,6 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
this.lockService = new CustomLockService();
lockService.setChangeLogLockWaitTime(factory.getLockWaitTimeoutMillis());
- lockService.setChangeLogLockRecheckTime(factory.getLockRecheckTimeMillis());
lockService.setDatabase(liquibase.getDatabase());
} catch (LiquibaseException exception) {
safeRollbackConnection();
@@ -94,16 +93,15 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
lockService.waitForLock();
this.maxAttempts = DEFAULT_MAX_ATTEMPTS;
return;
- } catch (LockException le) {
- if (le.getCause() != null && le.getCause() instanceof LockRetryException) {
- // Indicates we should try to acquire lock again in different transaction
- restart();
- maxAttempts--;
- } else {
- throw new IllegalStateException("Failed to retrieve lock", le);
-
- // TODO: Possibility to forcefully retrieve lock after timeout instead of just give-up?
- }
+ } catch (LockRetryException le) {
+ // Indicates we should try to acquire lock again in different transaction
+ safeRollbackConnection();
+ restart();
+ maxAttempts--;
+ } catch (RuntimeException re) {
+ safeRollbackConnection();
+ safeCloseConnection();
+ throw re;
}
}
}
@@ -111,15 +109,17 @@ public class LiquibaseDBLockProvider implements DBLockProvider {
@Override
public void releaseLock() {
- try {
- lockService.releaseLock();
- } catch (LockException e) {
- logger.error("Could not release lock", e);
- }
+ lockService.releaseLock();
lockService.reset();
}
@Override
+ public boolean supportsForcedUnlock() {
+ // Implementation based on "SELECT FOR UPDATE" can't force unlock as it's locked by other transaction
+ return false;
+ }
+
+ @Override
public void destroyLockInfo() {
try {
this.lockService.destroy();
diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
index 5ce8be3..3026f7d 100644
--- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
+++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/lock/LiquibaseDBLockProviderFactory.java
@@ -31,24 +31,17 @@ public class LiquibaseDBLockProviderFactory implements DBLockProviderFactory {
private static final Logger logger = Logger.getLogger(LiquibaseDBLockProviderFactory.class);
- private long lockRecheckTimeMillis;
private long lockWaitTimeoutMillis;
- protected long getLockRecheckTimeMillis() {
- return lockRecheckTimeMillis;
- }
-
protected long getLockWaitTimeoutMillis() {
return lockWaitTimeoutMillis;
}
@Override
public void init(Config.Scope config) {
- int lockRecheckTime = config.getInt("lockRecheckTime", 2);
int lockWaitTimeout = config.getInt("lockWaitTimeout", 900);
- this.lockRecheckTimeMillis = Time.toMillis(lockRecheckTime);
this.lockWaitTimeoutMillis = Time.toMillis(lockWaitTimeout);
- logger.debugf("Liquibase lock provider configured with lockWaitTime: %d seconds, lockRecheckTime: %d seconds", lockWaitTimeout, lockRecheckTime);
+ logger.debugf("Liquibase lock provider configured with lockWaitTime: %d seconds", lockWaitTimeout);
}
@Override
@@ -63,7 +56,6 @@ public class LiquibaseDBLockProviderFactory implements DBLockProviderFactory {
@Override
public void setTimeouts(long lockRecheckTimeMillis, long lockWaitTimeoutMillis) {
- this.lockRecheckTimeMillis = lockRecheckTimeMillis;
this.lockWaitTimeoutMillis = lockWaitTimeoutMillis;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index 5da9b4e..3172665 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -127,6 +127,10 @@ public class JpaRealmProvider implements RealmProvider {
em.refresh(realm);
RealmAdapter adapter = new RealmAdapter(session, em, realm);
session.users().preRemove(adapter);
+
+ realm.getDefaultGroups().clear();
+ em.flush();
+
int num = em.createNamedQuery("deleteGroupRoleMappingsByRealm")
.setParameter("realm", realm).executeUpdate();
num = em.createNamedQuery("deleteGroupAttributesByRealm")
diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
index e3383fe..1b36889 100644
--- a/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/lock/MongoDBLockProvider.java
@@ -125,6 +125,11 @@ public class MongoDBLockProvider implements DBLockProvider {
}
@Override
+ public boolean supportsForcedUnlock() {
+ return true;
+ }
+
+ @Override
public void destroyLockInfo() {
db.getCollection(DB_LOCK_COLLECTION).remove(new BasicDBObject());
logger.debugf("Destroyed lock collection");
pom.xml 53(+38 -15)
diff --git a/pom.xml b/pom.xml
index bc90c8a..074a35e 100755
--- a/pom.xml
+++ b/pom.xml
@@ -1,19 +1,19 @@
<!--
- ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
- ~ and other contributors as indicated by the @author tags.
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
+~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+~ and other contributors as indicated by the @author tags.
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~ http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
@@ -107,6 +107,7 @@
<minify.plugin.version>1.7.2</minify.plugin.version>
<osgi.bundle.plugin.version>2.3.7</osgi.bundle.plugin.version>
<wildfly.plugin.version>1.0.1.Final</wildfly.plugin.version>
+ <nexus.staging.plugin.version>1.6.5</nexus.staging.plugin.version>
<!-- Surefire Settings -->
<surefire.memory.settings>-Xms512m -Xmx2048m -XX:MetaspaceSize=96m -XX:MaxMetaspaceSize=256m</surefire.memory.settings>
@@ -1230,6 +1231,16 @@
<pluginManagement>
<plugins>
<plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ <version>${nexus.staging.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <nexusUrl>https://repository.jboss.org/nexus</nexusUrl>
+ <serverId>jboss-releases-repository</serverId>
+ </configuration>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.2</version>
@@ -1367,5 +1378,17 @@
</plugins>
</build>
</profile>
+
+ <profile>
+ <id>nexus-staging</id>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.sonatype.plugins</groupId>
+ <artifactId>nexus-staging-maven-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
</project>
diff --git a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
index b5fb417..d9dc131 100644
--- a/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/dblock/DBLockProvider.java
@@ -34,10 +34,19 @@ public interface DBLockProvider extends Provider {
void waitForLock();
+ /**
+ * Release previously acquired lock
+ */
void releaseLock();
/**
+ * @return true if provider supports forced unlock at startup
+ */
+ boolean supportsForcedUnlock();
+
+
+ /**
* Will destroy whole state of DB lock (drop table/collection to track locking).
* */
void destroyLockInfo();
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
index 3aa15d8..0028ae7 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
@@ -20,8 +20,10 @@ package org.keycloak.models;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderEventManager;
import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
import java.util.List;
+import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -30,6 +32,8 @@ import java.util.List;
public interface KeycloakSessionFactory extends ProviderEventManager {
KeycloakSession create();
+ Set<Spi> getSpis();
+
<T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz);
<T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);
diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index 64079b0..7f7f7e0 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -717,6 +717,7 @@ public class ModelToRepresentation {
public static AuthenticatorConfigRepresentation toRepresentation(AuthenticatorConfigModel model) {
AuthenticatorConfigRepresentation rep = new AuthenticatorConfigRepresentation();
+ rep.setId(model.getId());
rep.setAlias(model.getAlias());
rep.setConfig(model.getConfig());
return rep;
diff --git a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
index dc35f39..a2469ec 100644
--- a/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
+++ b/services/src/main/java/org/keycloak/email/DefaultEmailSenderProvider.java
@@ -25,6 +25,7 @@ import org.keycloak.models.UserModel;
import org.keycloak.services.ServicesLogger;
import javax.mail.Message;
+import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;
import javax.mail.Transport;
@@ -54,6 +55,7 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
@Override
public void send(RealmModel realm, UserModel user, String subject, String textBody, String htmlBody) throws EmailException {
+ Transport transport = null;
try {
String address = user.getEmail();
Map<String, String> config = realm.getSmtpConfig();
@@ -114,7 +116,7 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
msg.saveChanges();
msg.setSentDate(new Date());
- Transport transport = session.getTransport("smtp");
+ transport = session.getTransport("smtp");
if (auth) {
transport.connect(config.get("user"), config.get("password"));
} else {
@@ -124,6 +126,14 @@ public class DefaultEmailSenderProvider implements EmailSenderProvider {
} catch (Exception e) {
logger.failedToSendEmail(e);
throw new EmailException(e);
+ } finally {
+ if (transport != null) {
+ try {
+ transport.close();
+ } catch (MessagingException e) {
+ logger.warn("Failed to close transport", e);
+ }
+ }
}
}
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
index 893e816..58c39ec 100755
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/FreeMarkerAccountProvider.java
@@ -188,7 +188,7 @@ public class FreeMarkerAccountProvider implements AccountProvider {
switch (page) {
case TOTP:
- attributes.put("totp", new TotpBean(session, realm, user, baseUri));
+ attributes.put("totp", new TotpBean(session, realm, user));
break;
case FEDERATED_IDENTITY:
attributes.put("federatedIdentity", new AccountFederatedIdentityBean(session, realm, user, uriInfo.getBaseUri(), stateChecker));
diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
old mode 100755
new mode 100644
index 1312e5d..197f985
--- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
+++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/TotpBean.java
@@ -14,12 +14,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.keycloak.forms.account.freemarker.model;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32;
+import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.utils.TotpUtils;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -34,35 +37,15 @@ public class TotpBean {
private final String totpSecret;
private final String totpSecretEncoded;
+ private final String totpSecretQrCode;
private final boolean enabled;
- private final String contextUrl;
- private final String keyUri;
- public TotpBean(KeycloakSession session, RealmModel realm, UserModel user, URI baseUri) {
+ public TotpBean(KeycloakSession session, RealmModel realm, UserModel user) {
this.enabled = session.users().configuredForCredentialType(realm.getOTPPolicy().getType(), realm, user);
- this.contextUrl = baseUri.getPath();
-
- this.totpSecret = randomString(20);
- this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
- this.keyUri = realm.getOTPPolicy().getKeyURI(realm, user, this.totpSecret);
- }
-
- private static String randomString(int length) {
- String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVW1234567890";
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; i++) {
- char c = chars.charAt(random.nextInt(chars.length()));
- sb.append(c);
- }
- return sb.toString();
- }
-
- private static final SecureRandom random;
- static
- {
- random = new SecureRandom();
- random.nextInt();
+ this.totpSecret = HmacOTP.generateSecret(20);
+ this.totpSecretEncoded = TotpUtils.encode(totpSecret);
+ this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
}
public boolean isEnabled() {
@@ -74,19 +57,11 @@ public class TotpBean {
}
public String getTotpSecretEncoded() {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < totpSecretEncoded.length(); i += 4) {
- sb.append(totpSecretEncoded.substring(i, i + 4 < totpSecretEncoded.length() ? i + 4 : totpSecretEncoded.length()));
- if (i + 4 < totpSecretEncoded.length()) {
- sb.append(" ");
- }
- }
- return sb.toString();
+ return totpSecretEncoded;
}
- public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
- String contents = URLEncoder.encode(keyUri, "utf-8");
- return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
+ public String getTotpSecretQrCode() {
+ return totpSecretQrCode;
}
}
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
index 1a81870..44ee44d 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/FreeMarkerLoginFormsProvider.java
@@ -279,7 +279,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
switch (page) {
case LOGIN_CONFIG_TOTP:
- attributes.put("totp", new TotpBean(realm, user, baseUri));
+ attributes.put("totp", new TotpBean(realm, user));
break;
case LOGIN_UPDATE_PROFILE:
UpdateProfileContext userCtx = (UpdateProfileContext) attributes.get(LoginFormsProvider.UPDATE_PROFILE_CONTEXT_ATTR);
diff --git a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
index a403176..d1e1d9c 100755
--- a/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
+++ b/services/src/main/java/org/keycloak/forms/login/freemarker/model/TotpBean.java
@@ -20,6 +20,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.Base32;
import org.keycloak.models.utils.HmacOTP;
+import org.keycloak.utils.TotpUtils;
import java.io.UnsupportedEncodingException;
import java.net.URI;
@@ -32,17 +33,15 @@ public class TotpBean {
private final String totpSecret;
private final String totpSecretEncoded;
+ private final String totpSecretQrCode;
private final boolean enabled;
- private final String contextUrl;
- private final String keyUri;
- public TotpBean(RealmModel realm, UserModel user, URI baseUri) {
+ public TotpBean(RealmModel realm, UserModel user) {
this.enabled = user.isOtpEnabled();
- this.contextUrl = baseUri.getPath();
-
+
this.totpSecret = HmacOTP.generateSecret(20);
- this.totpSecretEncoded = Base32.encode(totpSecret.getBytes());
- this.keyUri = realm.getOTPPolicy().getKeyURI(realm, user, this.totpSecret);
+ this.totpSecretEncoded = TotpUtils.encode(totpSecret);
+ this.totpSecretQrCode = TotpUtils.qrCode(totpSecret, realm, user);
}
public boolean isEnabled() {
@@ -54,19 +53,11 @@ public class TotpBean {
}
public String getTotpSecretEncoded() {
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < totpSecretEncoded.length(); i += 4) {
- sb.append(totpSecretEncoded.substring(i, i + 4 < totpSecretEncoded.length() ? i + 4 : totpSecretEncoded.length()));
- if (i + 4 < totpSecretEncoded.length()) {
- sb.append(" ");
- }
- }
- return sb.toString();
+ return totpSecretEncoded;
}
- public String getTotpSecretQrCodeUrl() throws UnsupportedEncodingException {
- String contents = URLEncoder.encode(keyUri, "utf-8");
- return contextUrl + "qrcode" + "?size=246x246&contents=" + contents;
+ public String getTotpSecretQrCode() {
+ return totpSecretQrCode;
}
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
index 65a4339..86a71e3 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java
@@ -17,18 +17,17 @@
package org.keycloak.services.clientregistration;
-import org.keycloak.authentication.AuthenticationProcessor;
-import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
-import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
-import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
-import javax.ws.rs.*;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
@@ -52,12 +51,7 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
event.event(EventType.CLIENT_INFO);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
-
- if (auth.isAuthenticated()) {
- auth.requireView(client);
- } else {
- authenticateClient(client);
- }
+ auth.requireView(client);
ClientManager clientManager = new ClientManager(new RealmManager(session));
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
@@ -80,29 +74,4 @@ public class AdapterInstallationClientRegistrationProvider implements ClientRegi
public void close() {
}
- private void authenticateClient(ClientModel client) {
- if (client.isPublicClient()) {
- return;
- }
-
- AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
-
- Response response = processor.authenticateClient();
- if (response != null) {
- event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
- }
-
- ClientModel authClient = processor.getClient();
- if (client == null) {
- event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
- }
-
- if (!authClient.getClientId().equals(client.getClientId())) {
- event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
- }
- }
-
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
index 5dc7285..5564ef2 100644
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationAuth.java
@@ -17,20 +17,28 @@
package org.keycloak.services.clientregistration;
-import com.sun.xml.bind.v2.runtime.reflect.opt.Const;
+import org.jboss.resteasy.spi.Failure;
+import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.Config;
+import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.common.util.Time;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
-import org.keycloak.models.*;
+import org.keycloak.models.AdminRoles;
+import org.keycloak.models.ClientInitialAccessModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.services.ForbiddenException;
import org.keycloak.util.TokenUtil;
import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -49,8 +57,6 @@ public class ClientRegistrationAuth {
public ClientRegistrationAuth(KeycloakSession session, EventBuilder event) {
this.session = session;
this.event = event;
-
- init();
}
private void init() {
@@ -67,41 +73,39 @@ public class ClientRegistrationAuth {
return;
}
- jwt = ClientRegistrationTokenUtils.parseToken(realm, uri, split[1]);
+ jwt = ClientRegistrationTokenUtils.verifyToken(realm, uri, split[1]);
+ if (jwt == null) {
+ throw unauthorized();
+ }
if (isInitialAccessToken()) {
initialAccessModel = session.sessions().getClientInitialAccessModel(session.getContext().getRealm(), jwt.getId());
if (initialAccessModel == null) {
- throw new ForbiddenException();
+ throw unauthorized();
}
}
}
- public boolean isAuthenticated() {
- return jwt != null;
- }
-
- public boolean isBearerToken() {
- return TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType());
+ private boolean isBearerToken() {
+ return jwt != null && TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType());
}
public boolean isInitialAccessToken() {
- return ClientRegistrationTokenUtils.TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType());
+ return jwt != null && ClientRegistrationTokenUtils.TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType());
}
public boolean isRegistrationAccessToken() {
- return ClientRegistrationTokenUtils.TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType());
+ return jwt != null && ClientRegistrationTokenUtils.TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType());
}
public void requireCreate() {
- if (!isAuthenticated()) {
- event.error(Errors.NOT_ALLOWED);
- throw new UnauthorizedException();
- }
+ init();
if (isBearerToken()) {
if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.CREATE_CLIENT)) {
return;
+ } else {
+ throw forbidden();
}
} else if (isInitialAccessToken()) {
if (initialAccessModel.getRemainingCount() > 0) {
@@ -111,58 +115,55 @@ public class ClientRegistrationAuth {
}
}
- event.error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
+ throw unauthorized();
}
public void requireView(ClientModel client) {
- if (!isAuthenticated()) {
- event.error(Errors.NOT_ALLOWED);
- throw new UnauthorizedException();
- }
-
- if (client == null) {
- event.error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
- }
+ init();
if (isBearerToken()) {
if (hasRole(AdminRoles.MANAGE_CLIENTS, AdminRoles.VIEW_CLIENTS)) {
+ if (client == null) {
+ throw notFound();
+ }
return;
+ } else {
+ throw forbidden();
}
} else if (isRegistrationAccessToken()) {
- if (client.getRegistrationToken() != null && client.getRegistrationToken().equals(jwt.getId())) {
+ if (client.getRegistrationToken() != null && client != null && client.getRegistrationToken().equals(jwt.getId())) {
+ return;
+ }
+ } else if (isInitialAccessToken()) {
+ throw unauthorized();
+ } else {
+ if (authenticateClient(client)) {
return;
}
}
- event.error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
+ throw unauthorized();
}
public void requireUpdate(ClientModel client) {
- if (!isAuthenticated()) {
- event.error(Errors.NOT_ALLOWED);
- throw new UnauthorizedException();
- }
-
- if (client == null) {
- event.error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
- }
+ init();
if (isBearerToken()) {
if (hasRole(AdminRoles.MANAGE_CLIENTS)) {
+ if (client == null) {
+ throw notFound();
+ }
return;
+ } else {
+ throw forbidden();
}
} else if (isRegistrationAccessToken()) {
- if (client.getRegistrationToken() != null && client.getRegistrationToken().equals(jwt.getId())) {
+ if (client.getRegistrationToken() != null && client != null && client.getRegistrationToken().equals(jwt.getId())) {
return;
}
}
- event.error(Errors.NOT_ALLOWED);
- throw new ForbiddenException();
+ throw unauthorized();
}
public ClientInitialAccessModel getInitialAccessModel() {
@@ -207,4 +208,46 @@ public class ClientRegistrationAuth {
}
}
+ private boolean authenticateClient(ClientModel client) {
+ if (client.isPublicClient()) {
+ return true;
+ }
+
+ AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
+
+ Response response = processor.authenticateClient();
+ if (response != null) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw unauthorized();
+ }
+
+ ClientModel authClient = processor.getClient();
+ if (client == null) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw unauthorized();
+ }
+
+ if (!authClient.getClientId().equals(client.getClientId())) {
+ event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
+ throw unauthorized();
+ }
+
+ return true;
+ }
+
+ private Failure unauthorized() {
+ event.error(Errors.NOT_ALLOWED);
+ return new UnauthorizedException();
+ }
+
+ private Failure forbidden() {
+ event.error(Errors.NOT_ALLOWED);
+ return new ForbiddenException();
+ }
+
+ private Failure notFound() {
+ event.error(Errors.NOT_ALLOWED);
+ return new NotFoundException("Client not found");
+ }
+
}
diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
index 2cd74ba..2fe65cc 100755
--- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
+++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationTokenUtils.java
@@ -28,7 +28,6 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.JsonWebToken;
-import org.keycloak.services.ForbiddenException;
import org.keycloak.services.Urls;
import org.keycloak.util.TokenUtil;
@@ -57,37 +56,37 @@ public class ClientRegistrationTokenUtils {
return createToken(realm, uri, model.getId(), TYPE_INITIAL_ACCESS_TOKEN, model.getExpiration() > 0 ? model.getTimestamp() + model.getExpiration() : 0);
}
- public static JsonWebToken parseToken(RealmModel realm, UriInfo uri, String token) {
+ public static JsonWebToken verifyToken(RealmModel realm, UriInfo uri, String token) {
JWSInput input;
try {
input = new JWSInput(token);
} catch (JWSInputException e) {
- throw new ForbiddenException(e);
+ return null;
}
if (!RSAProvider.verify(input, realm.getPublicKey())) {
- throw new ForbiddenException("Invalid signature");
+ return null;
}
JsonWebToken jwt;
try {
jwt = input.readJsonContent(JsonWebToken.class);
} catch (JWSInputException e) {
- throw new ForbiddenException(e);
+ return null;
}
if (!getIssuer(realm, uri).equals(jwt.getIssuer())) {
- throw new ForbiddenException("Issuer doesn't match");
+ return null;
}
if (!jwt.isActive()) {
- throw new ForbiddenException("Expired token");
+ return null;
}
if (!(TokenUtil.TOKEN_TYPE_BEARER.equals(jwt.getType()) ||
TYPE_INITIAL_ACCESS_TOKEN.equals(jwt.getType()) ||
TYPE_REGISTRATION_ACCESS_TOKEN.equals(jwt.getType()))) {
- throw new ForbiddenException("Invalid token type");
+ return null;
}
return jwt;
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 3ca473b..90b495c 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -40,6 +40,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ private Set<Spi> spis = new HashSet<>();
private Map<Class<? extends Provider>, String> provider = new HashMap<Class<? extends Provider>, String>();
private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<ProviderEventListener>();
@@ -80,6 +81,8 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
protected void loadSPIs(ProviderManager pm, ServiceLoader<Spi> load) {
for (Spi spi : load) {
+ spis.add(spi);
+
Map<String, ProviderFactory> factories = new HashMap<String, ProviderFactory>();
factoriesMap.put(spi.getProviderClass(), factories);
@@ -139,6 +142,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
@Override
+ public Set<Spi> getSpis() {
+ return spis;
+ }
+
+ @Override
public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
return getProviderFactory(clazz, provider.get(clazz));
}
diff --git a/services/src/main/java/org/keycloak/services/managers/DBLockManager.java b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
index 1909c36..8aace4b 100644
--- a/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/DBLockManager.java
@@ -34,52 +34,38 @@ public class DBLockManager {
protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
- public void waitForLock(KeycloakSessionFactory sessionFactory) {
- KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+ private final KeycloakSession session;
- @Override
- public void run(KeycloakSession session) {
- DBLockProvider lock = getDBLock(session);
- lock.waitForLock();
- }
-
- });
+ public DBLockManager(KeycloakSession session) {
+ this.session = session;
}
- public void releaseLock(KeycloakSessionFactory sessionFactory) {
- KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
-
- @Override
- public void run(KeycloakSession session) {
- DBLockProvider lock = getDBLock(session);
+ public void checkForcedUnlock() {
+ if (Boolean.getBoolean("keycloak.dblock.forceUnlock")) {
+ DBLockProvider lock = getDBLock();
+ if (lock.supportsForcedUnlock()) {
+ logger.forcedReleaseDBLock();
lock.releaseLock();
+ } else {
+ throw new IllegalStateException("Forced unlock requested, but provider " + lock + " doesn't support it");
}
-
- });
- }
-
-
- public void checkForcedUnlock(KeycloakSessionFactory sessionFactory) {
- if (Boolean.getBoolean("keycloak.dblock.forceUnlock")) {
- logger.forcedReleaseDBLock();
- releaseLock(sessionFactory);
}
}
// Try to detect ID from realmProvider
- public DBLockProvider getDBLock(KeycloakSession session) {
- String realmProviderId = getRealmProviderId(session);
+ public DBLockProvider getDBLock() {
+ String realmProviderId = getRealmProviderId();
return session.getProvider(DBLockProvider.class, realmProviderId);
}
- public DBLockProviderFactory getDBLockFactory(KeycloakSession session) {
- String realmProviderId = getRealmProviderId(session);
+ public DBLockProviderFactory getDBLockFactory() {
+ String realmProviderId = getRealmProviderId();
return (DBLockProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(DBLockProvider.class, realmProviderId);
}
- private String getRealmProviderId(KeycloakSession session) {
+ private String getRealmProviderId() {
RealmProviderFactory realmProviderFactory = (RealmProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(RealmProvider.class);
return realmProviderFactory.getId();
}
diff --git a/services/src/main/java/org/keycloak/services/messages/Messages.java b/services/src/main/java/org/keycloak/services/messages/Messages.java
index d9a2fe3..7fae66d 100755
--- a/services/src/main/java/org/keycloak/services/messages/Messages.java
+++ b/services/src/main/java/org/keycloak/services/messages/Messages.java
@@ -154,6 +154,10 @@ public class Messages {
public static final String IDENTITY_PROVIDER_LINK_SUCCESS = "identityProviderLinkSuccess";
+ public static final String STALE_CODE = "staleCodeMessage";
+
+ public static final String STALE_CODE_ACCOUNT = "staleCodeAccountMessage";
+
public static final String IDENTITY_PROVIDER_NOT_UNIQUE = "identityProviderNotUniqueMessage";
public static final String REALM_SUPPORTS_NO_CREDENTIALS = "realmSupportsNoCredentialsMessage";
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
index 54378bf..1e42aad 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java
@@ -389,10 +389,16 @@ public class AuthenticationManagementResource {
String provider = data.get("provider");
// make sure provider is one of the registered providers
- ProviderFactory f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
+ ProviderFactory f;
+ if (parentFlow.getProviderId().equals(AuthenticationFlow.CLIENT_FLOW)) {
+ f = session.getKeycloakSessionFactory().getProviderFactory(ClientAuthenticator.class, provider);
+ } else {
+ f = session.getKeycloakSessionFactory().getProviderFactory(Authenticator.class, provider);
+ }
if (f == null) {
throw new BadRequestException("No authentication provider found for id: " + provider);
}
+
AuthenticationExecutionModel execution = new AuthenticationExecutionModel();
execution.setParentFlow(parentFlow.getId());
execution.setRequirement(AuthenticationExecutionModel.Requirement.DISABLED);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 4454ccb..3ea8fd1 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -95,10 +95,7 @@ public class ServerInfoAdminResource {
private void setProviders(ServerInfoRepresentation info) {
LinkedHashMap<String, SpiInfoRepresentation> spiReps = new LinkedHashMap<>();
- List<Spi> spis = new LinkedList<>();
- for (Spi spi : ServiceLoader.load(Spi.class)) {
- spis.add(spi);
- }
+ List<Spi> spis = new LinkedList<>(session.getKeycloakSessionFactory().getSpis());
Collections.sort(spis, new Comparator<Spi>() {
@Override
public int compare(Spi s1, Spi s2) {
diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
index 27d47b5..a9fb79f 100755
--- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
+++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java
@@ -34,6 +34,7 @@ import org.keycloak.common.ClientConnection;
import org.keycloak.common.util.ObjectUtil;
import org.keycloak.common.util.Time;
import org.keycloak.events.Details;
+import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.forms.login.LoginFormsProvider;
@@ -146,7 +147,12 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
try {
- ClientSessionCode clientSessionCode = parseClientSessionCode(code);
+ ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ if (parsedCode.response != null) {
+ return parsedCode.response;
+ }
+
+ ClientSessionCode clientSessionCode = parsedCode.clientSessionCode;
IdentityProvider identityProvider = getIdentityProvider(session, realmModel, providerId);
Response response = identityProvider.performLogin(createAuthenticationRequest(providerId, clientSessionCode));
@@ -245,14 +251,14 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
public Response authenticated(BrokeredIdentityContext context) {
- ClientSessionCode clientCode = null;
IdentityProviderModel identityProviderConfig = context.getIdpConfig();
- try {
- clientCode = parseClientSessionCode(context.getCode());
- } catch (Exception e) {
- return redirectToErrorPage(Messages.IDENTITY_PROVIDER_AUTHENTICATION_FAILED, e, identityProviderConfig.getProviderId());
+ ParsedCodeContext parsedCode = parseClientSessionCode(context.getCode());
+ if (parsedCode.response != null) {
+ return parsedCode.response;
}
+ ClientSessionCode clientCode = parsedCode.clientSessionCode;
+
String providerId = identityProviderConfig.getAlias();
if (!identityProviderConfig.isStoreToken()) {
if (isDebugEnabled()) {
@@ -318,6 +324,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
return Response.status(302).location(redirect).build();
} else {
+ Response response = validateUser(federatedUser, realmModel);
+ if (response != null) {
+ return response;
+ }
+
updateFederatedIdentity(context, federatedUser);
clientSession.setAuthenticatedUser(federatedUser);
@@ -325,12 +336,27 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
}
+ public Response validateUser(UserModel user, RealmModel realm) {
+ if (!user.isEnabled()) {
+ event.error(Errors.USER_DISABLED);
+ return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+ }
+ if (realm.isBruteForceProtected()) {
+ event.error(Errors.USER_TEMPORARILY_DISABLED);
+ return ErrorPage.error(session, Messages.ACCOUNT_DISABLED);
+ }
+ return null;
+ }
+
// Callback from LoginActionsService after first login with broker was done and Keycloak account is successfully linked/created
@GET
@Path("/after-first-broker-login")
public Response afterFirstBrokerLogin(@QueryParam("code") String code) {
- ClientSessionCode clientCode = parseClientSessionCode(code);
- ClientSessionModel clientSession = clientCode.getClientSession();
+ ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ if (parsedCode.response != null) {
+ return parsedCode.response;
+ }
+ ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
try {
this.event.detail(Details.CODE_ID, clientSession.getId())
@@ -443,8 +469,11 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@GET
@Path("/after-post-broker-login")
public Response afterPostBrokerLoginFlow(@QueryParam("code") String code) {
- ClientSessionCode clientCode = parseClientSessionCode(code);
- ClientSessionModel clientSession = clientCode.getClientSession();
+ ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ if (parsedCode.response != null) {
+ return parsedCode.response;
+ }
+ ClientSessionModel clientSession = parsedCode.clientSessionCode.getClientSession();
try {
SerializedBrokeredIdentityContext serializedCtx = SerializedBrokeredIdentityContext.readFromClientSession(clientSession, PostBrokerLoginConstants.PBL_BROKERED_IDENTITY_CONTEXT);
@@ -530,9 +559,15 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response cancelled(String code) {
- ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- return redirectToErrorPage(Messages.INVALID_CODE);
+ ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ if (parsedCode.response != null) {
+ return parsedCode.response;
+ }
+ ClientSessionCode clientCode = parsedCode.clientSessionCode;
+
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.CONSENT_DENIED);
+ if (accountManagementFailedLinking != null) {
+ return accountManagementFailedLinking;
}
return browserAuthentication(clientCode.getClientSession(), null);
@@ -540,10 +575,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
@Override
public Response error(String code, String message) {
- ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode.getClientSession() == null || !clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
- return redirectToErrorPage(Messages.INVALID_CODE);
+ ParsedCodeContext parsedCode = parseClientSessionCode(code);
+ if (parsedCode.response != null) {
+ return parsedCode.response;
+ }
+ ClientSessionCode clientCode = parsedCode.clientSessionCode;
+
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), message);
+ if (accountManagementFailedLinking != null) {
+ return accountManagementFailedLinking;
}
+
return browserAuthentication(clientCode.getClientSession(), message);
}
@@ -604,36 +646,60 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
}
- private ClientSessionCode parseClientSessionCode(String code) {
+ private ParsedCodeContext parseClientSessionCode(String code) {
ClientSessionCode clientCode = ClientSessionCode.parse(code, this.session, this.realmModel);
- if (clientCode != null && clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ if (clientCode != null) {
ClientSessionModel clientSession = clientCode.getClientSession();
- if (clientSession != null) {
- ClientModel client = clientSession.getClient();
+ if (clientSession.getUserSession() != null) {
+ this.event.session(clientSession.getUserSession());
+ }
- if (client == null) {
- throw new IdentityBrokerException("Invalid client");
- }
+ ClientModel client = clientSession.getClient();
+
+ if (client != null) {
logger.debugf("Got authorization code from client [%s].", client.getClientId());
this.event.client(client);
this.session.getContext().setClient(client);
- if (clientSession.getUserSession() != null) {
- this.event.session(clientSession.getUserSession());
+ if (!clientCode.isValid(AUTHENTICATE.name(), ClientSessionCode.ActionType.LOGIN)) {
+ logger.debugf("Authorization code is not valid. Client session ID: %s, Client session's action: %s", clientSession.getId(), clientSession.getAction());
+
+ // Check if error happened during login or during linking from account management
+ Response accountManagementFailedLinking = checkAccountManagementFailedLinking(clientCode.getClientSession(), Messages.STALE_CODE_ACCOUNT);
+ Response staleCodeError = (accountManagementFailedLinking != null) ? accountManagementFailedLinking : redirectToErrorPage(Messages.STALE_CODE);
+
+
+ return ParsedCodeContext.response(staleCodeError);
}
- }
- if (isDebugEnabled()) {
- logger.debugf("Authorization code is valid.");
- }
+ if (isDebugEnabled()) {
+ logger.debugf("Authorization code is valid.");
+ }
- return clientCode;
+ return ParsedCodeContext.clientSessionCode(clientCode);
+ }
}
- throw new IdentityBrokerException("Invalid code, please login again through your client.");
+ logger.debugf("Authorization code is not valid. Code: %s", code);
+ Response staleCodeError = redirectToErrorPage(Messages.STALE_CODE);
+ return ParsedCodeContext.response(staleCodeError);
+ }
+
+ private Response checkAccountManagementFailedLinking(ClientSessionModel clientSession, String error, Object... parameters) {
+ if (clientSession.getUserSession() != null && clientSession.getClient() != null && clientSession.getClient().getClientId().equals(ACCOUNT_MANAGEMENT_CLIENT_ID)) {
+
+ this.event.event(EventType.FEDERATED_IDENTITY_LINK);
+ UserModel user = clientSession.getUserSession().getUser();
+ this.event.user(user);
+ this.event.detail(Details.USERNAME, user.getUsername());
+
+ return redirectToAccountErrorPage(clientSession, error, parameters);
+ } else {
+ return null;
+ }
}
private AuthenticationRequest createAuthenticationRequest(String providerId, ClientSessionCode clientSessionCode) {
@@ -808,4 +874,22 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal
this.session.getTransaction().rollback();
}
}
+
+
+ private static class ParsedCodeContext {
+ private ClientSessionCode clientSessionCode;
+ private Response response;
+
+ public static ParsedCodeContext clientSessionCode(ClientSessionCode clientSessionCode) {
+ ParsedCodeContext ctx = new ParsedCodeContext();
+ ctx.clientSessionCode = clientSessionCode;
+ return ctx;
+ }
+
+ public static ParsedCodeContext response(Response response) {
+ ParsedCodeContext ctx = new ParsedCodeContext();
+ ctx.response = response;
+ return ctx;
+ }
+ }
}
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 4de67ed..0294e9c 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -25,6 +25,7 @@ import org.keycloak.Config;
import org.keycloak.exportimport.ExportImportManager;
import org.keycloak.migration.MigrationModelManager;
import org.keycloak.models.*;
+import org.keycloak.models.dblock.DBLockProvider;
import org.keycloak.services.managers.DBLockManager;
import org.keycloak.models.utils.PostMigrationEvent;
import org.keycloak.models.utils.RepresentationToModel;
@@ -82,7 +83,6 @@ public class KeycloakApplication extends Application {
singletons.add(new ServerVersionResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
- classes.add(QRCodeResource.class);
classes.add(ThemeResource.class);
classes.add(JsResource.class);
@@ -92,9 +92,10 @@ public class KeycloakApplication extends Application {
ExportImportManager exportImportManager;
- DBLockManager dbLockManager = new DBLockManager();
- dbLockManager.checkForcedUnlock(sessionFactory);
- dbLockManager.waitForLock(sessionFactory);
+ DBLockManager dbLockManager = new DBLockManager(sessionFactory.create());
+ dbLockManager.checkForcedUnlock();
+ DBLockProvider dbLock = dbLockManager.getDBLock();
+ dbLock.waitForLock();
try {
migrateModel();
@@ -131,7 +132,7 @@ public class KeycloakApplication extends Application {
importAddUser();
} finally {
- dbLockManager.releaseLock(sessionFactory);
+ dbLock.releaseLock();
}
if (exportImportManager.isRunExport()) {
diff --git a/services/src/main/java/org/keycloak/utils/TotpUtils.java b/services/src/main/java/org/keycloak/utils/TotpUtils.java
new file mode 100644
index 0000000..f9f3b60
--- /dev/null
+++ b/services/src/main/java/org/keycloak/utils/TotpUtils.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.utils;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.client.j2se.MatrixToImageWriter;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import org.keycloak.common.util.Base64;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.Base32;
+
+import java.io.ByteArrayOutputStream;
+import java.net.URLEncoder;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class TotpUtils {
+
+ public static String encode(String totpSecret) {
+ String encoded = Base32.encode(totpSecret.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < encoded.length(); i += 4) {
+ sb.append(encoded.substring(i, i + 4 < encoded.length() ? i + 4 : encoded.length()));
+ if (i + 4 < encoded.length()) {
+ sb.append(" ");
+ }
+ }
+ return sb.toString();
+ }
+
+ public static String qrCode(String totpSecret, RealmModel realm, UserModel user) {
+ try {
+ String keyUri = realm.getOTPPolicy().getKeyURI(realm, user, totpSecret);
+
+ int width = 246;
+ int height = 246;
+
+ QRCodeWriter writer = new QRCodeWriter();
+ final BitMatrix bitMatrix = writer.encode(keyUri, BarcodeFormat.QR_CODE, width, height);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ MatrixToImageWriter.writeToStream(bitMatrix, "png", bos);
+ bos.close();
+
+ return Base64.encodeBytes(bos.toByteArray());
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 6b842b0..40ed6ee 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -27,6 +27,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.services.managers.RealmManager;
@@ -35,6 +36,7 @@ import org.keycloak.util.JsonSerialization;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotFoundException;
+import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
@@ -441,4 +443,22 @@ public class RealmTest extends AbstractClientTest {
assertEquals(certificate, rep.getCertificate());
}
+ @Test
+ // KEYCLOAK-2700
+ public void deleteRealmWithDefaultGroups() throws IOException {
+ RealmRepresentation rep = new RealmRepresentation();
+ rep.setRealm("foo");
+
+ GroupRepresentation group = new GroupRepresentation();
+ group.setName("default1");
+ group.setPath("/default1");
+
+ rep.setGroups(Collections.singletonList(group));
+ rep.setDefaultGroups(Collections.singletonList("/default1"));
+
+ keycloak.realms().create(rep);
+
+ keycloak.realm(rep.getRealm()).remove();
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index eafe169..c2f4b7c 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -240,6 +240,20 @@ public class UserTest extends AbstractClientTest {
}
@Test
+ public void delete() {
+ Response response = realm.users().delete( createUser() );
+ assertEquals(204, response.getStatus());
+ response.close();
+ }
+
+ @Test
+ public void deleteNonExistent() {
+ Response response = realm.users().delete( "does-not-exist" );
+ assertEquals(404, response.getStatus());
+ response.close();
+ }
+
+ @Test
public void searchPaginated() {
createUsers();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
index b45213a..4ccf3cb 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractIdentityProviderTest.java
@@ -40,6 +40,7 @@ import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionSt
import org.keycloak.testsuite.pages.AccountFederatedIdentityPage;
import org.keycloak.testsuite.pages.AccountPasswordPage;
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
+import org.keycloak.testsuite.pages.ErrorPage;
import org.keycloak.testsuite.pages.LoginPage;
import org.keycloak.testsuite.pages.LoginUpdateProfilePage;
import org.keycloak.testsuite.pages.OAuthGrantPage;
@@ -109,6 +110,9 @@ public abstract class AbstractIdentityProviderTest {
@WebResource
protected AccountFederatedIdentityPage accountFederatedIdentityPage;
+ @WebResource
+ protected ErrorPage errorPage;
+
protected KeycloakSession session;
protected int logoutTimeOffset = 0;
@@ -168,10 +172,6 @@ public abstract class AbstractIdentityProviderTest {
Time.setOffset(0);
}
- String afterLogoutUrl = driver.getCurrentUrl();
- String afterLogoutPageSource = driver.getPageSource();
- System.out.println("afterLogoutUrl: " + afterLogoutUrl);
- //System.out.println("after logout page source: " + afterLogoutPageSource);
driver.navigate().to("http://localhost:8081/test-app");
@@ -215,7 +215,6 @@ public abstract class AbstractIdentityProviderTest {
String currentUrl = this.driver.getCurrentUrl();
assertTrue(currentUrl.startsWith("http://localhost:8082/auth/"));
- System.out.println(this.driver.getCurrentUrl());
// log in to identity provider
this.loginPage.login(username, "password");
doAfterProviderAuthentication();
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 76911d2..1af21f5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java
@@ -38,6 +38,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.IdentityProviderModel;
+import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserFederationProviderModel;
@@ -70,6 +71,73 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
}
@Test
+ public void testDisabledUser() {
+ setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+
+ driver.navigate().to("http://localhost:8081/test-app");
+ loginPage.clickSocial(getProviderId());
+ loginPage.login("test-user", "password");
+ System.out.println(driver.getPageSource());
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+
+ try {
+ KeycloakSession session = brokerServerRule.startSession();
+ session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(false);
+ brokerServerRule.stopSession(session, true);
+
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ loginPage.clickSocial(getProviderId());
+ loginPage.login("test-user", "password");
+
+ assertTrue(errorPage.isCurrent());
+ assertEquals("Account is disabled, contact admin.", errorPage.getError());
+ } finally {
+ KeycloakSession session = brokerServerRule.startSession();
+ session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(true);
+ brokerServerRule.stopSession(session, true);
+ }
+ }
+
+ @Test
+ public void testTemporarilyDisabledUser() {
+ setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF);
+
+ driver.navigate().to("http://localhost:8081/test-app");
+ loginPage.clickSocial(getProviderId());
+ loginPage.login("test-user", "password");
+ driver.navigate().to("http://localhost:8081/test-app/logout");
+
+ try {
+ KeycloakSession session = brokerServerRule.startSession();
+ RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
+ brokerRealm.setBruteForceProtected(true);
+ brokerRealm.setFailureFactor(2);
+ brokerServerRule.stopSession(session, true);
+
+ driver.navigate().to("http://localhost:8081/test-app");
+ loginPage.login("test-user", "fail");
+ loginPage.login("test-user", "fail");
+
+ driver.navigate().to("http://localhost:8081/test-app");
+
+ assertTrue(driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ loginPage.clickSocial(getProviderId());
+ loginPage.login("test-user", "password");
+
+ assertTrue(errorPage.isCurrent());
+ assertEquals("Account is disabled, contact admin.", errorPage.getError());
+ } finally {
+ KeycloakSession session = brokerServerRule.startSession();
+ RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker");
+ brokerRealm.setBruteForceProtected(false);
+ brokerRealm.setFailureFactor(0);
+ brokerServerRule.stopSession(session, true);
+ }
+ }
+
+ @Test
public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() {
IdentityProviderModel identityProviderModel = getIdentityProviderModel();
setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING);
@@ -362,7 +430,6 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
revokeGrant();
// Logout from account management
- System.out.println("*** logout from account management");
accountFederatedIdentityPage.logout();
assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
@@ -502,7 +569,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent
driver.navigate().to("http://localhost:8081/test-app/logout");
String currentUrl = this.driver.getCurrentUrl();
- System.out.println("after logout currentUrl: " + currentUrl);
+// System.out.println("after logout currentUrl: " + currentUrl);
assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
unconfigureUserRetrieveToken("test-user");
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 ad3e9ff..5626751 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
@@ -30,7 +30,6 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.Constants;
import org.keycloak.testsuite.KeycloakServer;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
-import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.AbstractKeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.util.JsonSerialization;
@@ -69,9 +68,6 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
};
@WebResource
- private OAuthGrantPage grantPage;
-
- @WebResource
protected AccountApplicationsPage accountApplicationsPage;
@Override
@@ -120,6 +116,16 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP
}
@Test
+ public void testDisabledUser() {
+ super.testDisabledUser();
+ }
+
+ @Test
+ public void testTemporarilyDisabledUser() {
+ super.testTemporarilyDisabledUser();
+ }
+
+ @Test
public void testLogoutWorksWithTokenTimeout() {
Keycloak keycloak = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
RealmRepresentation realm = keycloak.realm("realm-with-oidc-identity-provider").toRepresentation();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
new file mode 100644
index 0000000..d648cdd
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeycloakServerBrokerWithConsentTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.broker;
+
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.keycloak.admin.client.Keycloak;
+import org.keycloak.admin.client.resource.RealmResource;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.representations.idm.ClientRepresentation;
+import org.keycloak.representations.idm.IdentityProviderRepresentation;
+import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.testsuite.KeycloakServer;
+import org.keycloak.testsuite.rule.AbstractKeycloakRule;
+import org.openqa.selenium.NoSuchElementException;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class OIDCKeycloakServerBrokerWithConsentTest extends AbstractIdentityProviderTest {
+
+ private static final int PORT = 8082;
+
+ private static Keycloak keycloak1;
+ private static Keycloak keycloak2;
+
+ @ClassRule
+ public static AbstractKeycloakRule oidcServerRule = new AbstractKeycloakRule() {
+
+ @Override
+ protected void configureServer(KeycloakServer server) {
+ server.getConfig().setPort(PORT);
+ }
+
+ @Override
+ protected void configure(KeycloakSession session, RealmManager manager, RealmModel adminRealm) {
+ server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-kc-oidc.json"));
+
+ // Disable update profile
+ RealmModel realm = getRealm(session);
+ setUpdateProfileFirstLogin(realm, IdentityProviderRepresentation.UPFLM_OFF);
+ }
+
+ @Override
+ protected String[] getTestRealms() {
+ return new String[] { "realm-with-oidc-identity-provider" };
+ }
+ };
+
+
+ @BeforeClass
+ public static void before() {
+ keycloak1 = Keycloak.getInstance("http://localhost:8081/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
+ keycloak2 = Keycloak.getInstance("http://localhost:8082/auth", "master", "admin", "admin", org.keycloak.models.Constants.ADMIN_CLI_CLIENT_ID);
+
+ // Require broker to show consent screen
+ RealmResource brokeredRealm = keycloak2.realm("realm-with-oidc-identity-provider");
+ List<ClientRepresentation> clients = brokeredRealm.clients().findByClientId("broker-app");
+ Assert.assertEquals(1, clients.size());
+ ClientRepresentation brokerApp = clients.get(0);
+ brokerApp.setConsentRequired(true);
+ brokeredRealm.clients().get(brokerApp.getId()).update(brokerApp);
+
+
+ // Change timeouts on realm-with-broker to lower values
+ RealmResource realmWithBroker = keycloak1.realm("realm-with-broker");
+ RealmRepresentation realmRep = realmWithBroker.toRepresentation();
+ realmRep.setAccessCodeLifespanLogin(30);;
+ realmRep.setAccessCodeLifespan(30);
+ realmRep.setAccessCodeLifespanUserAction(30);
+ realmWithBroker.update(realmRep);
+ }
+
+
+ @Override
+ protected String getProviderId() {
+ return "kc-oidc-idp";
+ }
+
+
+ // KEYCLOAK-2769
+ @Test
+ public void testConsentDeniedWithExpiredClientSession() throws Exception {
+ // Login to broker
+ loginIDP("test-user");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert error page with backToApplication link displayed
+ errorPage.assertCurrent();
+ errorPage.clickBackToApplication();
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth"));
+
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+
+ // KEYCLOAK-2769
+ @Test
+ public void testConsentDeniedWithExpiredAndClearedClientSession() throws Exception {
+ // Login to broker again
+ loginIDP("test-user");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // Manually remove expiredSessions TODO: Will require custom endpoint when migrate to integration-arquillian
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ session.sessions().removeExpired(getRealm());
+
+ brokerServerRule.stopSession(this.session, true);
+ this.session = brokerServerRule.startSession();
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert error page without backToApplication link (clientSession expired and was removed on the server)
+ errorPage.assertCurrent();
+ try {
+ errorPage.clickBackToApplication();
+ fail("Not expected to have link backToApplication available");
+ } catch (NoSuchElementException nsee) {
+ // Expected;
+ }
+
+ } finally {
+ Time.setOffset(0);
+ }
+ }
+
+
+ // KEYCLOAK-2801
+ @Test
+ public void testAccountManagementLinkingAndExpiredClientSession() throws Exception {
+ // Login as pedroigor to account management
+ loginToAccountManagement("pedroigor");
+
+ // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ // Set time offset
+ Time.setOffset(60);
+ try {
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert account error page with "staleCodeAccount" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("The page expired. Please try one more time.", accountFederatedIdentityPage.getError());
+
+
+ // Try to link one more time
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ Time.setOffset(120);
+
+ // User granted consent
+ grantPage.assertCurrent();
+ grantPage.accept();
+
+ // Assert account error page with "staleCodeAccount" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("The page expired. Please try one more time.", accountFederatedIdentityPage.getError());
+
+ } finally {
+ Time.setOffset(0);
+ }
+
+ // Revoke consent
+ RealmResource brokeredRealm = keycloak2.realm("realm-with-oidc-identity-provider");
+ List<UserRepresentation> users = brokeredRealm.users().search("test-user", 0, 1);
+ brokeredRealm.users().get(users.get(0).getId()).revokeConsent("broker-app");
+ }
+
+
+ @Test
+ public void testLoginCancelConsent() throws Exception {
+ // Try to login
+ loginIDP("test-user");
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert back on login page
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/"));
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ }
+
+
+ // KEYCLOAK-2802
+ @Test
+ public void testAccountManagementLinkingCancelConsent() throws Exception {
+ // Login as pedroigor to account management
+ loginToAccountManagement("pedroigor");
+
+ // Link my "pedroigor" identity with "test-user" from brokered Keycloak
+ accountFederatedIdentityPage.clickAddProvider(getProviderId());
+
+ assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/"));
+ this.loginPage.login("test-user", "password");
+
+ // User rejected consent
+ grantPage.assertCurrent();
+ grantPage.cancel();
+
+ // Assert account error page with "consentDenied" error displayed
+ accountFederatedIdentityPage.assertCurrent();
+ Assert.assertEquals("Consent denied.", accountFederatedIdentityPage.getError());
+ }
+
+
+ private void loginToAccountManagement(String username) {
+ accountFederatedIdentityPage.realm("realm-with-broker");
+ accountFederatedIdentityPage.open();
+ assertTrue(driver.getTitle().equals("Log in to realm-with-broker"));
+ loginPage.login(username, "password");
+ assertTrue(accountFederatedIdentityPage.isCurrent());
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java
index bc0cb99..14fde53 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/DBLockTest.java
@@ -54,17 +54,17 @@ public class DBLockTest extends AbstractModelTest {
super.before();
// Set timeouts for testing
- DBLockManager lockManager = new DBLockManager();
- DBLockProviderFactory lockFactory = lockManager.getDBLockFactory(session);
+ DBLockManager lockManager = new DBLockManager(session);
+ DBLockProviderFactory lockFactory = lockManager.getDBLockFactory();
lockFactory.setTimeouts(LOCK_RECHECK_MILLIS, LOCK_TIMEOUT_MILLIS);
// Drop lock table, just to simulate racing threads for create lock table and insert lock record into it.
- lockManager.getDBLock(session).destroyLockInfo();
+ lockManager.getDBLock().destroyLockInfo();
commit();
}
- // @Test // TODO: Running -Dtest=DBLockTest,UserModelTest might cause issues sometimes. Reenable this once DB lock is refactored.
+ @Test
public void testLockConcurrently() throws Exception {
long startupTime = System.currentTimeMillis();
@@ -112,7 +112,7 @@ public class DBLockTest extends AbstractModelTest {
}
private void lock(KeycloakSession session, Semaphore semaphore) {
- DBLockProvider dbLock = new DBLockManager().getDBLock(session);
+ DBLockProvider dbLock = new DBLockManager(session).getDBLock();
dbLock.waitForLock();
try {
semaphore.increase();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
index b821331..3077db4 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/ErrorPage.java
@@ -16,8 +16,10 @@
*/
package org.keycloak.testsuite.pages;
+import org.junit.Assert;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.rule.WebResource;
+import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
@@ -32,12 +34,19 @@ public class ErrorPage extends AbstractPage {
@FindBy(className = "instruction")
private WebElement errorMessage;
+ @FindBy(id = "backToApplication")
+ private WebElement backToApplicationLink;
+
public String getError() {
return errorMessage.getText();
}
+ public void clickBackToApplication() {
+ backToApplicationLink.click();
+ }
+
public boolean isCurrent() {
- return driver.getTitle().equals("We're sorry...");
+ return driver.getTitle() != null && driver.getTitle().equals("We're sorry...");
}
@Override
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java
index 301fc86..424705c 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/provider/URLProvider.java
@@ -74,7 +74,7 @@ public class URLProvider extends URLResourceProvider {
}
try {
- if ("true".equals(System.getProperty("app.server.eap6"))) {
+ if ("eap6".equals(System.getProperty("app.server"))) {
if (url == null) {
url = new URL("http://localhost:8080/");
}
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/AbstractPageWithInjectedUrl.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/AbstractPageWithInjectedUrl.java
index fcbdc97..e678db4 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/AbstractPageWithInjectedUrl.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/page/AbstractPageWithInjectedUrl.java
@@ -32,7 +32,7 @@ public abstract class AbstractPageWithInjectedUrl extends AbstractPage {
//EAP6 URL fix
protected URL createInjectedURL(String url) {
- if (System.getProperty("app.server.eap6","false").equals("false")) {
+ if (!System.getProperty("app.server").equals("eap6")) {
return null;
}
try {
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
index 1e708b6..a3badef 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractServletsAdapterTest.java
@@ -48,6 +48,11 @@ public abstract class AbstractServletsAdapterTest extends AbstractAdapterTest {
.addAsWebInfResource(keycloakJSON, "keycloak.json")
.addAsWebInfResource(jbossDeploymentStructure, JBOSS_DEPLOYMENT_STRUCTURE_XML);
+ URL keystore = AbstractServletsAdapterTest.class.getResource(webInfPath + "keystore.jks");
+ if (keystore != null) {
+ deployment.addAsWebInfResource(keystore, "classes/keystore.jks");
+ }
+
addContextXml(deployment, name);
return deployment;
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
index fafbf29..b1df6e0 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractDemoExampleAdapterTest.java
@@ -344,16 +344,8 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
String serverLogPath = null;
- if (System.getProperty("app.server.wildfly", "false").equals("true")) {
- serverLogPath = System.getProperty("app.server.wildfly.home") + "/standalone/log/server.log";
- }
-
- if (System.getProperty("app.server.eap6", "false").equals("true")) {
- serverLogPath = System.getProperty("app.server.eap6.home") + "/standalone/log/server.log";
- }
-
- if (System.getProperty("app.server.eap7", "false").equals("true")) {
- serverLogPath = System.getProperty("app.server.eap7.home") + "/standalone/log/server.log";
+ if (System.getProperty("app.server").equals("wildfly") || System.getProperty("app.server").equals("eap6") || System.getProperty("app.server").equals("eap")) {
+ serverLogPath = System.getProperty("app.server.home") + "/standalone/log/server.log";
}
String appServerUrl;
@@ -364,6 +356,7 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
}
if (serverLogPath != null) {
+ log.info("Checking app server log at: " + serverLogPath);
File serverLog = new File(serverLogPath);
String serverLogContent = FileUtils.readFileToString(serverLog);
UserRepresentation bburke = ApiUtil.findUserByUsername(testRealmResource(), "bburke@redhat.com");
@@ -373,6 +366,8 @@ public abstract class AbstractDemoExampleAdapterTest extends AbstractExampleAdap
assertTrue(matcher.find());
assertTrue(serverLogContent.contains("User '" + bburke.getId() + "' invoking '" + appServerUrl + "database/customers' on client 'database-service'"));
+ } else {
+ log.info("Checking app server log on app-server: \"" + System.getProperty("app.server") + "\" is not supported.");
}
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
index e4c9226..e0d87f7 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/example/AbstractJSConsoleExampleAdapterTest.java
@@ -168,6 +168,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
jsConsoleExamplePage.init();
jsConsoleExamplePage.getProfile();
+ pause(500);
assertTrue(jsConsoleExamplePage.getOutputText().contains("Failed to load profile"));
jsConsoleExamplePage.logIn();
@@ -306,7 +307,7 @@ public abstract class AbstractJSConsoleExampleAdapterTest extends AbstractExampl
logInAndInit("implicit");
- pause(5000);
+ pause(6000);
assertTrue(jsConsoleExamplePage.getEventsText().contains("Access token expired"));
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
index 181d502..ab7b1f5 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoServletsAdapterTest.java
@@ -25,7 +25,6 @@ import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.common.Version;
-import org.keycloak.common.util.Time;
import org.keycloak.constants.AdapterConstants;
import org.keycloak.protocol.oidc.OIDCLoginProtocolService;
import org.keycloak.representations.VersionRepresentation;
@@ -49,6 +48,7 @@ import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlEquals;
import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWithLoginUrlOf;
+import static org.keycloak.testsuite.util.WaitUtils.pause;
/**
*
@@ -226,7 +226,8 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
demoRealmRep.setSsoSessionIdleTimeout(1);
testRealmResource().update(demoRealmRep);
-// Thread.sleep(2000);
+ pause(2000);
+
productPortal.navigateTo();
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
@@ -253,16 +254,16 @@ public abstract class AbstractDemoServletsAdapterTest extends AbstractServletsAd
demoRealmRep.setSsoSessionIdleTimeout(1);
testRealmResource().update(demoRealmRep);
- Time.setOffset(2);
+ pause(2000);
productPortal.navigateTo();
assertCurrentUrlStartsWithLoginUrlOf(testRealmPage);
// need to cleanup so other tests don't fail, so invalidate http sessions on remote clients.
demoRealmRep.setSsoSessionIdleTimeout(originalIdle);
+ testRealmResource().update(demoRealmRep);
// note: sessions invalidated after each test, see: AbstractKeycloakTest.afterAbstractKeycloakTest()
- Time.setOffset(0);
}
@Test
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
index ad3793e..1e2abdb 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java
@@ -37,6 +37,7 @@ import java.io.IOException;
import java.net.URI;
import java.util.LinkedList;
import java.util.List;
+import java.util.Set;
/**
@@ -188,19 +189,6 @@ public class GroupTest extends AbstractGroupTest {
Assert.assertEquals(1, level3Group.getRealmRoles().size());
Assert.assertTrue(level3Group.getRealmRoles().contains("level3Role"));
- try {
- GroupRepresentation notFound = realm.getGroupByPath("/notFound");
- Assert.fail();
- } catch (NotFoundException e) {
-
- }
- try {
- GroupRepresentation notFound = realm.getGroupByPath("/top/notFound");
- Assert.fail();
- } catch (NotFoundException e) {
-
- }
-
UserRepresentation user = realm.users().search("direct-login", -1, -1).get(0);
realm.users().get(user.getId()).joinGroup(level3Group.getId());
List<GroupRepresentation> membership = realm.users().get(user.getId()).groups();
@@ -231,5 +219,27 @@ public class GroupTest extends AbstractGroupTest {
realm.removeDefaultGroup(level3Group.getId());
defaultGroups = realm.getDefaultGroups();
Assert.assertEquals(0, defaultGroups.size());
+
+ realm.groups().group(topGroup.getId()).remove();
+
+ try {
+ realm.getGroupByPath("/top/level2/level3");
+ Assert.fail("Group should not have been found");
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ realm.getGroupByPath("/top/level2");
+ Assert.fail("Group should not have been found");
+ }
+ catch (NotFoundException e) {}
+
+ try {
+ realm.getGroupByPath("/top");
+ Assert.fail("Group should not have been found");
+ }
+ catch (NotFoundException e) {}
+
+ Assert.assertNull(login("direct-login", "resource-owner", "secret", user.getId()).getRealmAccess());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
index 4272025..0a4fe4b 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java
@@ -103,9 +103,9 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
try {
reg.getAdapterConfig(client.getClientId());
- fail("Expected 403");
+ fail("Expected 401");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@@ -115,9 +115,9 @@ public class AdapterInstallationConfigTest extends AbstractClientRegistrationTes
try {
reg.getAdapterConfig(client2.getClientId());
- fail("Expected 403");
+ fail("Expected 401");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
index 70bfed0..0ef5e26 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationTest.java
@@ -126,8 +126,14 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
@Test
public void getClientNotFound() throws ClientRegistrationException {
authManageClients();
+ assertNull(reg.get("invalid"));
+ }
+
+ @Test
+ public void getClientNotFoundNoAccess() throws ClientRegistrationException {
+ authNoAccess();
try {
- reg.get(CLIENT_ID);
+ reg.get("invalid");
fail("Expected 403");
} catch (ClientRegistrationException e) {
assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
@@ -181,10 +187,14 @@ public class ClientRegistrationTest extends AbstractClientRegistrationTest {
public void updateClientNotFound() throws ClientRegistrationException {
authManageClients();
try {
- updateClient();
- fail("Expected 403");
+ ClientRepresentation client = new ClientRepresentation();
+ client.setClientId("invalid");
+
+ reg.update(client);
+
+ fail("Expected 404");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(404, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
index 4f4d204..ad33f52 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/InitialAccessTokenTest.java
@@ -58,7 +58,7 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
try {
reg.create(rep);
} catch (ClientRegistrationException e) {
- Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ Assert.assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@@ -79,7 +79,7 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
try {
reg.create(rep);
} catch (ClientRegistrationException e) {
- Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ Assert.assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@@ -113,7 +113,7 @@ public class InitialAccessTokenTest extends AbstractClientRegistrationTest {
try {
reg.create(rep);
} catch (ClientRegistrationException e) {
- Assert.assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ Assert.assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
index c6fb960..96a0010 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java
@@ -59,8 +59,8 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
try {
reg.get(client.getClientId());
fail("Expected 403");
- } catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ } catch (Exception e) {
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
return null;
@@ -82,9 +82,9 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
reg.auth(Auth.token("invalid"));
try {
reg.get(client.getClientId());
- fail("Expected 403");
+ fail("Expected 401");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
}
@@ -109,9 +109,9 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
reg.auth(Auth.token("invalid"));
try {
reg.update(client);
- fail("Expected 403");
+ fail("Expected 401");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
assertEquals("http://root", getClient(client.getId()).getRootUrl());
@@ -128,9 +128,9 @@ public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest
reg.auth(Auth.token("invalid"));
try {
reg.delete(client);
- fail("Expected 403");
+ fail("Expected 401");
} catch (ClientRegistrationException e) {
- assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
+ assertEquals(401, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode());
}
assertNotNull(getClient(client.getId()));
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
index bb6ce6e..fbe54ce 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/demorealm.json
@@ -174,8 +174,8 @@
"redirectUris": [
"/secure-portal/*"
],
- "attributes": {
- "jwt.credential.certificate": "MIICnTCCAYUCBgFPPLDaTzANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdjbGllbnQxMB4XDTE1MDgxNzE3MjI0N1oXDTI1MDgxNzE3MjQyN1owEjEQMA4GA1UEAwwHY2xpZW50MTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIUjjgv+V3s96O+Za9002Lp/trtGuHBeaeVL9dFKMKzO2MPqdRmHB4PqNlDdd28Rwf5Xn6iWdFpyUKOnI/yXDLhdcuFpR0sMNK/C9Lt+hSpPFLuzDqgtPgDotlMxiHIWDOZ7g9/gPYNXbNvjv8nSiyqoguoCQiiafW90bPHsiVLdP7ZIUwCcfi1qQm7FhxRJ1NiW5dvUkuCnnWEf0XR+Wzc5eC9EgB0taLFiPsSEIlWMm5xlahYyXkPdNOqZjiRnrTWm5Y4uk8ZcsD/KbPTf/7t7cQXipVaswgjdYi1kK2/zRwOhg1QwWFX/qmvdd+fLxV0R6VqRDhn7Qep2cxwMxLsCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAKE6OA46sf20bz8LZPoiNsqRwBUDkaMGXfnob7s/hJZIIwDEx0IAQ3uKsG7q9wb+aA6s+v7S340zb2k3IxuhFaHaZpAd4CyR5cn1FHylbzoZ7rI/3ASqHDqpljdJaFqPH+m7nZWtyDvtZf+gkZ8OjsndwsSBK1d/jMZPp29qYbl1+XfO7RCp/jDqro/R3saYFaIFiEZPeKn1hUJn6BO48vxH1xspSu9FmlvDOEAOz4AuM58z4zRMP49GcFdCWr1wkonJUHaSptJaQwmBwLFUkCbE5I1ixGMb7mjEud6Y5jhfzJiZMo2U8RfcjNbrN0diZl3jB6LQIwESnhYSghaTjNQ=="
+ "attributes" : {
+ "jwt.credential.certificate" : "MIICqTCCAZECBgFT0Ngs/DANBgkqhkiG9w0BAQsFADAYMRYwFAYDVQQDDA1zZWN1cmUtcG9ydGFsMB4XDTE2MDQwMTA4MDA0MVoXDTI2MDQwMTA4MDIyMVowGDEWMBQGA1UEAwwNc2VjdXJlLXBvcnRhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJa4GixpmzP511AmI0eLPLORyJwXS8908MUvdG3hmh8jMOIhe28XjIFeZSY09vFxh22F2SUMjxU/B2Hw4PDJUkebuNR7rXhOIYCJAo6eEZzjSBY/wngFtfm74zJ/eLCobBtDvIld7jobdHTfE1Oz9+GzvtG0k7cm7ubrLT0J4I1UsFZj3b//3wa+O0vNaTwHC1Jz/m59VbtXqyO4xEzIdl416cnGCmEmk5qd5h1de2UoLi/CTad8HftIJhzN1qhlySzW/9Ha70aYlDH2hiibDsXDTrNaMdaaLik7I8Rv/nIbggysG863PKZo8wknDe62QctH5VYSSktiy4gjSJkGh7ECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAZnnx+AHQ8txugGcFK8gWjildDgk+v31fBHBDvmLQaSzsUaIOJaK4wnlwUI+VfR46HmBXhjlDCobFLUptd+kz0G7xapcIn3b5jLrySUUD7L+LAp1vNOQU4mKhTGS3IEvNB73D3GH9rQ+M3KEcoN3f99fNKqKsUdxbmZqGf4VOQ57PUfLBw4PJJGlROPosBc7ivPRyeYnKekhoCTynq30BAD1FA1BA8ppcY4ZVGADPTAgMJxpglpFY9LiqCwdLAGW1ttnsyIJ7DpT+kybhhk7c+MU7gyQdv8xPnMR0bSCB9hndowgBn5oZ393aMscwMNCzwJ0aWBs1sUyn3X0RIsu9Jg=="
}
},
{
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
index 6b8d13f..de290de 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
+++ b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keycloak.json
@@ -1,10 +1,17 @@
{
- "realm" : "demo",
- "resource" : "secure-portal",
- "realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
- "auth-server-url" : "http://localhost:8180/auth",
- "ssl-required" : "external",
- "credentials" : {
- "secret": "password"
- }
+ "realm": "demo",
+ "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+ "auth-server-url": "http://localhost:8180/auth",
+ "ssl-required": "external",
+ "resource": "secure-portal",
+ "credentials": {
+ "jwt": {
+ "client-key-password": "password",
+ "client-keystore-file": "classpath:keystore.jks",
+ "client-keystore-password": "password",
+ "client-key-alias": "secure-portal",
+ "token-timeout": 10,
+ "client-keystore-type": "jks"
+ }
+ }
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keystore.jks b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keystore.jks
new file mode 100644
index 0000000..399be7a
Binary files /dev/null and b/testsuite/integration-arquillian/tests/base/src/test/resources/adapter-test/secure-portal/WEB-INF/keystore.jks differ
diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml
index ae0fa92..913d3fa 100755
--- a/testsuite/integration-arquillian/tests/pom.xml
+++ b/testsuite/integration-arquillian/tests/pom.xml
@@ -753,7 +753,7 @@
<dependency>
<groupId>org.jboss.arquillian.container</groupId>
<artifactId>undertow-embedded</artifactId>
- <version>1.0.0.Alpha1-SNAPSHOT</version>
+ <version>1.0.0.Alpha2</version>
</dependency>
<dependency>
diff --git a/themes/src/main/resources/theme/base/account/messages/messages_en.properties b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
index 5803146..8c0727f 100755
--- a/themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -135,6 +135,8 @@ federatedIdentityRemovingLastProviderMessage=You can''t remove last federated id
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
identityProviderRemovedMessage=Identity provider removed successfully.
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
+staleCodeAccountMessage=The page expired. Please try one more time.
+consentDenied=Consent denied.
accountDisabledMessage=Account is disabled, contact admin.
diff --git a/themes/src/main/resources/theme/base/account/totp.ftl b/themes/src/main/resources/theme/base/account/totp.ftl
index 1cfefba..8ab36fa 100755
--- a/themes/src/main/resources/theme/base/account/totp.ftl
+++ b/themes/src/main/resources/theme/base/account/totp.ftl
@@ -30,7 +30,7 @@
</li>
<li>
<p>${msg("totpStep2")}</p>
- <img src="${totp.totpSecretQrCodeUrl}" alt="Figure: Barcode"><br/>
+ <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
<span class="code">${totp.totpSecretEncoded}</span>
</li>
<li>
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html b/themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
index 6956088..dd333a9 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/create-execution.html
@@ -1,6 +1,8 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
+
<div>
<h1 data-ng-show="parentFlow.providerId == 'basic-flow'">{{:: 'create-authenticator-execution' | translate}}</h1>
+ <h1 data-ng-show="parentFlow.providerId == 'client-flow'">{{:: 'create-authenticator-execution' | translate}}</h1>
<h1 data-ng-show="parentFlow.providerId == 'for-flow'">{{:: 'create-form-action-execution' | translate}}</h1>
</div>
<kc-tabs-authentication></kc-tabs-authentication>
diff --git a/themes/src/main/resources/theme/base/login/error.ftl b/themes/src/main/resources/theme/base/login/error.ftl
index 95de521..c069e26 100755
--- a/themes/src/main/resources/theme/base/login/error.ftl
+++ b/themes/src/main/resources/theme/base/login/error.ftl
@@ -8,7 +8,7 @@
<div id="kc-error-message">
<p class="instruction">${message.summary}</p>
<#if client?? && client.baseUrl?has_content>
- <p><a href="${client.baseUrl}">${msg("backToApplication")}</a></p>
+ <p><a id="backToApplication" href="${client.baseUrl}">${msg("backToApplication")}</a></p>
</#if>
</div>
</#if>
diff --git a/themes/src/main/resources/theme/base/login/login-config-totp.ftl b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
index 649a833..07a8801 100755
--- a/themes/src/main/resources/theme/base/login/login-config-totp.ftl
+++ b/themes/src/main/resources/theme/base/login/login-config-totp.ftl
@@ -34,7 +34,7 @@
</li>
<li>
<p>${msg("loginTotpStep2")}</p>
- <img src="${totp.totpSecretQrCodeUrl}" alt="Figure: Barcode"><br/>
+ <img src="data:image/png;base64, ${totp.totpSecretQrCode}" alt="Figure: Barcode"><br/>
<span class="code">${totp.totpSecretEncoded}</span>
</li>
<li>
diff --git a/themes/src/main/resources/theme/base/login/messages/messages_en.properties b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
index ee8a767..ec02b1c 100755
--- a/themes/src/main/resources/theme/base/login/messages/messages_en.properties
+++ b/themes/src/main/resources/theme/base/login/messages/messages_en.properties
@@ -202,6 +202,7 @@ invalidCodeMessage=An error occurred, please login again through your applicatio
identityProviderUnexpectedErrorMessage=Unexpected error when authenticating with identity provider
identityProviderNotFoundMessage=Could not find an identity provider with the identifier.
identityProviderLinkSuccess=Your account was successfully linked with {0} account {1} .
+staleCodeMessage=This page is no longer valid, please go back to your application and login again
realmSupportsNoCredentialsMessage=Realm does not support any credential type.
identityProviderNotUniqueMessage=Realm supports multiple identity providers. Could not determine which identity provider should be used to authenticate with.
emailVerifiedMessage=Your email address has been verified.
diff --git a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
index c1353d7..be0c31e 100644
--- a/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
+++ b/themes/src/main/resources/theme/keycloak/account/resources/css/account.css
@@ -204,17 +204,20 @@ ol li {
ol li img {
margin-top: 15px;
- width: 180px;
margin-bottom: 5px;
border: 1px solid #eee;
}
ol li span {
- bottom: 80px;
- left: 200px;
+ padding: 15px;
+ background-color: #f5f5f5;
+ border: 1px solid #eee;
+ top: 46px;
+ left: 270px;
+ right: 50px;
position: absolute;
font-family: courier, monospace;
- font-size: 13px;
+ font-size: 25px;
}
hr + .form-horizontal {