diff --git a/client-api/src/main/java/org/keycloak/client/registration/Auth.java b/client-api/src/main/java/org/keycloak/client/registration/Auth.java new file mode 100644 index 0000000000..5b0e85fa8d --- /dev/null +++ b/client-api/src/main/java/org/keycloak/client/registration/Auth.java @@ -0,0 +1,58 @@ +package org.keycloak.client.registration; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpRequest; +import org.keycloak.common.util.Base64; +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * @author Stian Thorgersen + */ +public abstract class Auth { + + public abstract void addAuth(HttpRequest request); + + public static Auth token(String token) { + return new BearerTokenAuth(token); + } + + public static Auth token(ClientRepresentation client) { + return new BearerTokenAuth(client.getRegistrationAccessToken()); + } + + public static Auth client(String clientId, String clientSecret) { + return new BasicAuth(clientId, clientSecret); + } + + private static class BearerTokenAuth extends Auth { + + private String token; + + public BearerTokenAuth(String token) { + this.token = token; + } + + @Override + public void addAuth(HttpRequest request) { + request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token); + } + } + + private static class BasicAuth extends Auth { + + private String username; + private String password; + + public BasicAuth(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public void addAuth(HttpRequest request) { + String val = Base64.encodeBytes((username + ":" + password).getBytes()); + request.setHeader(HttpHeaders.AUTHORIZATION, "Basic " + val); + } + } + +} diff --git a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java index 82a3b3759b..e59de7634c 100644 --- a/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java +++ b/client-api/src/main/java/org/keycloak/client/registration/ClientRegistration.java @@ -1,18 +1,9 @@ package org.keycloak.client.registration; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpPut; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.keycloak.representations.adapters.config.AdapterConfig; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.common.util.Base64; import org.keycloak.util.JsonSerialization; import java.io.IOException; @@ -23,160 +14,58 @@ import java.io.InputStream; */ public class ClientRegistration { - private String clientRegistrationUrl; - private HttpClient httpClient; - private Auth auth; + private final String DEFAULT = "default"; + private final String INSTALLATION = "install"; - public static ClientRegistrationBuilder create() { - return new ClientRegistrationBuilder(); + private HttpUtil httpUtil; + + public ClientRegistration(String authServerUrl, String realm) { + httpUtil = new HttpUtil(HttpClients.createDefault(), HttpUtil.getUrl(authServerUrl, "realms", realm, "clients")); } - private ClientRegistration() { + public ClientRegistration(String authServerUrl, String realm, HttpClient httpClient) { + httpUtil = new HttpUtil(httpClient, HttpUtil.getUrl(authServerUrl, "realms", realm, "clients")); + } + + public void close() throws ClientRegistrationException { + if (httpUtil != null) { + httpUtil.close(); + } + httpUtil = null; + } + + public ClientRegistration auth(Auth auth) { + httpUtil.setAuth(auth); + return this; } public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException { String content = serialize(client); - InputStream resultStream = doPost(content); + InputStream resultStream = httpUtil.doPost(content, DEFAULT); return deserialize(resultStream, ClientRepresentation.class); } - public ClientRepresentation get() throws ClientRegistrationException { - if (auth instanceof ClientIdSecretAuth) { - String clientId = ((ClientIdSecretAuth) auth).clientId; - return get(clientId); - } else { - throw new ClientRegistrationException("Requires client authentication"); - } + public ClientRepresentation get(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(DEFAULT, clientId); + return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; } - public ClientRepresentation get(String clientId) throws ClientRegistrationException { - InputStream resultStream = doGet(clientId); - return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null; + public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException { + InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId); + return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null; } public void update(ClientRepresentation client) throws ClientRegistrationException { String content = serialize(client); - doPut(content, client.getClientId()); + httpUtil.doPut(content, DEFAULT, client.getClientId()); } - public void delete() throws ClientRegistrationException { - if (auth instanceof ClientIdSecretAuth) { - String clientId = ((ClientIdSecretAuth) auth).clientId; - delete(clientId); - } else { - throw new ClientRegistrationException("Requires client authentication"); - } + public void delete(ClientRepresentation client) throws ClientRegistrationException { + delete(client.getClientId()); } public void delete(String clientId) throws ClientRegistrationException { - doDelete(clientId); - } - - public void close() throws ClientRegistrationException { - if (httpClient instanceof CloseableHttpClient) { - try { - ((CloseableHttpClient) httpClient).close(); - } catch (IOException e) { - throw new ClientRegistrationException("Failed to close http client", e); - } - } - } - - private InputStream doPost(String content) throws ClientRegistrationException { - try { - HttpPost request = new HttpPost(clientRegistrationUrl); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - request.setEntity(new StringEntity(content)); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - InputStream responseStream = null; - if (response.getEntity() != null) { - responseStream = response.getEntity().getContent(); - } - - if (response.getStatusLine().getStatusCode() == 201) { - return responseStream; - } else { - responseStream.close(); - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private InputStream doGet(String endpoint) throws ClientRegistrationException { - try { - HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint); - - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - InputStream responseStream = null; - if (response.getEntity() != null) { - responseStream = response.getEntity().getContent(); - } - - if (response.getStatusLine().getStatusCode() == 200) { - return responseStream; - } else if (response.getStatusLine().getStatusCode() == 404) { - responseStream.close(); - return null; - } else { - responseStream.close(); - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private void doPut(String content, String endpoint) throws ClientRegistrationException { - try { - HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setHeader(HttpHeaders.ACCEPT, "application/json"); - request.setEntity(new StringEntity(content)); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - if (response.getEntity() != null) { - response.getEntity().getContent().close(); - } - - if (response.getStatusLine().getStatusCode() != 200) { - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } - } - - private void doDelete(String endpoint) throws ClientRegistrationException { - try { - HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint); - - auth.addAuth(request); - - HttpResponse response = httpClient.execute(request); - if (response.getEntity() != null) { - response.getEntity().getContent().close(); - } - - if (response.getStatusLine().getStatusCode() != 200) { - throw new HttpErrorException(response.getStatusLine()); - } - } catch (IOException e) { - throw new ClientRegistrationException("Failed to send request", e); - } + httpUtil.doDelete(DEFAULT, clientId); } private String serialize(ClientRepresentation client) throws ClientRegistrationException { @@ -195,81 +84,4 @@ public class ClientRegistration { } } - public static class ClientRegistrationBuilder { - - private String realm; - - private String authServerUrl; - - private Auth auth; - - private HttpClient httpClient; - - public ClientRegistrationBuilder realm(String realm) { - this.realm = realm; - return this; - } - public ClientRegistrationBuilder authServerUrl(String authServerUrl) { - this.authServerUrl = authServerUrl; - return this; - } - - public ClientRegistrationBuilder auth(String token) { - this.auth = new TokenAuth(token); - return this; - } - - public ClientRegistrationBuilder auth(String clientId, String clientSecret) { - this.auth = new ClientIdSecretAuth(clientId, clientSecret); - return this; - } - - public ClientRegistrationBuilder httpClient(HttpClient httpClient) { - this.httpClient = httpClient; - return this; - } - - public ClientRegistration build() { - ClientRegistration clientRegistration = new ClientRegistration(); - clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default"; - - clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault(); - clientRegistration.auth = auth; - - return clientRegistration; - } - - } - - public interface Auth { - void addAuth(HttpRequest httpRequest); - } - - public static class AuthorizationHeaderAuth implements Auth { - private String credentials; - - public AuthorizationHeaderAuth(String credentials) { - this.credentials = credentials; - } - - public void addAuth(HttpRequest httpRequest) { - httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials); - } - } - - public static class TokenAuth extends AuthorizationHeaderAuth { - public TokenAuth(String token) { - super("Bearer " + token); - } - } - - public static class ClientIdSecretAuth extends AuthorizationHeaderAuth { - private String clientId; - - public ClientIdSecretAuth(String clientId, String clientSecret) { - super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes())); - this.clientId = clientId; - } - } - } diff --git a/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java new file mode 100644 index 0000000000..699d378020 --- /dev/null +++ b/client-api/src/main/java/org/keycloak/client/registration/HttpUtil.java @@ -0,0 +1,159 @@ +package org.keycloak.client.registration; + +import org.apache.http.HttpHeaders; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.HttpErrorException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Stian Thorgersen + */ +class HttpUtil { + + private HttpClient httpClient; + + private String baseUri; + + private Auth auth; + + HttpUtil(HttpClient httpClient, String baseUri) { + this.httpClient = httpClient; + this.baseUri = baseUri; + } + + void setAuth(Auth auth) { + this.auth = auth; + } + + InputStream doPost(String content, String... path) throws ClientRegistrationException { + try { + HttpPost request = new HttpPost(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + request.setHeader(HttpHeaders.ACCEPT, "application/json"); + request.setEntity(new StringEntity(content)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + InputStream responseStream = null; + if (response.getEntity() != null) { + responseStream = response.getEntity().getContent(); + } + + if (response.getStatusLine().getStatusCode() == 201) { + return responseStream; + } else { + responseStream.close(); + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + InputStream doGet(String... path) throws ClientRegistrationException { + try { + HttpGet request = new HttpGet(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.ACCEPT, "application/json"); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + InputStream responseStream = null; + if (response.getEntity() != null) { + responseStream = response.getEntity().getContent(); + } + + if (response.getStatusLine().getStatusCode() == 200) { + return responseStream; + } else if (response.getStatusLine().getStatusCode() == 404) { + responseStream.close(); + return null; + } else { + responseStream.close(); + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + void doPut(String content, String... path) throws ClientRegistrationException { + try { + HttpPut request = new HttpPut(getUrl(baseUri, path)); + + request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); + request.setHeader(HttpHeaders.ACCEPT, "application/json"); + request.setEntity(new StringEntity(content)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + if (response.getEntity() != null) { + response.getEntity().getContent().close(); + } + + if (response.getStatusLine().getStatusCode() != 200) { + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + void doDelete(String... path) throws ClientRegistrationException { + try { + HttpDelete request = new HttpDelete(getUrl(baseUri, path)); + + addAuth(request); + + HttpResponse response = httpClient.execute(request); + if (response.getEntity() != null) { + response.getEntity().getContent().close(); + } + + if (response.getStatusLine().getStatusCode() != 200) { + throw new HttpErrorException(response.getStatusLine()); + } + } catch (IOException e) { + throw new ClientRegistrationException("Failed to send request", e); + } + } + + void close() throws ClientRegistrationException { + if (httpClient instanceof CloseableHttpClient) { + try { + ((CloseableHttpClient) httpClient).close(); + } catch (IOException e) { + throw new ClientRegistrationException("Failed to close http client", e); + } + } + } + + static String getUrl(String baseUri, String... path) { + StringBuilder s = new StringBuilder(); + s.append(baseUri); + for (String p : path) { + s.append('/'); + s.append(p); + } + return s.toString(); + } + + private void addAuth(HttpRequestBase request) { + if (auth != null) { + auth.addAuth(request); + } + } + +} diff --git a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java index 1ade852c88..bec8acf648 100644 --- a/common/src/main/java/org/keycloak/common/util/ObjectUtil.java +++ b/common/src/main/java/org/keycloak/common/util/ObjectUtil.java @@ -24,4 +24,8 @@ public class ObjectUtil { return str1.equals(str2); } + + public static String capitalize(String str) { + return str.substring(0, 1).toUpperCase() + str.substring(1); + } } diff --git a/connections/file/pom.xml b/connections/file/pom.xml deleted file mode 100755 index a4a749021f..0000000000 --- a/connections/file/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - keycloak-parent - org.keycloak - 1.7.0.Final-SNAPSHOT - ../../pom.xml - - 4.0.0 - - keycloak-connections-file - Keycloak Connections File - - - - - org.keycloak - keycloak-export-import-api - - - org.keycloak - keycloak-export-import-single-file - - - org.keycloak - keycloak-core - - - org.keycloak - keycloak-model-api - - - org.codehaus.jackson - jackson-mapper-asl - provided - - - org.jboss.logging - jboss-logging - - - diff --git a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java b/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java deleted file mode 100644 index b821943901..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProvider.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.file; - -import org.keycloak.models.KeycloakSession; - -/** - * Provides the InMemoryModel and notifies the factory to save it when - * the session is done. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class DefaultFileConnectionProvider implements FileConnectionProvider { - - private final DefaultFileConnectionProviderFactory factory; - private final KeycloakSession session; - private final InMemoryModel inMemoryModel; - - private boolean isRollbackOnly = false; - - public DefaultFileConnectionProvider(DefaultFileConnectionProviderFactory factory, - KeycloakSession session, - InMemoryModel inMemoryModel) { - this.factory = factory; - this.session = session; - this.inMemoryModel = inMemoryModel; - } - - @Override - public InMemoryModel getModel() { - return inMemoryModel; - } - - @Override - public void sessionClosed(KeycloakSession session) { - factory.sessionClosed(session); - } - - @Override - public void close() { - } - - @Override - public void begin() { - } - - @Override - public void commit() { - factory.commit(session); - } - - @Override - public void rollback() { - factory.rollback(session); - } - - @Override - public void setRollbackOnly() { - isRollbackOnly = true; - } - - @Override - public boolean getRollbackOnly() { - return isRollbackOnly; - } - - @Override - public boolean isActive() { - return factory.isActive(session); - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java b/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java deleted file mode 100755 index 9bfe36248c..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/DefaultFileConnectionProviderFactory.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.file; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.codehaus.jackson.JsonToken; -import org.jboss.logging.Logger; -import org.keycloak.Config; -import org.keycloak.exportimport.Strategy; -import org.keycloak.exportimport.util.ExportUtils; -import org.keycloak.exportimport.util.ImportUtils; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.util.JsonSerialization; - -/** - * This class dispenses a FileConnectionProvider to Keycloak sessions. It - * makes sure that only one InMemoryModel is provided for each session and it - * handles thread contention for the file where the model is read or saved. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class DefaultFileConnectionProviderFactory implements FileConnectionProviderFactory { - - protected static final Logger logger = Logger.getLogger(DefaultFileConnectionProviderFactory.class); - - private File kcdata; - private final Map allProviders = new HashMap(); - - @Override - public void init(Config.Scope config) { - String fileName = config.get("fileName"); - if (fileName == null) { - fileName = "keycloak-model.json"; - } - - String directory = config.get("directory"); - if (directory == null) { - directory = System.getProperty("jboss.server.data.dir"); - } - if (directory == null) { - directory = "."; - } - - kcdata = new File(directory, fileName); - } - - public void sessionClosed(KeycloakSession session) { - synchronized(allProviders) { - allProviders.remove(session); - //logger.info("Removed session " + session.hashCode()); - //logger.info("sessionClosed: Session count=" + allModels.size()); - } - } - - void readModelFile(KeycloakSession session) { - synchronized(allProviders) { - if (!kcdata.exists()) { - return; - } - - FileInputStream fis = null; - try { - fis = new FileInputStream(kcdata); - Model model = JsonSerialization.readValue(fis, Model.class); - ImportUtils.importFromStream(session, JsonSerialization.mapper, fis, Strategy.IGNORE_EXISTING); - session.realms().getMigrationModel().setStoredVersion(model.getModelVersion()); - - ImportUtils.importRealms(session, model.getRealms(), Strategy.IGNORE_EXISTING); - } catch (IOException ioe) { - logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe); - } finally { - //logger.info("Read model file for session=" + session.hashCode()); - try { - if (fis != null) { - fis.close(); - } - } catch (IOException e) { - logger.error("Failed to close output stream.", e); - } - } - } - } - - void writeModelFile(KeycloakSession session) { - synchronized(allProviders) { - FileOutputStream outStream = null; - - try { - outStream = new FileOutputStream(kcdata); - exportModel(session, outStream); - } catch (IOException e) { - logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e); - } finally { - //logger.info("Wrote model file for session=" + session.hashCode()); - try { - if (outStream != null) { - outStream.close(); - } - } catch (IOException e) { - logger.error("Failed to close output stream.", e); - } - } - } - } - - private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException { - List realms = session.realms().getRealms(); - List reps = new ArrayList(); - for (RealmModel realm : realms) { - reps.add(ExportUtils.exportRealm(session, realm, true)); - } - Model model = new Model(); - model.setRealms(reps); - model.setModelVersion(session.realms().getMigrationModel().getStoredVersion()); - JsonSerialization.prettyMapper.writeValue(outStream, model); - } - - @Override - public FileConnectionProvider create(KeycloakSession session) { - synchronized (allProviders) { - FileConnectionProvider fcProvider = allProviders.get(session); - if (fcProvider == null) { - InMemoryModel model = new InMemoryModel(); - fcProvider = new DefaultFileConnectionProvider(this, session, model); - allProviders.put(session, fcProvider); - session.getTransaction().enlist(fcProvider); - readModelFile(session); - //logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size()); - } - - return fcProvider; - } - } - - // commitCount is used for debugging. This allows you to easily run a test - // to a particular point and then examine the JSON file. - //private static int commitCount = 0; - void commit(KeycloakSession session) { - //commitCount++; - synchronized (allProviders) { - // in case commit was somehow called twice on the same session - if (!allProviders.containsKey(session)) return; - - try { - writeModelFile(session); - } finally { - allProviders.remove(session); - //logger.info("Removed session " + session.hashCode()); - //logger.info("*** commitCount=" + commitCount); - //logger.info("commit(): Session count=" + allModels.size()); - } - - // if (commitCount == 16) {Thread.dumpStack();System.exit(0);} - } - } - - void rollback(KeycloakSession session) { - synchronized (allProviders) { - allProviders.remove(session); - //logger.info("rollback(): Session count=" + allModels.size()); - } - } - - boolean isActive(KeycloakSession session) { - synchronized (allProviders) { - return allProviders.containsKey(session); - } - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "default"; - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java deleted file mode 100644 index a3ecfeff59..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProvider.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.file; - -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakTransaction; -import org.keycloak.provider.Provider; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public interface FileConnectionProvider extends Provider, KeycloakTransaction { - - InMemoryModel getModel(); - - void sessionClosed(KeycloakSession session); -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java deleted file mode 100644 index 92d161a9c8..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionProviderFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.file; - -import org.keycloak.provider.ProviderFactory; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public interface FileConnectionProviderFactory extends ProviderFactory { -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java b/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java deleted file mode 100644 index 5929a0ba61..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/FileConnectionSpi.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.keycloak.connections.file; - -import org.keycloak.provider.Provider; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.provider.Spi; - -/** - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileConnectionSpi implements Spi { - - @Override - public boolean isInternal() { - return true; - } - - @Override - public String getName() { - return "connectionsFile"; - } - - @Override - public Class getProviderClass() { - return FileConnectionProvider.class; - } - - @Override - public Class getProviderFactoryClass() { - return FileConnectionProviderFactory.class; - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java b/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java deleted file mode 100755 index 2476b44e58..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/InMemoryModel.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.file; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; - -/** - * This class provides an in-memory copy of the entire model for each - * Keycloak session. At the start of the session, the model is read - * from JSON. When the session's transaction ends, the model is written back - * out. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class InMemoryModel { - private final Map allRealms = new HashMap(); - - // realmId, userId, userModel - private final Map> allUsers = new HashMap>(); - - private String modelVersion; - - public InMemoryModel() { - } - - public void putRealm(String id, RealmModel realm) { - allRealms.put(id, realm); - allUsers.put(id, new HashMap()); - } - - public String getModelVersion() { - return modelVersion; - } - - public void setModelVersion(String modelVersion) { - this.modelVersion = modelVersion; - } - - public RealmModel getRealm(String id) { - return allRealms.get(id); - } - - public Collection getRealms() { - return allRealms.values(); - } - - public RealmModel getRealmByName(String name) { - for (RealmModel realm : getRealms()) { - if (realm.getName().equals(name)) return realm; - } - - return null; - } - - public boolean removeRealm(String id) { - allUsers.remove(id); - return (allRealms.remove(id) != null); - } - - protected Map realmUsers(String realmId) { - Map realmUsers = allUsers.get(realmId); - if (realmUsers == null) throw new NullPointerException("Realm users not found for id=" + realmId); - return realmUsers; - } - - public void putUser(String realmId, String userId, UserModel user) { - realmUsers(realmId).put(userId, user); - } - - public UserModel getUser(String realmId, String userId) { - return realmUsers(realmId).get(userId); - } - - public boolean hasUserWithUsername(String realmId, String username) { - for (UserModel user : getUsers(realmId)) { - if (user.getUsername().equals(username)) return true; - } - - return false; - } - - public Collection getUsers(String realmId) { - return realmUsers(realmId).values(); - } - - public boolean removeUser(String realmId, String userId) { - return (realmUsers(realmId).remove(userId) != null); - } - -} diff --git a/connections/file/src/main/java/org/keycloak/connections/file/Model.java b/connections/file/src/main/java/org/keycloak/connections/file/Model.java deleted file mode 100755 index a609cea3f7..0000000000 --- a/connections/file/src/main/java/org/keycloak/connections/file/Model.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.keycloak.connections.file; - -import org.keycloak.representations.idm.RealmRepresentation; - -import java.util.List; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class Model { - private String modelVersion; - private List realms; - - public String getModelVersion() { - return modelVersion; - } - - public void setModelVersion(String modelVersion) { - this.modelVersion = modelVersion; - } - - public List getRealms() { - return realms; - } - - public void setRealms(List realms) { - this.realms = realms; - } -} diff --git a/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory b/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory deleted file mode 100644 index d46ba7fb24..0000000000 --- a/connections/file/src/main/resources/META-INF/services/org.keycloak.connections.file.FileConnectionProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.connections.file.DefaultFileConnectionProviderFactory \ No newline at end of file diff --git a/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi deleted file mode 100644 index b0ddd93218..0000000000 --- a/connections/file/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.connections.file.FileConnectionSpi \ No newline at end of file diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml index 1120252613..aed99fcbe1 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.7.0.xml @@ -58,6 +58,9 @@ + + + \ No newline at end of file diff --git a/connections/pom.xml b/connections/pom.xml index 891cd59c65..e74e99cfaa 100755 --- a/connections/pom.xml +++ b/connections/pom.xml @@ -17,7 +17,6 @@ jpa-liquibase infinispan mongo - file mongo-update http-client diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index 099950512b..514c0fb953 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -19,6 +19,7 @@ public class ClientRepresentation { protected Boolean enabled; protected String clientAuthenticatorType; protected String secret; + protected String registrationAccessToken; protected String[] defaultRoles; protected List redirectUris; protected List webOrigins; @@ -124,6 +125,14 @@ public class ClientRepresentation { this.secret = secret; } + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + public List getRedirectUris() { return redirectUris; } @@ -251,4 +260,5 @@ public class ClientRepresentation { public void setProtocolMappers(List protocolMappers) { this.protocolMappers = protocolMappers; } + } diff --git a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java index cd5292f071..4414f240af 100755 --- a/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/GroupRepresentation.java @@ -14,6 +14,7 @@ import java.util.Map; public class GroupRepresentation { protected String id; protected String name; + protected String path; protected Map> attributes; protected List realmRoles; protected Map> clientRoles; @@ -35,6 +36,14 @@ public class GroupRepresentation { this.name = name; } + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + public List getRealmRoles() { return realmRoles; } diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index 81ce0957c5..ad786a3cc0 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -36,10 +36,6 @@ org.keycloak keycloak-model-jpa - - org.keycloak - keycloak-model-file - org.keycloak keycloak-model-sessions-infinispan diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml deleted file mode 100755 index 5400a883d3..0000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-connections-file/main/module.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml deleted file mode 100755 index 2612e06451..0000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-model-file/main/module.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index c339f44671..7e61fb4bd6 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -8,7 +8,6 @@ - @@ -33,7 +32,6 @@ - @@ -70,4 +68,4 @@ - \ No newline at end of file + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml index 77ce3ad8dc..340c3bff4c 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-services/main/module.xml @@ -18,7 +18,6 @@ - @@ -43,7 +42,6 @@ - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml index 60ccb65be0..9f60836a8f 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml @@ -173,10 +173,6 @@ - - - - @@ -224,11 +220,11 @@ - + - + @@ -250,12 +246,6 @@ - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index c339f44671..7e61fb4bd6 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -8,7 +8,6 @@ - @@ -33,7 +32,6 @@ - @@ -70,4 +68,4 @@ - \ No newline at end of file + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml deleted file mode 100755 index a881b2b672..0000000000 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-connections-file/main/module.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml deleted file mode 100755 index 46f8ffd25d..0000000000 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-model-file/main/module.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml index 44703f8a5d..aa895e8b66 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-services/main/module.xml @@ -18,7 +18,6 @@ - @@ -43,7 +42,6 @@ - diff --git a/docbook/auth-server-docs/pom.xml b/docbook/auth-server-docs/pom.xml index a7c2ddd33f..546b18d5e6 100755 --- a/docbook/auth-server-docs/pom.xml +++ b/docbook/auth-server-docs/pom.xml @@ -114,6 +114,10 @@ picketlink.version ${picketlink.version} + + wildfly.version + ${wildfly.version} + saxon diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml index 10cb89db71..12e2b1ac78 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/auth-spi.xml @@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor +
+ Modifying First Broker Login Flow + + First Broker Login flow is used during first login with some identity provider. Term First Login means that there is not yet existing Keycloak account + linked with the particular authenticated identity provider account. More details about this flow are in the Identity provider chapter. + +
+
Authentication of clients Keycloak actually supports pluggable authentication for OpenID Connect diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml index 7aefdb676d..49d58ad945 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/custom-attributes.xml @@ -8,7 +8,7 @@ - Create a new theme within the themes/admin/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/admin directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -19,15 +19,15 @@ import=common/keycloak ]]> - Copy the file themes/admin/base/resources/partials/user-attribute-entry.html into the - a mirror directory in your theme: themes/admin/mytheme/resources/partials/user-attribute-entry.html. + Copy the file themes/base/admin/resources/partials/user-attributes.html into the + a mirror directory in your theme: themes/mytheme/admin/resources/partials/user-attributes.html. What you are doing here is overriding the user attribute entry page in the admin console and putting in what attributes you want. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead of creating a new theme, you can. - In the user-attribute-entry.html file add your custom user attribute entry form item. For example + In the user-attributes.html file add your custom user attribute entry form item. For example
@@ -52,7 +52,7 @@ import=common/keycloak - Create a new theme within the themes/login/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/login directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -63,8 +63,8 @@ import=common/keycloak styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.css ../patternfly/lib/zocial/zocial.css css/login.css]]> - Copy the file themes/login/base/register.ftl into the - a mirror directory in your theme: themes/login/mytheme/register.ftl. + Copy the file themes/base/login/register.ftl into the + a mirror directory in your theme: themes/mytheme/login/register.ftl. What you are doing here is overriding the registration page and adding what attributes you want. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead @@ -101,7 +101,7 @@ styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login. - Create a new theme within the themes/account/mytheme directory in your distribution. + Create a new theme within the themes/mytheme/account directory in your distribution. Where mytheme is whatever you want to name your theme. @@ -113,8 +113,8 @@ import=common/keycloak styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]> - Copy the file themes/account/base/account.ftl into the - a mirror directory in your theme: themes/account/mytheme/account.ftl. + Copy the file themes/base/account/account.ftl into the + a mirror directory in your theme: themes/mytheme/account/account.ftl. What you are doing here is overriding the profile page and adding what attributes you want to manage. This file already contains an example of entering address data. You can remove this if you want and replace it with something else. Also, if you want to edit this file directly instead diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml index a262b850fe..41c36f0c4f 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/identity-broker.xml @@ -66,7 +66,7 @@ -
+
Overview @@ -127,10 +127,11 @@ Now Keycloak is going to check if the response from the identity provider is valid. If valid, it will create an user - or just skip that if the user already exists. If it is a new user, Keycloak will ask informations about the user to the identity provider + or just skip that if the user already exists. If it is a new user, Keycloak may ask informations about the user to the identity provider (or just read that from a security token) and create the user locally. This is what we call identity federation. - If the user already exists Keycloak will ask him to link the identity returned from the identity provider - with his existing account. A process that we call account linking. + If the user already exists Keycloak may ask him to link the identity returned from the identity provider + with his existing account. A process that we call account linking. What exactly is done is configurable + and can be specified by setup of First Login Flow . At the end of this step, Keycloak authenticates the user and issues its own token in order to access the requested resource in the service provider. @@ -210,7 +211,7 @@ Social providers allows you to enable social authentication to your realm. Keycloak makes it easy to let users log in to your application using an existing account with a social network. - Currently Facebook, Google and Twitter are supported with more planned for the future. + Currently Facebook, Google, Twitter, GitHub, LinkedIn and StackOverflow are supported with more planned for the future. @@ -274,6 +275,15 @@ be used by any other means. + + + Authenticate By Default + + + If enabled, Keycloak will automatically redirect to this identity provider even before displaying login screen. + In other words, steps 3 and 4 from the base flow are skipped. + + Store Tokens @@ -293,20 +303,6 @@ to access any stored external tokens via the broker service. - - - Update Profile on First Login - - - Allows you to force users to update their profile right after the authentication finishes and - before the account is actually created in Keycloak. When "On", users will be always presented with the - update profile page asking for additional information in order to federate their identities. - When "On missing info", users will be presented with the update profile page only if some - mandatory information (email, first name, last name) is not provided by identity provider. - If "Off", the account will be created with the minimal information obtained from the identity provider - during the authentication process. - - Trust email @@ -326,6 +322,16 @@ You can put number into this field, providers with lower numbers are shown first. + + + First Login Flow + + + Alias of authentication flow, which is triggered during first login with this identity provider. Term First Login + means that there is not yet existing Keycloak account linked with the authenticated identity provider account. + More details in First Login section. + + @@ -340,8 +346,8 @@ Forcing users to register to your realm when they want to access applications is hard. So is trying to remember yet another username and password combination. Social identity providers makes it easy for users to register on your realm and quickly sign in using a social network. - Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter and - even Github. + Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter, + Github, LinkedId and StackOverflow.
@@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]>
Automatically Select and Identity Provider - Applications can automatically select an identity provider in order to authenticate an user. In this case, the user will not be presented to the login page but automatically redirected to the identity provider. + Each Identity provider has option Authenticate By Default, which allows that Identity provider is automatically + selected during authentication. User won't even see Keycloak login page, but is automatically redirected to the identity provider. + + + Applications can also automatically select an identity provider in order to authenticate an user. + Selection per application is preferred over Authenticate By Default option if you need more control + on when exactly is Identity provider automatically selected. Keycloak supports a specific HTTP query parameter that you can use as a hint to tell the server which identity provider should be used to authenticate the user. @@ -1283,6 +1295,122 @@ keycloak.createLoginUrl({
+
+ First Login Flow + + When Keycloak successfully authenticates user through identity provider (step 8 in Overview chapter), + there can be two situations: + + + + There is already Keycloak user account linked with the authenticated identity provider account. In this case, + Keycloak will just authenticate as the existing user and redirect back to application (step 9 in Overview chapter). + + + + + There is not yet existing Keycloak user account linked with the identity provider account. This situation is more tricky. + Usually you just want to register new account into Keycloak database, but what if there is existing Keycloak account with same email like the identity provider account? + Automatically link identity provider account with existing Keycloak account is not very good option as there are possible security flaws related to that... + + + + + + Because we had various requirements what to do in second case, we changed the behaviour to be flexible and configurable + through Authentication Flows SPI. In admin console in Identity provider settings, there is option + First Login Flow, which allows you to choose, which workflow will be used after "first login" with this identity provider account. + By default it points to first broker login flow, but you can configure and use your own flow and use different flows for different identity providers etc. + + + The flow itself is configured in admin console under Authentication tab. When you choose First Broker Login flow, + you will see what authenticators are used by default. You can either re-configure existing flow (For example disable some authenticators, + mark some of them as required, configure some authenticators etc). Or you can even create new authentication flow and/or + write your own Authenticator implementations and use it in your flow. See Authentication Flows SPI for more details on how to do it. + + + For First Broker Login case, it might be useful if your Authenticator is subclass of org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator + so you have access to all details about authenticated Identity provider account. But it's not a requirement. + +
+ Default First Login Flow + + Let's describe the default behaviour provided by First Broker Login flow. There are those authenticators: + + + Review Profile + + + This authenticator might display the profile info page, where user can review his profile retrieved from identity provider. + The authenticator is configurable. You can set Update Profile On First Login option. + When On, users will be always presented with the profile page asking for additional information + in order to federate their identities. When missing, users will be presented with + the profile page only if some mandatory information (email, first name, last name) is not provided by identity provider. + If Off, the profile page won't be displayed, unless user clicks in later phase on Review profile info + link (page displayed in later phase by Confirm Link Existing Account authenticator) + + + + + + Create User If Unique + + + This authenticator checks if there is already existing Keycloak account with same email or username like + the account from identity provider. If it's not, then authenticator just creates new Keyclok account and + link it with identity provider and whole flow is finished. Otherwise it goes to the next Handle Existing Account subflow. + If you always want to ensure that there is no duplicated account, you can mark this authenticator as REQUIRED . + In this case, the user will see the error page if there is existing Keycloak account and user needs + to link his identity provider account through Account management. + + + This authenticator also has config option Require Password Update After Registration . + When enabled, user is required to update password after account is created. + + + + + + Confirm Link Existing Account + + + User will see the info page, that there is existing Keycloak account with same email. He can either + review his profile again and use different email or username (flow is restarted and goes back to Review Profile authenticator). + Or he can confirm that he wants to link identity provider account with his existing Keycloak account. + Disable this authenticator if you don't want users to see this confirmation page, but go straight + to linking identity provider account by email verification or re-authentication. + + + + + + Verify Existing Account By Email + + + This authenticator is ALTERNATIVE by default, so it's used only if realm has SMTP setup configured. + It will send mail to user, where he can confirm that he wants to link identity provider with his Keycloak account. + Disable this if you don't want to confirm linking by email, but instead you always want users to reauthenticate with their password (and alternatively OTP). + + + + + + Verify Existing Account By Re-authentication + + + This authenticator is used if email authenticator is disabled or non-available (SMTP not configured for realm). It + will display login screen where user needs to authenticate with his password to link his Keycloak account with Identity provider. + User can also re-authenticate with some different identity provider, which is already linked to his keycloak account. + You can also force users to use OTP, otherwise it's optional and used only if OTP is already set for user account. + + + + + + +
+
+
Examples diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml index 558f943f12..78d9a4b3d6 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml @@ -43,9 +43,9 @@
- Install on existing WildFly 9.0.1.Final + Install on existing WildFly &wildfly.version; - Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download + Keycloak can be installed into an existing WildFly &wildfly.version; server. To do this download keycloak-overlay-&project.version;.zip or keycloak-overlay-&project.version;.tar.gz. Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak run: @@ -62,11 +62,15 @@ To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with the desired server-config. If you are running the server in standalone mode run: - cd <WILDFLY_HOME>/bin - ./jboss-cli.sh -c --file=keycloak-install.cli + +cd <WILDFLY_HOME>/bin +./jboss-cli.sh -c --file=keycloak-install.cli + Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run: - cd <WILDFLY_HOME>/bin - ./jboss-cli.sh -c --file=keycloak-install-ha.cli + +cd <WILDFLY_HOME>/bin +./jboss-cli.sh -c --file=keycloak-install-ha.cli + You may see exceptions in the server log, but after restarting the server they should be gone. You can restart the server with: <WILDFLY_HOME>/bin/jboss-cli.sh -c :reload @@ -75,7 +79,7 @@
Install on existing JBoss EAP 6.4.0.GA - Same procedure as WildFly 9.0.1.Final, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz. + Same procedure as WildFly &wildfly.version;, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz.
@@ -85,7 +89,7 @@ To install it first download keycloak-demo-&project.version;.zip or keycloak-demo-&project.version;.tar.gz. Once downloaded extract it inside keycloak-demo-&project.version; you'll find keycloak which contains - a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find docs + a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find docs and examples which contains everything you need to get started developing applications that use Keycloak. @@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is settings you can specify before boot time. This is configured in the standalone/configuration/keycloak-server.json. By default the setting is like this: - Possible configuration options are: @@ -659,25 +663,25 @@ All configuration options are optional. Default value for directory is to do with the keytool utility that comes with the Java jdk. - - $ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950 - Enter keystore password: secret - Re-enter new password: secret - What is your first and last name? - [Unknown]: localhost - What is the name of your organizational unit? - [Unknown]: Keycloak - What is the name of your organization? - [Unknown]: Red Hat - What is the name of your City or Locality? - [Unknown]: Westford - What is the name of your State or Province? - [Unknown]: MA - What is the two-letter country code for this unit? - [Unknown]: US - Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct? - [no]: yes - + +$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950 + Enter keystore password: secret + Re-enter new password: secret + What is your first and last name? + [Unknown]: localhost + What is the name of your organizational unit? + [Unknown]: Keycloak + What is the name of your organization? + [Unknown]: Red Hat + What is the name of your City or Locality? + [Unknown]: Westford + What is the name of your State or Province? + [Unknown]: MA + What is the two-letter country code for this unit? + [Unknown]: US + Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct? + [no]: yes + You should answer What is your first and last name ? question with @@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is The first thing to do is generate a Certificate Request: - - $ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq - + +$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq + Where yourdomain is a DNS name for which this certificate is generated for. Keytool generates the request: - - -----BEGIN NEW CERTIFICATE REQUEST----- - MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y - ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0 - MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY - Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1 - 29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as - H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw - Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35 - 2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i - n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf - PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ - 9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X - MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S - vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8= - -----END NEW CERTIFICATE REQUEST----- - + +-----BEGIN NEW CERTIFICATE REQUEST----- +MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y +ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY +Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1 +29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as +H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw +Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35 +2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i +n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf +PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ +9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X +MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S +vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8= +-----END NEW CERTIFICATE REQUEST----- + Send this ca request to your CA. The CA will issue you a signed certificate and send it to you. Before you import your new cert, you must obtain and import the root certificate of the CA. You can download the cert from CA (ie.: root.crt) and import as follows: - - $ keytool -import -keystore keycloak.jks -file root.crt -alias root - + +$ keytool -import -keystore keycloak.jks -file root.crt -alias root + Last step is import your new CA generated certificate to your keystore: - - $ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer - + +$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer +
@@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is
To the security-realms element add: - - - - - - - ]]> + + + + + + + +]]> Find the element <server name="default-server"> (it's a child element of <subsystem xmlns="urn:jboss:domain:undertow:1.0">) and add: - - ]]> + ]]> Check the Wildfly Undertow documentation for more information on fine tuning the socket connections. @@ -813,44 +818,20 @@ All configuration options are optional. Default value for directory is
- Adding Keycloak server in Domain Mode + Keycloak server in Domain Mode In domain mode, you start the server with the "domain" command instead of the "standalone" command. In this case, the Keycloak subsystem is defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml. Inside domain.xml, you will see more than one - profile. A Keycloak subsystem can be defined in zero or more of those profiles. + profile. The Keycloak subsystem is defined for all initial profiles. - To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the extensions - element add the Keycloak extension: - - ... - - -]]> - Then you need to add the server to the required server profiles. By default WildFly starts two servers - in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak - subsystem to the profile element with name full: - - ... - - - true - auth - - -]]> + THe server is also added to server profiles. By default two servers are started + in the main-server-group which uses the full profile. - To configure the server copy standalone/configuration/keycloak-server.json to - domain/servers/<SERVER NAME>/configuration. The configuration should be identical + You need to make sure domain/servers/<SERVER NAME>/configuration is identical for all servers in a group. - - Follow the Clustering section of the documentation to configure Keycloak - for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode. - To deploy custom providers and themes you should deploys these as modules and make sure the modules are available to all servers in the group. See Providers and @@ -865,12 +846,12 @@ All configuration options are optional. Default value for directory is To do this, add the default-web-module attribute in the Undertow subystem in standalone.xml. - - - - - + + + + ]]> diff --git a/events/api/src/main/java/org/keycloak/events/Errors.java b/events/api/src/main/java/org/keycloak/events/Errors.java index 34c5979c7c..b0cbc6a3e1 100755 --- a/events/api/src/main/java/org/keycloak/events/Errors.java +++ b/events/api/src/main/java/org/keycloak/events/Errors.java @@ -44,8 +44,7 @@ public interface Errors { String NOT_ALLOWED = "not_allowed"; - String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists"; - String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists"; + String FEDERATED_IDENTITY_EXISTS = "federated_identity_account_exists"; String SSL_REQUIRED = "ssl_required"; String USER_SESSION_NOT_FOUND = "user_session_not_found"; diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index 5cffe7845c..b75728bd15 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -48,6 +48,8 @@ public enum EventType { SEND_VERIFY_EMAIL_ERROR(true), SEND_RESET_PASSWORD(true), SEND_RESET_PASSWORD_ERROR(true), + SEND_IDENTITY_PROVIDER_LINK(true), + SEND_IDENTITY_PROVIDER_LINK_ERROR(true), RESET_PASSWORD(true), RESET_PASSWORD_ERROR(true), @@ -66,8 +68,6 @@ public enum EventType { IDENTITY_PROVIDER_RESPONSE_ERROR(false), IDENTITY_PROVIDER_RETRIEVE_TOKEN(false), IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false), - IDENTITY_PROVIDER_ACCCOUNT_LINKING(false), - IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false), IMPERSONATE(true), CUSTOM_REQUIRED_ACTION(true), CUSTOM_REQUIRED_ACTION_ERROR(true), diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties index 3d2eeea5fa..49801041cc 100755 --- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties @@ -133,6 +133,7 @@ federatedIdentityLinkNotActiveMessage=This identity is not active anymore. federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password. identityProviderRedirectErrorMessage=Failed to redirect to identity provider. identityProviderRemovedMessage=Identity provider removed successfully. +identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user. accountDisabledMessage=Account is disabled, contact admin. diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ebf3a83e46..bcbc98dc2d 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -394,7 +394,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t trust-email=Trust Email trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm. gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page). -first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. +first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account. openid-connect-config=OpenID Connect Config openid-connect-config.tooltip=OIDC SP and external IDP configuration. authorization-url=Authorization URL diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js index 2fd5382eb6..fe74ff27a0 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -447,6 +447,21 @@ module.config([ '$routeProvider', function($routeProvider) { }, controller : 'UserRoleMappingCtrl' }) + .when('/realms/:realm/users/:user/groups', { + templateUrl : resourceUrl + '/partials/user-group-membership.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + user : function(UserLoader) { + return UserLoader(); + }, + groups : function(GroupListLoader) { + return GroupListLoader(); + } + }, + controller : 'UserGroupMembershipCtrl' + }) .when('/realms/:realm/users/:user/sessions', { templateUrl : resourceUrl + '/partials/user-sessions.html', resolve : { @@ -628,9 +643,21 @@ module.config([ '$routeProvider', function($routeProvider) { group : function(GroupLoader) { return GroupLoader(); } - }, + }, controller : 'GroupDetailCtrl' }) + .when('/realms/:realm/groups/:group/members', { + templateUrl : resourceUrl + '/partials/group-members.html', + resolve : { + realm : function(RealmLoader) { + return RealmLoader(); + }, + group : function(GroupLoader) { + return GroupLoader(); + } + }, + controller : 'GroupMembersCtrl' + }) .when('/realms/:realm/groups/:group/role-mappings', { templateUrl : resourceUrl + '/partials/group-role-mappings.html', resolve : { diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index e35e1c62d2..c5f631609b 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -877,7 +877,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se $scope.viewImportDetails = function() { $modal.open({ templateUrl: resourceUrl + '/partials/modal/view-object.html', - controller: 'JsonModalCtrl', + controller: 'ObjectModalCtrl', resolve: { object: function () { return $scope.client; diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js index 7e82f32b36..4e3b98634c 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/groups.js @@ -319,3 +319,50 @@ module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group, }); + +module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) { + $scope.realm = realm; + $scope.page = 0; + + + $scope.query = { + realm: realm.realm, + groupId: group.id, + max : 5, + first : 0 + } + + + $scope.firstPage = function() { + $scope.query.first = 0; + $scope.searchQuery(); + } + + $scope.previousPage = function() { + $scope.query.first -= parseInt($scope.query.max); + if ($scope.query.first < 0) { + $scope.query.first = 0; + } + $scope.searchQuery(); + } + + $scope.nextPage = function() { + $scope.query.first += parseInt($scope.query.max); + $scope.searchQuery(); + } + + $scope.searchQuery = function() { + console.log("query.search: " + $scope.query.search); + $scope.searchLoaded = false; + + $scope.users = GroupMembership.query($scope.query, function() { + console.log('search loaded'); + $scope.searchLoaded = true; + $scope.lastSearch = $scope.query.search; + }); + }; + + $scope.searchQuery(); + +}); + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js index 4f45d63bb3..f42f8aa2fb 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js @@ -1090,3 +1090,64 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov }); +module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, groups, user, UserGroupMembership, UserGroupMapping, Notifications, $location, Dialog) { + $scope.realm = realm; + $scope.user = user; + $scope.groupList = groups; + $scope.selectedGroup = null; + $scope.tree = []; + + UserGroupMembership.query({realm: realm.realm, userId: user.id}, function(data) { + $scope.groupMemberships = data; + + }); + + $scope.joinGroup = function() { + if (!$scope.tree.currentNode) { + Notifications.error('Please select a group to add'); + return; + }; + UserGroupMapping.update({realm: realm.realm, userId: user.id, groupId: $scope.tree.currentNode.id}, function() { + Notifications.success('Added group membership'); + $route.reload(); + }); + + }; + + $scope.leaveGroup = function() { + UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() { + Notifications.success('Removed group membership'); + $route.reload(); + }); + + }; + + var isLeaf = function(node) { + return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0); + }; + + $scope.getGroupClass = function(node) { + if (node.id == "realm") { + return 'pficon pficon-users'; + } + if (isLeaf(node)) { + return 'normal'; + } + if (node.subGroups.length && node.collapsed) return 'collapsed'; + if (node.subGroups.length && !node.collapsed) return 'expanded'; + return 'collapsed'; + + } + + $scope.getSelectedClass = function(node) { + if (node.selected) { + return 'selected'; + } else if ($scope.cutNode && $scope.cutNode.id == node.id) { + return 'cut'; + } + return undefined; + } + +}); + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js index b9e4705c7e..065f831877 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -1512,6 +1512,34 @@ module.factory('GroupCompositeClientRoleMapping', function($resource) { }); }); +module.factory('GroupMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/members', { + realm : '@realm', + groupId : '@groupId' + }); +}); + + +module.factory('UserGroupMembership', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', { + realm : '@realm', + userId : '@userId' + }); +}); + +module.factory('UserGroupMapping', function($resource) { + return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/:groupId', { + realm : '@realm', + userId : '@userId', + groupId : '@groupId' + }, { + update : { + method : 'PUT' + } + }); +}); + + diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html index 4237f7dce4..04c2339238 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-mappers-add.html @@ -37,7 +37,7 @@ {{mapper.name}} {{mapperTypes[mapper.protocolMapper].category}} {{mapperTypes[mapper.protocolMapper].name}} - + {{:: 'no-mappers-available' | translate}} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html new file mode 100755 index 0000000000..6c20930275 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/group-members.html @@ -0,0 +1,50 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
UsernameLast NameFirst NameEmailActions
+
+ + + +
+
{{user.username}}{{user.lastName}}{{user.firstName}}{{user.email}} + +
No group membersNo group members
+
+ + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html index 0161a3011e..8a81237f4d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/required-actions.html @@ -20,8 +20,8 @@ {{requiredAction.name}} - - + + No required actions configured diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html index 7904e56b1c..d81bea7c08 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-mappings.html @@ -94,7 +94,6 @@
- diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html new file mode 100755 index 0000000000..a8e2c131c9 --- /dev/null +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/user-group-membership.html @@ -0,0 +1,85 @@ +
+ + + + +
+
+ + +
+
+
+ + + + + + + + + + + +
+
+ + Groups user is a member of. Select a listed group and click the Leave button to leave the group. + +
+ +
+
+
+ + + +
+
+
+ + + + + + + + + + + +
+ +
+ + Groups a user can join. Select a group and click the join button. + +
+ +
+
+
+
+ +
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html old mode 100644 new mode 100755 index edc3a66481..7508c83a53 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html @@ -10,6 +10,7 @@
  • Attributes
  • Credentials
  • Role Mappings
  • +
  • Groups
  • Consents
  • Sessions
  • Identity Provider Links
  • diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl index 02923d97b5..f77eb38bfa 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-confirm.ftl @@ -12,8 +12,8 @@
    - - + +
    diff --git a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl index ab3e83e091..0ba068603c 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl +++ b/forms/common-themes/src/main/resources/theme/base/login/login-idp-link-email.ftl @@ -5,10 +5,10 @@ <#elseif section = "header"> ${msg("emailLinkIdpTitle", idpAlias)} <#elseif section = "form"> -

    +

    ${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}

    -

    +

    ${msg("emailLinkIdp2")} ${msg("doClickHere")} ${msg("emailLinkIdp3")}

    diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties index 803ef2dcc0..68853082ca 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -185,7 +185,6 @@ resetCredentialNotAllowedMessage=Reset Credential not allowed permissionNotApprovedMessage=Permission not approved. noRelayStateInResponseMessage=No relay state in response from identity provider. -identityProviderAlreadyLinkedMessage=The identity returned by the identity provider is already linked to another user. insufficientPermissionMessage=Insufficient permissions to link identities. couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider. couldNotObtainTokenMessage=Could not obtain token from identity provider. diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java index 3a04fbfb2e..25b0166fd7 100755 --- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java @@ -21,6 +21,7 @@ import javax.mail.internet.MimeMultipart; import org.jboss.logging.Logger; import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.common.util.ObjectUtil; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; import org.keycloak.email.freemarker.beans.EventBean; @@ -89,7 +90,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); - String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1); + String realmName = ObjectUtil.capitalize(realm.getName()); attributes.put("realmName", realmName); send("passwordResetSubject", "password-reset.ftl", attributes); @@ -102,12 +103,12 @@ public class FreeMarkerEmailProvider implements EmailProvider { attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); - String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1); + String realmName = ObjectUtil.capitalize(realm.getName()); attributes.put("realmName", realmName); BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT); String idpAlias = brokerContext.getIdpConfig().getAlias(); - idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1); + idpAlias = ObjectUtil.capitalize(idpAlias); attributes.put("identityProviderContext", brokerContext); attributes.put("identityProviderAlias", idpAlias); @@ -123,7 +124,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); - String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1); + String realmName = ObjectUtil.capitalize(realm.getName()); attributes.put("realmName", realmName); send("executeActionsSubject", "executeActions.ftl", attributes); @@ -137,7 +138,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); - String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1); + String realmName = ObjectUtil.capitalize(realm.getName()); attributes.put("realmName", realmName); send("emailVerificationSubject", "email-verification.ftl", attributes); @@ -253,7 +254,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { private String toCamelCase(EventType event){ StringBuilder sb = new StringBuilder("event"); for(String s : event.name().toString().toLowerCase().split("_")){ - sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1)); + sb.append(ObjectUtil.capitalize(s)); } return sb.toString(); } diff --git a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java index ef8cc74cec..d1b4df9bd6 100755 --- a/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java +++ b/forms/login-freemarker/src/main/java/org/keycloak/login/freemarker/FreeMarkerLoginFormsProvider.java @@ -22,6 +22,7 @@ import org.keycloak.OAuth2Constants; import org.keycloak.authentication.requiredactions.util.UpdateProfileContext; import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext; import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.common.util.ObjectUtil; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; import org.keycloak.freemarker.BrowserSecurityHeaderSetup; @@ -288,7 +289,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { case LOGIN_IDP_LINK_EMAIL: BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT); String idpAlias = brokerContext.getIdpConfig().getAlias(); - idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1); + idpAlias = ObjectUtil.capitalize(idpAlias); attributes.put("brokerContext", brokerContext); attributes.put("idpAlias", idpAlias); @@ -470,7 +471,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider { public Response createIdpLinkEmailPage() { BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT); String idpAlias = brokerContext.getIdpConfig().getAlias(); - idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1); + idpAlias = ObjectUtil.capitalize(idpAlias);; setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias); return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL); diff --git a/integration/jetty/jetty-core/pom.xml b/integration/jetty/jetty-core/pom.xml index f1029a955d..5c0700cf2e 100755 --- a/integration/jetty/jetty-core/pom.xml +++ b/integration/jetty/jetty-core/pom.xml @@ -71,21 +71,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/integration/jetty/jetty8.1/pom.xml b/integration/jetty/jetty8.1/pom.xml index 580f9c0d8b..b7577ec3a4 100755 --- a/integration/jetty/jetty8.1/pom.xml +++ b/integration/jetty/jetty8.1/pom.xml @@ -70,21 +70,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/integration/jetty/jetty9.1/pom.xml b/integration/jetty/jetty9.1/pom.xml index f0c4a5b256..0ad77eaa7a 100755 --- a/integration/jetty/jetty9.1/pom.xml +++ b/integration/jetty/jetty9.1/pom.xml @@ -81,21 +81,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/integration/jetty/jetty9.2/pom.xml b/integration/jetty/jetty9.2/pom.xml index ab82620684..7ae5028261 100755 --- a/integration/jetty/jetty9.2/pom.xml +++ b/integration/jetty/jetty9.2/pom.xml @@ -67,21 +67,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java b/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java index 1aa67f0f70..3be665b912 100644 --- a/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java +++ b/integration/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PaxWebIntegrationService.java @@ -1,9 +1,9 @@ package org.keycloak.adapters.osgi; import java.net.URL; +import java.security.SecureRandom; import java.util.Arrays; import java.util.List; -import java.util.Random; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.util.security.Constraint; @@ -133,7 +133,8 @@ public class PaxWebIntegrationService { Constraint constraint = constraintMapping.getConstraint(); String[] roles = constraint.getRoles(); // name property is unavailable on constraint object :/ - String name = "Constraint-" + new Random().nextInt(); + + String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE); int dataConstraint = constraint.getDataConstraint(); String dataConstraintStr; diff --git a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java index aad0ec5ec5..641ca709ea 100755 --- a/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java +++ b/integration/servlet-adapter-spi/src/main/java/org/keycloak/adapters/servlet/FilterSessionStore.java @@ -90,6 +90,9 @@ public class FilterSessionStore implements AdapterSessionStore { MultivaluedHashMap getParams() { if (parameters != null) return parameters; + + if (body == null) return new MultivaluedHashMap(); + String contentType = getContentType(); contentType = contentType.toLowerCase(); if (contentType.startsWith("application/x-www-form-urlencoded")) { diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java index 8753f45bd0..c421aeab1e 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java @@ -90,6 +90,9 @@ public interface ClientModel extends RoleContainerModel { String getSecret(); public void setSecret(String secret); + String getRegistrationSecret(); + void setRegistrationSecret(String registrationSecret); + boolean isFullScopeAllowed(); void setFullScopeAllowed(boolean value); diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java index cf8bc6dbf5..f430e5f64f 100755 --- a/model/api/src/main/java/org/keycloak/models/KeycloakContext.java +++ b/model/api/src/main/java/org/keycloak/models/KeycloakContext.java @@ -5,6 +5,7 @@ import org.keycloak.models.utils.RealmImporter; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; +import java.net.URI; import java.util.Locale; /** @@ -12,6 +13,8 @@ import java.util.Locale; */ public interface KeycloakContext { + URI getAuthServerUrl(); + String getContextPath(); UriInfo getUri(); diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index aa4f725b09..f15614fe4b 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -17,6 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity { private boolean enabled; private String clientAuthenticatorType; private String secret; + private String registrationSecret; private String protocol; private int notBefore; private boolean publicClient; @@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity { this.secret = secret; } + public String getRegistrationSecret() { + return registrationSecret; + } + + public void setRegistrationSecret(String registrationSecret) { + this.registrationSecret = registrationSecret; + } + public int getNotBefore() { return notBefore; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java index a874f5eecb..40a8999910 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java +++ b/model/api/src/main/java/org/keycloak/models/utils/DefaultAuthenticationFlows.java @@ -1,6 +1,8 @@ package org.keycloak.models.utils; import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; import java.util.Map; import org.keycloak.models.AuthenticationExecutionModel; @@ -25,8 +27,10 @@ public class DefaultAuthenticationFlows { public static final String CLIENT_AUTHENTICATION_FLOW = "clients"; public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login"; + public static final String FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW = "Handle Existing Account"; public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config"; + public static final String IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS = "create unique user config"; public static void addFlows(RealmModel realm) { if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm); @@ -34,7 +38,7 @@ public class DefaultAuthenticationFlows { if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm); if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm); if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm); - if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm); + if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false); } public static void migrateFlows(RealmModel realm) { if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true); @@ -42,7 +46,7 @@ public class DefaultAuthenticationFlows { if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm); if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm); if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm); - if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm); + if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true); } public static void registrationFlow(RealmModel realm) { @@ -320,7 +324,7 @@ public class DefaultAuthenticationFlows { realm.addAuthenticatorExecution(execution); } - public static void firstBrokerLoginFlow(RealmModel realm) { + public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) { AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel(); firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW); firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account"); @@ -347,7 +351,7 @@ public class DefaultAuthenticationFlows { AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel(); - createUserIfUniqueConfig.setAlias("create unique user config"); + createUserIfUniqueConfig.setAlias(IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS); config = new HashMap<>(); config.put("require.password.update.after.registration", "false"); createUserIfUniqueConfig.setConfig(config); @@ -366,7 +370,7 @@ public class DefaultAuthenticationFlows { AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel(); linkExistingAccountFlow.setTopLevel(false); linkExistingAccountFlow.setBuiltIn(true); - linkExistingAccountFlow.setAlias("Handle Existing Account"); + linkExistingAccountFlow.setAlias(FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW); linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider"); linkExistingAccountFlow.setProviderId("basic-flow"); linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow); @@ -421,10 +425,19 @@ public class DefaultAuthenticationFlows { execution = new AuthenticationExecutionModel(); execution.setParentFlow(verifyByReauthenticationAccountFlow.getId()); execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL); - // TODO: read the requirement from browser authenticator -// if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) { -// execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED); -// } + + if (migrate) { + // Try to read OTP requirement from browser flow + AuthenticationFlowModel browserFlow = realm.getBrowserFlow(); + List browserExecutions = new LinkedList<>(); + KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions); + for (AuthenticationExecutionModel browserExecution : browserExecutions) { + if (browserExecution.getAuthenticator().equals("auth-otp-form")) { + execution.setRequirement(browserExecution.getRequirement()); + } + } + } + execution.setAuthenticator("auth-otp-form"); execution.setPriority(20); execution.setAuthenticatorFlow(false); diff --git a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java index b840de6dee..9f5c5deeb6 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java +++ b/model/api/src/main/java/org/keycloak/models/utils/FormMessage.java @@ -23,6 +23,9 @@ public class FormMessage { private String message; private Object[] parameters; + public FormMessage() { + } + /** * Create message. * diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index c2fd73e7d9..ad5997a9a0 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -1,6 +1,8 @@ package org.keycloak.models.utils; import org.bouncycastle.openssl.PEMWriter; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.GroupModel; @@ -16,6 +18,7 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.AuthenticationExecutionRepresentation; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.PemUtils; @@ -31,6 +34,7 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -386,4 +390,24 @@ public final class KeycloakModelUtils { realm.addDefaultRole(Constants.OFFLINE_ACCESS_ROLE); } } + + + /** + * Recursively find all AuthenticationExecutionModel from specified flow or all it's subflows + * + * @param realm + * @param flow + * @param result input should be empty list. At the end will be all executions added to this list + */ + public static void deepFindAuthenticationExecutions(RealmModel realm, AuthenticationFlowModel flow, List result) { + List executions = realm.getAuthenticationExecutions(flow.getId()); + for (AuthenticationExecutionModel execution : executions) { + if (execution.isAuthenticatorFlow()) { + AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId()); + deepFindAuthenticationExecutions(realm, subFlow, result); + } else { + result.add(execution); + } + } + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index c62d96e446..0df3516980 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -11,8 +11,6 @@ import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.ModelException; import org.keycloak.models.OTPPolicy; -import org.keycloak.models.session.PersistentClientSessionModel; -import org.keycloak.models.session.PersistentUserSessionModel; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; @@ -47,7 +45,6 @@ import org.keycloak.representations.idm.UserSessionRepresentation; import org.keycloak.common.util.Time; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -60,10 +57,25 @@ import java.util.Set; * @version $Revision: 1 $ */ public class ModelToRepresentation { + public static void buildGroupPath(StringBuilder sb, GroupModel group) { + if (group.getParent() != null) { + buildGroupPath(sb, group.getParent()); + } + sb.append('/').append(group.getName()); + } + + public static String buildGroupPath(GroupModel group) { + StringBuilder sb = new StringBuilder(); + buildGroupPath(sb, group); + return sb.toString(); + } + + public static GroupRepresentation toRepresentation(GroupModel group, boolean full) { GroupRepresentation rep = new GroupRepresentation(); rep.setId(group.getId()); rep.setName(group.getName()); + rep.setPath(buildGroupPath(group)); if (!full) return rep; // Role mappings Set roles = group.getRoleMappings(); @@ -375,6 +387,7 @@ public class ModelToRepresentation { rep.setNotBefore(clientModel.getNotBefore()); rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout()); rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType()); + rep.setRegistrationAccessToken(clientModel.getRegistrationSecret()); Set redirectUris = clientModel.getRedirectUris(); if (redirectUris != null) { diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index a31d35592b..a46ee57503 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -737,6 +737,8 @@ public class RepresentationToModel { KeycloakModelUtils.generateSecret(client); } + client.setRegistrationSecret(resourceRep.getRegistrationAccessToken()); + if (resourceRep.getAttributes() != null) { for (Map.Entry entry : resourceRep.getAttributes().entrySet()) { client.setAttribute(entry.getKey(), entry.getValue()); @@ -813,6 +815,7 @@ public class RepresentationToModel { if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired()); if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout()); if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType()); + if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken()); resource.updateClient(); if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol()); diff --git a/model/file/pom.xml b/model/file/pom.xml deleted file mode 100755 index bb5333e042..0000000000 --- a/model/file/pom.xml +++ /dev/null @@ -1,62 +0,0 @@ - - - - keycloak-parent - org.keycloak - 1.7.0.Final-SNAPSHOT - ../../pom.xml - - 4.0.0 - - keycloak-model-file - Keycloak Model File - - - - - org.keycloak - keycloak-export-import-api - - - org.keycloak - keycloak-export-import-single-file - - - org.keycloak - keycloak-core - provided - - - org.keycloak - keycloak-model-api - - - org.keycloak - keycloak-connections-file - - - org.codehaus.jackson - jackson-mapper-asl - provided - - - org.jboss.logging - jboss-logging - provided - - - - - - org.apache.maven.plugins - maven-compiler-plugin - - ${maven.compiler.source} - ${maven.compiler.target} - - - - - - diff --git a/model/file/src/main/java/org/keycloak/models/file/FileRealmProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileRealmProvider.java deleted file mode 100755 index 33d4fa36d3..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/FileRealmProvider.java +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file; - -import org.keycloak.connections.file.FileConnectionProvider; -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.migration.MigrationModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.RoleModel; -import org.keycloak.models.entities.RealmEntity; -import org.keycloak.models.file.adapter.MigrationModelAdapter; -import org.keycloak.models.file.adapter.RealmAdapter; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Realm Provider for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileRealmProvider implements RealmProvider { - - private final KeycloakSession session; - private FileConnectionProvider fcProvider; - private final InMemoryModel inMemoryModel; - - public FileRealmProvider(KeycloakSession session, FileConnectionProvider fcProvider) { - this.session = session; - this.fcProvider = fcProvider; - session.enlistForClose(this); - this.inMemoryModel = fcProvider.getModel(); - } - - @Override - public void close() { - fcProvider.sessionClosed(session); - } - - @Override - public MigrationModel getMigrationModel() { - return new MigrationModelAdapter(inMemoryModel); - } - - @Override - public RealmModel createRealm(String name) { - return createRealm(KeycloakModelUtils.generateId(), name); - } - - @Override - public RealmModel createRealm(String id, String name) { - if (getRealmByName(name) != null) throw new ModelDuplicateException("Realm " + name + " already exists."); - RealmEntity realmEntity = new RealmEntity(); - realmEntity.setName(name); - realmEntity.setId(id); - RealmAdapter realm = new RealmAdapter(session, realmEntity, inMemoryModel); - inMemoryModel.putRealm(id, realm); - - return realm; - } - - @Override - public GroupModel getGroupById(String id, RealmModel realm) { - return null; - } - - @Override - public RealmModel getRealm(String id) { - RealmModel model = inMemoryModel.getRealm(id); - return model; - } - - @Override - public List getRealms() { - return new ArrayList(inMemoryModel.getRealms()); - } - - @Override - public RealmModel getRealmByName(String name) { - RealmModel model = inMemoryModel.getRealmByName(name); - return model; - } - - @Override - public boolean removeRealm(String id) { - return inMemoryModel.removeRealm(id); - } - - @Override - public RoleModel getRoleById(String id, RealmModel realm) { - return realm.getRoleById(id); - } - - @Override - public ClientModel getClientById(String id, RealmModel realm) { - return realm.getClientById(id); - } - -} diff --git a/model/file/src/main/java/org/keycloak/models/file/FileRealmProviderFactory.java b/model/file/src/main/java/org/keycloak/models/file/FileRealmProviderFactory.java deleted file mode 100644 index 211d8a5e64..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/FileRealmProviderFactory.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file; - -import org.keycloak.Config; -import org.keycloak.connections.file.FileConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmProvider; -import org.keycloak.models.RealmProviderFactory; - - -/** - * RealmProviderFactory for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileRealmProviderFactory implements RealmProviderFactory { - - @Override - public void init(Config.Scope config) { - - } - - @Override - public String getId() { - return "file"; - } - - @Override - public RealmProvider create(KeycloakSession session) { - FileConnectionProvider fcProvider = session.getProvider(FileConnectionProvider.class); - return new FileRealmProvider(session, fcProvider); - } - - @Override - public void close() { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - -} diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java deleted file mode 100755 index e4b29d3dbf..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/FileUserProvider.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file; - -import org.keycloak.connections.file.FileConnectionProvider; -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.CredentialValidationOutput; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ModelException; -import org.keycloak.models.session.PersistentClientSessionModel; -import org.keycloak.models.session.PersistentUserSessionModel; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserProvider; -import org.keycloak.models.entities.FederatedIdentityEntity; -import org.keycloak.models.entities.PersistentClientSessionEntity; -import org.keycloak.models.entities.PersistentUserSessionEntity; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.models.file.adapter.UserAdapter; -import org.keycloak.models.utils.CredentialValidation; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * UserProvider for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileUserProvider implements UserProvider { - - private final KeycloakSession session; - private FileConnectionProvider fcProvider; - private final InMemoryModel inMemoryModel; - - public FileUserProvider(KeycloakSession session, FileConnectionProvider fcProvider) { - this.session = session; - this.fcProvider = fcProvider; - session.enlistForClose(this); - this.inMemoryModel = fcProvider.getModel(); - } - - @Override - public void close() { - fcProvider.sessionClosed(session); - } - - @Override - public UserModel getUserById(String userId, RealmModel realm) { - return inMemoryModel.getUser(realm.getId(), userId); - } - - @Override - public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - return null; - } - - @Override - public List getGroupMembers(RealmModel realm, GroupModel group) { - return null; - } - - @Override - public void preRemove(RealmModel realm, GroupModel group) { - - } - - @Override - public UserModel getUserByUsername(String username, RealmModel realm) { - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - if (user.getUsername() == null) continue; - if (user.getUsername().equals(username.toLowerCase())) return user; - } - - return null; - } - - @Override - public UserModel getUserByEmail(String email, RealmModel realm) { - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - if (user.getEmail() == null) continue; - if (user.getEmail().equals(email.toLowerCase())) return user; - } - - return null; - } - - @Override - public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) { - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - Set identities = this.getFederatedIdentities(user, realm); - for (FederatedIdentityModel idModel : identities) { - if (idModel.getUserId().equals(socialLink.getUserId())) return user; - } - } - - return null; - } - - @Override - public UserModel getUserByServiceAccountClient(ClientModel client) { - for (UserModel user : inMemoryModel.getUsers(client.getRealm().getId())) { - if (client.getId().equals(user.getServiceAccountClientLink())) { - return user; - } - } - return null; - } - - @Override - public List getUsers(RealmModel realm, boolean includeServiceAccounts) { - return getUsers(realm, -1, -1, includeServiceAccounts); - } - - @Override - public int getUsersCount(RealmModel realm) { - return inMemoryModel.getUsers(realm.getId()).size(); - } - - @Override - public List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { - List users = new ArrayList<>(inMemoryModel.getUsers(realm.getId())); - - if (!includeServiceAccounts) { - users = filterServiceAccountUsers(users); - } - - List sortedList = sortedSubList(users, firstResult, maxResults); - return sortedList; - } - - private List filterServiceAccountUsers(List users) { - List result = new ArrayList<>(); - for (UserModel user : users) { - if (user.getServiceAccountClientLink() == null) { - result.add(user); - } - } - return result; - } - - protected List sortedSubList(List list, int firstResult, int maxResults) { - if (list.isEmpty()) return list; - - Collections.sort(list); - int first = (firstResult <= 0) ? 0 : firstResult; - int last = first + maxResults; // could be int overflow - if ((maxResults > list.size() - first) || (last > list.size())) { // int overflow or regular overflow - last = list.size(); - } - - if (maxResults <= 0) { - last = list.size(); - } - - return list.subList(first, last); - } - - @Override - public List searchForUser(String search, RealmModel realm) { - return searchForUser(search, realm, -1, -1); - } - - @Override - public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { - search = search.trim(); - Pattern caseInsensitivePattern = Pattern.compile("(?i:.*" + search + ".*)", Pattern.CASE_INSENSITIVE); - - int spaceInd = search.lastIndexOf(" "); - boolean isFirstAndLastSearch = spaceInd != -1; - Pattern firstNamePattern = null; - Pattern lastNamePattern = null; - if (isFirstAndLastSearch) { - String firstNamePatternString = search.substring(0, spaceInd); - String lastNamePatternString = search.substring(spaceInd + 1); - firstNamePattern = Pattern.compile("(?i:.*" + firstNamePatternString + ".*$)", Pattern.CASE_INSENSITIVE); - lastNamePattern = Pattern.compile("(?i:^.*" + lastNamePatternString + ".*)", Pattern.CASE_INSENSITIVE); - } - - List found = new ArrayList(); - - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - String firstName = user.getFirstName(); - String lastName = user.getLastName(); - // Case when we have search string like "ohn Bow". Then firstName must end with "ohn" AND lastName must start with "bow" (everything case-insensitive) - if (isFirstAndLastSearch) { - if (isAMatch(firstNamePattern, firstName) && - isAMatch(lastNamePattern, lastName)) { - found.add(user); - continue; - } - } - - if (isAMatch(caseInsensitivePattern, firstName) || - isAMatch(caseInsensitivePattern, lastName) || - isAMatch(caseInsensitivePattern, user.getUsername()) || - isAMatch(caseInsensitivePattern, user.getEmail())) { - found.add(user); - } - } - - // Remove users with service account link - found = filterServiceAccountUsers(found); - - return sortedSubList(found, firstResult, maxResults); - } - - @Override - public List searchForUserByAttributes(Map attributes, RealmModel realm) { - return searchForUserByAttributes(attributes, realm, -1, -1); - } - - protected boolean isAMatch(Pattern pattern, String value) { - return (value != null) && (pattern != null) && pattern.matcher(value).matches(); - } - - @Override - public List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults) { - Pattern usernamePattern = null; - Pattern firstNamePattern = null; - Pattern lastNamePattern = null; - Pattern emailPattern = null; - for (Map.Entry entry : attributes.entrySet()) { - if (entry.getKey().equalsIgnoreCase(UserModel.USERNAME)) { - usernamePattern = Pattern.compile(".*" + entry.getValue() + ".*", Pattern.CASE_INSENSITIVE); - } else if (entry.getKey().equalsIgnoreCase(UserModel.FIRST_NAME)) { - firstNamePattern = Pattern.compile(".*" + entry.getValue() + ".*", Pattern.CASE_INSENSITIVE); - } else if (entry.getKey().equalsIgnoreCase(UserModel.LAST_NAME)) { - lastNamePattern = Pattern.compile(".*" + entry.getValue() + ".*", Pattern.CASE_INSENSITIVE); - } else if (entry.getKey().equalsIgnoreCase(UserModel.EMAIL)) { - emailPattern = Pattern.compile(".*" + entry.getValue() + ".*", Pattern.CASE_INSENSITIVE); - } - } - - List found = new ArrayList(); - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - if (isAMatch(usernamePattern, user.getUsername()) || - isAMatch(firstNamePattern, user.getFirstName()) || - isAMatch(lastNamePattern, user.getLastName()) || - isAMatch(emailPattern, user.getEmail())) { - found.add(user); - } - } - - return sortedSubList(found, firstResult, maxResults); - } - - @Override - public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { - Collection users = inMemoryModel.getUsers(realm.getId()); - - List matchedUsers = new ArrayList<>(); - for (UserModel user : users) { - List vals = user.getAttribute(attrName); - if (vals.contains(attrValue)) { - matchedUsers.add(user); - } - } - - return matchedUsers; - } - - @Override - public Set getFederatedIdentities(UserModel userModel, RealmModel realm) { - UserEntity userEntity = ((UserAdapter)getUserById(userModel.getId(), realm)).getUserEntity(); - List linkEntities = userEntity.getFederatedIdentities(); - - if (linkEntities == null) { - return Collections.EMPTY_SET; - } - - Set result = new HashSet(); - for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) { - FederatedIdentityModel model = new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), - federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName()); - result.add(model); - } - return result; - } - - private FederatedIdentityEntity findSocialLink(UserModel userModel, String socialProvider, RealmModel realm) { - UserModel user = getUserById(userModel.getId(), realm); - UserEntity userEntity = ((UserAdapter)getUserById(userModel.getId(), realm)).getUserEntity(); - List linkEntities = userEntity.getFederatedIdentities(); - if (linkEntities == null) { - return null; - } - - for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) { - if (federatedIdentityEntity.getIdentityProvider().equals(socialProvider)) { - return federatedIdentityEntity; - } - } - return null; - } - - - @Override - public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { - FederatedIdentityEntity federatedIdentityEntity = findSocialLink(user, socialProvider, realm); - return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName()) : null; - } - - @Override - public UserAdapter addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - if (inMemoryModel.hasUserWithUsername(realm.getId(), username.toLowerCase())) - throw new ModelDuplicateException("User with username " + username + " already exists in realm."); - - UserAdapter userModel = addUserEntity(realm, id, username.toLowerCase()); - - if (addDefaultRoles) { - for (String r : realm.getDefaultRoles()) { - userModel.grantRole(realm.getRole(r)); - } - - for (ClientModel application : realm.getClients()) { - for (String r : application.getDefaultRoles()) { - userModel.grantRole(application.getRole(r)); - } - } - } - - if (addDefaultRequiredActions) { - for (RequiredActionProviderModel r : realm.getRequiredActionProviders()) { - if (r.isEnabled() && r.isDefaultAction()) { - userModel.addRequiredAction(r.getAlias()); - } - } - } - - - return userModel; - } - - protected UserAdapter addUserEntity(RealmModel realm, String userId, String username) { - if (realm == null) throw new NullPointerException("realm == null"); - if (username == null) throw new NullPointerException("username == null"); - - if (userId == null) userId = KeycloakModelUtils.generateId(); - - UserEntity userEntity = new UserEntity(); - userEntity.setId(userId); - userEntity.setCreatedTimestamp(System.currentTimeMillis()); - userEntity.setUsername(username); - // Compatibility with JPA model, which has user disabled by default - // userEntity.setEnabled(true); - userEntity.setRealmId(realm.getId()); - - UserAdapter user = new UserAdapter(realm, userEntity, inMemoryModel); - inMemoryModel.putUser(realm.getId(), userId, user); - - return user; - } - - @Override - public boolean removeUser(RealmModel realm, UserModel user) { - return inMemoryModel.removeUser(realm.getId(), user.getId()); - } - - - @Override - public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { - UserAdapter userAdapter = (UserAdapter)getUserById(user.getId(), realm); - UserEntity userEntity = userAdapter.getUserEntity(); - FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity(); - federatedIdentityEntity.setIdentityProvider(socialLink.getIdentityProvider()); - federatedIdentityEntity.setUserId(socialLink.getUserId()); - federatedIdentityEntity.setUserName(socialLink.getUserName().toLowerCase()); - - //check if it already exitsts - do I need to do this? - for (FederatedIdentityEntity fedIdent : userEntity.getFederatedIdentities()) { - if (fedIdent.equals(federatedIdentityEntity)) return; - } - - userEntity.getFederatedIdentities().add(federatedIdentityEntity); - } - - @Override - public boolean removeFederatedIdentity(RealmModel realm, UserModel userModel, String socialProvider) { - UserModel user = getUserById(userModel.getId(), realm); - UserEntity userEntity = ((UserAdapter) user).getUserEntity(); - FederatedIdentityEntity federatedIdentityEntity = findSocialLink(userEntity, socialProvider); - if (federatedIdentityEntity == null) { - return false; - } - - userEntity.getFederatedIdentities().remove(federatedIdentityEntity); - return true; - } - - private FederatedIdentityEntity findSocialLink(UserEntity userEntity, String socialProvider) { - List linkEntities = userEntity.getFederatedIdentities(); - if (linkEntities == null) { - return null; - } - - for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) { - if (federatedIdentityEntity.getIdentityProvider().equals(socialProvider)) { - return federatedIdentityEntity; - } - } - return null; - } - - @Override - public UserModel addUser(RealmModel realm, String username) { - return this.addUser(realm, KeycloakModelUtils.generateId(), username.toLowerCase(), true, true); - } - - @Override - public void grantToAllUsers(RealmModel realm, RoleModel role) { - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - user.grantRole(role); - } - } - - @Override - public void preRemove(RealmModel realm) { - // Nothing to do here? Federation links are attached to users, which are removed by InMemoryModel - } - - @Override - public void preRemove(RealmModel realm, UserFederationProviderModel link) { - Set toBeRemoved = new HashSet(); - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - String fedLink = user.getFederationLink(); - if (fedLink == null) continue; - if (fedLink.equals(link.getId())) toBeRemoved.add(user); - } - - for (UserModel user : toBeRemoved) { - inMemoryModel.removeUser(realm.getId(), user.getId()); - } - } - - @Override - public void preRemove(RealmModel realm, RoleModel role) { - // todo not sure what to do for this - } - - @Override - public void preRemove(RealmModel realm, ClientModel client) { - // TODO - } - - @Override - public void preRemove(ClientModel client, ProtocolMapperModel protocolMapper) { - // TODO - } - - @Override - public boolean validCredentials(RealmModel realm, UserModel user, List input) { - return CredentialValidation.validCredentials(realm, user, input); - } - - @Override - public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) { - return CredentialValidation.validCredentials(realm, user, input); - } - - @Override - public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { - federatedUser = getUserById(federatedUser.getId(), realm); - UserEntity userEntity = ((UserAdapter) federatedUser).getUserEntity(); - FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider()); - - federatedIdentityEntity.setToken(federatedIdentityModel.getToken()); - } - - private FederatedIdentityEntity findFederatedIdentityLink(UserEntity userEntity, String identityProvider) { - List linkEntities = userEntity.getFederatedIdentities(); - if (linkEntities == null) { - return null; - } - - for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) { - if (federatedIdentityEntity.getIdentityProvider().equals(identityProvider)) { - return federatedIdentityEntity; - } - } - return null; - } - - @Override - public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel... input) { - //throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - return null; // not supported yet - } -} diff --git a/model/file/src/main/java/org/keycloak/models/file/FileUserProviderFactory.java b/model/file/src/main/java/org/keycloak/models/file/FileUserProviderFactory.java deleted file mode 100644 index e7f3674229..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/FileUserProviderFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file; - -import org.keycloak.Config; -import org.keycloak.connections.file.FileConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.UserProvider; -import org.keycloak.models.UserProviderFactory; - -/** - * UserProviderFactory for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class FileUserProviderFactory implements UserProviderFactory { - - @Override - public void init(Config.Scope config) { - } - - @Override - public String getId() { - return "file"; - } - - @Override - public UserProvider create(KeycloakSession session) { - FileConnectionProvider fcProvider = session.getProvider(FileConnectionProvider.class); - return new FileUserProvider(session, fcProvider); - } - - @Override - public void close() { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - } - -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java deleted file mode 100755 index 81e60ee295..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java +++ /dev/null @@ -1,663 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file.adapter; - -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.entities.ClientEntity; -import org.keycloak.models.entities.ProtocolMapperEntity; -import org.keycloak.models.entities.RoleEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * ApplicationModel used for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class ClientAdapter implements ClientModel { - - private final RealmModel realm; - private KeycloakSession session; - private final ClientEntity entity; - private final InMemoryModel inMemoryModel; - - private final Map allRoles = new HashMap(); - private final Map allScopeMappings = new HashMap(); - - public ClientAdapter(KeycloakSession session, RealmModel realm, ClientEntity entity, InMemoryModel inMemoryModel) { - this.realm = realm; - this.session = session; - this.entity = entity; - this.inMemoryModel = inMemoryModel; - } - - @Override - public void updateClient() { - } - - @Override - public String getId() { - return entity.getId(); - } - - @Override - public String getName() { - return entity.getName(); - } - - @Override - public void setName(String name) { - entity.setName(name); - } - - @Override - public String getDescription() { return entity.getDescription(); } - - @Override - public void setDescription(String description) { entity.setDescription(description); } - - @Override - public Set getWebOrigins() { - Set result = new HashSet(); - if (entity.getWebOrigins() != null) { - result.addAll(entity.getWebOrigins()); - } - return result; - } - - @Override - public void setWebOrigins(Set webOrigins) { - List result = new ArrayList(); - result.addAll(webOrigins); - entity.setWebOrigins(result); - } - - @Override - public void addWebOrigin(String webOrigin) { - Set webOrigins = getWebOrigins(); - webOrigins.add(webOrigin); - setWebOrigins(webOrigins); - } - - @Override - public void removeWebOrigin(String webOrigin) { - Set webOrigins = getWebOrigins(); - webOrigins.remove(webOrigin); - setWebOrigins(webOrigins); - } - - @Override - public Set getRedirectUris() { - Set result = new HashSet(); - if (entity.getRedirectUris() != null) { - result.addAll(entity.getRedirectUris()); - } - return result; - } - - @Override - public void setRedirectUris(Set redirectUris) { - List result = new ArrayList(); - result.addAll(redirectUris); - entity.setRedirectUris(result); - } - - @Override - public void addRedirectUri(String redirectUri) { - if (entity.getRedirectUris().contains(redirectUri)) return; - entity.getRedirectUris().add(redirectUri); - } - - @Override - public void removeRedirectUri(String redirectUri) { - entity.getRedirectUris().remove(redirectUri); - } - - @Override - public boolean isEnabled() { - return entity.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - entity.setEnabled(enabled); - } - - @Override - public String getClientAuthenticatorType() { - return entity.getClientAuthenticatorType(); - } - - @Override - public void setClientAuthenticatorType(String clientAuthenticatorType) { - entity.setClientAuthenticatorType(clientAuthenticatorType); - } - - @Override - public boolean validateSecret(String secret) { - return secret.equals(entity.getSecret()); - } - - @Override - public String getSecret() { - return entity.getSecret(); - } - - @Override - public void setSecret(String secret) { - entity.setSecret(secret); - } - - @Override - public boolean isPublicClient() { - return entity.isPublicClient(); - } - - @Override - public void setPublicClient(boolean flag) { - entity.setPublicClient(flag); - } - - - @Override - public boolean isFrontchannelLogout() { - return entity.isFrontchannelLogout(); - } - - @Override - public void setFrontchannelLogout(boolean flag) { - entity.setFrontchannelLogout(flag); - } - - @Override - public boolean isFullScopeAllowed() { - return entity.isFullScopeAllowed(); - } - - @Override - public void setFullScopeAllowed(boolean value) { - entity.setFullScopeAllowed(value); - } - - @Override - public RealmModel getRealm() { - return realm; - } - - @Override - public int getNotBefore() { - return entity.getNotBefore(); - } - - @Override - public void setNotBefore(int notBefore) { - entity.setNotBefore(notBefore); - } - - @Override - public Set getScopeMappings() { - return new HashSet(allScopeMappings.values()); - } - - @Override - public Set getRealmScopeMappings() { - Set allScopes = getScopeMappings(); - - Set realmRoles = new HashSet(); - for (RoleModel role : allScopes) { - RoleAdapter roleAdapter = (RoleAdapter)role; - if (roleAdapter.isRealmRole()) { - realmRoles.add(role); - } - } - return realmRoles; - } - - @Override - public void addScopeMapping(RoleModel role) { - allScopeMappings.put(role.getId(), role); - } - - @Override - public void deleteScopeMapping(RoleModel role) { - allScopeMappings.remove(role.getId()); - } - - @Override - public String getProtocol() { - return entity.getProtocol(); - } - - @Override - public void setProtocol(String protocol) { - entity.setProtocol(protocol); - - } - - @Override - public void setAttribute(String name, String value) { - entity.getAttributes().put(name, value); - - } - - @Override - public void removeAttribute(String name) { - entity.getAttributes().remove(name); - } - - @Override - public String getAttribute(String name) { - return entity.getAttributes().get(name); - } - - @Override - public Map getAttributes() { - Map copy = new HashMap(); - copy.putAll(entity.getAttributes()); - return copy; - } - - @Override - public Set getProtocolMappers() { - Set result = new HashSet(); - for (ProtocolMapperEntity entity : this.entity.getProtocolMappers()) { - ProtocolMapperModel model = getProtocolMapperById(entity.getId()); - if (model != null) result.add(model); - } - return result; - } - - @Override - public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) { - if (getProtocolMapperByName(model.getProtocol(), model.getName()) != null) { - throw new RuntimeException("protocol mapper name must be unique per protocol"); - } - ProtocolMapperEntity entity = new ProtocolMapperEntity(); - String id = model.getId() != null ? model.getId() : KeycloakModelUtils.generateId(); - entity.setId(id); - entity.setProtocol(model.getProtocol()); - entity.setName(model.getName()); - entity.setProtocolMapper(model.getProtocolMapper()); - entity.setConfig(model.getConfig()); - entity.setConsentRequired(model.isConsentRequired()); - entity.setConsentText(model.getConsentText()); - this.entity.getProtocolMappers().add(entity); - return entityToModel(entity); - } - - @Override - public void removeProtocolMapper(ProtocolMapperModel mapping) { - ProtocolMapperEntity toBeRemoved = null; - for (ProtocolMapperEntity e : entity.getProtocolMappers()) { - if (e.getId().equals(mapping.getId())) { - toBeRemoved = e; - break; - } - } - - entity.getProtocolMappers().remove(toBeRemoved); - } - - @Override - public void updateProtocolMapper(ProtocolMapperModel mapping) { - ProtocolMapperEntity entity = getProtocolMapperEntityById(mapping.getId()); - entity.setProtocolMapper(mapping.getProtocolMapper()); - entity.setConsentRequired(mapping.isConsentRequired()); - entity.setConsentText(mapping.getConsentText()); - if (entity.getConfig() != null) { - entity.getConfig().clear(); - entity.getConfig().putAll(mapping.getConfig()); - } else { - entity.setConfig(mapping.getConfig()); - } - } - - protected ProtocolMapperEntity getProtocolMapperEntityById(String id) { - for (ProtocolMapperEntity e : entity.getProtocolMappers()) { - if (e.getId().equals(id)) { - return e; - } - } - return null; - } - - protected ProtocolMapperEntity getProtocolMapperEntityByName(String protocol, String name) { - for (ProtocolMapperEntity e : entity.getProtocolMappers()) { - if (e.getProtocol().equals(protocol) && e.getName().equals(name)) { - return e; - } - } - return null; - - } - - @Override - public ProtocolMapperModel getProtocolMapperById(String id) { - ProtocolMapperEntity entity = getProtocolMapperEntityById(id); - if (entity == null) return null; - return entityToModel(entity); - } - - @Override - public ProtocolMapperModel getProtocolMapperByName(String protocol, String name) { - ProtocolMapperEntity entity = getProtocolMapperEntityByName(protocol, name); - if (entity == null) return null; - return entityToModel(entity); - } - - protected ProtocolMapperModel entityToModel(ProtocolMapperEntity entity) { - ProtocolMapperModel mapping = new ProtocolMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setProtocol(entity.getProtocol()); - mapping.setProtocolMapper(entity.getProtocolMapper()); - mapping.setConsentRequired(entity.isConsentRequired()); - mapping.setConsentText(entity.getConsentText()); - Map config = new HashMap(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - mapping.setConfig(config); - return mapping; - } - - @Override - public String getClientId() { - return entity.getClientId(); - } - - @Override - public void setClientId(String clientId) { - if (appNameExists(clientId)) throw new ModelDuplicateException("Application named " + clientId + " already exists."); - entity.setClientId(clientId); - } - - private boolean appNameExists(String name) { - for (ClientModel app : realm.getClients()) { - if (app == this) continue; - if (app.getClientId().equals(name)) return true; - } - - return false; - } - - @Override - public boolean isSurrogateAuthRequired() { - return entity.isSurrogateAuthRequired(); - } - - @Override - public void setSurrogateAuthRequired(boolean surrogateAuthRequired) { - entity.setSurrogateAuthRequired(surrogateAuthRequired); - } - - @Override - public String getManagementUrl() { - return entity.getManagementUrl(); - } - - @Override - public void setManagementUrl(String url) { - entity.setManagementUrl(url); - } - - @Override - public void setRootUrl(String url) { - entity.setRootUrl(url); - } - - @Override - public String getRootUrl() { - return entity.getRootUrl(); - } - - @Override - public void setBaseUrl(String url) { - entity.setBaseUrl(url); - } - - @Override - public String getBaseUrl() { - return entity.getBaseUrl(); - } - - @Override - public boolean isBearerOnly() { - return entity.isBearerOnly(); - } - - @Override - public void setBearerOnly(boolean only) { - entity.setBearerOnly(only); - } - - @Override - public boolean isConsentRequired() { - return entity.isConsentRequired(); - } - - @Override - public void setConsentRequired(boolean consentRequired) { - entity.setConsentRequired(consentRequired); - } - - @Override - public boolean isServiceAccountsEnabled() { - return entity.isServiceAccountsEnabled(); - } - - @Override - public void setServiceAccountsEnabled(boolean serviceAccountsEnabled) { - entity.setServiceAccountsEnabled(serviceAccountsEnabled); - } - - @Override - public boolean isDirectGrantsOnly() { - return entity.isDirectGrantsOnly(); - } - - @Override - public void setDirectGrantsOnly(boolean flag) { - entity.setDirectGrantsOnly(flag); - } - - @Override - public RoleAdapter getRole(String name) { - for (RoleAdapter role : allRoles.values()) { - if (role.getName().equals(name)) return role; - } - return null; - } - - @Override - public RoleAdapter addRole(String name) { - return this.addRole(KeycloakModelUtils.generateId(), name); - } - - @Override - public RoleAdapter addRole(String id, String name) { - if (roleNameExists(name)) throw new ModelDuplicateException("Role named " + name + " already exists."); - RoleEntity roleEntity = new RoleEntity(); - roleEntity.setId(id); - roleEntity.setName(name); - roleEntity.setClientId(getId()); - - RoleAdapter role = new RoleAdapter(getRealm(), roleEntity, this); - allRoles.put(id, role); - - return role; - } - - private boolean roleNameExists(String name) { - for (RoleModel role : allRoles.values()) { - if (role.getName().equals(name)) return true; - } - - return false; - } - - @Override - public boolean removeRole(RoleModel role) { - boolean removed = (allRoles.remove(role.getId()) != null); - - // remove application roles from users - for (UserModel user : inMemoryModel.getUsers(realm.getId())) { - user.deleteRoleMapping(role); - } - - // delete scope mappings from applications - for (ClientModel app : realm.getClients()) { - app.deleteScopeMapping(role); - } - - // remove role from the realm - realm.removeRole(role); - - this.deleteScopeMapping(role); - - return removed; - } - - @Override - public Set getRoles() { - return new HashSet(allRoles.values()); - } - - @Override - public boolean hasScope(RoleModel role) { - if (isFullScopeAllowed()) return true; - Set roles = getScopeMappings(); - if (roles.contains(role)) return true; - - for (RoleModel mapping : roles) { - if (mapping.hasRole(role)) return true; - } - roles = getRoles(); - if (roles.contains(role)) return true; - - for (RoleModel mapping : roles) { - if (mapping.hasRole(role)) return true; - } - return false; - } - - @Override - public Set getClientScopeMappings(ClientModel client) { - Set allScopes = client.getScopeMappings(); - - Set appRoles = new HashSet(); - for (RoleModel role : allScopes) { - RoleAdapter roleAdapter = (RoleAdapter)role; - if (getId().equals(roleAdapter.getRoleEntity().getClientId())) { - appRoles.add(role); - } - } - return appRoles; - } - - @Override - public List getDefaultRoles() { - return entity.getDefaultRoles(); - } - - @Override - public void addDefaultRole(String name) { - RoleModel role = getRole(name); - if (role == null) { - addRole(name); - } - - List defaultRoles = getDefaultRoles(); - if (defaultRoles.contains(name)) return; - - String[] defaultRoleNames = defaultRoles.toArray(new String[defaultRoles.size() + 1]); - defaultRoleNames[defaultRoleNames.length - 1] = name; - updateDefaultRoles(defaultRoleNames); - } - - @Override - public void updateDefaultRoles(String[] defaultRoles) { - List roleNames = new ArrayList(); - for (String roleName : defaultRoles) { - RoleModel role = getRole(roleName); - if (role == null) { - addRole(roleName); - } - - roleNames.add(roleName); - } - - entity.setDefaultRoles(roleNames); - } - - @Override - public int getNodeReRegistrationTimeout() { - return entity.getNodeReRegistrationTimeout(); - } - - @Override - public void setNodeReRegistrationTimeout(int timeout) { - entity.setNodeReRegistrationTimeout(timeout); - } - - @Override - public Map getRegisteredNodes() { - return entity.getRegisteredNodes() == null ? Collections.emptyMap() : Collections.unmodifiableMap(entity.getRegisteredNodes()); - } - - @Override - public void registerNode(String nodeHost, int registrationTime) { - if (entity.getRegisteredNodes() == null) { - entity.setRegisteredNodes(new HashMap()); - } - - entity.getRegisteredNodes().put(nodeHost, registrationTime); - } - - @Override - public void unregisterNode(String nodeHost) { - if (entity.getRegisteredNodes() == null) return; - - entity.getRegisteredNodes().remove(nodeHost); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof ClientModel)) return false; - - ClientModel that = (ClientModel) o; - return that.getId().equals(getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/GroupAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/GroupAdapter.java deleted file mode 100755 index 14f92cb45e..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/GroupAdapter.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.keycloak.models.file.adapter; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.entities.GroupEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * - * @author Marek Posolda - */ -public class GroupAdapter implements GroupModel { - - private final GroupEntity group; - private RealmModel realm; - private KeycloakSession session; - - public GroupAdapter(KeycloakSession session, RealmModel realm, GroupEntity group) { - this.group = group; - this.realm = realm; - this.session = session; - } - - @Override - public String getId() { - return group.getId(); - } - - @Override - public String getName() { - return group.getName(); - } - - @Override - public void setName(String name) { - group.setName(name); - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof GroupModel)) return false; - - GroupModel that = (GroupModel) o; - return that.getId().equals(getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public void setSingleAttribute(String name, String value) { - if (group.getAttributes() == null) { - group.setAttributes(new HashMap>()); - } - - List attrValues = new ArrayList<>(); - attrValues.add(value); - group.getAttributes().put(name, attrValues); - } - - @Override - public void setAttribute(String name, List values) { - if (group.getAttributes() == null) { - group.setAttributes(new HashMap>()); - } - - group.getAttributes().put(name, values); - } - - @Override - public void removeAttribute(String name) { - if (group.getAttributes() == null) return; - - group.getAttributes().remove(name); - } - - @Override - public String getFirstAttribute(String name) { - if (group.getAttributes()==null) return null; - - List attrValues = group.getAttributes().get(name); - return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0); - } - - @Override - public List getAttribute(String name) { - if (group.getAttributes()==null) return Collections.emptyList(); - List attrValues = group.getAttributes().get(name); - return (attrValues == null) ? Collections.emptyList() : Collections.unmodifiableList(attrValues); - } - - @Override - public Map> getAttributes() { - return group.getAttributes()==null ? Collections.>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes()); - } - - @Override - public boolean hasRole(RoleModel role) { - Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); - } - - @Override - public void grantRole(RoleModel role) { - if (group.getRoleIds() == null) { - group.setRoleIds(new LinkedList()); - } - if (group.getRoleIds().contains(role.getId())) { - return; - } - group.getRoleIds().add(role.getId()); - } - - @Override - public Set getRoleMappings() { - if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET; - Set roles = new HashSet<>(); - for (String id : group.getRoleIds()) { - roles.add(realm.getRoleById(id)); - } - return roles; - } - - @Override - public Set getRealmRoleMappings() { - Set allRoles = getRoleMappings(); - - // Filter to retrieve just realm roles - Set realmRoles = new HashSet(); - for (RoleModel role : allRoles) { - if (role.getContainer() instanceof RealmModel) { - realmRoles.add(role); - } - } - return realmRoles; - } - - @Override - public void deleteRoleMapping(RoleModel role) { - if (group == null || role == null) return; - if (group.getRoleIds() == null) return; - group.getRoleIds().remove(role.getId()); - } - - @Override - public Set getClientRoleMappings(ClientModel app) { - Set result = new HashSet(); - Set roles = getRoleMappings(); - - for (RoleModel role : roles) { - if (app.equals(role.getContainer())) { - result.add(role); - } - } - return result; - } - - @Override - public GroupModel getParent() { - if (group.getParentId() == null) return null; - return realm.getGroupById(group.getParentId()); - } - - @Override - public String getParentId() { - return group.getParentId(); - } - - @Override - public Set getSubGroups() { - Set subGroups = new HashSet<>(); - for (GroupModel groupModel : realm.getGroups()) { - if (groupModel.getParent().equals(this)) { - subGroups.add(groupModel); - } - } - return subGroups; - } - - @Override - public void setParent(GroupModel group) { - this.group.setParentId(group.getId()); - - } - - @Override - public void addChild(GroupModel subGroup) { - subGroup.setParent(this); - - } - - @Override - public void removeChild(GroupModel subGroup) { - subGroup.setParent(null); - - } -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/MigrationModelAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/MigrationModelAdapter.java deleted file mode 100755 index 13695db46b..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/MigrationModelAdapter.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.keycloak.models.file.adapter; - -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.migration.MigrationModel; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class MigrationModelAdapter implements MigrationModel { - protected InMemoryModel em; - - public MigrationModelAdapter(InMemoryModel em) { - this.em = em; - } - - @Override - public String getStoredVersion() { - return em.getModelVersion(); - } - - @Override - public void setStoredVersion(String version) { - em.setModelVersion(version); - } -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java deleted file mode 100755 index 643c3c8560..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ /dev/null @@ -1,1840 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file.adapter; - -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.common.enums.SslRequired; -import org.keycloak.models.AuthenticationExecutionModel; -import org.keycloak.models.AuthenticationFlowModel; -import org.keycloak.models.AuthenticatorConfigModel; -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.IdentityProviderMapperModel; -import org.keycloak.models.IdentityProviderModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RequiredActionProviderModel; -import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserFederationMapperEventImpl; -import org.keycloak.models.UserFederationMapperModel; -import org.keycloak.models.UserFederationProviderCreationEventImpl; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.entities.AuthenticationExecutionEntity; -import org.keycloak.models.entities.AuthenticationFlowEntity; -import org.keycloak.models.entities.AuthenticatorConfigEntity; -import org.keycloak.models.entities.ClientEntity; -import org.keycloak.models.entities.IdentityProviderMapperEntity; -import org.keycloak.models.entities.RealmEntity; -import org.keycloak.models.entities.RequiredActionProviderEntity; -import org.keycloak.models.entities.RequiredCredentialEntity; -import org.keycloak.models.entities.RoleEntity; -import org.keycloak.models.entities.UserFederationMapperEntity; -import org.keycloak.models.entities.UserFederationProviderEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.security.Key; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.cert.X509Certificate; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * RealmModel for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class RealmAdapter implements RealmModel { - - private final InMemoryModel inMemoryModel; - private final RealmEntity realm; - - protected volatile transient PublicKey publicKey; - protected volatile transient PrivateKey privateKey; - protected volatile transient X509Certificate certificate; - protected volatile transient Key codeSecretKey; - - private volatile transient PasswordPolicy passwordPolicy; - private volatile transient OTPPolicy otpPolicy; - private volatile transient KeycloakSession session; - - private final Map allApps = new HashMap(); - private ClientModel masterAdminApp = null; - private final Map allRoles = new HashMap(); - private final Map allGroups = new HashMap(); - private final Map allIdProviders = new HashMap(); - - public RealmAdapter(KeycloakSession session, RealmEntity realm, InMemoryModel inMemoryModel) { - this.session = session; - this.realm = realm; - this.inMemoryModel = inMemoryModel; - } - - public RealmEntity getRealmEnity() { - return realm; - } - - @Override - public String getId() { - return realm.getId(); - } - - @Override - public String getName() { - return realm.getName(); - } - - @Override - public void setName(String name) { - if (getName() == null) { - realm.setName(name); - return; - } - - if (getName().equals(name)) return; // allow setting name to same value - - if (inMemoryModel.getRealmByName(name) != null) throw new ModelDuplicateException("Realm " + name + " already exists."); - realm.setName(name); - } - - @Override - public boolean isEnabled() { - return realm.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - realm.setEnabled(enabled); - } - - @Override - public SslRequired getSslRequired() { - return SslRequired.valueOf(realm.getSslRequired()); - } - - @Override - public void setSslRequired(SslRequired sslRequired) { - realm.setSslRequired(sslRequired.name()); - } - - @Override - public boolean isRegistrationAllowed() { - return realm.isRegistrationAllowed(); - } - - @Override - public void setRegistrationAllowed(boolean registrationAllowed) { - realm.setRegistrationAllowed(registrationAllowed); - } - - @Override - public boolean isRegistrationEmailAsUsername() { - return realm.isRegistrationEmailAsUsername(); - } - - @Override - public void setRegistrationEmailAsUsername(boolean registrationEmailAsUsername) { - realm.setRegistrationEmailAsUsername(registrationEmailAsUsername); - } - - @Override - public boolean isRememberMe() { - return realm.isRememberMe(); - } - - @Override - public void setRememberMe(boolean rememberMe) { - realm.setRememberMe(rememberMe); - } - - @Override - public boolean isBruteForceProtected() { - return realm.isBruteForceProtected(); - } - - @Override - public void setBruteForceProtected(boolean value) { - realm.setBruteForceProtected(value); - } - - @Override - public int getMaxFailureWaitSeconds() { - return realm.getMaxFailureWaitSeconds(); - } - - @Override - public void setMaxFailureWaitSeconds(int val) { - realm.setMaxFailureWaitSeconds(val); - } - - @Override - public int getWaitIncrementSeconds() { - return realm.getWaitIncrementSeconds(); - } - - @Override - public void setWaitIncrementSeconds(int val) { - realm.setWaitIncrementSeconds(val); - } - - @Override - public long getQuickLoginCheckMilliSeconds() { - return realm.getQuickLoginCheckMilliSeconds(); - } - - @Override - public void setQuickLoginCheckMilliSeconds(long val) { - realm.setQuickLoginCheckMilliSeconds(val); - } - - @Override - public int getMinimumQuickLoginWaitSeconds() { - return realm.getMinimumQuickLoginWaitSeconds(); - } - - @Override - public void setMinimumQuickLoginWaitSeconds(int val) { - realm.setMinimumQuickLoginWaitSeconds(val); - } - - - @Override - public int getMaxDeltaTimeSeconds() { - return realm.getMaxDeltaTimeSeconds(); - } - - @Override - public void setMaxDeltaTimeSeconds(int val) { - realm.setMaxDeltaTimeSeconds(val); - } - - @Override - public int getFailureFactor() { - return realm.getFailureFactor(); - } - - @Override - public void setFailureFactor(int failureFactor) { - realm.setFailureFactor(failureFactor); - } - - - @Override - public boolean isVerifyEmail() { - return realm.isVerifyEmail(); - } - - @Override - public void setVerifyEmail(boolean verifyEmail) { - realm.setVerifyEmail(verifyEmail); - } - - @Override - public boolean isResetPasswordAllowed() { - return realm.isResetPasswordAllowed(); - } - - @Override - public void setResetPasswordAllowed(boolean resetPassword) { - realm.setResetPasswordAllowed(resetPassword); - } - - @Override - public boolean isEditUsernameAllowed() { - return realm.isEditUsernameAllowed(); - } - - @Override - public void setEditUsernameAllowed(boolean editUsernameAllowed) { - realm.setEditUsernameAllowed(editUsernameAllowed); - } - - @Override - public PasswordPolicy getPasswordPolicy() { - if (passwordPolicy == null) { - passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy()); - } - return passwordPolicy; - } - - @Override - public void setPasswordPolicy(PasswordPolicy policy) { - this.passwordPolicy = policy; - realm.setPasswordPolicy(policy.toString()); - } - - @Override - public OTPPolicy getOTPPolicy() { - if (otpPolicy == null) { - otpPolicy = new OTPPolicy(); - otpPolicy.setDigits(realm.getOtpPolicyDigits()); - otpPolicy.setAlgorithm(realm.getOtpPolicyAlgorithm()); - otpPolicy.setInitialCounter(realm.getOtpPolicyInitialCounter()); - otpPolicy.setLookAheadWindow(realm.getOtpPolicyLookAheadWindow()); - otpPolicy.setType(realm.getOtpPolicyType()); - otpPolicy.setPeriod(realm.getOtpPolicyPeriod()); - } - return otpPolicy; - } - - @Override - public void setOTPPolicy(OTPPolicy policy) { - realm.setOtpPolicyAlgorithm(policy.getAlgorithm()); - realm.setOtpPolicyDigits(policy.getDigits()); - realm.setOtpPolicyInitialCounter(policy.getInitialCounter()); - realm.setOtpPolicyLookAheadWindow(policy.getLookAheadWindow()); - realm.setOtpPolicyType(policy.getType()); - realm.setOtpPolicyPeriod(policy.getPeriod()); - - } - - @Override - public int getNotBefore() { - return realm.getNotBefore(); - } - - @Override - public void setNotBefore(int notBefore) { - realm.setNotBefore(notBefore); - } - - - @Override - public boolean isRevokeRefreshToken() { - return realm.isRevokeRefreshToken(); - } - - @Override - public void setRevokeRefreshToken(boolean revokeRefreshToken) { - realm.setRevokeRefreshToken(revokeRefreshToken); - } - - @Override - public int getSsoSessionIdleTimeout() { - return realm.getSsoSessionIdleTimeout(); - } - - @Override - public void setSsoSessionIdleTimeout(int seconds) { - realm.setSsoSessionIdleTimeout(seconds); - } - - @Override - public int getSsoSessionMaxLifespan() { - return realm.getSsoSessionMaxLifespan(); - } - - @Override - public void setSsoSessionMaxLifespan(int seconds) { - realm.setSsoSessionMaxLifespan(seconds); - } - - @Override - public int getOfflineSessionIdleTimeout() { - return realm.getOfflineSessionIdleTimeout(); - } - - @Override - public void setOfflineSessionIdleTimeout(int seconds) { - realm.setOfflineSessionIdleTimeout(seconds); - } - - @Override - public int getAccessTokenLifespan() { - return realm.getAccessTokenLifespan(); - } - - @Override - public void setAccessTokenLifespan(int tokenLifespan) { - realm.setAccessTokenLifespan(tokenLifespan); - } - - @Override - public int getAccessCodeLifespan() { - return realm.getAccessCodeLifespan(); - } - - @Override - public void setAccessCodeLifespan(int accessCodeLifespan) { - realm.setAccessCodeLifespan(accessCodeLifespan); - } - - @Override - public int getAccessCodeLifespanUserAction() { - return realm.getAccessCodeLifespanUserAction(); - } - - @Override - public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) { - realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction); - } - - @Override - public String getPublicKeyPem() { - return realm.getPublicKeyPem(); - } - - @Override - public void setPublicKeyPem(String publicKeyPem) { - realm.setPublicKeyPem(publicKeyPem); - this.publicKey = null; - } - - @Override - public X509Certificate getCertificate() { - if (certificate != null) return certificate; - certificate = KeycloakModelUtils.getCertificate(getCertificatePem()); - return certificate; - } - - @Override - public void setCertificate(X509Certificate certificate) { - this.certificate = certificate; - String certificatePem = KeycloakModelUtils.getPemFromCertificate(certificate); - setCertificatePem(certificatePem); - } - - @Override - public String getCertificatePem() { - return realm.getCertificatePem(); - } - - @Override - public void setCertificatePem(String certificate) { - realm.setCertificatePem(certificate); - - } - - - @Override - public String getPrivateKeyPem() { - return realm.getPrivateKeyPem(); - } - - @Override - public void setPrivateKeyPem(String privateKeyPem) { - realm.setPrivateKeyPem(privateKeyPem); - this.privateKey = null; - } - - @Override - public PublicKey getPublicKey() { - if (publicKey != null) return publicKey; - publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem()); - return publicKey; - } - - @Override - public void setPublicKey(PublicKey publicKey) { - this.publicKey = publicKey; - String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey); - setPublicKeyPem(publicKeyPem); - } - - @Override - public PrivateKey getPrivateKey() { - if (privateKey != null) return privateKey; - privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem()); - return privateKey; - } - - @Override - public void setPrivateKey(PrivateKey privateKey) { - this.privateKey = privateKey; - String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey); - setPrivateKeyPem(privateKeyPem); - } - - @Override - public String getCodeSecret() { - return realm.getCodeSecret(); - } - - @Override - public Key getCodeSecretKey() { - if (codeSecretKey == null) { - codeSecretKey = KeycloakModelUtils.getSecretKey(getCodeSecret()); - } - return codeSecretKey; - } - - @Override - public void setCodeSecret(String codeSecret) { - realm.setCodeSecret(codeSecret); - } - - @Override - public String getLoginTheme() { - return realm.getLoginTheme(); - } - - @Override - public void setLoginTheme(String name) { - realm.setLoginTheme(name); - } - - @Override - public String getAccountTheme() { - return realm.getAccountTheme(); - } - - @Override - public void setAccountTheme(String name) { - realm.setAccountTheme(name); - } - - @Override - public String getAdminTheme() { - return realm.getAdminTheme(); - } - - @Override - public void setAdminTheme(String name) { - realm.setAdminTheme(name); - } - - @Override - public String getEmailTheme() { - return realm.getEmailTheme(); - } - - @Override - public void setEmailTheme(String name) { - realm.setEmailTheme(name); - } - - @Override - public RoleAdapter getRole(String name) { - for (RoleAdapter role : allRoles.values()) { - if (role.getName().equals(name)) return role; - } - return null; - } - - @Override - public RoleModel addRole(String name) { - return this.addRole(KeycloakModelUtils.generateId(), name); - } - - @Override - public RoleModel addRole(String id, String name) { - if (id == null) throw new NullPointerException("id == null"); - if (name == null) throw new NullPointerException("name == null"); - if (hasRoleWithName(name)) throw new ModelDuplicateException("Realm already contains role with name " + name + "."); - - RoleEntity roleEntity = new RoleEntity(); - roleEntity.setId(id); - roleEntity.setName(name); - roleEntity.setRealmId(getId()); - - RoleAdapter roleModel = new RoleAdapter(this, roleEntity, this); - allRoles.put(id, roleModel); - return roleModel; - } - - @Override - public boolean removeRole(RoleModel role) { - return removeRoleById(role.getId()); - } - - @Override - public boolean removeRoleById(String id) { - if (id == null) throw new NullPointerException("id == null"); - - // try realm roles first - if (allRoles.remove(id) != null) return true; - - for (ClientModel app : getClients()) { - for (RoleModel appRole : app.getRoles()) { - if (id.equals(appRole.getId())) { - app.removeRole(appRole); - return true; - } - } - } - - return false; - } - - @Override - public Set getRoles() { - return new HashSet(allRoles.values()); - } - - @Override - public RoleModel getRoleById(String id) { - RoleModel found = allRoles.get(id); - if (found != null) return found; - - for (ClientModel app : getClients()) { - for (RoleModel appRole : app.getRoles()) { - if (appRole.getId().equals(id)) return appRole; - } - } - - return null; - } - - @Override - public GroupModel getGroupById(String id) { - GroupModel found = allGroups.get(id); - if (found != null) return found; - return null; - } - - @Override - public void moveGroup(GroupModel group, GroupModel toParent) { - if (group.getParentId() != null) { - group.getParent().removeChild(group); - } - group.setParent(toParent); - if (toParent != null) toParent.addChild(group); - else addTopLevelGroup(group); - } - @Override - public List getGroups() { - List list = new LinkedList<>(); - for (GroupAdapter group : allGroups.values()) { - list.add(group); - } - return list; - } - - @Override - public List getTopLevelGroups() { - List list = new LinkedList<>(); - for (GroupAdapter group : allGroups.values()) { - if (group.getParent() == null) list.add(group); - } - return list; - } - - @Override - public boolean removeGroup(GroupModel group) { - return allGroups.remove(group.getId()) != null; - } - - @Override - public List getDefaultRoles() { - return realm.getDefaultRoles(); - } - - @Override - public void addDefaultRole(String name) { - RoleModel role = getRole(name); - if (role == null) { - addRole(name); - } - - List roleNames = getDefaultRoles(); - if (roleNames.contains(name)) throw new IllegalArgumentException("Realm " + realm.getName() + " already contains default role named " + name); - - roleNames.add(name); - realm.setDefaultRoles(roleNames); - } - - boolean hasRoleWithName(String name) { - for (RoleModel role : allRoles.values()) { - if (role.getName().equals(name)) return true; - } - - return false; - } - - @Override - public void updateDefaultRoles(String[] defaultRoles) { - List roleNames = new ArrayList(); - for (String roleName : defaultRoles) { - RoleModel role = getRole(roleName); - if (role == null) { - addRole(roleName); - } - - roleNames.add(roleName); - } - - realm.setDefaultRoles(roleNames); - } - - @Override - public ClientModel getClientById(String id) { - return allApps.get(id); - } - - @Override - public ClientModel getClientByClientId(String clientId) { - for (ClientModel app : getClients()) { - if (app.getClientId().equals(clientId)) return app; - } - - return null; - } - - @Override - public Map getClientNameMap() { - Map resourceMap = new HashMap(); - for (ClientModel resource : getClients()) { - resourceMap.put(resource.getClientId(), resource); - } - return resourceMap; - } - - @Override - public List getClients() { - return new ArrayList(allApps.values()); - } - - @Override - public ClientModel addClient(String name) { - return this.addClient(KeycloakModelUtils.generateId(), name); - } - - @Override - public ClientModel addClient(String id, String clientId) { - if (clientId == null) throw new NullPointerException("name == null"); - if (id == null) throw new NullPointerException("id == null"); - - if (getClientNameMap().containsKey(clientId)) { - throw new ModelDuplicateException("Application named '" + clientId + "' already exists."); - } - - ClientEntity appEntity = new ClientEntity(); - appEntity.setId(id); - appEntity.setClientId(clientId); - appEntity.setRealmId(getId()); - appEntity.setEnabled(true); - - final ClientModel app = new ClientAdapter(session, this, appEntity, inMemoryModel); - session.getKeycloakSessionFactory().publish(new ClientCreationEvent() { - @Override - public ClientModel getCreatedClient() { - return app; - } - }); - - allApps.put(id, app); - - return app; - } - - @Override - public boolean removeClient(String id) { - ClientModel appToBeRemoved = this.getClientById(id); - if (appToBeRemoved == null) return false; - - // remove any composite role assignments for this app - for (RoleModel role : this.getRoles()) { - RoleAdapter roleAdapter = (RoleAdapter)role; - roleAdapter.removeApplicationComposites(id); - } - - for (RoleModel role : appToBeRemoved.getRoles()) { - appToBeRemoved.removeRole(role); - } - - return (allApps.remove(id) != null); - } - - boolean hasUserWithEmail(String email) { - for (UserModel user : inMemoryModel.getUsers(getId())) { - if (user.getEmail() == null) continue; - if (user.getEmail().equals(email)) return true; - } - - return false; - } - - @Override - public void addRequiredCredential(String type) { - if (type == null) throw new NullPointerException("Credential type can not be null"); - - RequiredCredentialModel credentialModel = initRequiredCredentialModel(type); - - List requiredCredList = realm.getRequiredCredentials(); - for (RequiredCredentialEntity cred : requiredCredList) { - if (type.equals(cred.getType())) return; - } - - addRequiredCredential(credentialModel, requiredCredList); - } - - protected void addRequiredCredential(RequiredCredentialModel credentialModel, List persistentCollection) { - RequiredCredentialEntity credEntity = new RequiredCredentialEntity(); - credEntity.setType(credentialModel.getType()); - credEntity.setFormLabel(credentialModel.getFormLabel()); - credEntity.setInput(credentialModel.isInput()); - credEntity.setSecret(credentialModel.isSecret()); - - persistentCollection.add(credEntity); - } - - @Override - public void updateRequiredCredentials(Set creds) { - updateRequiredCredentials(creds, realm.getRequiredCredentials()); - } - - protected void updateRequiredCredentials(Set creds, List credsEntities) { - Set already = new HashSet(); - Set toRemove = new HashSet(); - for (RequiredCredentialEntity entity : credsEntities) { - if (!creds.contains(entity.getType())) { - toRemove.add(entity); - } else { - already.add(entity.getType()); - } - } - for (RequiredCredentialEntity entity : toRemove) { - credsEntities.remove(entity); - } - for (String cred : creds) { - if (!already.contains(cred)) { - RequiredCredentialModel credentialModel = initRequiredCredentialModel(cred); - addRequiredCredential(credentialModel, credsEntities); - } - } - } - - @Override - public List getRequiredCredentials() { - return convertRequiredCredentialEntities(realm.getRequiredCredentials()); - } - - protected List convertRequiredCredentialEntities(Collection credEntities) { - - List result = new ArrayList(); - for (RequiredCredentialEntity entity : credEntities) { - RequiredCredentialModel credentialModel = new RequiredCredentialModel(); - credentialModel.setFormLabel(entity.getFormLabel()); - credentialModel.setInput(entity.isInput()); - credentialModel.setSecret(entity.isSecret()); - credentialModel.setType(entity.getType()); - - result.add(credentialModel); - } - return result; - } - - protected RequiredCredentialModel initRequiredCredentialModel(String type) { - RequiredCredentialModel credentialModel = RequiredCredentialModel.BUILT_IN.get(type); - if (credentialModel == null) { - throw new RuntimeException("Unknown credential type " + type); - } - return credentialModel; - } - - @Override - public Map getBrowserSecurityHeaders() { - return realm.getBrowserSecurityHeaders(); - } - - @Override - public void setBrowserSecurityHeaders(Map headers) { - realm.setBrowserSecurityHeaders(headers); - } - - @Override - public Map getSmtpConfig() { - return realm.getSmtpConfig(); - } - - @Override - public void setSmtpConfig(Map smtpConfig) { - realm.setSmtpConfig(smtpConfig); - } - - @Override - public List getIdentityProviders() { - return new ArrayList(allIdProviders.values()); - } - - @Override - public IdentityProviderModel getIdentityProviderByAlias(String alias) { - for (IdentityProviderModel identityProviderModel : getIdentityProviders()) { - if (identityProviderModel.getAlias().equals(alias)) { - return identityProviderModel; - } - } - - return null; - } - - @Override - public void addIdentityProvider(IdentityProviderModel identityProvider) { - if (identityProvider.getAlias() == null) throw new NullPointerException("identityProvider.getAlias() == null"); - if (identityProvider.getInternalId() == null) identityProvider.setInternalId(KeycloakModelUtils.generateId()); - allIdProviders.put(identityProvider.getInternalId(), identityProvider); - } - - @Override - public void removeIdentityProviderByAlias(String alias) { - for (IdentityProviderModel provider : getIdentityProviders()) { - if (provider.getAlias().equals(alias)) { - allIdProviders.remove(provider.getInternalId()); - break; - } - } - } - - @Override - public void updateIdentityProvider(IdentityProviderModel identityProvider) { - removeIdentityProviderByAlias(identityProvider.getAlias()); - addIdentityProvider(identityProvider); - } - - @Override - public UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) { - KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders()); - - UserFederationProviderEntity entity = new UserFederationProviderEntity(); - entity.setId(KeycloakModelUtils.generateId()); - entity.setPriority(priority); - entity.setProviderName(providerName); - entity.setConfig(config); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - entity.setFullSyncPeriod(fullSyncPeriod); - entity.setChangedSyncPeriod(changedSyncPeriod); - entity.setLastSync(lastSync); - realm.getUserFederationProviders().add(entity); - - UserFederationProviderModel providerModel = new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync); - - session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, providerModel)); - - return providerModel; - } - - @Override - public void removeUserFederationProvider(UserFederationProviderModel provider) { - Iterator it = realm.getUserFederationProviders().iterator(); - while (it.hasNext()) { - UserFederationProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(), - entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync())); - - Set mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId()); - for (UserFederationMapperEntity mapper : mappers) { - realm.getUserFederationMappers().remove(mapper); - } - - it.remove(); - } - } - } - - @Override - public void updateUserFederationProvider(UserFederationProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); - - Iterator it = realm.getUserFederationProviders().iterator(); - while (it.hasNext()) { - UserFederationProviderEntity entity = it.next(); - if (entity.getId().equals(model.getId())) { - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(model.getDisplayName()); - } - entity.setFullSyncPeriod(model.getFullSyncPeriod()); - entity.setChangedSyncPeriod(model.getChangedSyncPeriod()); - entity.setLastSync(model.getLastSync()); - } - } - } - - @Override - public List getUserFederationProviders() { - List entities = realm.getUserFederationProviders(); - List copy = new LinkedList(); - for (UserFederationProviderEntity entity : entities) { - copy.add(entity); - - } - Collections.sort(copy, new Comparator() { - - @Override - public int compare(UserFederationProviderEntity o1, UserFederationProviderEntity o2) { - return o1.getPriority() - o2.getPriority(); - } - - }); - List result = new LinkedList(); - for (UserFederationProviderEntity entity : copy) { - result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(), - entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync())); - } - - return result; - } - - @Override - public void setUserFederationProviders(List providers) { - for (UserFederationProviderModel currentProvider : providers) { - KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); - } - - List entities = new LinkedList(); - for (UserFederationProviderModel model : providers) { - UserFederationProviderEntity entity = new UserFederationProviderEntity(); - if (model.getId() != null) { - entity.setId(model.getId()); - } else { - String id = KeycloakModelUtils.generateId(); - entity.setId(id); - model.setId(id); - } - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName == null) { - entity.setDisplayName(entity.getId()); - } - entity.setDisplayName(displayName); - entity.setFullSyncPeriod(model.getFullSyncPeriod()); - entity.setChangedSyncPeriod(model.getChangedSyncPeriod()); - entity.setLastSync(model.getLastSync()); - entities.add(entity); - - session.getKeycloakSessionFactory().publish(new UserFederationProviderCreationEventImpl(this, model)); - } - - realm.setUserFederationProviders(entities); - } - - @Override - public boolean isEventsEnabled() { - return realm.isEventsEnabled(); - } - - @Override - public void setEventsEnabled(boolean enabled) { - realm.setEventsEnabled(enabled); - } - - @Override - public long getEventsExpiration() { - return realm.getEventsExpiration(); - } - - @Override - public void setEventsExpiration(long expiration) { - realm.setEventsExpiration(expiration); - } - - @Override - public Set getEventsListeners() { - return new HashSet(realm.getEventsListeners()); - } - - @Override - public void setEventsListeners(Set listeners) { - if (listeners != null) { - realm.setEventsListeners(new ArrayList(listeners)); - } else { - realm.setEventsListeners(Collections.EMPTY_LIST); - } - } - - @Override - public Set getEnabledEventTypes() { - return new HashSet(realm.getEnabledEventTypes()); - } - - @Override - public void setEnabledEventTypes(Set enabledEventTypes) { - if (enabledEventTypes != null) { - realm.setEnabledEventTypes(new ArrayList(enabledEventTypes)); - } else { - realm.setEnabledEventTypes(Collections.EMPTY_LIST); - } - } - - @Override - public boolean isAdminEventsEnabled() { - return realm.isAdminEventsEnabled(); - } - - @Override - public void setAdminEventsEnabled(boolean enabled) { - realm.setAdminEventsEnabled(enabled); - } - - @Override - public boolean isAdminEventsDetailsEnabled() { - return realm.isAdminEventsDetailsEnabled(); - } - - @Override - public void setAdminEventsDetailsEnabled(boolean enabled) { - realm.setAdminEventsDetailsEnabled(enabled); - } - - @Override - public ClientModel getMasterAdminClient() { - return this.masterAdminApp; - } - - @Override - public void setMasterAdminClient(ClientModel client) { - if (client == null) { - realm.setMasterAdminClient(null); - this.masterAdminApp = null; - } else { - String appId = client.getId(); - if (appId == null) { - throw new IllegalStateException("Master Admin app not initialized."); - } - realm.setMasterAdminClient(appId); - this.masterAdminApp = client; - } - } - - @Override - public boolean isIdentityFederationEnabled() { - //TODO: not sure if we will support identity federation storage for file - return getIdentityProviders() != null && !getIdentityProviders().isEmpty(); - } - - @Override - public int getAccessCodeLifespanLogin() { - return realm.getAccessCodeLifespanLogin(); - } - - @Override - public void setAccessCodeLifespanLogin(int accessCodeLifespanLogin) { - realm.setAccessCodeLifespanLogin(accessCodeLifespanLogin); - } - - @Override - public boolean isInternationalizationEnabled() { - return realm.isInternationalizationEnabled(); - } - - @Override - public void setInternationalizationEnabled(boolean enabled) { - realm.setInternationalizationEnabled(enabled); - } - - @Override - public Set getSupportedLocales() { - return new HashSet<>(realm.getSupportedLocales()); - } - - @Override - public void setSupportedLocales(Set locales) { - realm.setSupportedLocales(new ArrayList<>(locales)); - } - - @Override - public String getDefaultLocale() { - return realm.getDefaultLocale(); - } - - @Override - public void setDefaultLocale(String locale) { - realm.setDefaultLocale(locale); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof RealmModel)) return false; - - RealmModel that = (RealmModel) o; - return that.getId().equals(getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public Set getIdentityProviderMappers() { - Set mappings = new HashSet<>(); - for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - IdentityProviderMapperModel mapping = entityToModel(entity); - mappings.add(mapping); - } - return mappings; - } - @Override - public Set getIdentityProviderMappersByAlias(String brokerAlias) { - Set mappings = new HashSet<>(); - for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - if (!entity.getIdentityProviderAlias().equals(brokerAlias)) { - continue; - } - IdentityProviderMapperModel mapping = entityToModel(entity); - mappings.add(mapping); - } - return mappings; - } - - @Override - public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) { - if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) { - throw new RuntimeException("identity provider mapper name must be unique per identity provider"); - } - String id = KeycloakModelUtils.generateId(); - IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity(); - entity.setId(id); - entity.setName(model.getName()); - entity.setIdentityProviderAlias(model.getIdentityProviderAlias()); - entity.setIdentityProviderMapper(model.getIdentityProviderMapper()); - entity.setConfig(model.getConfig()); - - this.realm.getIdentityProviderMappers().add(entity); - return entityToModel(entity); - } - - protected IdentityProviderMapperEntity getIdentityProviderMapperEntity(String id) { - for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - if (entity.getId().equals(id)) { - return entity; - } - } - return null; - - } - - protected IdentityProviderMapperEntity getIdentityProviderMapperEntityByName(String alias, String name) { - for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - if (entity.getIdentityProviderAlias().equals(alias) && entity.getName().equals(name)) { - return entity; - } - } - return null; - - } - - @Override - public void removeIdentityProviderMapper(IdentityProviderMapperModel mapping) { - IdentityProviderMapperEntity toDelete = getIdentityProviderMapperEntity(mapping.getId()); - if (toDelete != null) { - this.realm.getIdentityProviderMappers().remove(toDelete); - } - - } - - @Override - public void updateIdentityProviderMapper(IdentityProviderMapperModel mapping) { - IdentityProviderMapperEntity entity = getIdentityProviderMapperEntity(mapping.getId()); - entity.setIdentityProviderAlias(mapping.getIdentityProviderAlias()); - entity.setIdentityProviderMapper(mapping.getIdentityProviderMapper()); - if (entity.getConfig() == null) { - entity.setConfig(mapping.getConfig()); - } else { - entity.getConfig().clear(); - entity.getConfig().putAll(mapping.getConfig()); - } - - } - - @Override - public IdentityProviderMapperModel getIdentityProviderMapperById(String id) { - IdentityProviderMapperEntity entity = getIdentityProviderMapperEntity(id); - if (entity == null) return null; - return entityToModel(entity); - } - - @Override - public IdentityProviderMapperModel getIdentityProviderMapperByName(String alias, String name) { - IdentityProviderMapperEntity entity = getIdentityProviderMapperEntityByName(alias, name); - if (entity == null) return null; - return entityToModel(entity); - } - - protected IdentityProviderMapperModel entityToModel(IdentityProviderMapperEntity entity) { - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - mapping.setConfig(config); - return mapping; - } - - @Override - public AuthenticationFlowModel getBrowserFlow() { - String flowId = realm.getBrowserFlow(); - if (flowId == null) return null; - return getAuthenticationFlowById(flowId); - } - - @Override - public void setBrowserFlow(AuthenticationFlowModel flow) { - realm.setBrowserFlow(flow.getId()); - - } - - @Override - public AuthenticationFlowModel getRegistrationFlow() { - String flowId = realm.getRegistrationFlow(); - if (flowId == null) return null; - return getAuthenticationFlowById(flowId); - } - - @Override - public void setRegistrationFlow(AuthenticationFlowModel flow) { - realm.setRegistrationFlow(flow.getId()); - - } - - @Override - public AuthenticationFlowModel getDirectGrantFlow() { - String flowId = realm.getDirectGrantFlow(); - if (flowId == null) return null; - return getAuthenticationFlowById(flowId); - } - - @Override - public void setDirectGrantFlow(AuthenticationFlowModel flow) { - realm.setDirectGrantFlow(flow.getId()); - - } - - @Override - public AuthenticationFlowModel getResetCredentialsFlow() { - String flowId = realm.getResetCredentialsFlow(); - if (flowId == null) return null; - return getAuthenticationFlowById(flowId); - } - - @Override - public void setResetCredentialsFlow(AuthenticationFlowModel flow) { - realm.setResetCredentialsFlow(flow.getId()); - } - - public AuthenticationFlowModel getClientAuthenticationFlow() { - String flowId = realm.getClientAuthenticationFlow(); - if (flowId == null) return null; - return getAuthenticationFlowById(flowId); - } - - - public void setClientAuthenticationFlow(AuthenticationFlowModel flow) { - realm.setClientAuthenticationFlow(flow.getId()); - } - - @Override - public List getAuthenticationFlows() { - List flows = realm.getAuthenticationFlows(); - if (flows.size() == 0) return Collections.EMPTY_LIST; - List models = new LinkedList<>(); - for (AuthenticationFlowEntity entity : flows) { - AuthenticationFlowModel model = entityToModel(entity); - models.add(model); - } - return models; - } - - - - @Override - public AuthenticationFlowModel getFlowByAlias(String alias) { - for (AuthenticationFlowModel flow : getAuthenticationFlows()) { - if (flow.getAlias().equals(alias)) { - return flow; - } - } - return null; - } - - - protected AuthenticationFlowModel entityToModel(AuthenticationFlowEntity entity) { - AuthenticationFlowModel model = new AuthenticationFlowModel(); - model.setId(entity.getId()); - model.setAlias(entity.getAlias()); - model.setDescription(entity.getDescription()); - model.setProviderId(entity.getProviderId()); - model.setBuiltIn(entity.isBuiltIn()); - model.setTopLevel(entity.isTopLevel()); - return model; - } - - @Override - public AuthenticationFlowModel getAuthenticationFlowById(String id) { - for (AuthenticationFlowModel model : getAuthenticationFlows()) { - if (model.getId().equals(id)) return model; - } - return null; - } - - protected AuthenticationFlowEntity getFlowEntity(String id) { - List flows = realm.getAuthenticationFlows(); - for (AuthenticationFlowEntity entity : flows) { - if (id.equals(entity.getId())) return entity; - } - return null; - - } - - @Override - public void removeAuthenticationFlow(AuthenticationFlowModel model) { - AuthenticationFlowEntity toDelete = getFlowEntity(model.getId()); - if (toDelete == null) return; - realm.getAuthenticationFlows().remove(toDelete); - } - - @Override - public void updateAuthenticationFlow(AuthenticationFlowModel model) { - AuthenticationFlowEntity toUpdate = getFlowEntity(model.getId()); - if (toUpdate == null) return; - toUpdate.setAlias(model.getAlias()); - toUpdate.setDescription(model.getDescription()); - toUpdate.setProviderId(model.getProviderId()); - toUpdate.setBuiltIn(model.isBuiltIn()); - toUpdate.setTopLevel(model.isTopLevel()); - - } - - @Override - public AuthenticationFlowModel addAuthenticationFlow(AuthenticationFlowModel model) { - AuthenticationFlowEntity entity = new AuthenticationFlowEntity(); - String id = (model.getId() == null) ? KeycloakModelUtils.generateId(): model.getId(); - entity.setId(id); - entity.setAlias(model.getAlias()); - entity.setDescription(model.getDescription()); - entity.setProviderId(model.getProviderId()); - entity.setBuiltIn(model.isBuiltIn()); - entity.setTopLevel(model.isTopLevel()); - realm.getAuthenticationFlows().add(entity); - model.setId(entity.getId()); - return model; - } - - @Override - public List getAuthenticationExecutions(String flowId) { - AuthenticationFlowEntity flow = getFlowEntity(flowId); - if (flow == null) return Collections.EMPTY_LIST; - - List queryResult = flow.getExecutions(); - List executions = new LinkedList<>(); - for (AuthenticationExecutionEntity entity : queryResult) { - AuthenticationExecutionModel model = entityToModel(entity); - executions.add(model); - } - Collections.sort(executions, AuthenticationExecutionModel.ExecutionComparator.SINGLETON); - return executions; - } - - public AuthenticationExecutionModel entityToModel(AuthenticationExecutionEntity entity) { - AuthenticationExecutionModel model = new AuthenticationExecutionModel(); - model.setId(entity.getId()); - model.setRequirement(entity.getRequirement()); - model.setPriority(entity.getPriority()); - model.setAuthenticator(entity.getAuthenticator()); - model.setParentFlow(entity.getParentFlow()); - model.setFlowId(entity.getFlowId()); - model.setAuthenticatorFlow(entity.isAuthenticatorFlow()); - model.setAuthenticatorConfig(entity.getAuthenticatorConfig()); - return model; - } - - @Override - public AuthenticationExecutionModel getAuthenticationExecutionById(String id) { - AuthenticationExecutionEntity execution = getAuthenticationExecutionEntity(id); - return entityToModel(execution); - } - - public AuthenticationExecutionEntity getAuthenticationExecutionEntity(String id) { - List flows = realm.getAuthenticationFlows(); - for (AuthenticationFlowEntity entity : flows) { - for (AuthenticationExecutionEntity exe : entity.getExecutions()) { - if (exe.getId().equals(id)) { - return exe; - } - } - } - return null; - } - - @Override - public AuthenticationExecutionModel addAuthenticatorExecution(AuthenticationExecutionModel model) { - AuthenticationExecutionEntity entity = new AuthenticationExecutionEntity(); - String id = (model.getId() == null) ? KeycloakModelUtils.generateId(): model.getId(); - entity.setId(id); - entity.setAuthenticator(model.getAuthenticator()); - entity.setPriority(model.getPriority()); - entity.setRequirement(model.getRequirement()); - entity.setAuthenticatorFlow(model.isAuthenticatorFlow()); - entity.setFlowId(model.getFlowId()); - entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); - AuthenticationFlowEntity flow = getFlowEntity(model.getId()); - flow.getExecutions().add(entity); - model.setId(entity.getId()); - return model; - - } - - @Override - public void updateAuthenticatorExecution(AuthenticationExecutionModel model) { - AuthenticationExecutionEntity entity = null; - AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow()); - for (AuthenticationExecutionEntity exe : flow.getExecutions()) { - if (exe.getId().equals(model.getId())) { - entity = exe; - } - } - if (entity == null) return; - entity.setAuthenticatorFlow(model.isAuthenticatorFlow()); - entity.setAuthenticator(model.getAuthenticator()); - entity.setPriority(model.getPriority()); - entity.setRequirement(model.getRequirement()); - entity.setFlowId(model.getFlowId()); - entity.setAuthenticatorConfig(model.getAuthenticatorConfig()); - } - - @Override - public void removeAuthenticatorExecution(AuthenticationExecutionModel model) { - AuthenticationExecutionEntity entity = null; - AuthenticationFlowEntity flow = getFlowEntity(model.getParentFlow()); - for (AuthenticationExecutionEntity exe : flow.getExecutions()) { - if (exe.getId().equals(model.getId())) { - entity = exe; - } - } - if (entity == null) return; - flow.getExecutions().remove(entity); - - } - - @Override - public List getAuthenticatorConfigs() { - List authenticators = new LinkedList<>(); - for (AuthenticatorConfigEntity entity : realm.getAuthenticatorConfigs()) { - authenticators.add(entityToModel(entity)); - } - return authenticators; - } - - @Override - public AuthenticatorConfigModel getAuthenticatorConfigByAlias(String alias) { - for (AuthenticatorConfigModel config : getAuthenticatorConfigs()) { - if (config.getAlias().equals(alias)) { - return config; - } - } - return null; - } - - - @Override - public AuthenticatorConfigModel addAuthenticatorConfig(AuthenticatorConfigModel model) { - AuthenticatorConfigEntity auth = new AuthenticatorConfigEntity(); - String id = (model.getId() == null) ? KeycloakModelUtils.generateId(): model.getId(); - auth.setId(id); - auth.setAlias(model.getAlias()); - auth.setConfig(model.getConfig()); - realm.getAuthenticatorConfigs().add(auth); - model.setId(auth.getId()); - return model; - } - - @Override - public void removeAuthenticatorConfig(AuthenticatorConfigModel model) { - AuthenticatorConfigEntity entity = getAuthenticatorEntity(model.getId()); - if (entity == null) return; - realm.getAuthenticatorConfigs().remove(entity); - - } - - @Override - public AuthenticatorConfigModel getAuthenticatorConfigById(String id) { - AuthenticatorConfigEntity entity = getAuthenticatorEntity(id); - if (entity == null) return null; - return entityToModel(entity); - } - - public AuthenticatorConfigEntity getAuthenticatorEntity(String id) { - AuthenticatorConfigEntity entity = null; - for (AuthenticatorConfigEntity auth : realm.getAuthenticatorConfigs()) { - if (auth.getId().equals(id)) { - entity = auth; - break; - } - } - return entity; - } - - public AuthenticatorConfigModel entityToModel(AuthenticatorConfigEntity entity) { - AuthenticatorConfigModel model = new AuthenticatorConfigModel(); - model.setId(entity.getId()); - model.setAlias(entity.getAlias()); - Map config = new HashMap<>(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - model.setConfig(config); - return model; - } - - @Override - public void updateAuthenticatorConfig(AuthenticatorConfigModel model) { - AuthenticatorConfigEntity entity = getAuthenticatorEntity(model.getId()); - if (entity == null) return; - entity.setAlias(model.getAlias()); - if (entity.getConfig() == null) { - entity.setConfig(model.getConfig()); - } else { - entity.getConfig().clear(); - entity.getConfig().putAll(model.getConfig()); - } - } - - @Override - public RequiredActionProviderModel addRequiredActionProvider(RequiredActionProviderModel model) { - RequiredActionProviderEntity auth = new RequiredActionProviderEntity(); - auth.setId(KeycloakModelUtils.generateId()); - auth.setAlias(model.getAlias()); - auth.setName(model.getName()); - auth.setProviderId(model.getProviderId()); - auth.setConfig(model.getConfig()); - auth.setEnabled(model.isEnabled()); - auth.setDefaultAction(model.isDefaultAction()); - realm.getRequiredActionProviders().add(auth); - model.setId(auth.getId()); - return model; - } - - @Override - public void removeRequiredActionProvider(RequiredActionProviderModel model) { - RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); - if (entity == null) return; - realm.getRequiredActionProviders().remove(entity); - } - - @Override - public RequiredActionProviderModel getRequiredActionProviderById(String id) { - RequiredActionProviderEntity entity = getRequiredActionProviderEntity(id); - if (entity == null) return null; - return entityToModel(entity); - } - - public RequiredActionProviderModel entityToModel(RequiredActionProviderEntity entity) { - RequiredActionProviderModel model = new RequiredActionProviderModel(); - model.setId(entity.getId()); - model.setProviderId(entity.getProviderId()); - model.setAlias(entity.getAlias()); - model.setName(entity.getName()); - model.setEnabled(entity.isEnabled()); - model.setDefaultAction(entity.isDefaultAction()); - Map config = new HashMap<>(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - model.setConfig(config); - return model; - } - - @Override - public void updateRequiredActionProvider(RequiredActionProviderModel model) { - RequiredActionProviderEntity entity = getRequiredActionProviderEntity(model.getId()); - if (entity == null) return; - entity.setAlias(model.getAlias()); - entity.setProviderId(model.getProviderId()); - entity.setEnabled(model.isEnabled()); - entity.setName(model.getName()); - entity.setDefaultAction(model.isDefaultAction()); - if (entity.getConfig() == null) { - entity.setConfig(model.getConfig()); - } else { - entity.getConfig().clear(); - entity.getConfig().putAll(model.getConfig()); - } - } - - @Override - public List getRequiredActionProviders() { - List actions = new LinkedList<>(); - for (RequiredActionProviderEntity entity : realm.getRequiredActionProviders()) { - actions.add(entityToModel(entity)); - } - return actions; - } - - public RequiredActionProviderEntity getRequiredActionProviderEntity(String id) { - RequiredActionProviderEntity entity = null; - for (RequiredActionProviderEntity auth : realm.getRequiredActionProviders()) { - if (auth.getId().equals(id)) { - entity = auth; - break; - } - } - return entity; - } - - @Override - public RequiredActionProviderModel getRequiredActionProviderByAlias(String alias) { - for (RequiredActionProviderModel action : getRequiredActionProviders()) { - if (action.getAlias().equals(alias)) return action; - } - return null; - } - - - - - @Override - public Set getUserFederationMappers() { - Set mappers = new HashSet(); - for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { - UserFederationMapperModel mapper = entityToModel(entity); - mappers.add(mapper); - } - return mappers; - } - - @Override - public Set getUserFederationMappersByFederationProvider(String federationProviderId) { - Set mappers = new HashSet(); - Set mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); - for (UserFederationMapperEntity entity : mapperEntities) { - mappers.add(entityToModel(entity)); - } - return mappers; - } - - @Override - public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) { - if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) { - throw new ModelDuplicateException("User federation mapper must be unique per federation provider. There is already: " + model.getName()); - } - String id = KeycloakModelUtils.generateId(); - UserFederationMapperEntity entity = new UserFederationMapperEntity(); - entity.setId(id); - entity.setName(model.getName()); - entity.setFederationProviderId(model.getFederationProviderId()); - entity.setFederationMapperType(model.getFederationMapperType()); - entity.setConfig(model.getConfig()); - - this.realm.getUserFederationMappers().add(entity); - UserFederationMapperModel mapperModel = entityToModel(entity); - - session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapperModel, this, session)); - - return mapperModel; - } - - protected UserFederationMapperEntity getUserFederationMapperEntity(String id) { - for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { - if (entity.getId().equals(id)) { - return entity; - } - } - return null; - - } - - protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) { - for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { - if (entity.getFederationProviderId().equals(federationProviderId) && entity.getName().equals(name)) { - return entity; - } - } - return null; - - } - - protected Set getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) { - Set mappers = new HashSet(); - for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { - if (federationProviderId.equals(entity.getFederationProviderId())) { - mappers.add(entity); - } - } - return mappers; - } - - @Override - public void removeUserFederationMapper(UserFederationMapperModel mapper) { - UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId()); - if (toDelete != null) { - this.realm.getUserFederationMappers().remove(toDelete); - } - } - - @Override - public void updateUserFederationMapper(UserFederationMapperModel mapper) { - UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId()); - entity.setFederationProviderId(mapper.getFederationProviderId()); - entity.setFederationMapperType(mapper.getFederationMapperType()); - if (entity.getConfig() == null) { - entity.setConfig(mapper.getConfig()); - } else { - entity.getConfig().clear(); - entity.getConfig().putAll(mapper.getConfig()); - } - - session.getKeycloakSessionFactory().publish(new UserFederationMapperEventImpl(mapper, this, session)); - } - - @Override - public UserFederationMapperModel getUserFederationMapperById(String id) { - UserFederationMapperEntity entity = getUserFederationMapperEntity(id); - if (entity == null) return null; - return entityToModel(entity); - } - - @Override - public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) { - UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name); - if (entity == null) return null; - return entityToModel(entity); - } - - protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) { - UserFederationMapperModel mapper = new UserFederationMapperModel(); - mapper.setId(entity.getId()); - mapper.setName(entity.getName()); - mapper.setFederationProviderId(entity.getFederationProviderId()); - mapper.setFederationMapperType(entity.getFederationMapperType()); - Map config = new HashMap(); - if (entity.getConfig() != null) config.putAll(entity.getConfig()); - mapper.setConfig(config); - return mapper; - } - - @Override - public GroupModel createGroup(String name) { - return null; - } - - @Override - public void addTopLevelGroup(GroupModel subGroup) { - - } -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java deleted file mode 100755 index 7706459df7..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file.adapter; - -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleContainerModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.entities.RoleEntity; -import org.keycloak.models.utils.KeycloakModelUtils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * RoleModel for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class RoleAdapter implements RoleModel { - - private final RoleEntity role; - private RoleContainerModel roleContainer; - private final RealmModel realm; - - private final Set compositeRoles = new HashSet(); - - public RoleAdapter(RealmModel realm, RoleEntity roleEntity) { - this(realm, roleEntity, null); - } - - public RoleAdapter(RealmModel realm, RoleEntity roleEntity, RoleContainerModel roleContainer) { - this.role = roleEntity; - this.roleContainer = roleContainer; - this.realm = realm; - } - - public RoleEntity getRoleEntity() { - return this.role; - } - - public boolean isRealmRole() { - return role.getRealmId() != null; - } - - @Override - public String getId() { - return role.getId(); - } - - @Override - public String getName() { - return role.getName(); - } - - @Override - public void setName(String name) { - RealmAdapter realmAdapter = (RealmAdapter)realm; - if (role.getName().equals(name)) return; - if (realmAdapter.hasRoleWithName(name)) throw new ModelDuplicateException("Role name " + name + " already exists."); - role.setName(name); - } - - @Override - public String getDescription() { - return role.getDescription(); - } - - @Override - public void setDescription(String description) { - role.setDescription(description); - } - - @Override - public boolean isScopeParamRequired() { - return role.isScopeParamRequired(); - } - - @Override - public void setScopeParamRequired(boolean scopeParamRequired) { - role.setScopeParamRequired(scopeParamRequired); - } - - @Override - public boolean isComposite() { - return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0; - } - - @Override - public void addCompositeRole(RoleModel childRole) { - List compositeRoleIds = role.getCompositeRoleIds(); - if (compositeRoleIds == null) compositeRoleIds = new ArrayList(); - compositeRoleIds.add(childRole.getId()); - role.setCompositeRoleIds(compositeRoleIds); - compositeRoles.add(childRole); - } - - /** - * Recursively remove composite roles for the specified app - * @param appId - */ - public void removeApplicationComposites(String appId) { - if (!isComposite()) return; - Set toBeRemoved = new HashSet(); - for (RoleModel compositeRole : getComposites()) { - RoleAdapter roleAdapter = (RoleAdapter)compositeRole; - if (appId.equals(roleAdapter.getRoleEntity().getClientId())) { - toBeRemoved.add(compositeRole); - } else { - roleAdapter.removeApplicationComposites(appId); - } - } - - for (RoleModel compositeRole : toBeRemoved) { - removeCompositeRole(compositeRole); - } - } - - @Override - public void removeCompositeRole(RoleModel childRole) { - compositeRoles.remove(childRole); - List compositeRoleIds = role.getCompositeRoleIds(); - if (compositeRoleIds == null) return; // shouldn't happen - compositeRoleIds.remove(childRole.getId()); - role.setCompositeRoleIds(compositeRoleIds); - } - - @Override - public Set getComposites() { - return Collections.unmodifiableSet(compositeRoles); - } - - @Override - public RoleContainerModel getContainer() { - if (roleContainer == null) { - // Compute it - if (role.getRealmId() != null) { - roleContainer = realm;//new RealmAdapter(session, realm); - } else if (role.getClientId() != null) { - roleContainer = realm.getClientById(role.getClientId());//new ApplicationAdapter(session, realm, appEntity); - } else { - throw new IllegalStateException("Both realmId and applicationId are null for role: " + this); - } - } - return roleContainer; - } - - @Override - public boolean hasRole(RoleModel role) { - if (this.equals(role)) return true; - if (!isComposite()) return false; - - Set visited = new HashSet(); - return KeycloakModelUtils.searchFor(role, this, visited); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof RoleModel)) return false; - - RoleModel that = (RoleModel) o; - return that.getId().equals(getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - -} diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java deleted file mode 100755 index d0dc92b756..0000000000 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/UserAdapter.java +++ /dev/null @@ -1,614 +0,0 @@ -/* - * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * 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.models.file.adapter; - -import org.keycloak.connections.file.InMemoryModel; -import org.keycloak.models.ClientModel; - -import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt; - -import org.keycloak.models.GroupModel; -import org.keycloak.models.ModelDuplicateException; -import org.keycloak.models.OTPPolicy; -import org.keycloak.models.UserConsentModel; -import org.keycloak.models.PasswordPolicy; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserCredentialValueModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.entities.CredentialEntity; -import org.keycloak.models.entities.FederatedIdentityEntity; -import org.keycloak.models.entities.RoleEntity; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.models.utils.Pbkdf2PasswordEncoder; -import org.keycloak.common.util.Time; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * UserModel for JSON persistence. - * - * @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc. - */ -public class UserAdapter implements UserModel, Comparable { - - private final InMemoryModel inMemoryModel; - private final UserEntity user; - private final RealmModel realm; - - private final Set allRoles = new HashSet(); - private final Set allGroups = new HashSet(); - - public UserAdapter(RealmModel realm, UserEntity userEntity, InMemoryModel inMemoryModel) { - this.user = userEntity; - this.realm = realm; - if (userEntity.getFederatedIdentities() == null) { - userEntity.setFederatedIdentities(new ArrayList()); - } - this.inMemoryModel = inMemoryModel; - } - - public UserEntity getUserEntity() { - return this.user; - } - - @Override - public String getId() { - return user.getId(); - } - - @Override - public String getUsername() { - return user.getUsername(); - } - - @Override - public void setUsername(String username) { - username = KeycloakModelUtils.toLowerCaseSafe(username); - - if (getUsername() == null) { - user.setUsername(username); - return; - } - - if (getUsername().equals(username)) return; // allow setting to same name - - if (inMemoryModel.hasUserWithUsername(realm.getId(), username)) - throw new ModelDuplicateException("User with username " + username + " already exists in realm."); - user.setUsername(username); - } - - @Override - public Long getCreatedTimestamp() { - return user.getCreatedTimestamp(); - } - - @Override - public void setCreatedTimestamp(Long timestamp) { - user.setCreatedTimestamp(timestamp); - } - - @Override - public boolean isEnabled() { - return user.isEnabled(); - } - - @Override - public void setEnabled(boolean enabled) { - user.setEnabled(enabled); - } - - @Override - public String getFirstName() { - return user.getFirstName(); - } - - @Override - public void setFirstName(String firstName) { - user.setFirstName(firstName); - } - - @Override - public String getLastName() { - return user.getLastName(); - } - - @Override - public void setLastName(String lastName) { - user.setLastName(lastName); - } - - @Override - public String getEmail() { - return user.getEmail(); - } - - @Override - public void setEmail(String email) { - email = KeycloakModelUtils.toLowerCaseSafe(email); - - if (email == null) { - user.setEmail(email); - return; - } - - if (email.equals(getEmail())) return; - - RealmAdapter realmAdapter = (RealmAdapter)realm; - if (realmAdapter.hasUserWithEmail(email)) throw new ModelDuplicateException("User with email address " + email + " already exists."); - user.setEmail(email); - } - - @Override - public boolean isEmailVerified() { - return user.isEmailVerified(); - } - - @Override - public void setEmailVerified(boolean verified) { - user.setEmailVerified(verified); - } - - @Override - public void setSingleAttribute(String name, String value) { - if (user.getAttributes() == null) { - user.setAttributes(new HashMap>()); - } - - List attrValues = new ArrayList<>(); - attrValues.add(value); - user.getAttributes().put(name, attrValues); - } - - @Override - public void setAttribute(String name, List values) { - if (user.getAttributes() == null) { - user.setAttributes(new HashMap>()); - } - - user.getAttributes().put(name, values); - } - - @Override - public void removeAttribute(String name) { - if (user.getAttributes() == null) return; - - user.getAttributes().remove(name); - } - - @Override - public String getFirstAttribute(String name) { - if (user.getAttributes()==null) return null; - - List attrValues = user.getAttributes().get(name); - return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0); - } - - @Override - public List getAttribute(String name) { - if (user.getAttributes()==null) return Collections.emptyList(); - List attrValues = user.getAttributes().get(name); - return (attrValues == null) ? Collections.emptyList() : Collections.unmodifiableList(attrValues); - } - - @Override - public Map> getAttributes() { - return user.getAttributes()==null ? Collections.>emptyMap() : Collections.unmodifiableMap((Map) user.getAttributes()); - } - - @Override - public Set getRequiredActions() { - List requiredActions = user.getRequiredActions(); - if (requiredActions == null) requiredActions = new ArrayList(); - return new HashSet(requiredActions); - } - - @Override - public void addRequiredAction(RequiredAction action) { - String actionName = action.name(); - addRequiredAction(actionName); - } - - @Override - public void addRequiredAction(String actionName) { - List requiredActions = user.getRequiredActions(); - if (requiredActions == null) requiredActions = new ArrayList<>(); - if (!requiredActions.contains(actionName)) { - requiredActions.add(actionName); - } - user.setRequiredActions(requiredActions); - } - - @Override - public void removeRequiredAction(RequiredAction action) { - String actionName = action.name(); - removeRequiredAction(actionName); - } - - @Override - public void removeRequiredAction(String actionName) { - List requiredActions = user.getRequiredActions(); - if (requiredActions == null) return; - requiredActions.remove(actionName); - user.setRequiredActions(requiredActions); - } - - @Override - public boolean isOtpEnabled() { - return user.isTotp(); - } - - @Override - public void setOtpEnabled(boolean totp) { - user.setTotp(totp); - } - - @Override - public void updateCredential(UserCredentialModel cred) { - - if (cred.getType().equals(UserCredentialModel.PASSWORD)) { - updatePasswordCredential(cred); - } else if (UserCredentialModel.isOtp(cred.getType())){ - updateOtpCredential(cred); - - }else { - CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType()); - - if (credentialEntity == null) { - credentialEntity = setCredentials(user, cred); - credentialEntity.setValue(cred.getValue()); - user.getCredentials().add(credentialEntity); - } else { - credentialEntity.setValue(cred.getValue()); - } - } - } - - private void updateOtpCredential(UserCredentialModel cred) { - CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType()); - - if (credentialEntity == null) { - credentialEntity = setCredentials(user, cred); - credentialEntity.setValue(cred.getValue()); - OTPPolicy otpPolicy = realm.getOTPPolicy(); - credentialEntity.setAlgorithm(otpPolicy.getAlgorithm()); - credentialEntity.setDigits(otpPolicy.getDigits()); - credentialEntity.setCounter(otpPolicy.getInitialCounter()); - credentialEntity.setPeriod(otpPolicy.getPeriod()); - user.getCredentials().add(credentialEntity); - } else { - credentialEntity.setValue(cred.getValue()); - OTPPolicy policy = realm.getOTPPolicy(); - credentialEntity.setDigits(policy.getDigits()); - credentialEntity.setCounter(policy.getInitialCounter()); - credentialEntity.setAlgorithm(policy.getAlgorithm()); - credentialEntity.setPeriod(policy.getPeriod()); - } - } - - - private void updatePasswordCredential(UserCredentialModel cred) { - CredentialEntity credentialEntity = getCredentialEntity(user, cred.getType()); - - if (credentialEntity == null) { - credentialEntity = setCredentials(user, cred); - setValue(credentialEntity, cred); - user.getCredentials().add(credentialEntity); - } else { - - int expiredPasswordsPolicyValue = -1; - PasswordPolicy policy = realm.getPasswordPolicy(); - if(policy != null) { - expiredPasswordsPolicyValue = policy.getExpiredPasswords(); - } - - if (expiredPasswordsPolicyValue != -1) { - user.getCredentials().remove(credentialEntity); - credentialEntity.setType(UserCredentialModel.PASSWORD_HISTORY); - user.getCredentials().add(credentialEntity); - - List credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY); - if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) { - user.getCredentials().removeAll(credentialEntities.subList(expiredPasswordsPolicyValue - 1, credentialEntities.size())); - } - - credentialEntity = setCredentials(user, cred); - setValue(credentialEntity, cred); - user.getCredentials().add(credentialEntity); - } else { - List credentialEntities = getCredentialEntities(user, UserCredentialModel.PASSWORD_HISTORY); - if (credentialEntities != null && credentialEntities.size() > 0) { - user.getCredentials().removeAll(credentialEntities); - } - setValue(credentialEntity, cred); - } - } - } - - private CredentialEntity setCredentials(UserEntity user, UserCredentialModel cred) { - CredentialEntity credentialEntity = new CredentialEntity(); - credentialEntity.setType(cred.getType()); - credentialEntity.setDevice(cred.getDevice()); - return credentialEntity; - } - - private void setValue(CredentialEntity credentialEntity, UserCredentialModel cred) { - byte[] salt = getSalt(); - int hashIterations = 1; - PasswordPolicy policy = realm.getPasswordPolicy(); - if (policy != null) { - hashIterations = policy.getHashIterations(); - if (hashIterations == -1) - hashIterations = 1; - } - credentialEntity.setCreatedDate(Time.toMillis(Time.currentTime())); - credentialEntity.setValue(new Pbkdf2PasswordEncoder(salt).encode(cred.getValue(), hashIterations)); - credentialEntity.setSalt(salt); - credentialEntity.setHashIterations(hashIterations); - } - - private CredentialEntity getCredentialEntity(UserEntity userEntity, String credType) { - for (CredentialEntity entity : userEntity.getCredentials()) { - if (entity.getType().equals(credType)) { - return entity; - } - } - - return null; - } - - private List getCredentialEntities(UserEntity userEntity, String credType) { - List credentialEntities = new ArrayList(); - for (CredentialEntity entity : userEntity.getCredentials()) { - if (entity.getType().equals(credType)) { - credentialEntities.add(entity); - } - } - - // Avoiding direct use of credSecond.getCreatedDate() - credFirst.getCreatedDate() to prevent Integer Overflow - // Orders from most recent to least recent - Collections.sort(credentialEntities, new Comparator() { - public int compare(CredentialEntity credFirst, CredentialEntity credSecond) { - if (credFirst.getCreatedDate() > credSecond.getCreatedDate()) { - return -1; - } else if (credFirst.getCreatedDate() < credSecond.getCreatedDate()) { - return 1; - } else { - return 0; - } - } - }); - return credentialEntities; - } - - @Override - public List getCredentialsDirectly() { - List credentials = new ArrayList(user.getCredentials()); - List result = new ArrayList(); - - for (CredentialEntity credEntity : credentials) { - UserCredentialValueModel credModel = new UserCredentialValueModel(); - credModel.setType(credEntity.getType()); - credModel.setDevice(credEntity.getDevice()); - credModel.setCreatedDate(credEntity.getCreatedDate()); - credModel.setValue(credEntity.getValue()); - credModel.setSalt(credEntity.getSalt()); - credModel.setHashIterations(credEntity.getHashIterations()); - if (UserCredentialModel.isOtp(credEntity.getType())) { - credModel.setCounter(credEntity.getCounter()); - if (credEntity.getAlgorithm() == null) { - // for migration where these values would be null - credModel.setAlgorithm(realm.getOTPPolicy().getAlgorithm()); - } else { - credModel.setAlgorithm(credEntity.getAlgorithm()); - } - if (credEntity.getDigits() == 0) { - // for migration where these values would be 0 - credModel.setDigits(realm.getOTPPolicy().getDigits()); - } else { - credModel.setDigits(credEntity.getDigits()); - } - - if (credEntity.getPeriod() == 0) { - // for migration where these values would be 0 - credModel.setPeriod(realm.getOTPPolicy().getPeriod()); - } else { - credModel.setPeriod(credEntity.getPeriod()); - } - } - - result.add(credModel); - } - - return result; - } - - @Override - public void updateCredentialDirectly(UserCredentialValueModel credModel) { - CredentialEntity credentialEntity = getCredentialEntity(user, credModel.getType()); - - if (credentialEntity == null) { - credentialEntity = new CredentialEntity(); - // credentialEntity.setId(KeycloakModelUtils.generateId()); - credentialEntity.setType(credModel.getType()); - // credentialEntity.setUser(user); - credModel.setCreatedDate(credModel.getCreatedDate()); - user.getCredentials().add(credentialEntity); - } - - credentialEntity.setValue(credModel.getValue()); - credentialEntity.setSalt(credModel.getSalt()); - credentialEntity.setDevice(credModel.getDevice()); - credentialEntity.setHashIterations(credModel.getHashIterations()); - credentialEntity.setCounter(credModel.getCounter()); - credentialEntity.setAlgorithm(credModel.getAlgorithm()); - credentialEntity.setDigits(credModel.getDigits()); - credentialEntity.setPeriod(credModel.getPeriod()); - } - - @Override - public Set getGroups() { - return Collections.unmodifiableSet(allGroups); - } - - @Override - public void joinGroup(GroupModel group) { - allGroups.add(group); - - } - - @Override - public void leaveGroup(GroupModel group) { - if (user == null || group == null) return; - allGroups.remove(group); - - } - - @Override - public boolean isMemberOf(GroupModel group) { - return KeycloakModelUtils.isMember(getGroups(), group); - } - - @Override - public boolean hasRole(RoleModel role) { - Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); - } - - @Override - public void grantRole(RoleModel role) { - allRoles.add(role); - } - - @Override - public Set getRoleMappings() { - return Collections.unmodifiableSet(allRoles); - } - - @Override - public Set getRealmRoleMappings() { - Set allRoleMappings = getRoleMappings(); - - // Filter to retrieve just realm roles TODO: Maybe improve to avoid filter programmatically... Maybe have separate fields for realmRoles and appRoles on user? - Set realmRoles = new HashSet(); - for (RoleModel role : allRoleMappings) { - RoleEntity roleEntity = ((RoleAdapter) role).getRoleEntity(); - - if (realm.getId().equals(roleEntity.getRealmId())) { - realmRoles.add(role); - } - } - return realmRoles; - } - - @Override - public void deleteRoleMapping(RoleModel role) { - if (user == null || role == null) return; - allRoles.remove(role); - } - - @Override - public Set getClientRoleMappings(ClientModel app) { - Set result = new HashSet(); - - for (RoleModel role : allRoles) { - RoleEntity roleEntity = ((RoleAdapter)role).getRoleEntity(); - if (app.getId().equals(roleEntity.getClientId())) { - result.add(new RoleAdapter(realm, roleEntity, app)); - } - } - return result; - } - - @Override - public String getFederationLink() { - return user.getFederationLink(); - } - - @Override - public void setFederationLink(String link) { - user.setFederationLink(link); - } - - @Override - public String getServiceAccountClientLink() { - return user.getServiceAccountClientLink(); - } - - @Override - public void setServiceAccountClientLink(String clientInternalId) { - user.setServiceAccountClientLink(clientInternalId); - } - - @Override - public void addConsent(UserConsentModel consent) { - // TODO - } - - @Override - public UserConsentModel getConsentByClient(String clientId) { - // TODO - return null; - } - - @Override - public List getConsents() { - // TODO - return null; - } - - @Override - public void updateConsent(UserConsentModel consent) { - // TODO - } - - @Override - public boolean revokeConsentForClient(String clientId) { - // TODO - return false; - } - - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || !(o instanceof UserModel)) return false; - - UserModel that = (UserModel) o; - return that.getId().equals(getId()); - } - - @Override - public int hashCode() { - return getId().hashCode(); - } - - @Override - public int compareTo(Object user) { - if (this == user) return 0; - return (getUsername().compareTo(((UserModel)user).getUsername())); - } -} diff --git a/model/file/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory b/model/file/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory deleted file mode 100644 index 173ba2d258..0000000000 --- a/model/file/src/main/resources/META-INF/services/org.keycloak.models.RealmProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.models.file.FileRealmProviderFactory \ No newline at end of file diff --git a/model/file/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory b/model/file/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory deleted file mode 100644 index 691ada44ce..0000000000 --- a/model/file/src/main/resources/META-INF/services/org.keycloak.models.UserProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.models.file.FileUserProviderFactory \ No newline at end of file diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java index 477c5d6ab2..7a873735bc 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java @@ -120,6 +120,15 @@ public class ClientAdapter implements ClientModel { getDelegateForUpdate(); updated.setSecret(secret); } + public String getRegistrationSecret() { + if (updated != null) return updated.getRegistrationSecret(); + return cached.getRegistrationSecret(); + } + + public void setRegistrationSecret(String registrationsecret) { + getDelegateForUpdate(); + updated.setRegistrationSecret(registrationsecret); + } public boolean isPublicClient() { if (updated != null) return updated.isPublicClient(); diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java index 2d582222a8..1c04b9df10 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java @@ -31,6 +31,7 @@ public class CachedClient implements Serializable { private boolean enabled; private String clientAuthenticatorType; private String secret; + private String registrationSecret; private String protocol; private Map attributes = new HashMap(); private boolean publicClient; @@ -57,6 +58,7 @@ public class CachedClient implements Serializable { id = model.getId(); clientAuthenticatorType = model.getClientAuthenticatorType(); secret = model.getSecret(); + registrationSecret = model.getRegistrationSecret(); clientId = model.getClientId(); name = model.getName(); description = model.getDescription(); @@ -129,6 +131,10 @@ public class CachedClient implements Serializable { return secret; } + public String getRegistrationSecret() { + return registrationSecret; + } + public boolean isPublicClient() { return publicClient; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index 3baddd19a7..5ea0b11619 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -177,6 +177,16 @@ public class ClientAdapter implements ClientModel { entity.setSecret(secret); } + @Override + public String getRegistrationSecret() { + return entity.getRegistrationSecret(); + } + + @Override + public void setRegistrationSecret(String registrationSecret) { + entity.setRegistrationSecret(registrationSecret); + } + @Override public boolean validateSecret(String secret) { return secret.equals(entity.getSecret()); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 4a26cd83c3..7b8e3a6cca 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -1,5 +1,6 @@ package org.keycloak.models.jpa; +import org.keycloak.Config; import org.keycloak.connections.jpa.util.JpaUtils; import org.keycloak.common.enums.SslRequired; import org.keycloak.models.AuthenticationExecutionModel; @@ -1195,8 +1196,13 @@ public class RealmAdapter implements RealmModel { @Override public ClientModel getMasterAdminClient() { - ClientEntity client = realm.getMasterAdminClient(); - return client!=null ? new ClientAdapter(this, em, session, realm.getMasterAdminClient()) : null; + ClientEntity masterAdminClient = realm.getMasterAdminClient(); + if (masterAdminClient == null) { + return null; + } + + RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm()); + return new ClientAdapter(masterRealm, em, session, masterAdminClient); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java index f26f65126e..881b12967f 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java @@ -42,6 +42,8 @@ public class ClientEntity { private boolean enabled; @Column(name="SECRET") private String secret; + @Column(name="REGISTRATION_SECRET") + private String registrationSecret; @Column(name="CLIENT_AUTHENTICATOR_TYPE") private String clientAuthenticatorType; @Column(name="NOT_BEFORE") @@ -201,6 +203,14 @@ public class ClientEntity { this.secret = secret; } + public String getRegistrationSecret() { + return registrationSecret; + } + + public void setRegistrationSecret(String registrationSecret) { + this.registrationSecret = registrationSecret; + } + public int getNotBefore() { return notBefore; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index e99f142297..cbacd096d2 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java @@ -177,6 +177,17 @@ public class ClientAdapter extends AbstractMongoAdapter imple updateMongoEntity(); } + @Override + public String getRegistrationSecret() { + return getMongoEntity().getRegistrationSecret(); + } + + @Override + public void setRegistrationSecret(String registrationSecretsecret) { + getMongoEntity().setRegistrationSecret(registrationSecretsecret); + updateMongoEntity(); + } + @Override public boolean isPublicClient() { return getMongoEntity().isPublicClient(); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 59bc5536f5..30266af27a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -1223,7 +1223,13 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public ClientModel getMasterAdminClient() { MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext); - return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null; + if (appData == null) { + return null; + } + + MongoRealmEntity masterRealm = getMongoStore().loadEntity(MongoRealmEntity.class, appData.getRealmId(), invocationContext); + RealmModel masterRealmModel = new RealmAdapter(session, masterRealm, invocationContext); + return new ClientAdapter(session, masterRealmModel, appData, invocationContext); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 87729d4a6b..83979c3dff 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -453,7 +453,7 @@ public class UserAdapter extends AbstractMongoAdapter implement @Override public Set getGroups() { - if (user.getGroupIds() == null && user.getGroupIds().size() == 0) return Collections.EMPTY_SET; + if (user.getGroupIds() == null || user.getGroupIds().size() == 0) return Collections.EMPTY_SET; Set groups = new HashSet<>(); for (String id : user.getGroupIds()) { groups.add(realm.getGroupById(id)); diff --git a/model/pom.xml b/model/pom.xml index 2f78805ae2..0322abb35b 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -29,7 +29,6 @@ invalidation-cache jpa mongo - file sessions-infinispan diff --git a/pom.xml b/pom.xml index 12a346e201..d545efd5f9 100755 --- a/pom.xml +++ b/pom.xml @@ -48,8 +48,8 @@ 1.6.1 1.4.01 1.7.7 - 9.0.1.Final - 1.0.1.Final + 9.0.2.Final + 1.0.2.Final 1.0.0.Final @@ -597,11 +597,6 @@ keycloak-broker-saml ${project.version} - - org.keycloak - keycloak-connections-file - ${project.version} - org.keycloak keycloak-connections-infinispan @@ -838,7 +833,7 @@ org.keycloak keycloak-wf9-server-subsystem ${project.version} - + org.keycloak keycloak-subsystem @@ -959,11 +954,6 @@ keycloak-model-api ${project.version} - - org.keycloak - keycloak-model-file - ${project.version} - org.keycloak keycloak-invalidation-cache-infinispan @@ -1441,7 +1431,7 @@ org.wildfly.build wildfly-feature-pack-build-maven-plugin ${wildfly.build-tools.version} - + org.wildfly.build wildfly-server-provisioning-maven-plugin @@ -1462,7 +1452,7 @@ 3.1.1 - + diff --git a/saml/client-adapter/jetty/jetty-core/pom.xml b/saml/client-adapter/jetty/jetty-core/pom.xml index 52cc27366d..87bf21a642 100755 --- a/saml/client-adapter/jetty/jetty-core/pom.xml +++ b/saml/client-adapter/jetty/jetty-core/pom.xml @@ -59,21 +59,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/saml/client-adapter/jetty/jetty8.1/pom.xml b/saml/client-adapter/jetty/jetty8.1/pom.xml index 937b8ee67f..1e888f59d6 100755 --- a/saml/client-adapter/jetty/jetty8.1/pom.xml +++ b/saml/client-adapter/jetty/jetty8.1/pom.xml @@ -54,21 +54,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/saml/client-adapter/jetty/jetty9.1/pom.xml b/saml/client-adapter/jetty/jetty9.1/pom.xml index f9bc11b04f..b8ca67b6ea 100755 --- a/saml/client-adapter/jetty/jetty9.1/pom.xml +++ b/saml/client-adapter/jetty/jetty9.1/pom.xml @@ -69,21 +69,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/saml/client-adapter/jetty/jetty9.2/pom.xml b/saml/client-adapter/jetty/jetty9.2/pom.xml index d7dc8b89ba..9382c4a0fd 100755 --- a/saml/client-adapter/jetty/jetty9.2/pom.xml +++ b/saml/client-adapter/jetty/jetty9.2/pom.xml @@ -69,21 +69,21 @@ org.eclipse.jetty jetty-server ${jetty9.version} - compile + provided org.eclipse.jetty jetty-util ${jetty9.version} - compile + provided org.eclipse.jetty jetty-security ${jetty9.version} - compile + provided diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java index da9961301a..42ff3ee0c0 100755 --- a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/EntityDescriptorDescriptionConverter.java @@ -33,6 +33,8 @@ import java.util.Map; */ public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory { + public static final String ID = "saml2-entity-descriptor"; + @Override public boolean isSupported(String description) { description = description.trim(); @@ -161,7 +163,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo @Override public String getId() { - return "saml2-entity-descriptor"; + return ID; } } diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java new file mode 100644 index 0000000000..298623d582 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProvider.java @@ -0,0 +1,79 @@ +package org.keycloak.protocol.saml.clientregistration; + +import org.jboss.logging.Logger; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; +import org.keycloak.exportimport.ClientDescriptionConverter; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.saml.EntityDescriptorDescriptionConverter; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.ErrorResponse; +import org.keycloak.services.clientregistration.ClientRegAuth; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; + +/** + * @author Stian Thorgersen + */ +public class EntityDescriptorClientRegistrationProvider implements ClientRegistrationProvider { + + private static final Logger logger = Logger.getLogger(EntityDescriptorClientRegistrationProvider.class); + + private KeycloakSession session; + private EventBuilder event; + private ClientRegAuth auth; + + public EntityDescriptorClientRegistrationProvider(KeycloakSession session) { + this.session = session; + } + +// @POST +// @Consumes(MediaType.APPLICATION_XML) +// @Produces(MediaType.APPLICATION_JSON) +// public Response create(String descriptor) { +// event.event(EventType.CLIENT_REGISTER); +// +// auth.requireCreate(); +// +// ClientRepresentation client = session.getProvider(ClientDescriptionConverter.class, EntityDescriptorDescriptionConverter.ID).convertToInternal(descriptor); +// +// try { +// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); +// client = ModelToRepresentation.toRepresentation(clientModel); +// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build(); +// +// logger.infov("Created client {0}", client.getClientId()); +// +// event.client(client.getClientId()).success(); +// +// return Response.created(uri).entity(client).build(); +// } catch (ModelDuplicateException e) { +// return ErrorResponse.exists("Client " + client.getClientId() + " already exists"); +// } +// } + + @Override + public void close() { + } + + @Override + public void setAuth(ClientRegAuth auth) { + this.auth = auth; + } + + @Override + public void setEvent(EventBuilder event) { + this.event = event; + } + +} diff --git a/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java new file mode 100644 index 0000000000..393ff36fc2 --- /dev/null +++ b/saml/saml-protocol/src/main/java/org/keycloak/protocol/saml/clientregistration/EntityDescriptorClientRegistrationProviderFactory.java @@ -0,0 +1,38 @@ +package org.keycloak.protocol.saml.clientregistration; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.ClientRegistrationProviderFactory; + +/** + * @author Stian Thorgersen + */ +public class EntityDescriptorClientRegistrationProviderFactory implements ClientRegistrationProviderFactory { + + public static final String ID = "saml2-entity-descriptor"; + + @Override + public ClientRegistrationProvider create(KeycloakSession session) { + return new EntityDescriptorClientRegistrationProvider(session); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return ID; + } + +} diff --git a/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory new file mode 100644 index 0000000000..e4f81175e6 --- /dev/null +++ b/saml/saml-protocol/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory @@ -0,0 +1 @@ +org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java index ffb23004a3..b4ee957e43 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpCreateUserIfUniqueAuthenticator.java @@ -10,11 +10,12 @@ import org.keycloak.authentication.AuthenticationFlowContext; import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo; import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; import org.keycloak.models.AuthenticatorConfigModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; -import org.keycloak.models.utils.FormMessage; import org.keycloak.services.messages.Messages; /** @@ -78,6 +79,15 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator .setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue()) .createErrorPage(); context.challenge(challengeResponse); + + if (context.getExecution().isRequired()) { + context.getEvent() + .user(duplication.getExistingUserId()) + .detail("existing_" + duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue()) + .removeDetail(Details.AUTH_METHOD) + .removeDetail(Details.AUTH_TYPE) + .error(Errors.FEDERATED_IDENTITY_EXISTS); + } } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java index d6bf10fad3..ae28d3edc9 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpEmailVerificationAuthenticator.java @@ -14,6 +14,10 @@ import org.keycloak.authentication.authenticators.broker.util.SerializedBrokered import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; +import org.keycloak.events.Details; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; import org.keycloak.login.LoginFormsProvider; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; @@ -52,6 +56,15 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator String link = UriBuilder.fromUri(context.getActionUrl()) .queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY)) .build().toString(); + + EventBuilder event = context.getEvent().clone().event(EventType.SEND_IDENTITY_PROVIDER_LINK) + .user(existingUser) + .detail(Details.USERNAME, existingUser.getUsername()) + .detail(Details.EMAIL, existingUser.getEmail()) + .detail(Details.CODE_ID, clientSession.getId()) + .removeDetail(Details.AUTH_METHOD) + .removeDetail(Details.AUTH_TYPE); + long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction()); try { @@ -60,15 +73,11 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator .setUser(existingUser) .setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext) .sendConfirmIdentityBrokerLink(link, expiration); -// event.clone().event(EventType.SEND_RESET_PASSWORD) -// .user(user) -// .detail(Details.USERNAME, username) -// .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success(); + + event.success(); } catch (EmailException e) { -// event.clone().event(EventType.SEND_RESET_PASSWORD) -// .detail(Details.USERNAME, username) -// .user(user) -// .error(Errors.EMAIL_SEND_FAILED); + event.error(Errors.EMAIL_SEND_FAILED); + logger.error("Failed to send email to confirm identity broker linking", e); Response challenge = context.form() .setError(Messages.EMAIL_SENT_ERROR) diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java index b293d1bb43..5e732d8be7 100644 --- a/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/broker/IdpUsernamePasswordForm.java @@ -37,7 +37,7 @@ public class IdpUsernamePasswordForm extends UsernamePasswordForm { // Restore formData for the case of error setupForm(context, formData, existingUser); - return validatePassword(context, formData); + return validatePassword(context, existingUser, formData); } protected LoginFormsProvider setupForm(AuthenticationFlowContext context, MultivaluedMap formData, UserModel existingUser) { diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java index 294d220696..0506f2156b 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/AbstractUsernameFormAuthenticator.java @@ -71,12 +71,16 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth context.failureChallenge(AuthenticationFlowError.INVALID_USER, challengeResponse); return true; } + return false; + } + + public boolean enabledUser(AuthenticationFlowContext context, UserModel user) { if (!user.isEnabled()) { context.getEvent().user(user); context.getEvent().error(Errors.USER_DISABLED); Response challengeResponse = disabledUser(context); context.failureChallenge(AuthenticationFlowError.USER_DISABLED, challengeResponse); - return true; + return false; } if (context.getRealm().isBruteForceProtected()) { if (context.getProtector().isTemporarilyDisabled(context.getSession(), context.getRealm(), user.getUsername())) { @@ -84,13 +88,13 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth context.getEvent().error(Errors.USER_TEMPORARILY_DISABLED); Response challengeResponse = temporarilyDisabledUser(context); context.failureChallenge(AuthenticationFlowError.USER_TEMPORARILY_DISABLED, challengeResponse); - return true; + return false; } } - return false; + return true; } - public boolean validateUser(AuthenticationFlowContext context, MultivaluedMap inputData) { + public boolean validateUserAndPassword(AuthenticationFlowContext context, MultivaluedMap inputData) { String username = inputData.getFirst(AuthenticationManager.FORM_USERNAME); if (username == null) { context.getEvent().error(Errors.USER_NOT_FOUND); @@ -117,7 +121,18 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth return false; } - if (invalidUser(context, user)) return false; + if (invalidUser(context, user)){ + return false; + } + + if (!validatePassword(context, user, inputData)){ + return false; + } + + if(!enabledUser(context, user)){ + return false; + } + String rememberMe = inputData.getFirst("rememberMe"); boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on"); if (remember) { @@ -130,29 +145,27 @@ public abstract class AbstractUsernameFormAuthenticator extends AbstractFormAuth return true; } - public boolean validatePassword(AuthenticationFlowContext context, MultivaluedMap inputData) { + public boolean validatePassword(AuthenticationFlowContext context, UserModel user, MultivaluedMap inputData) { List credentials = new LinkedList<>(); String password = inputData.getFirst(CredentialRepresentation.PASSWORD); if (password == null || password.isEmpty()) { - if (context.getUser() != null) { - context.getEvent().user(context.getUser()); - } - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = invalidCredentials(context); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); - context.clearUser(); + invalidPassword(context, user); return false; } credentials.add(UserCredentialModel.password(password)); - boolean valid = context.getSession().users().validCredentials(context.getRealm(), context.getUser(), credentials); + boolean valid = context.getSession().users().validCredentials(context.getRealm(), user, credentials); if (!valid) { - context.getEvent().user(context.getUser()); - context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); - Response challengeResponse = invalidCredentials(context); - context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); - context.clearUser(); + invalidPassword(context, user); return false; } return true; } + + private void invalidPassword(AuthenticationFlowContext context, UserModel user) { + context.getEvent().user(user); + context.getEvent().error(Errors.INVALID_USER_CREDENTIALS); + Response challengeResponse = invalidCredentials(context); + context.failureChallenge(AuthenticationFlowError.INVALID_CREDENTIALS, challengeResponse); + context.clearUser(); + } } diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java index 70d9fd9902..74636384bc 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/browser/UsernamePasswordForm.java @@ -38,7 +38,7 @@ public class UsernamePasswordForm extends AbstractUsernameFormAuthenticator impl } protected boolean validateForm(AuthenticationFlowContext context, MultivaluedMap formData) { - return validateUser(context, formData) && validatePassword(context, formData); + return validateUserAndPassword(context, formData); } @Override diff --git a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java index 56b47bb585..955dfe4769 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/OIDCClientDescriptionConverter.java @@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.clientregistration.oidc.DescriptionConverter; import org.keycloak.util.JsonSerialization; import java.io.IOException; @@ -17,6 +18,8 @@ import java.io.IOException; */ public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory { + public static final String ID = "openid-connect"; + @Override public boolean isSupported(String description) { description = description.trim(); @@ -26,15 +29,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte @Override public ClientRepresentation convertToInternal(String description) { try { - OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class); - - ClientRepresentation client = new ClientRepresentation(); - client.setClientId(KeycloakModelUtils.generateId()); - client.setName(oidcRep.getClientName()); - client.setRedirectUris(oidcRep.getRedirectUris()); - client.setBaseUrl(oidcRep.getClientUri()); - - return client; + OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class); + return DescriptionConverter.toInternal(clientOIDC); } catch (IOException e) { throw new RuntimeException(e); } @@ -59,7 +55,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte @Override public String getId() { - return "openid-connect"; + return ID; } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java index 9b862eff8f..2250dd076f 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/LogoutEndpoint.java @@ -190,7 +190,7 @@ public class LogoutEndpoint { } private ClientModel authorizeClient() { - ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient(); + ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient(); if (client.isBearerOnly()) { throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 1fb3e4a727..34161d8b5c 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -145,7 +145,7 @@ public class TokenEndpoint { } private void checkClient() { - AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm); + AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event); client = clientAuth.getClient(); clientAuthAttributes = clientAuth.getClientAuthAttributes(); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java index 7de415c984..4a83ebddce 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/representations/OIDCClientRepresentation.java @@ -12,12 +12,48 @@ public class OIDCClientRepresentation { @JsonProperty("redirect_uris") private List redirectUris; + @JsonProperty("token_endpoint_auth_method") + private String tokenEndpointAuthMethod; + + @JsonProperty("grant_types") + private String grantTypes; + + @JsonProperty("response_types") + private String responseTypes; + @JsonProperty("client_name") private String clientName; @JsonProperty("client_uri") private String clientUri; + @JsonProperty("logo_uri") + private String logoUri; + + @JsonProperty("scope") + private String scope; + + @JsonProperty("contacts") + private String contacts; + + @JsonProperty("tos_uri") + private String tos_uri; + + @JsonProperty("policy_uri") + private String policy_uri; + + @JsonProperty("jwks_uri") + private String jwks_uri; + + @JsonProperty("jwks") + private String jwks; + + @JsonProperty("software_id") + private String softwareId; + + @JsonProperty("software_version") + private String softwareVersion; + public List getRedirectUris() { return redirectUris; } @@ -26,6 +62,30 @@ public class OIDCClientRepresentation { this.redirectUris = redirectUris; } + public String getTokenEndpointAuthMethod() { + return tokenEndpointAuthMethod; + } + + public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) { + this.tokenEndpointAuthMethod = tokenEndpointAuthMethod; + } + + public String getGrantTypes() { + return grantTypes; + } + + public void setGrantTypes(String grantTypes) { + this.grantTypes = grantTypes; + } + + public String getResponseTypes() { + return responseTypes; + } + + public void setResponseTypes(String responseTypes) { + this.responseTypes = responseTypes; + } + public String getClientName() { return clientName; } @@ -42,4 +102,76 @@ public class OIDCClientRepresentation { this.clientUri = clientUri; } + public String getLogoUri() { + return logoUri; + } + + public void setLogoUri(String logoUri) { + this.logoUri = logoUri; + } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + + public String getContacts() { + return contacts; + } + + public void setContacts(String contacts) { + this.contacts = contacts; + } + + public String getTos_uri() { + return tos_uri; + } + + public void setTos_uri(String tos_uri) { + this.tos_uri = tos_uri; + } + + public String getPolicy_uri() { + return policy_uri; + } + + public void setPolicy_uri(String policy_uri) { + this.policy_uri = policy_uri; + } + + public String getJwks_uri() { + return jwks_uri; + } + + public void setJwks_uri(String jwks_uri) { + this.jwks_uri = jwks_uri; + } + + public String getJwks() { + return jwks; + } + + public void setJwks(String jwks) { + this.jwks = jwks; + } + + public String getSoftwareId() { + return softwareId; + } + + public void setSoftwareId(String softwareId) { + this.softwareId = softwareId; + } + + public String getSoftwareVersion() { + return softwareVersion; + } + + public void setSoftwareVersion(String softwareVersion) { + this.softwareVersion = softwareVersion; + } + } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java index c017ba3019..a2d0d6c778 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/utils/AuthorizeClientUtil.java @@ -19,18 +19,8 @@ import javax.ws.rs.core.Response; */ public class AuthorizeClientUtil { - public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) { - AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow(); - String flowId = clientAuthFlow.getId(); - - AuthenticationProcessor processor = new AuthenticationProcessor(); - processor.setFlowId(flowId) - .setConnection(session.getContext().getConnection()) - .setEventBuilder(event) - .setRealm(realm) - .setSession(session) - .setUriInfo(session.getContext().getUri()) - .setRequest(session.getContext().getContextObject(HttpRequest.class)); + public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) { + AuthenticationProcessor processor = getAuthenticationProcessor(session, event); Response response = processor.authenticateClient(); if (response != null) { @@ -45,6 +35,24 @@ public class AuthorizeClientUtil { return new ClientAuthResult(client, processor.getClientAuthAttributes()); } + public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) { + RealmModel realm = session.getContext().getRealm(); + + AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow(); + String flowId = clientAuthFlow.getId(); + + AuthenticationProcessor processor = new AuthenticationProcessor(); + processor.setFlowId(flowId) + .setConnection(session.getContext().getConnection()) + .setEventBuilder(event) + .setRealm(realm) + .setSession(session) + .setUriInfo(session.getContext().getUri()) + .setRequest(session.getContext().getContextObject(HttpRequest.class)); + + return processor; + } + public static class ClientAuthResult { private final ClientModel client; diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java index a78f99e43e..26aba4b49d 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakContext.java @@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.UriInfo; +import java.net.URI; import java.util.Locale; /** @@ -29,6 +30,13 @@ public class DefaultKeycloakContext implements KeycloakContext { this.session = session; } + @Override + public URI getAuthServerUrl() { + UriInfo uri = getUri(); + KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class); + return keycloakApplication.getBaseUri(uri); + } + @Override public String getContextPath() { KeycloakApplication app = getContextObject(KeycloakApplication.class); diff --git a/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java new file mode 100644 index 0000000000..feffc5f495 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProvider.java @@ -0,0 +1,92 @@ +package org.keycloak.services.clientregistration; + +import org.jboss.resteasy.spi.UnauthorizedException; +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.core.MediaType; +import javax.ws.rs.core.Response; + +/** + * @author Stian Thorgersen + */ +public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider { + + private KeycloakSession session; + private EventBuilder event; + private ClientRegAuth auth; + + public AdapterInstallationClientRegistrationProvider(KeycloakSession session) { + this.session = session; + } + + @GET + @Path("{clientId}") + @Produces(MediaType.APPLICATION_JSON) + public Response get(@PathParam("clientId") String clientId) { + event.event(EventType.CLIENT_INFO); + + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + + if (auth.isAuthenticated()) { + auth.requireView(client); + } else { + authenticateClient(client); + } + + ClientManager clientManager = new ClientManager(new RealmManager(session)); + Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl()); + + event.client(client.getClientId()).success(); + return Response.ok(rep).build(); + } + + @Override + public void setAuth(ClientRegAuth auth) { + this.auth = auth; + } + + @Override + public void setEvent(EventBuilder event) { + this.event = event; + } + + @Override + 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/AdapterInstallationClientRegistrationProviderFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProviderFactory.java new file mode 100644 index 0000000000..4b9700cd0c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/AdapterInstallationClientRegistrationProviderFactory.java @@ -0,0 +1,34 @@ +package org.keycloak.services.clientregistration; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; + +/** + * @author Stian Thorgersen + */ +public class AdapterInstallationClientRegistrationProviderFactory implements ClientRegistrationProviderFactory { + + @Override + public ClientRegistrationProvider create(KeycloakSession session) { + return new AdapterInstallationClientRegistrationProvider(session); + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + } + + @Override + public void close() { + } + + @Override + public String getId() { + return "install"; + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java new file mode 100644 index 0000000000..496434993c --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegAuth.java @@ -0,0 +1,128 @@ +package org.keycloak.services.clientregistration; + +import org.jboss.resteasy.spi.UnauthorizedException; +import org.keycloak.events.Errors; +import org.keycloak.events.EventBuilder; +import org.keycloak.models.*; +import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; +import org.keycloak.representations.AccessToken; +import org.keycloak.services.ForbiddenException; +import org.keycloak.services.managers.AppAuthManager; +import org.keycloak.services.managers.AuthenticationManager; + +import javax.ws.rs.core.HttpHeaders; + +/** + * @author Stian Thorgersen + */ +public class ClientRegAuth { + + private KeycloakSession session; + private EventBuilder event; + + private String token; + private AccessToken.Access bearerRealmAccess; + + private boolean authenticated = false; + + public ClientRegAuth(KeycloakSession session, EventBuilder event) { + this.session = session; + this.event = event; + + init(); + } + + private void init() { + RealmModel realm = session.getContext().getRealm(); + + String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); + if (authorizationHeader == null) { + return; + } + + String[] split = authorizationHeader.split(" "); + if (!split[0].equalsIgnoreCase("bearer")) { + return; + } + + if (split[1].indexOf('.') == -1) { + token = split[1]; + authenticated = true; + } else { + AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm); + bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID); + authenticated = true; + } + } + + public boolean isAuthenticated() { + return authenticated; + } + + public void requireCreate() { + if (!authenticated) { + event.error(Errors.NOT_ALLOWED); + throw new UnauthorizedException(); + } + + if (bearerRealmAccess != null) { + if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) { + return; + } + } + + event.error(Errors.NOT_ALLOWED); + throw new ForbiddenException(); + } + + public void requireView(ClientModel client) { + if (!authenticated) { + event.error(Errors.NOT_ALLOWED); + throw new UnauthorizedException(); + } + + if (client == null) { + event.error(Errors.NOT_ALLOWED); + throw new ForbiddenException(); + } + + if (bearerRealmAccess != null) { + if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) { + return; + } + } else if (token != null) { + if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) { + return; + } + } + + event.error(Errors.NOT_ALLOWED); + throw new ForbiddenException(); + } + + public void requireUpdate(ClientModel client) { + if (!authenticated) { + event.error(Errors.NOT_ALLOWED); + throw new UnauthorizedException(); + } + + if (client == null) { + event.error(Errors.NOT_ALLOWED); + throw new ForbiddenException(); + } + + if (bearerRealmAccess != null) { + if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) { + return; + } + } else if (token != null) { + if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) { + return; + } + } + + event.error(Errors.NOT_ALLOWED); + throw new ForbiddenException(); + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java index d1d664868a..0c6bc4e1b7 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationProvider.java @@ -9,7 +9,7 @@ import org.keycloak.provider.Provider; */ public interface ClientRegistrationProvider extends Provider { - void setRealm(RealmModel realm); + void setAuth(ClientRegAuth auth); void setEvent(EventBuilder event); diff --git a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java index 8b215face8..ea508def4f 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/ClientRegistrationService.java @@ -2,36 +2,43 @@ package org.keycloak.services.clientregistration; import org.keycloak.events.EventBuilder; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.services.managers.AppAuthManager; +import org.keycloak.services.ErrorResponseException; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; /** * @author Stian Thorgersen */ public class ClientRegistrationService { - private RealmModel realm; - private EventBuilder event; @Context private KeycloakSession session; - public ClientRegistrationService(RealmModel realm, EventBuilder event) { - this.realm = realm; + public ClientRegistrationService(EventBuilder event) { this.event = event; } @Path("{provider}") public Object getProvider(@PathParam("provider") String providerId) { + checkSsl(); + ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId); - provider.setRealm(realm); provider.setEvent(event); + provider.setAuth(new ClientRegAuth(session, event)); return provider; } + private void checkSsl() { + if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) { + if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) { + throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN); + } + } + } + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java index 04cb46a69c..603ed089da 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/DefaultClientRegistrationProvider.java @@ -1,22 +1,16 @@ package org.keycloak.services.clientregistration; -import org.jboss.logging.Logger; -import org.keycloak.events.Errors; import org.keycloak.events.EventBuilder; import org.keycloak.events.EventType; -import org.keycloak.models.*; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.models.utils.RepresentationToModel; -import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; -import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.services.ErrorResponse; -import org.keycloak.services.ForbiddenException; -import org.keycloak.services.managers.AppAuthManager; -import org.keycloak.services.managers.AuthenticationManager; import javax.ws.rs.*; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.net.URI; @@ -26,31 +20,29 @@ import java.net.URI; */ public class DefaultClientRegistrationProvider implements ClientRegistrationProvider { - private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class); - private KeycloakSession session; private EventBuilder event; - private RealmModel realm; + private ClientRegAuth auth; public DefaultClientRegistrationProvider(KeycloakSession session) { this.session = session; } - @POST @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) public Response create(ClientRepresentation client) { event.event(EventType.CLIENT_REGISTER); - authenticate(true, null); + auth.requireCreate(); try { - ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true); + ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); + clientModel.setRegistrationSecret(TokenGenerator.createRegistrationAccessToken()); + client = ModelToRepresentation.toRepresentation(clientModel); URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build(); - logger.infov("Created client {0}", client.getClientId()); - event.client(client.getClientId()).success(); return Response.created(uri).entity(client).build(); } catch (ModelDuplicateException e) { @@ -64,10 +56,10 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv public Response get(@PathParam("clientId") String clientId) { event.event(EventType.CLIENT_INFO); - ClientModel client = authenticate(false, clientId); - if (client == null) { - return Response.status(Response.Status.NOT_FOUND).build(); - } + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireView(client); + + event.client(client.getClientId()).success(); return Response.ok(ModelToRepresentation.toRepresentation(client)).build(); } @@ -77,12 +69,12 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) { event.event(EventType.CLIENT_UPDATE).client(clientId); - ClientModel client = authenticate(false, clientId); + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireUpdate(client); + RepresentationToModel.updateClient(rep, client); - logger.infov("Updated client {0}", rep.getClientId()); - - event.success(); + event.client(client.getClientId()).success(); return Response.status(Response.Status.OK).build(); } @@ -91,9 +83,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv public Response delete(@PathParam("clientId") String clientId) { event.event(EventType.CLIENT_DELETE).client(clientId); - ClientModel client = authenticate(false, clientId); - if (realm.removeClient(client.getId())) { - event.success(); + ClientModel client = session.getContext().getRealm().getClientByClientId(clientId); + auth.requireUpdate(client); + + if (session.getContext().getRealm().removeClient(client.getId())) { + event.client(client.getClientId()).success(); return Response.ok().build(); } else { return Response.status(Response.Status.NOT_FOUND).build(); @@ -101,50 +95,8 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv } @Override - public void close() { - - } - - - private ClientModel authenticate(boolean create, String clientId) { - String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION); - - boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer"); - - if (bearer) { - AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm); - AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID); - if (realmAccess != null) { - if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) { - return create ? null : realm.getClientByClientId(clientId); - } - - if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) { - return create ? null : realm.getClientByClientId(clientId); - } - } - } else if (!create) { - ClientModel client; - - try { - AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm); - client = clientAuth.getClient(); - - if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) { - return client; - } - } catch (Throwable t) { - } - } - - event.error(Errors.NOT_ALLOWED); - - throw new ForbiddenException(); - } - - @Override - public void setRealm(RealmModel realm) { -this.realm = realm; + public void setAuth(ClientRegAuth auth) { + this.auth = auth; } @Override @@ -152,4 +104,8 @@ this.realm = realm; this.event = event; } + @Override + public void close() { + } + } diff --git a/services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProvider.java deleted file mode 100644 index baf9df1a7e..0000000000 --- a/services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProvider.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.keycloak.services.clientregistration; - -import org.keycloak.events.EventBuilder; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; - -/** - * @author Stian Thorgersen - */ -public class OIDCClientRegistrationProvider implements ClientRegistrationProvider { - - private KeycloakSession session; - private RealmModel realm; - private EventBuilder event; - - public OIDCClientRegistrationProvider(KeycloakSession session) { - this.session = session; - } - - @Override - public void close() { - } - - @Override - public void setRealm(RealmModel realm) { - this.realm = realm; - } - - @Override - public void setEvent(EventBuilder event) { - this.event = event; - } - -} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java b/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java new file mode 100644 index 0000000000..3c49d0f0ea --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/TokenGenerator.java @@ -0,0 +1,27 @@ +package org.keycloak.services.clientregistration; + +import org.keycloak.common.util.Base64Url; + +import java.security.SecureRandom; + +/** + * @author Stian Thorgersen + */ +public class TokenGenerator { + + private static final int REGISTRATION_ACCESS_TOKEN_BYTES = 32; + + private TokenGenerator() { + } + + public String createInitialAccessToken() { + return null; + } + + public static String createRegistrationAccessToken() { + byte[] buf = new byte[REGISTRATION_ACCESS_TOKEN_BYTES]; + new SecureRandom().nextBytes(buf); + return Base64Url.encode(buf); + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java new file mode 100644 index 0000000000..1e0784c288 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/DescriptionConverter.java @@ -0,0 +1,38 @@ +package org.keycloak.services.clientregistration.oidc; + +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; + +/** + * @author Stian Thorgersen + */ +public class DescriptionConverter { + + public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) { + ClientRepresentation client = new ClientRepresentation(); + client.setClientId(KeycloakModelUtils.generateId()); + client.setName(clientOIDC.getClientName()); + client.setRedirectUris(clientOIDC.getRedirectUris()); + client.setBaseUrl(clientOIDC.getClientUri()); + return client; + } + + public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) { + OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation(); + response.setClientId(client.getClientId()); + + response.setClientName(client.getName()); + response.setClientUri(client.getBaseUrl()); + + response.setClientSecret(client.getSecret()); + response.setClientSecretExpiresAt(0); + + response.setRedirectUris(client.getRedirectUris()); + + response.setRegistrationAccessToken(client.getRegistrationAccessToken()); + + return response; + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java new file mode 100644 index 0000000000..4d131cc913 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProvider.java @@ -0,0 +1,100 @@ +package org.keycloak.services.clientregistration.oidc; + +import org.jboss.logging.Logger; +import org.keycloak.common.util.Time; +import org.keycloak.events.EventBuilder; +import org.keycloak.events.EventType; +import org.keycloak.models.ClientModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter; +import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.services.ErrorResponse; +import org.keycloak.services.clientregistration.ClientRegAuth; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.TokenGenerator; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.net.URI; + +/** + * @author Stian Thorgersen + */ +public class OIDCClientRegistrationProvider implements ClientRegistrationProvider { + + private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class); + + private KeycloakSession session; + private EventBuilder event; + private ClientRegAuth auth; + + public OIDCClientRegistrationProvider(KeycloakSession session) { + this.session = session; + } + +// @POST +// @Consumes(MediaType.APPLICATION_JSON) +// @Produces(MediaType.APPLICATION_JSON) +// public Response create(OIDCClientRepresentation clientOIDC) { +// event.event(EventType.CLIENT_REGISTER); +// +// auth.requireCreate(); +// +// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC); +// +// try { +// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true); +// +// client = ModelToRepresentation.toRepresentation(clientModel); +// +// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken(); +// +// clientModel.setRegistrationSecret(registrationAccessToken); +// +// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build(); +// +// logger.infov("Created client {0}", client.getClientId()); +// +// event.client(client.getClientId()).success(); +// +// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client); +// +// response.setClientName(client.getName()); +// response.setClientUri(client.getBaseUrl()); +// +// response.setClientSecret(client.getSecret()); +// response.setClientSecretExpiresAt(0); +// +// response.setRedirectUris(client.getRedirectUris()); +// +// response.setRegistrationAccessToken(registrationAccessToken); +// response.setRegistrationClientUri(uri.toString()); +// +// return Response.created(uri).entity(response).build(); +// } catch (ModelDuplicateException e) { +// return ErrorResponse.exists("Client " + client.getClientId() + " already exists"); +// } +// } + + @Override + public void close() { + } + + @Override + public void setAuth(ClientRegAuth auth) { + this.auth = auth; + } + + @Override + public void setEvent(EventBuilder event) { + this.event = event; + } + +} diff --git a/services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProviderFactory.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java similarity index 77% rename from services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProviderFactory.java rename to services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java index a3ba0005d4..6f112f8816 100644 --- a/services/src/main/java/org/keycloak/services/clientregistration/OIDCClientRegistrationProviderFactory.java +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientRegistrationProviderFactory.java @@ -1,8 +1,10 @@ -package org.keycloak.services.clientregistration; +package org.keycloak.services.clientregistration.oidc; import org.keycloak.Config; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.services.clientregistration.ClientRegistrationProvider; +import org.keycloak.services.clientregistration.ClientRegistrationProviderFactory; /** * @author Stian Thorgersen diff --git a/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java new file mode 100644 index 0000000000..cd4eea4949 --- /dev/null +++ b/services/src/main/java/org/keycloak/services/clientregistration/oidc/OIDCClientResponseRepresentation.java @@ -0,0 +1,77 @@ +package org.keycloak.services.clientregistration.oidc; + +import org.codehaus.jackson.annotate.JsonProperty; +import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation; + +/** + * @author Stian Thorgersen + */ +public class OIDCClientResponseRepresentation extends OIDCClientRepresentation { + + @JsonProperty("client_id") + private String clientId; + + @JsonProperty("client_secret") + private String clientSecret; + + @JsonProperty("client_id_issued_at") + private int clientIdIssuedAt; + + @JsonProperty("client_secret_expires_at") + private int clientSecretExpiresAt; + + @JsonProperty("registration_client_uri") + private String registrationClientUri; + + @JsonProperty("registration_access_token") + private String registrationAccessToken; + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + + public int getClientIdIssuedAt() { + return clientIdIssuedAt; + } + + public void setClientIdIssuedAt(int clientIdIssuedAt) { + this.clientIdIssuedAt = clientIdIssuedAt; + } + + public int getClientSecretExpiresAt() { + return clientSecretExpiresAt; + } + + public void setClientSecretExpiresAt(int clientSecretExpiresAt) { + this.clientSecretExpiresAt = clientSecretExpiresAt; + } + + public String getRegistrationClientUri() { + return registrationClientUri; + } + + public void setRegistrationClientUri(String registrationClientUri) { + this.registrationClientUri = registrationClientUri; + } + + public String getRegistrationAccessToken() { + return registrationAccessToken; + } + + public void setRegistrationAccessToken(String registrationAccessToken) { + this.registrationAccessToken = registrationAccessToken; + } + +} diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 71f849b6d9..af24034223 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -61,6 +61,7 @@ import org.keycloak.services.messages.Messages; import org.keycloak.services.util.ResolveRelative; import org.keycloak.services.validation.Validation; import org.keycloak.common.util.UriUtils; +import org.keycloak.util.JsonSerialization; import javax.ws.rs.Consumes; import javax.ws.rs.GET; @@ -74,6 +75,8 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.Variant; + +import java.io.IOException; import java.lang.reflect.Method; import java.net.URI; import java.util.HashSet; @@ -116,6 +119,9 @@ public class AccountService extends AbstractSecuredLocalService { public static final String KEYCLOAK_STATE_CHECKER = "KEYCLOAK_STATE_CHECKER"; + // Used when some other context (ie. IdentityBrokerService) wants to forward error to account management and display it here + public static final String ACCOUNT_MGMT_FORWARDED_ERROR_NOTE = "ACCOUNT_MGMT_FORWARDED_ERROR"; + private final AppAuthManager authManager; private EventBuilder event; private AccountProvider account; @@ -217,6 +223,17 @@ public class AccountService extends AbstractSecuredLocalService { setReferrerOnPage(); + String forwardedError = auth.getClientSession().getNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE); + if (forwardedError != null) { + try { + FormMessage errorMessage = JsonSerialization.readValue(forwardedError, FormMessage.class); + account.setError(errorMessage.getMessage(), errorMessage.getParameters()); + auth.getClientSession().removeNote(ACCOUNT_MGMT_FORWARDED_ERROR_NOTE); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + } + return account.createResponse(page); } else { return login(path); @@ -724,7 +741,9 @@ public class AccountService extends AbstractSecuredLocalService { logger.debugv("Social provider {0} removed successfully from user {1}", providerId, user.getUsername()); event.event(EventType.REMOVE_FEDERATED_IDENTITY).client(auth.getClient()).user(auth.getUser()) - .detail(Details.USERNAME, link.getUserId() + "@" + link.getIdentityProvider()) + .detail(Details.USERNAME, auth.getUser().getUsername()) + .detail(Details.IDENTITY_PROVIDER, link.getIdentityProvider()) + .detail(Details.IDENTITY_PROVIDER_USERNAME, link.getUserName()) .success(); setReferrerOnPage(); diff --git a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java index ddb301df7c..a93371282c 100755 --- a/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java +++ b/services/src/main/java/org/keycloak/services/resources/ClientsManagementService.java @@ -153,7 +153,7 @@ public class ClientsManagementService { } protected ClientModel authorizeClient() { - ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient(); + ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient(); if (client.isPublicClient()) { Map error = new HashMap(); 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 fda37efc00..5912536c11 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -65,6 +65,7 @@ import org.keycloak.services.Urls; import org.keycloak.services.validation.Validation; import org.keycloak.social.SocialIdentityProvider; import org.keycloak.common.util.ObjectUtil; +import org.keycloak.util.JsonSerialization; import javax.ws.rs.*; import javax.ws.rs.core.Context; @@ -74,6 +75,7 @@ import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; @@ -368,6 +370,13 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal context.getUsername(), context.getToken()); session.users().addFederatedIdentity(realmModel, federatedUser, federatedIdentityModel); + EventBuilder event = this.event.clone().user(federatedUser) + .detail(Details.CODE_ID, clientSession.getId()) + .detail(Details.USERNAME, federatedUser.getUsername()) + .detail(Details.IDENTITY_PROVIDER, providerId) + .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername()) + .removeDetail("auth_method"); + String isRegisteredNewUser = clientSession.getNote(AbstractIdpAuthenticator.BROKER_REGISTERED_NEW_USER); if (Boolean.parseBoolean(isRegisteredNewUser)) { @@ -388,15 +397,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal federatedUser.setEmailVerified(true); } - this.event.clone().user(federatedUser).event(EventType.REGISTER) - .detail(Details.IDENTITY_PROVIDER, providerId) - .detail(Details.IDENTITY_PROVIDER_USERNAME, context.getUsername()) - .removeDetail("auth_method") + event.event(EventType.REGISTER) + .detail(Details.REGISTER_METHOD, "broker") + .detail(Details.EMAIL, federatedUser.getEmail()) .success(); } else { LOGGER.debugf("Linked existing keycloak user '%s' with identity provider '%s' . Identity provider username is '%s' .", federatedUser.getUsername(), providerId, context.getUsername()); + event.event(EventType.FEDERATED_IDENTITY_LINK) + .success(); + updateFederatedIdentity(context, federatedUser); } @@ -410,7 +421,6 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return finishBrokerAuthentication(context, federatedUser, clientSession, providerId); } } catch (Exception e) { - // TODO? return redirectToErrorPage(Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR, e); } } @@ -453,10 +463,10 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } private Response performAccountLinking(ClientSessionModel clientSession, BrokeredIdentityContext context, FederatedIdentityModel federatedIdentityModel, UserModel federatedUser) { - this.event.event(EventType.IDENTITY_PROVIDER_ACCCOUNT_LINKING); + this.event.event(EventType.FEDERATED_IDENTITY_LINK); if (federatedUser != null) { - return redirectToErrorPage(Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias()); + return redirectToAccountErrorPage(clientSession, Messages.IDENTITY_PROVIDER_ALREADY_LINKED, context.getIdpConfig().getAlias()); } UserModel authenticatedUser = clientSession.getUserSession().getUser(); @@ -466,19 +476,21 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } if (!authenticatedUser.isEnabled()) { - fireErrorEvent(Errors.USER_DISABLED); - return redirectToErrorPage(Messages.ACCOUNT_DISABLED); + return redirectToAccountErrorPage(clientSession, Messages.ACCOUNT_DISABLED); } if (!authenticatedUser.hasRole(this.realmModel.getClientByClientId(ACCOUNT_MANAGEMENT_CLIENT_ID).getRole(MANAGE_ACCOUNT))) { - fireErrorEvent(Errors.NOT_ALLOWED); return redirectToErrorPage(Messages.INSUFFICIENT_PERMISSION); } this.session.users().addFederatedIdentity(this.realmModel, authenticatedUser, federatedIdentityModel); context.getIdp().attachUserSession(clientSession.getUserSession(), clientSession, context); - this.event.success(); + this.event.user(authenticatedUser) + .detail(Details.USERNAME, authenticatedUser.getUsername()) + .detail(Details.IDENTITY_PROVIDER, federatedIdentityModel.getIdentityProvider()) + .detail(Details.IDENTITY_PROVIDER_USERNAME, federatedIdentityModel.getUserName()) + .success(); return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build(); } @@ -568,6 +580,20 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal return ErrorPage.error(this.session, message, parameters); } + private Response redirectToAccountErrorPage(ClientSessionModel clientSession, String message, Object ... parameters) { + fireErrorEvent(message); + + FormMessage errorMessage = new FormMessage(message, parameters); + try { + String serializedError = JsonSerialization.writeValueAsString(errorMessage); + clientSession.setNote(AccountService.ACCOUNT_MGMT_FORWARDED_ERROR_NOTE, serializedError); + } catch (IOException ioe) { + throw new RuntimeException(ioe); + } + + return Response.status(302).location(UriBuilder.fromUri(clientSession.getRedirectUri()).build()).build(); + } + private Response redirectToLoginPage(Throwable t, ClientSessionCode clientCode) { String message = t.getMessage(); diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index 8f845c051f..7f15d2d025 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -509,6 +509,10 @@ public class LoginActionsService { BrokeredIdentityContext brokerContext = serializedCtx.deserialize(session, clientSession); AuthenticationFlowModel firstBrokerLoginFlow = realm.getAuthenticationFlowById(brokerContext.getIdpConfig().getFirstBrokerLoginFlowId()); + event.detail(Details.IDENTITY_PROVIDER, brokerContext.getIdpConfig().getAlias()) + .detail(Details.IDENTITY_PROVIDER_USERNAME, brokerContext.getUsername()); + + AuthenticationProcessor processor = new AuthenticationProcessor() { @Override diff --git a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java index f18f000fd2..4b5e4f201b 100755 --- a/services/src/main/java/org/keycloak/services/resources/RealmsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/RealmsResource.java @@ -113,11 +113,11 @@ public class RealmsResource { return service; } - @Path("{realm}/client-registration") + @Path("{realm}/clients") public ClientRegistrationService getClientsService(final @PathParam("realm") String name) { RealmModel realm = init(name); EventBuilder event = new EventBuilder(realm, session, clientConnection); - ClientRegistrationService service = new ClientRegistrationService(realm, event); + ClientRegistrationService service = new ClientRegistrationService(event); ResteasyProviderFactory.getInstance().injectProperties(service); return service; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 5d76778196..17a16f50c3 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -50,6 +50,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; +import static java.lang.Boolean.TRUE; + /** * Base resource class for managing one particular client of a realm. @@ -103,7 +105,7 @@ public class ClientResource { auth.requireManage(); try { - if (rep.isServiceAccountsEnabled() && !client.isServiceAccountsEnabled()) { + if (TRUE.equals(rep.isServiceAccountsEnabled()) && !client.isServiceAccountsEnabled()) { new ClientManager(new RealmManager(session)).enableServiceAccount(client);; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java index 6c973e10c7..87253c103f 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/GroupResource.java @@ -10,6 +10,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -19,12 +20,15 @@ import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.net.URI; +import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -256,6 +260,42 @@ public class GroupResource { } + /** + * Get users + * + * Returns a list of users, filtered according to query parameters + * + * @param firstResult Pagination offset + * @param maxResults Pagination size + * @return + */ + @GET + @NoCache + @Path("{id}/members") + @Produces(MediaType.APPLICATION_JSON) + public List getMembers(@PathParam("id") String id, + @QueryParam("first") Integer firstResult, + @QueryParam("max") Integer maxResults) { + auth.requireView(); + + GroupModel group = session.realms().getGroupById(id, realm); + if (group == null) { + throw new NotFoundException("Group not found"); + } + + firstResult = firstResult != null ? firstResult : -1; + maxResults = maxResults != null ? maxResults : -1; + + List results = new ArrayList(); + List userModels = session.users().getGroupMembers(realm, group, firstResult, maxResults); + + for (UserModel user : userModels) { + results.add(ModelToRepresentation.toRepresentation(user)); + } + return results; + } + + } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 9ba114c778..a85c9d2bfd 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -17,6 +17,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.ModelDuplicateException; @@ -36,6 +37,7 @@ import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.idm.ClientMappingsRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.MappingsRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; @@ -676,7 +678,7 @@ public class UsersResource { } - /** + /** * Set up a temporary password for the user * * User will have to reset the temporary password next time they log in. @@ -911,4 +913,57 @@ public class UsersResource { return clientSession; } + @GET + @Path("{id}/groups") + @NoCache + @Produces(MediaType.APPLICATION_JSON) + public List groupMembership(@PathParam("id") String id) { + auth.requireView(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + List memberships = new LinkedList<>(); + for (GroupModel group : user.getGroups()) { + memberships.add(ModelToRepresentation.toRepresentation(group, false)); + } + return memberships; + } + + @DELETE + @Path("{id}/groups/{groupId}") + @NoCache + public void removeMembership(@PathParam("id") String id, @PathParam("groupId") String groupId) { + auth.requireManage(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + GroupModel group = session.realms().getGroupById(groupId, realm); + if (group == null) { + throw new NotFoundException("Group not found"); + } + if (user.isMemberOf(group)) user.leaveGroup(group); + } + + @PUT + @Path("{id}/groups/{groupId}") + @NoCache + public void joinGroup(@PathParam("id") String id, @PathParam("groupId") String groupId) { + auth.requireManage(); + + UserModel user = session.users().getUserById(id, realm); + if (user == null) { + throw new NotFoundException("User not found"); + } + GroupModel group = session.realms().getGroupById(groupId, realm); + if (group == null) { + throw new NotFoundException("Group not found"); + } + if (!user.isMemberOf(group)) user.joinGroup(group); + } + + } diff --git a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory index 3e8773a31a..d9b8c4159d 100644 --- a/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory +++ b/services/src/main/resources/META-INF/services/org.keycloak.services.clientregistration.ClientRegistrationProviderFactory @@ -1 +1,3 @@ -org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory \ No newline at end of file +org.keycloak.services.clientregistration.DefaultClientRegistrationProviderFactory +org.keycloak.services.clientregistration.oidc.OIDCClientRegistrationProviderFactory +org.keycloak.services.clientregistration.AdapterInstallationClientRegistrationProviderFactory \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 6317df80ba..5c409ab304 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -15,7 +15,18 @@ - - - + + + org.keycloak + keycloak-util-embedded-ldap + + + bouncycastle + bcprov-jdk15 + + + + diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java index 4fd57baa4c..852133be36 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/AdminConsoleRealm.java @@ -57,7 +57,7 @@ public class AdminConsoleRealm extends AdminConsoleRealmsRoot { private WebElement rolesLink; @FindBy(partialLinkText = "Identity Providers") private WebElement identityProvidersLink; - @FindBy(partialLinkText = "User Feferation") + @FindBy(partialLinkText = "User Federation") private WebElement userFederationLink; @FindBy(partialLinkText = "Authentication") private WebElement authenticationLink; diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java index 0ea6af2b51..b6f182a5b4 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/authentication/RequiredActions.java @@ -7,11 +7,17 @@ import org.openqa.selenium.support.FindBy; /** * @author tkyjovsk * @author mhajas + * @author Vaclav Muzikar */ public class RequiredActions extends Authentication { - public final static String ENABLED = "enabled"; - public final static String DEFAULT_ACTION = "defaultAction"; + public final static String ENABLED = ".enabled"; + public final static String DEFAULT = ".defaultAction"; + public final static String CONFIGURE_TOTP = "CONFIGURE_TOTP"; + public final static String UPDATE_PROFILE = "UPDATE_PROFILE"; + public final static String TERMS_AND_CONDITIONS = "terms_and_conditions"; + public final static String UPDATE_PASSWORD = "UPDATE_PASSWORD"; + public final static String VERIFY_EMAIL = "VERIFY_EMAIL"; @FindBy(tagName = "table") private WebElement requiredActionTable; @@ -21,51 +27,59 @@ public class RequiredActions extends Authentication { return super.getUriFragment() + "/required-actions"; } - private void setRequiredActionValue(String row, String column, boolean value) { - WebElement checkbox = requiredActionTable.findElement(By.xpath("//td[text()='" + row + "']/..//input[@ng-model='requiredAction." + column + "']")); + private void setRequiredActionValue(String id, boolean value) { + WebElement checkbox = requiredActionTable.findElement(By.id(id)); if (checkbox.isSelected() != value) { checkbox.click(); } } + private void setRequiredActionEnabledValue(String id, boolean value) { + setRequiredActionValue(id + ENABLED, value); + } + + private void setRequiredActionDefaultValue(String id, boolean value) { + setRequiredActionValue(id + DEFAULT, value); + } + public void setTermsAndConditionEnabled(boolean value) { - setRequiredActionValue("Terms and Conditions", ENABLED, value); + setRequiredActionEnabledValue(TERMS_AND_CONDITIONS, value); } public void setTermsAndConditionDefaultAction(boolean value) { - setRequiredActionValue("Terms and Conditions", DEFAULT_ACTION, value); + setRequiredActionDefaultValue(TERMS_AND_CONDITIONS, value); } public void setVerifyEmailEnabled(boolean value) { - setRequiredActionValue("Verify Email", ENABLED, value); + setRequiredActionEnabledValue(VERIFY_EMAIL, value); } public void setVerifyEmailDefaultAction(boolean value) { - setRequiredActionValue("Verify Email", DEFAULT_ACTION, value); + setRequiredActionDefaultValue(VERIFY_EMAIL, value); } public void setUpdatePasswordEnabled(boolean value) { - setRequiredActionValue("Update Password", ENABLED, value); + setRequiredActionEnabledValue(UPDATE_PASSWORD, value); } public void setUpdatePasswordDefaultAction(boolean value) { - setRequiredActionValue("Update Password", DEFAULT_ACTION, value); + setRequiredActionDefaultValue(UPDATE_PASSWORD, value); } public void setConfigureTotpEnabled(boolean value) { - setRequiredActionValue("Configure Totp", ENABLED, value); + setRequiredActionEnabledValue(CONFIGURE_TOTP, value); } public void setConfigureTotpDefaultAction(boolean value) { - setRequiredActionValue("Configure Totp", DEFAULT_ACTION, value); + setRequiredActionDefaultValue(CONFIGURE_TOTP, value); } public void setUpdateProfileEnabled(boolean value) { - setRequiredActionValue("Update Profile", ENABLED, value); + setRequiredActionEnabledValue(UPDATE_PROFILE, value); } public void setUpdateProfileDefaultAction(boolean value) { - setRequiredActionValue("Update Profile", DEFAULT_ACTION, value); + setRequiredActionDefaultValue(UPDATE_PROFILE, value); } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java index d56dca5482..10fb7e5f89 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/ClientMappers.java @@ -1,14 +1,112 @@ package org.keycloak.testsuite.console.page.clients; +import org.keycloak.representations.idm.ProtocolMapperRepresentation; +import org.keycloak.testsuite.console.page.fragment.DataTable; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +import java.util.ArrayList; +import java.util.List; + /** * * @author tkyjovsk + * @author Vaclav Muzikar */ public class ClientMappers extends Client { + public static final String ADD_BUILTIN = "Add Builtin"; + + @FindBy(tagName = "table") + private ClientMapperTable table; + @Override public String getUriFragment() { return super.getUriFragment() + "/mappers"; } + public ClientMapperTable mapperTable() { + return table; + } + + public class ClientMapperTable extends DataTable { + + public List searchMappings(String searchPattern) { + search(searchPattern); + return getMappingsFromRows(); + } + + public void createMapper() { + waitAjaxForBody(); + clickHeaderLink(CREATE); + } + + public void addBuiltin() { + waitAjaxForBody(); + clickHeaderLink(ADD_BUILTIN); + } + + public void clickMapper(String mapperName) { + waitAjaxForBody(); + body().findElement(By.linkText(mapperName)).click(); + } + + public void clickMapper(ProtocolMapperRepresentation mapper) { + clickMapper(mapper.getName()); + } + + private void clickMapperActionButton(String mapperName, String buttonText) { + waitAjaxForBody(); + clickRowActionButton(getRowByLinkText(mapperName), buttonText); + } + + private void clickMapperActionButton(ProtocolMapperRepresentation mapper, String buttonName) { + clickMapperActionButton(mapper.getName(), buttonName); + } + + public void editMapper(String mapperName) { + clickMapperActionButton(mapperName, EDIT); + } + + public void editMapper(ProtocolMapperRepresentation mapper) { + clickMapperActionButton(mapper, EDIT); + } + + public void deleteMapper(String mapperName) { + clickMapperActionButton(mapperName, DELETE); + } + + public void deleteMapper(ProtocolMapperRepresentation mapper) { + clickMapperActionButton(mapper, DELETE); + } + + public ProtocolMapperRepresentation getMappingFromRow(WebElement row) { + if (!row.isDisplayed()) {return null;} // Is that necessary? + + ProtocolMapperRepresentation mappingsRepresentation = new ProtocolMapperRepresentation(); + List cols = row.findElements(By.tagName("td")); + + + mappingsRepresentation.setName(cols.get(0).getText()); + //mappingsRepresentation.setProtocol(cols.get(1).getText()); + mappingsRepresentation.setProtocolMapper(cols.get(2).getText()); + + return mappingsRepresentation; + } + + public List getMappingsFromRows() { + List mappings = new ArrayList(); + + for (WebElement row : rows()) { + ProtocolMapperRepresentation mapperRepresentation = getMappingFromRow(row); + if (mapperRepresentation != null) { + mappings.add(mapperRepresentation); + } + } + + return mappings; + } + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java new file mode 100644 index 0000000000..962e7a51a1 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappers.java @@ -0,0 +1,21 @@ +package org.keycloak.testsuite.console.page.clients; + +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.AdminConsoleCreate; + +/** + * @author Vaclav Muzikar + */ +public class CreateClientMappers extends AdminConsoleCreate { + + @Page + private CreateClientMappersForm form; + + public CreateClientMappers() { + setEntity("mappers"); + } + + public CreateClientMappersForm form() { + return form; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java new file mode 100644 index 0000000000..900b4d207a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/clients/CreateClientMappersForm.java @@ -0,0 +1,187 @@ +package org.keycloak.testsuite.console.page.clients; + +import org.jboss.arquillian.test.api.ArquillianResource; +import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; +import org.keycloak.testsuite.page.Form; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.interactions.Actions; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.Select; + +import java.util.List; + +/** + * @author Vaclav Muzikar + * + * TODO: SAML + */ +public class CreateClientMappersForm extends Form { + + // Mappers types + public static final String HARDCODED_ROLE = "Hardcoded Role"; + public static final String HARDCODED_CLAIM = "Hardcoded claim"; + public static final String USER_SESSION_NOTE = "User Session Note"; + public static final String ROLE_NAME_MAPPER = "Role Name Mapper"; + public static final String USER_ADDRESS = "User Address"; + public static final String USERS_FULL_NAME = "User's full name"; + public static final String USER_ATTRIBUTE = "User Attribute"; + public static final String USER_PROPERTY = "User Property"; + + @FindBy(id = "name") + private WebElement nameElement; + + @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='consentRequired']]") + private OnOffSwitch consentRequiredSwitch; + + @FindBy(id = "consentText") + private WebElement consentTextElement; + + @FindBy(id = "mapperTypeCreate") + private Select mapperTypeSelect; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Property']//following-sibling::node()//input[@type='text']") + private WebElement propertyInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Attribute']//following-sibling::node()//input[@type='text']") + private WebElement userAttributeInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='User Session Note']//following-sibling::node()//input[@type='text']") + private WebElement userSessionNoteInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Multivalued']//following-sibling::node()//div[@class='onoffswitch']") + private OnOffSwitch multivaluedInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Role']//following-sibling::node()//input[@type='text']") + private WebElement roleInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='New Role Name']//following-sibling::node()//input[@type='text']") + private WebElement newRoleInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Token Claim Name']//following-sibling::node()//input[@type='text']") + private WebElement tokenClaimNameInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim value']//following-sibling::node()//input[@type='text']") + private WebElement tokenClaimValueInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Claim JSON Type']//following-sibling::node()//select") + private Select claimJSONTypeInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to ID token']//following-sibling::node()//div[@class='onoffswitch']") + private OnOffSwitch addToIDTokenInput; + + @FindBy(xpath = ".//div[@properties='mapperType.properties']//label[text()='Add to access token']//following-sibling::node()//div[@class='onoffswitch']") + private OnOffSwitch addToAccessTokenInput; + + public boolean isConsentRequired() { + return consentRequiredSwitch.isOn(); + } + + public void setConsentRequired(boolean consentRequired) { + consentRequiredSwitch.setOn(consentRequired); + } + + public String getConsentText() { + return getInputValue(consentTextElement); + } + + public void setConsentText(String consentText) { + setInputValue(consentTextElement, consentText); + } + + public String getMapperType() { + return mapperTypeSelect.getFirstSelectedOption().getText(); + } + + public void setMapperType(String type) { + mapperTypeSelect.selectByVisibleText(type); + } + + public String getProperty() { + return getInputValue(propertyInput); + } + + public void setProperty(String value) { + setInputValue(propertyInput, value); + } + + public String getUserAttribute() { + return getInputValue(userAttributeInput); + } + + public void setUserAttribute(String value) { + setInputValue(userAttributeInput, value); + } + + public String getUserSessionNote() { + return getInputValue(userSessionNoteInput); + } + + public void setUserSessionNote(String value) { + setInputValue(userSessionNoteInput, value); + } + + public boolean isMultivalued() { + return multivaluedInput.isOn(); + } + + public void setMultivalued(boolean value) { + multivaluedInput.setOn(value); + } + + public String getRole() { + return getInputValue(roleInput); + } + + public void setRole(String value) { + setInputValue(roleInput, value); + } + + public String getNewRole() { + return getInputValue(newRoleInput); + } + + public void setNewRole(String value) { + setInputValue(newRoleInput, value); + } + + public String getTokenClaimName() { + return getInputValue(tokenClaimNameInput); + } + + public void setTokenClaimName(String value) { + setInputValue(tokenClaimNameInput, value); + } + + public String getTokenClaimValue() { + return getInputValue(tokenClaimValueInput); + } + + public void setTokenClaimValue(String value) { + setInputValue(tokenClaimValueInput, value); + } + + public String getClaimJSONType() { + return claimJSONTypeInput.getFirstSelectedOption().getText(); + } + + public void setClaimJSONType(String value) { + claimJSONTypeInput.selectByVisibleText(value); + } + + public boolean isAddToIDToken() { + return addToIDTokenInput.isOn(); + } + + public void setAddToIDToken(boolean value) { + addToIDTokenInput.setOn(value); + } + + public boolean isAddToAccessToken() { + return addToAccessTokenInput.isOn(); + } + + public void setAddToAccessToken(boolean value) { + addToAccessTokenInput.setOn(value); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java new file mode 100644 index 0000000000..6347392990 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateKerberosUserProvider.java @@ -0,0 +1,28 @@ +package org.keycloak.testsuite.console.page.federation; + +import org.jboss.arquillian.graphene.page.Page; +import org.keycloak.testsuite.console.page.AdminConsoleCreate; + +/** + * + * @author pdrozd + */ +public class CreateKerberosUserProvider extends AdminConsoleCreate { + + @Page + private KerberosUserProviderForm form; + + public CreateKerberosUserProvider() { + setEntity("user-federation"); + } + + @Override + public String getUriFragment() { + return super.getUriFragment() + "/providers/kerberos"; + } + + public KerberosUserProviderForm form() { + return form; + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java index 4dc47f99da..13ba716f63 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/CreateLdapUserProvider.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.console.page.federation; +import org.jboss.arquillian.graphene.page.Page; import org.keycloak.testsuite.console.page.AdminConsoleCreate; /** @@ -8,6 +9,9 @@ import org.keycloak.testsuite.console.page.AdminConsoleCreate; */ public class CreateLdapUserProvider extends AdminConsoleCreate { + @Page + private LdapUserProviderForm form; + public CreateLdapUserProvider() { setEntity("user-federation"); } @@ -17,4 +21,7 @@ public class CreateLdapUserProvider extends AdminConsoleCreate { return super.getUriFragment() + "/providers/ldap"; } + public LdapUserProviderForm form() { + return form; + } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java new file mode 100644 index 0000000000..1fb068f575 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/KerberosUserProviderForm.java @@ -0,0 +1,81 @@ +package org.keycloak.testsuite.console.page.federation; + +import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement; + +import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; +import org.keycloak.testsuite.page.Form; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; +import org.openqa.selenium.support.ui.Select; + +/** + * @author pdrozd + */ +public class KerberosUserProviderForm extends Form { + + @FindBy(id = "consoleDisplayName") + private WebElement consoleDisplayNameInput; + + @FindBy(id = "priority") + private WebElement priorityInput; + + @FindBy(id = "kerberosRealm") + private WebElement kerberosRealmInput; + + @FindBy(id = "serverPrincipal") + private WebElement serverPrincipalInput; + + @FindBy(id = "keyTab") + private WebElement keyTabInput; + + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]") + private OnOffSwitch debug; + + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='allowPasswordAuthentication']]") + private OnOffSwitch allowPwdAuth; + + @FindBy(id = "editMode") + private Select editModeSelect; + + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='updateProfileFirstLogin']]") + private OnOffSwitch updateProfileFirstLogin; + + public void setConsoleDisplayNameInput(String name) { + setInputValue(consoleDisplayNameInput, name); + } + + public void setPriorityInput(Integer priority) { + setInputValue(priorityInput, String.valueOf(priority)); + } + + public void setKerberosRealmInput(String kerberosRealm) { + waitGuiForElement(By.id("kerberosRealm")); + setInputValue(kerberosRealmInput, kerberosRealm); + } + + public void setServerPrincipalInput(String serverPrincipal) { + setInputValue(serverPrincipalInput, serverPrincipal); + } + + public void setKeyTabInput(String keyTab) { + setInputValue(keyTabInput, keyTab); + } + + public void setDebugEnabled(boolean debugEnabled) { + this.debug.setOn(debugEnabled); + } + + public void setAllowPasswordAuthentication(boolean enabled) { + allowPwdAuth.setOn(enabled); + } + + public void selectEditMode(String mode) { + waitGuiForElement(By.id("editMode")); + editModeSelect.selectByVisibleText(mode); + } + + public void setUpdateProfileFirstLogin(boolean enabled) { + updateProfileFirstLogin.setOn(enabled); + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java index a9b88829e4..3acc5ec14d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/federation/LdapUserProviderForm.java @@ -1,5 +1,8 @@ package org.keycloak.testsuite.console.page.federation; +import static org.keycloak.testsuite.util.WaitUtils.waitAjaxForElement; +import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement; + import org.jboss.arquillian.graphene.findby.FindByJQuery; import org.keycloak.testsuite.console.page.fragment.OnOffSwitch; import org.keycloak.testsuite.page.Form; @@ -8,10 +11,8 @@ import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy; import org.openqa.selenium.support.ui.Select; -import static org.keycloak.testsuite.util.WaitUtils.waitGuiForElement; - /** - * Created by fkiss. + * @author fkiss, pdrozd */ public class LdapUserProviderForm extends Form { @@ -24,24 +25,33 @@ public class LdapUserProviderForm extends Form { @FindBy(id = "usernameLDAPAttribute") private WebElement usernameLDAPAttributeInput; + @FindBy(id = "rdnLDAPAttribute") + private WebElement rdnLDAPAttributeInput; + + @FindBy(id = "uuidLDAPAttribute") + private WebElement uuidLDAPAttributeInput; + @FindBy(id = "userObjectClasses") private WebElement userObjectClassesInput; @FindBy(id = "ldapConnectionUrl") private WebElement ldapConnectionUrlInput; - @FindBy(id = "ldapBaseDn") - private WebElement ldapBaseDnInput; - @FindBy(id = "ldapUsersDn") private WebElement ldapUserDnInput; + @FindBy(id = "authType") + private Select authTypeSelect; + @FindBy(id = "ldapBindDn") private WebElement ldapBindDnInput; @FindBy(id = "ldapBindCredential") private WebElement ldapBindCredentialInput; + @FindBy(id = "searchScope") + private Select searchScopeSelect; + @FindBy(id = "kerberosRealm") private WebElement kerberosRealmInput; @@ -72,59 +82,173 @@ public class LdapUserProviderForm extends Form { @FindByJQuery("a:contains('Test authentication')") private WebElement testAuthenticationButton; - @FindByJQuery("div[class='onoffswitch']:eq(0)") + @FindByJQuery("a:contains('Synchronize changed users')") + private WebElement synchronizeChangedUsersButton; + + @FindByJQuery("button:contains('Synchronize all users')") + private WebElement synchronizeAllUsersButton; + + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='syncRegistrations']]") private OnOffSwitch syncRegistrations; - @FindByJQuery("div[class='onoffswitch']:eq(1)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='connectionPooling']]") private OnOffSwitch connectionPooling; - @FindByJQuery("div[class='onoffswitch']:eq(2)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='pagination']]") private OnOffSwitch pagination; - @FindByJQuery("div[class='onoffswitch']:eq(3)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]") + private OnOffSwitch enableAccountAfterPasswordUpdate; + + @FindBy(xpath = "//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]") private OnOffSwitch allowKerberosAuth; - @FindByJQuery("div[class='onoffswitch']:eq(4)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='debug']]") private OnOffSwitch debug; - @FindByJQuery("div[class='onoffswitch']:eq(5)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='useKerberosForPasswordAuthentication']]") private OnOffSwitch useKerberosForPwdAuth; - @FindByJQuery("div[class='onoffswitch']:eq(6)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='compositeSwitch']]") private OnOffSwitch periodicFullSync; - @FindByJQuery("div[class='onoffswitch']:eq(7)") + @FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='changedSyncEnabled']]") private OnOffSwitch periodicChangedUsersSync; - @FindByJQuery("button:contains('Save')") - private WebElement saveButton; + public void setConsoleDisplayNameInput(String name) { + setInputValue(consoleDisplayNameInput, name); + } - public void selectEditMode(String mode){ + public void setPriorityInput(Integer priority) { + setInputValue(priorityInput, String.valueOf(priority)); + } + + public void setUsernameLDAPAttributeInput(String usernameLDAPAttribute) { + setInputValue(usernameLDAPAttributeInput, usernameLDAPAttribute); + } + + public void setRdnLDAPAttributeInput(String rdnLDAPAttribute) { + setInputValue(rdnLDAPAttributeInput, rdnLDAPAttribute); + } + + public void setUuidLDAPAttributeInput(String uuidLDAPAttribute) { + setInputValue(uuidLDAPAttributeInput, uuidLDAPAttribute); + } + + public void setUserObjectClassesInput(String userObjectClasses) { + setInputValue(userObjectClassesInput, userObjectClasses); + } + + public void setLdapConnectionUrlInput(String ldapConnectionUrl) { + setInputValue(ldapConnectionUrlInput, ldapConnectionUrl); + } + + public void setLdapUserDnInput(String ldapUserDn) { + setInputValue(ldapUserDnInput, ldapUserDn); + } + + public void setLdapBindDnInput(String ldapBindDn) { + setInputValue(ldapBindDnInput, ldapBindDn); + } + + public void setLdapBindCredentialInput(String ldapBindCredential) { + setInputValue(ldapBindCredentialInput, ldapBindCredential); + } + + public void setKerberosRealmInput(String kerberosRealm) { + waitAjaxForElement(kerberosRealmInput); + setInputValue(kerberosRealmInput, kerberosRealm); + } + + public void setServerPrincipalInput(String serverPrincipal) { + waitAjaxForElement(serverPrincipalInput); + setInputValue(serverPrincipalInput, serverPrincipal); + } + + public void setKeyTabInput(String keyTab) { + waitAjaxForElement(keyTabInput); + setInputValue(keyTabInput, keyTab); + } + + public void setBatchSizeForSyncInput(String batchSizeForSync) { + setInputValue(batchSizeForSyncInput, batchSizeForSync); + } + + public void selectEditMode(String mode) { waitGuiForElement(By.id("editMode")); editModeSelect.selectByVisibleText(mode); } - public void selectVendor(String vendor){ - waitGuiForElement(By.id("editMode")); + public void selectVendor(String vendor) { + waitGuiForElement(By.id("vendor")); vendorSelect.selectByVisibleText(vendor); } - public void configureLdap(String displayName, String editMode, String vendor, String connectionUrl, String userDN, String ldapBindDn, String ldapBindCredential){ - consoleDisplayNameInput.sendKeys(displayName); - editModeSelect.selectByVisibleText(editMode); - selectVendor(vendor); - ldapConnectionUrlInput.sendKeys(connectionUrl); - ldapUserDnInput.sendKeys(userDN); - ldapBindDnInput.sendKeys(ldapBindDn); - ldapBindCredentialInput.sendKeys(ldapBindCredential); - saveButton.click(); + public void selectAuthenticationType(String authenticationType) { + waitGuiForElement(By.id("authType")); + authTypeSelect.selectByVisibleText(authenticationType); } - public void testConnection(){ + public void selectSearchScope(String searchScope) { + waitGuiForElement(By.id("searchScope")); + searchScopeSelect.selectByVisibleText(searchScope); + } + + public void setSyncRegistrationsEnabled(boolean syncRegistrationsEnabled) { + this.syncRegistrations.setOn(syncRegistrationsEnabled); + } + + public void setConnectionPoolingEnabled(boolean connectionPoolingEnabled) { + this.connectionPooling.setOn(connectionPoolingEnabled); + } + + public void setPaginationEnabled(boolean paginationEnabled) { + this.pagination.setOn(paginationEnabled); + } + + public void setAccountAfterPasswordUpdateEnabled(boolean enabled) { + if ((!enableAccountAfterPasswordUpdate.isOn() && enabled) + || !enabled && enableAccountAfterPasswordUpdate.isOn()) { + driver.findElement(By + .xpath("//div[contains(@class,'onoffswitch') and ./input[@id='userAccountControlsAfterPasswordUpdate']]")) + .findElements(By.tagName("span")).get(0).click(); + } + } + + public void setAllowKerberosAuthEnabled(boolean enabled) { + if ((!allowKerberosAuth.isOn() && enabled) || !enabled && allowKerberosAuth.isOn()) { + driver.findElement( + By.xpath("//div[contains(@class,'onoffswitch') and ./input[@id='allowKerberosAuthentication']]")) + .findElements(By.tagName("span")).get(0).click(); + } + } + + public void setDebugEnabled(boolean debugEnabled) { + this.debug.setOn(debugEnabled); + } + + public void setUseKerberosForPwdAuthEnabled(boolean useKerberosForPwdAuthEnabled) { + this.useKerberosForPwdAuth.setOn(useKerberosForPwdAuthEnabled); + } + + public void setPeriodicFullSyncEnabled(boolean periodicFullSyncEnabled) { + this.periodicFullSync.setOn(periodicFullSyncEnabled); + } + + public void setPeriodicChangedUsersSyncEnabled(boolean periodicChangedUsersSyncEnabled) { + this.periodicChangedUsersSync.setOn(periodicChangedUsersSyncEnabled); + } + + public void testConnection() { testConnectionButton.click(); } - public void testAuthentication(){ + public void testAuthentication() { testAuthenticationButton.click(); } + + public void synchronizeAllUsers() { + waitAjaxForElement(synchronizeAllUsersButton); + synchronizeAllUsersButton.click(); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java index 6319c4c820..118e058631 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/fragment/OnOffSwitch.java @@ -36,6 +36,14 @@ public class OnOffSwitch { @ArquillianResource private Actions actions; + public OnOffSwitch() { + } + + public OnOffSwitch(WebElement root, Actions actions) { + this.root = root; + this.actions = actions; + } + public boolean isOn() { waitAjaxForElement(root); return root.findElement(By.tagName("input")).isSelected(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java new file mode 100644 index 0000000000..8b8dfad45c --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AbstractClientRegistrationTest.java @@ -0,0 +1,96 @@ +package org.keycloak.testsuite.client; + +import org.junit.After; +import org.junit.Before; +import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.models.AdminRoles; +import org.keycloak.models.Constants; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.testsuite.AbstractKeycloakTest; + +import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.Response; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Stian Thorgersen + */ +public abstract class AbstractClientRegistrationTest extends AbstractKeycloakTest { + + static final String REALM_NAME = "test"; + + ClientRegistration reg; + + @Before + public void before() throws Exception { + reg = new ClientRegistration(testContext.getAuthServerContextRoot() + "/auth", "test"); + } + + @After + public void after() throws Exception { + reg.close(); + } + + @Override + public void addTestRealms(List testRealms) { + RealmRepresentation rep = new RealmRepresentation(); + rep.setEnabled(true); + rep.setRealm(REALM_NAME); + rep.setUsers(new LinkedList()); + + LinkedList credentials = new LinkedList<>(); + CredentialRepresentation password = new CredentialRepresentation(); + password.setType(CredentialRepresentation.PASSWORD); + password.setValue("password"); + credentials.add(password); + + UserRepresentation user = new UserRepresentation(); + user.setEnabled(true); + user.setUsername("manage-clients"); + user.setCredentials(credentials); + user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS))); + + rep.getUsers().add(user); + + UserRepresentation user2 = new UserRepresentation(); + user2.setEnabled(true); + user2.setUsername("create-clients"); + user2.setCredentials(credentials); + user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT))); + + rep.getUsers().add(user2); + + UserRepresentation user3 = new UserRepresentation(); + user3.setEnabled(true); + user3.setUsername("no-access"); + user3.setCredentials(credentials); + + rep.getUsers().add(user3); + + testRealms.add(rep); + } + + public ClientRepresentation createClient(ClientRepresentation client) { + Response response = adminClient.realm(REALM_NAME).clients().create(client); + String id = response.getLocation().toString(); + id = id.substring(id.lastIndexOf('/') + 1); + client.setId(id); + response.close(); + return client; + } + + public ClientRepresentation getClient(String clientId) { + try { + return adminClient.realm(REALM_NAME).clients().get(clientId).toRepresentation(); + } catch (NotFoundException e) { + return null; + } + } + +} 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 new file mode 100644 index 0000000000..b8bdb412a3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/AdapterInstallationConfigTest.java @@ -0,0 +1,103 @@ +package org.keycloak.testsuite.client; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.HttpErrorException; +import org.keycloak.representations.adapters.config.AdapterConfig; +import org.keycloak.representations.idm.ClientRepresentation; + +import javax.ws.rs.core.Response; + +import static org.junit.Assert.*; + +/** + * @author Stian Thorgersen + */ +public class AdapterInstallationConfigTest extends AbstractClientRegistrationTest { + + private ClientRepresentation client; + private ClientRepresentation client2; + private ClientRepresentation clientPublic; + + @Before + public void before() throws Exception { + super.before(); + + client = new ClientRepresentation(); + client.setEnabled(true); + client.setClientId("RegistrationAccessTokenTest"); + client.setSecret("RegistrationAccessTokenTestClientSecret"); + client.setPublicClient(false); + client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken"); + client.setRootUrl("http://root"); + client = createClient(client); + + client2 = new ClientRepresentation(); + client2.setEnabled(true); + client2.setClientId("RegistrationAccessTokenTest2"); + client2.setSecret("RegistrationAccessTokenTestClientSecret"); + client2.setPublicClient(false); + client2.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken"); + client2.setRootUrl("http://root"); + client2 = createClient(client2); + + clientPublic = new ClientRepresentation(); + clientPublic.setEnabled(true); + clientPublic.setClientId("RegistrationAccessTokenTestPublic"); + clientPublic.setPublicClient(true); + clientPublic.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessTokenPublic"); + clientPublic.setRootUrl("http://root"); + clientPublic = createClient(clientPublic); + } + + @Test + public void getConfigWithRegistrationAccessToken() throws ClientRegistrationException { + reg.auth(Auth.token(client.getRegistrationAccessToken())); + + AdapterConfig config = reg.getAdapterConfig(client.getClientId()); + assertNotNull(config); + } + + @Test + public void getConfig() throws ClientRegistrationException { + reg.auth(Auth.client(client.getClientId(), client.getSecret())); + + AdapterConfig config = reg.getAdapterConfig(client.getClientId()); + assertNotNull(config); + } + + @Test + public void getConfigMissingSecret() throws ClientRegistrationException { + reg.auth(null); + + try { + reg.getAdapterConfig(client.getClientId()); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + } + + @Test + public void getConfigWrongClient() throws ClientRegistrationException { + reg.auth(Auth.client(client.getClientId(), client.getSecret())); + + try { + reg.getAdapterConfig(client2.getClientId()); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + } + + @Test + public void getConfigPublicClient() throws ClientRegistrationException { + reg.auth(null); + + AdapterConfig config = reg.getAdapterConfig(clientPublic.getClientId()); + assertNotNull(config); + } + +} 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 1d4b01129b..8b84b10af9 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 @@ -1,302 +1,211 @@ package org.keycloak.testsuite.client; -import org.junit.After; -import org.junit.Before; import org.junit.Test; -import org.keycloak.client.registration.ClientRegistration; +import org.keycloak.client.registration.Auth; import org.keycloak.client.registration.ClientRegistrationException; import org.keycloak.client.registration.HttpErrorException; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.Constants; -import org.keycloak.representations.AccessTokenResponse; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.CredentialRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.representations.idm.UserRepresentation; -import org.keycloak.testsuite.AbstractKeycloakTest; +import javax.ws.rs.NotFoundException; import java.util.Collections; -import java.util.LinkedList; -import java.util.List; import static org.junit.Assert.*; /** * @author Stian Thorgersen */ -public class ClientRegistrationTest extends AbstractKeycloakTest { +public class ClientRegistrationTest extends AbstractClientRegistrationTest { - private static final String REALM_NAME = "test"; private static final String CLIENT_ID = "test-client"; private static final String CLIENT_SECRET = "test-client-secret"; - private ClientRegistration clientRegistrationAsAdmin; - private ClientRegistration clientRegistrationAsClient; - - @Before - public void before() throws ClientRegistrationException { - clientRegistrationAsAdmin = clientBuilder().auth(getToken("manage-clients", "password")).build(); - clientRegistrationAsClient = clientBuilder().auth(CLIENT_ID, CLIENT_SECRET).build(); - } - - @After - public void after() throws ClientRegistrationException { - clientRegistrationAsAdmin.close(); - clientRegistrationAsClient.close(); - } - - @Override - public void addTestRealms(List testRealms) { - RealmRepresentation rep = new RealmRepresentation(); - rep.setEnabled(true); - rep.setRealm(REALM_NAME); - rep.setUsers(new LinkedList()); - - LinkedList credentials = new LinkedList<>(); - CredentialRepresentation password = new CredentialRepresentation(); - password.setType(CredentialRepresentation.PASSWORD); - password.setValue("password"); - credentials.add(password); - - UserRepresentation user = new UserRepresentation(); - user.setEnabled(true); - user.setUsername("manage-clients"); - user.setCredentials(credentials); - user.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.MANAGE_CLIENTS))); - - rep.getUsers().add(user); - - UserRepresentation user2 = new UserRepresentation(); - user2.setEnabled(true); - user2.setUsername("create-clients"); - user2.setCredentials(credentials); - user2.setClientRoles(Collections.singletonMap(Constants.REALM_MANAGEMENT_CLIENT_ID, Collections.singletonList(AdminRoles.CREATE_CLIENT))); - - rep.getUsers().add(user2); - - UserRepresentation user3 = new UserRepresentation(); - user3.setEnabled(true); - user3.setUsername("no-access"); - user3.setCredentials(credentials); - - rep.getUsers().add(user3); - - testRealms.add(rep); - } - - private void registerClient(ClientRegistration clientRegistration) throws ClientRegistrationException { + private ClientRepresentation registerClient() throws ClientRegistrationException { ClientRepresentation client = new ClientRepresentation(); client.setClientId(CLIENT_ID); client.setSecret(CLIENT_SECRET); - ClientRepresentation createdClient = clientRegistration.create(client); + ClientRepresentation createdClient = reg.create(client); assertEquals(CLIENT_ID, createdClient.getClientId()); client = adminClient.realm(REALM_NAME).clients().get(createdClient.getId()).toRepresentation(); assertEquals(CLIENT_ID, client.getClientId()); - AccessTokenResponse token2 = oauthClient.getToken(REALM_NAME, CLIENT_ID, CLIENT_SECRET, "manage-clients", "password"); - assertNotNull(token2.getToken()); + return client; } @Test public void registerClientAsAdmin() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); + authManageClients(); + registerClient(); } @Test public void registerClientAsAdminWithCreateOnly() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build(); - try { - registerClient(clientRegistration); - } finally { - clientRegistration.close(); - } + authCreateClients(); + registerClient(); } @Test public void registerClientAsAdminWithNoAccess() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build(); + authNoAccess(); try { - registerClient(clientRegistration); + registerClient(); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } + @Test + public void getClientAsAdmin() throws ClientRegistrationException { + registerClientAsAdmin(); + ClientRepresentation rep = reg.get(CLIENT_ID); + assertNotNull(rep); + } + @Test public void getClientAsAdminWithCreateOnly() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build(); + registerClientAsAdmin(); + authCreateClients(); try { - clientRegistration.get(CLIENT_ID); + reg.get(CLIENT_ID); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); - } - } - - @Test - public void wrongClient() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - - ClientRepresentation client = new ClientRepresentation(); - client.setClientId("test-client-2"); - client.setSecret("test-client-2-secret"); - - clientRegistrationAsAdmin.create(client); - - ClientRegistration clientRegistration = clientBuilder().auth("test-client-2", "test-client-2-secret").build(); - - client = clientRegistration.get("test-client-2"); - assertNotNull(client); - assertEquals("test-client-2", client.getClientId()); - - try { - try { - clientRegistration.get(CLIENT_ID); - fail("Expected 403"); - } catch (ClientRegistrationException e) { - assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } - - client = clientRegistrationAsAdmin.get(CLIENT_ID); - try { - clientRegistration.update(client); - fail("Expected 403"); - } catch (ClientRegistrationException e) { - assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } - - try { - clientRegistration.delete(CLIENT_ID); - fail("Expected 403"); - } catch (ClientRegistrationException e) { - assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } - } - finally { - clientRegistration.close(); } } @Test public void getClientAsAdminWithNoAccess() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build(); + registerClientAsAdmin(); + authNoAccess(); try { - clientRegistration.get(CLIENT_ID); + reg.get(CLIENT_ID); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } - private void updateClient(ClientRegistration clientRegistration) throws ClientRegistrationException { - ClientRepresentation client = clientRegistration.get(CLIENT_ID); + @Test + public void getClientNotFound() throws ClientRegistrationException { + authManageClients(); + try { + reg.get(CLIENT_ID); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + } + + private void updateClient() throws ClientRegistrationException { + ClientRepresentation client = reg.get(CLIENT_ID); client.setRedirectUris(Collections.singletonList("http://localhost:8080/app")); - clientRegistration.update(client); + reg.update(client); - ClientRepresentation updatedClient = clientRegistration.get(CLIENT_ID); + ClientRepresentation updatedClient = reg.get(CLIENT_ID); assertEquals(1, updatedClient.getRedirectUris().size()); assertEquals("http://localhost:8080/app", updatedClient.getRedirectUris().get(0)); } + @Test public void updateClientAsAdmin() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - updateClient(clientRegistrationAsAdmin); + registerClientAsAdmin(); + + authManageClients(); + updateClient(); } @Test public void updateClientAsAdminWithCreateOnly() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build(); + authCreateClients(); try { - updateClient(clientRegistration); + updateClient(); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } @Test public void updateClientAsAdminWithNoAccess() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build(); + authNoAccess(); try { - updateClient(clientRegistration); + updateClient(); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } @Test - public void updateClientAsClient() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - updateClient(clientRegistrationAsClient); + public void updateClientNotFound() throws ClientRegistrationException { + authManageClients(); + try { + updateClient(); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } } - private void deleteClient(ClientRegistration clientRegistration) throws ClientRegistrationException { - clientRegistration.delete(CLIENT_ID); - - // Can't authenticate as client after client is deleted - ClientRepresentation client = clientRegistrationAsAdmin.get(CLIENT_ID); - assertNull(client); + private void deleteClient(ClientRepresentation client) throws ClientRegistrationException { + reg.delete(CLIENT_ID); + try { + adminClient.realm("test").clients().get(client.getId()).toRepresentation(); + fail("Expected 403"); + } catch (NotFoundException e) { + } } @Test public void deleteClientAsAdmin() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - deleteClient(clientRegistrationAsAdmin); + authCreateClients(); + ClientRepresentation client = registerClient(); + + authManageClients(); + deleteClient(client); } @Test public void deleteClientAsAdminWithCreateOnly() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("create-clients", "password")).build(); + authManageClients(); + ClientRepresentation client = registerClient(); try { - deleteClient(clientRegistration); + authCreateClients(); + deleteClient(client); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } @Test public void deleteClientAsAdminWithNoAccess() throws ClientRegistrationException { - ClientRegistration clientRegistration = clientBuilder().auth(getToken("no-access", "password")).build(); + authManageClients(); + ClientRepresentation client = registerClient(); try { - deleteClient(clientRegistration); + authNoAccess(); + deleteClient(client); fail("Expected 403"); } catch (ClientRegistrationException e) { assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); - } finally { - clientRegistration.close(); } } - @Test - public void deleteClientAsClient() throws ClientRegistrationException { - registerClient(clientRegistrationAsAdmin); - deleteClient(clientRegistrationAsClient); + private void authCreateClients() { + reg.auth(Auth.token(getToken("create-clients", "password"))); } - private ClientRegistration.ClientRegistrationBuilder clientBuilder() { - return ClientRegistration.create().realm("test").authServerUrl(testContext.getAuthServerContextRoot() + "/auth"); + private void authManageClients() { + reg.auth(Auth.token(getToken("manage-clients", "password"))); + } + + private void authNoAccess() { + reg.auth(Auth.token(getToken("no-access", "password"))); } private String getToken(String username, String password) { 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 new file mode 100644 index 0000000000..d76da19bcb --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/RegistrationAccessTokenTest.java @@ -0,0 +1,94 @@ +package org.keycloak.testsuite.client; + +import org.junit.Before; +import org.junit.Test; +import org.keycloak.client.registration.Auth; +import org.keycloak.client.registration.ClientRegistrationException; +import org.keycloak.client.registration.HttpErrorException; +import org.keycloak.representations.idm.ClientRepresentation; + +import javax.ws.rs.core.Response; + +import static org.junit.Assert.*; + +/** + * @author Stian Thorgersen + */ +public class RegistrationAccessTokenTest extends AbstractClientRegistrationTest { + + private ClientRepresentation client; + + @Before + public void before() throws Exception { + super.before(); + + client = new ClientRepresentation(); + client.setEnabled(true); + client.setClientId("RegistrationAccessTokenTest"); + client.setSecret("RegistrationAccessTokenTestClientSecret"); + client.setRegistrationAccessToken("RegistrationAccessTokenTestRegistrationAccessToken"); + client.setRootUrl("http://root"); + client = createClient(client); + + reg.auth(Auth.token(client.getRegistrationAccessToken())); + } + + @Test + public void getClientWithRegistrationToken() throws ClientRegistrationException { + ClientRepresentation rep = reg.get(client.getClientId()); + assertNotNull(rep); + } + + @Test + public void getClientWithBadRegistrationToken() throws ClientRegistrationException { + reg.auth(Auth.token("invalid")); + try { + reg.get(client.getClientId()); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + } + + @Test + public void updateClientWithRegistrationToken() throws ClientRegistrationException { + client.setRootUrl("http://newroot"); + reg.update(client); + + assertEquals("http://newroot", getClient(client.getId()).getRootUrl()); + } + + @Test + public void updateClientWithBadRegistrationToken() throws ClientRegistrationException { + client.setRootUrl("http://newroot"); + + reg.auth(Auth.token("invalid")); + try { + reg.update(client); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + + assertEquals("http://root", getClient(client.getId()).getRootUrl()); + } + + @Test + public void deleteClientWithRegistrationToken() throws ClientRegistrationException { + reg.delete(client); + assertNull(getClient(client.getId())); + } + + @Test + public void deleteClientWithBadRegistrationToken() throws ClientRegistrationException { + reg.auth(Auth.token("invalid")); + try { + reg.delete(client); + fail("Expected 403"); + } catch (ClientRegistrationException e) { + assertEquals(403, ((HttpErrorException) e.getCause()).getStatusLine().getStatusCode()); + } + assertNotNull(getClient(client.getId())); + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java new file mode 100644 index 0000000000..47521e500a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/KerberosUserFederationTest.java @@ -0,0 +1,75 @@ +package org.keycloak.testsuite.console.federation; + +import static org.junit.Assert.assertEquals; + +import org.jboss.arquillian.graphene.page.Page; +import org.junit.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; +import org.keycloak.testsuite.console.AbstractConsoleTest; +import org.keycloak.testsuite.console.page.federation.CreateKerberosUserProvider; + +/** + * @author pdrozd + */ +public class KerberosUserFederationTest extends AbstractConsoleTest { + + private static final String UNSYNCED = "UNSYNCED"; + + private static final String READ_ONLY = "READ_ONLY"; + + @Page + private CreateKerberosUserProvider createKerberosUserProvider; + + @Test + public void configureKerberosProvider() { + createKerberosUserProvider.navigateTo(); + createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos"); + createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG"); + createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG"); + createKerberosUserProvider.form().setKeyTabInput("http.keytab"); + createKerberosUserProvider.form().setDebugEnabled(true); + createKerberosUserProvider.form().setAllowPasswordAuthentication(true); + createKerberosUserProvider.form().selectEditMode(READ_ONLY); + createKerberosUserProvider.form().setUpdateProfileFirstLogin(true); + createKerberosUserProvider.form().save(); + assertFlashMessageSuccess(); + RealmRepresentation realm = testRealmResource().toRepresentation(); + UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); + assertKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "true", "true"); + } + + @Test + public void invalidSettingsTest() { + createKerberosUserProvider.navigateTo(); + createKerberosUserProvider.form().setConsoleDisplayNameInput("kerberos"); + createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG"); + createKerberosUserProvider.form().setKeyTabInput("http.keytab"); + createKerberosUserProvider.form().setDebugEnabled(true); + createKerberosUserProvider.form().setAllowPasswordAuthentication(true); + createKerberosUserProvider.form().selectEditMode(UNSYNCED); + createKerberosUserProvider.form().setUpdateProfileFirstLogin(true); + createKerberosUserProvider.form().save(); + assertFlashMessageDanger(); + createKerberosUserProvider.form().setServerPrincipalInput(""); + createKerberosUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG");; + createKerberosUserProvider.form().save(); + assertFlashMessageDanger(); + createKerberosUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG");; + createKerberosUserProvider.form().setKeyTabInput(""); + createKerberosUserProvider.form().save(); + assertFlashMessageDanger(); + createKerberosUserProvider.form().setKeyTabInput("http.keytab");; + createKerberosUserProvider.form().save(); + assertFlashMessageSuccess(); + } + + private void assertKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication, String updateProfileFirstLogin) { + assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm")); + assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal")); + assertEquals(keyTab, ufpr.getConfig().get("keyTab")); + assertEquals(debug, ufpr.getConfig().get("debug")); + assertEquals(useKerberosForPasswordAuthentication, ufpr.getConfig().get("allowKerberosAuthentication")); + assertEquals(updateProfileFirstLogin, ufpr.getConfig().get("updateProfileFirstLogin")); + } +} \ No newline at end of file diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java index e70da4679f..e04036213e 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/federation/LdapUserFederationTest.java @@ -1,71 +1,192 @@ package org.keycloak.testsuite.console.federation; +import static org.junit.Assert.assertEquals; + +import java.util.Properties; + import org.jboss.arquillian.graphene.page.Page; -import org.junit.*; -import org.keycloak.models.LDAPConstants; - -import org.keycloak.representations.idm.UserRepresentation; +import org.junit.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.testsuite.console.AbstractConsoleTest; -import org.keycloak.testsuite.console.page.federation.LdapUserProviderForm; -import org.keycloak.testsuite.console.page.federation.UserFederation; -import org.keycloak.testsuite.console.page.users.Users; -import org.keycloak.testsuite.util.LDAPTestConfiguration; - -import java.util.Map; - -import static org.junit.Assert.assertTrue; -import static org.keycloak.representations.idm.CredentialRepresentation.PASSWORD; -import static org.keycloak.testsuite.admin.Users.setPasswordFor; +import org.keycloak.testsuite.console.page.federation.CreateLdapUserProvider; +import org.keycloak.util.ldap.LDAPEmbeddedServer; /** - * Created by fkiss. + * @author fkiss, pdrozd */ public class LdapUserFederationTest extends AbstractConsoleTest { - @Page - private LdapUserProviderForm ldapUserProviderForm; + private static final String UNSYNCED = "UNSYNCED"; + + private static final String READ_ONLY = "READ_ONLY"; + + private static final String RED_HAT_DIRECTORY_SERVER = "Red Hat Directory Server"; + + private static final String WRITABLE = "WRITABLE"; + + private static final String ACTIVE_DIRECTORY = "Active Directory"; @Page - private UserFederation userFederationPage; + private CreateLdapUserProvider createLdapUserProvider; - @Page - private Users usersPage; - - @Before - public void beforeTestLdapUserFederation() { - //configure().userFederation(); - } - - @Ignore @Test - public void addAndConfigureProvider() { - adminConsolePage.navigateTo(); - testRealmLoginPage.form().login(testUser); + public void configureAdProvider() { + createLdapUserProvider.navigateTo(); + createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY); + createLdapUserProvider.form().setConsoleDisplayNameInput("ldap"); + createLdapUserProvider.form().selectEditMode(WRITABLE); + createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389"); + createLdapUserProvider.form().setLdapBindDnInput("KEYCLOAK/Administrator"); + createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org"); + createLdapUserProvider.form().setLdapBindCredentialInput("secret"); + createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(false); + // enable kerberos + createLdapUserProvider.form().setAllowKerberosAuthEnabled(true); + createLdapUserProvider.form().setKerberosRealmInput("KEYCLOAK.ORG"); + createLdapUserProvider.form().setServerPrincipalInput("HTTP/localhost@KEYCLOAK.ORG"); + createLdapUserProvider.form().setKeyTabInput("http.keytab"); + createLdapUserProvider.form().setDebugEnabled(true); + createLdapUserProvider.form().save(); + assertFlashMessageSuccess(); - String name = "ldapname"; - - String LDAP_CONNECTION_PROPERTIES_LOCATION = "ldap/ldap-connection.properties"; - LDAPTestConfiguration ldapTestConfiguration = LDAPTestConfiguration.readConfiguration(LDAP_CONNECTION_PROPERTIES_LOCATION); - - UserRepresentation newUser = new UserRepresentation(); - String testUsername = "defaultrole tester"; - newUser.setUsername(testUsername); - setPasswordFor(newUser, PASSWORD); - - Map ldapConfig = ldapTestConfiguration.getLDAPConfig(); - - //addLdapProviderTest - configure().userFederation(); - userFederationPage.addProvider("ldap"); - ldapUserProviderForm.configureLdap(ldapConfig.get(LDAPConstants.LDAP_PROVIDER), ldapConfig.get(LDAPConstants.EDIT_MODE), ldapConfig.get(LDAPConstants.VENDOR), ldapConfig.get(LDAPConstants.CONNECTION_URL), ldapConfig.get(LDAPConstants.USERS_DN), ldapConfig.get(LDAPConstants.BIND_DN), ldapConfig.get(LDAPConstants.BIND_CREDENTIAL)); + RealmRepresentation realm = testRealmResource().toRepresentation(); + UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); + assertLdapProviderSetting(ufpr, "ldap", 0, WRITABLE, "false", "ad", "1", "true", "true", "false"); + assertLdapBasicMapping(ufpr, "cn", "cn", "objectGUID", "person, organizationalPerson, user", + "ou=People,dc=keycloak,dc=org"); + assertLdapSyncSetings(ufpr, "1000", 0, 0); + assertLdapKerberosSetings(ufpr, "KEYCLOAK.ORG", "HTTP/localhost@KEYCLOAK.ORG", "http.keytab", "true", "false"); } - @Ignore @Test - public void caseSensitiveSearch() { - // This should fail for now due to case-sensitivity - adminConsolePage.navigateTo(); - testRealmLoginPage.form().login("johnKeycloak", "Password1"); - assertTrue(flashMessage.getText(), flashMessage.isDanger()); + public void configureRhdsProvider() { + createLdapUserProvider.navigateTo(); + createLdapUserProvider.form().selectVendor(RED_HAT_DIRECTORY_SERVER); + createLdapUserProvider.form().setConsoleDisplayNameInput("ldap"); + createLdapUserProvider.form().selectEditMode(READ_ONLY); + createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389"); + createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system"); + createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org"); + createLdapUserProvider.form().setLdapBindCredentialInput("secret"); + createLdapUserProvider.form().save(); + assertFlashMessageSuccess(); + + RealmRepresentation realm = testRealmResource().toRepresentation(); + UserFederationProviderRepresentation ufpr = realm.getUserFederationProviders().get(0); + assertLdapProviderSetting(ufpr, "ldap", 0, READ_ONLY, "false", "rhds", "1", "true", "true", "true"); + assertLdapBasicMapping(ufpr, "uid", "uid", "nsuniqueid", "inetOrgPerson, organizationalPerson", + "ou=People,dc=keycloak,dc=org"); + assertLdapSyncSetings(ufpr, "1000", 0, 0); } -} \ No newline at end of file + + @Test + public void invalidSettingsTest() { + createLdapUserProvider.navigateTo(); + createLdapUserProvider.form().selectVendor(ACTIVE_DIRECTORY); + createLdapUserProvider.form().setConsoleDisplayNameInput("ldap"); + createLdapUserProvider.form().selectEditMode(UNSYNCED); + createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system"); + createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org"); + createLdapUserProvider.form().setLdapBindCredentialInput("secret"); + createLdapUserProvider.form().save(); + assertFlashMessageDanger(); + createLdapUserProvider.form().setLdapUserDnInput(""); + createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:389"); + createLdapUserProvider.form().save(); + assertFlashMessageDanger(); + createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org"); + createLdapUserProvider.form().setLdapBindDnInput(""); + createLdapUserProvider.form().save(); + assertFlashMessageDanger(); + createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system"); + createLdapUserProvider.form().setLdapBindCredentialInput(""); + createLdapUserProvider.form().save(); + assertFlashMessageDanger(); + createLdapUserProvider.form().setLdapBindCredentialInput("secret"); + createLdapUserProvider.form().save(); + assertFlashMessageSuccess(); + } + + @Test + public void testConnection() throws Exception { + createLdapUserProvider.navigateTo(); + createLdapUserProvider.form().selectVendor("Other"); + createLdapUserProvider.form().setConsoleDisplayNameInput("ldap"); + createLdapUserProvider.form().selectEditMode(WRITABLE); + createLdapUserProvider.form().setLdapConnectionUrlInput("ldap://localhost:10389"); + createLdapUserProvider.form().setLdapBindDnInput("uid=admin,ou=system"); + createLdapUserProvider.form().setLdapUserDnInput("ou=People,dc=keycloak,dc=org"); + createLdapUserProvider.form().setLdapBindCredentialInput("secret"); + createLdapUserProvider.form().setAccountAfterPasswordUpdateEnabled(true); + createLdapUserProvider.form().save(); + assertFlashMessageSuccess(); + LDAPEmbeddedServer ldapServer = null; + try { + ldapServer = startEmbeddedLdapServer(); + createLdapUserProvider.form().testConnection(); + assertFlashMessageSuccess(); + createLdapUserProvider.form().testAuthentication(); + assertFlashMessageSuccess(); + createLdapUserProvider.form().synchronizeAllUsers(); + assertFlashMessageSuccess(); + createLdapUserProvider.form().setLdapBindCredentialInput("secret1"); + createLdapUserProvider.form().testAuthentication(); + assertFlashMessageDanger(); + } finally { + if (ldapServer != null) { + ldapServer.stop(); + } + } + } + + private void assertLdapProviderSetting(UserFederationProviderRepresentation ufpr, String name, int priority, + String editMode, String syncRegistrations, String vendor, String searchScope, String connectionPooling, + String pagination, String enableAccountAfterPasswordUpdate) { + assertEquals(name, ufpr.getDisplayName()); + assertEquals(priority, ufpr.getPriority()); + assertEquals(editMode, ufpr.getConfig().get("editMode")); + assertEquals(syncRegistrations, ufpr.getConfig().get("syncRegistrations")); + assertEquals(vendor, ufpr.getConfig().get("vendor")); + assertEquals(searchScope, ufpr.getConfig().get("searchScope")); + assertEquals(connectionPooling, ufpr.getConfig().get("connectionPooling")); + assertEquals(pagination, ufpr.getConfig().get("pagination")); + assertEquals(enableAccountAfterPasswordUpdate, ufpr.getConfig().get("userAccountControlsAfterPasswordUpdate")); + } + + private void assertLdapBasicMapping(UserFederationProviderRepresentation ufpr, String usernameLdapAttribute, + String rdnLdapAttr, String uuidLdapAttr, String userObjectClasses, String userDN) { + assertEquals(usernameLdapAttribute, ufpr.getConfig().get("usernameLDAPAttribute")); + assertEquals(rdnLdapAttr, ufpr.getConfig().get("rdnLDAPAttribute")); + assertEquals(uuidLdapAttr, ufpr.getConfig().get("uuidLDAPAttribute")); + assertEquals(userObjectClasses, ufpr.getConfig().get("userObjectClasses")); + assertEquals(userDN, ufpr.getConfig().get("usersDn")); + } + + private void assertLdapKerberosSetings(UserFederationProviderRepresentation ufpr, String kerberosRealm, + String serverPrincipal, String keyTab, String debug, String useKerberosForPasswordAuthentication) { + assertEquals(kerberosRealm, ufpr.getConfig().get("kerberosRealm")); + assertEquals(serverPrincipal, ufpr.getConfig().get("serverPrincipal")); + assertEquals(keyTab, ufpr.getConfig().get("keyTab")); + assertEquals(debug, ufpr.getConfig().get("debug")); + assertEquals(useKerberosForPasswordAuthentication, + ufpr.getConfig().get("useKerberosForPasswordAuthentication")); + } + + private void assertLdapSyncSetings(UserFederationProviderRepresentation ufpr, String batchSize, + int periodicFullSync, int periodicChangedUsersSync) { + assertEquals(batchSize, ufpr.getConfig().get("batchSizeForSync")); + assertEquals(periodicFullSync, ufpr.getFullSyncPeriod()); + assertEquals(periodicChangedUsersSync, ufpr.getChangedSyncPeriod()); + } + + private LDAPEmbeddedServer startEmbeddedLdapServer() throws Exception { + Properties defaultProperties = new Properties(); + defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_DSF, LDAPEmbeddedServer.DSF_INMEMORY); + defaultProperties.setProperty(LDAPEmbeddedServer.PROPERTY_LDIF_FILE, "classpath:ldap/users.ldif"); + LDAPEmbeddedServer ldapEmbeddedServer = new LDAPEmbeddedServer(defaultProperties); + ldapEmbeddedServer.init(); + ldapEmbeddedServer.start(); + return ldapEmbeddedServer; + } +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif new file mode 100644 index 0000000000..176e19b81a --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/ldap/users.ldif @@ -0,0 +1,20 @@ +dn: dc=keycloak,dc=org +objectclass: dcObject +objectclass: organization +o: Keycloak +dc: Keycloak + +dn: ou=People,dc=keycloak,dc=org +objectclass: top +objectclass: organizationalUnit +ou: People + +dn: ou=RealmRoles,dc=keycloak,dc=org +objectclass: top +objectclass: organizationalUnit +ou: RealmRoles + +dn: ou=FinanceRoles,dc=keycloak,dc=org +objectclass: top +objectclass: organizationalUnit +ou: FinanceRoles diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java new file mode 100644 index 0000000000..7d780cd72f --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractFirstBrokerLoginTest.java @@ -0,0 +1,397 @@ +package org.keycloak.testsuite.broker; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.mail.internet.MimeMessage; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticator; +import org.keycloak.authentication.authenticators.broker.IdpCreateUserIfUniqueAuthenticatorFactory; +import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory; +import org.keycloak.authentication.authenticators.broker.IdpReviewProfileAuthenticatorFactory; +import org.keycloak.common.util.ObjectUtil; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.AuthenticationFlowModel; +import org.keycloak.models.AuthenticatorConfigModel; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.pages.IdpConfirmLinkPage; +import org.keycloak.testsuite.pages.IdpLinkEmailPage; +import org.keycloak.testsuite.pages.LoginPasswordResetPage; +import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; +import org.keycloak.testsuite.pages.LoginUpdateProfileEditUsernameAllowedPage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; +import org.openqa.selenium.WebElement; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Marek Posolda + */ +public abstract class AbstractFirstBrokerLoginTest extends AbstractIdentityProviderTest { + + protected static final String APP_REALM_ID = "realm-with-broker"; + + @WebResource + protected LoginUpdateProfileEditUsernameAllowedPage updateProfileWithUsernamePage; + + @WebResource + protected IdpConfirmLinkPage idpConfirmLinkPage; + + @WebResource + protected IdpLinkEmailPage idpLinkEmailPage; + + @WebResource + protected LoginPasswordUpdatePage passwordUpdatePage; + + + + /** + * Tests that if updateProfile is off and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists + */ + @Test + public void testErrorPageWhenDuplicationNotAllowed_updateProfileOff() { + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED); + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF); + } + + }, APP_REALM_ID); + + loginIDP("pedroigor"); + + WebElement element = this.driver.findElement(By.className("instruction")); + + assertNotNull(element); + + assertEquals("User with email psilva@redhat.com already exists. Please login to account management to link the account.", element.getText()); + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE); + } + + }, APP_REALM_ID); + } + + + /** + * Tests that if updateProfile is on and CreateUserIfUnique authenticator mandatory, error page will be shown if user with same email already exists + */ + @Test + public void testErrorPageWhenDuplicationNotAllowed_updateProfileOn() { + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.REQUIRED); + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_ON); + } + + }, APP_REALM_ID); + + loginIDP("test-user"); + + this.updateProfileWithUsernamePage.assertCurrent(); + this.updateProfileWithUsernamePage.update("Test", "User", "test-user@redhat.com", "pedroigor"); + + WebElement element = this.driver.findElement(By.className("instruction")); + + assertNotNull(element); + + assertEquals("User with username pedroigor already exists. Please login to account management to link the account.", element.getText()); + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_FLOW, IdpCreateUserIfUniqueAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE); + } + + }, APP_REALM_ID); + } + + + /** + * Test user registers with IdentityProvider and needs to update password when it's required by IdpCreateUserIfUniqueAuthenticator + */ + @Test + public void testRegistrationWithPasswordUpdateRequired() { + // Require updatePassword after user registered with broker + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS); + authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "true"); + realmWithBroker.updateAuthenticatorConfig(authenticatorConfig); + + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_MISSING); + } + + }, APP_REALM_ID); + + loginIDP("pedroigor"); + this.updateProfileWithUsernamePage.assertCurrent(); + this.updateProfileWithUsernamePage.update("Test", "User", "some-user@redhat.com", "some-user"); + + // Need to update password now + this.passwordUpdatePage.assertCurrent(); + this.passwordUpdatePage.changePassword("password1", "password1"); + + + // assert authenticated + assertFederatedUser("some-user", "some-user@redhat.com", "pedroigor"); + + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + AuthenticatorConfigModel authenticatorConfig = realmWithBroker.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS); + authenticatorConfig.getConfig().put(IdpCreateUserIfUniqueAuthenticatorFactory.REQUIRE_PASSWORD_UPDATE_AFTER_REGISTRATION, "false"); + realmWithBroker.updateAuthenticatorConfig(authenticatorConfig); + } + + }, APP_REALM_ID); + } + + + /** + * Tests that duplication is detected, the confirmation page is displayed, user clicks on "Review profile" and goes back to updateProfile page and resolves duplication + * by create new user + */ + @Test + public void testFixDuplicationsByReviewProfile() { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + loginIDP("pedroigor"); + + // There is user with same email. Update profile to use different email + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickReviewProfile(); + + this.updateProfileWithUsernamePage.assertCurrent(); + this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "pedroigor"); + + // There is user with same username. Update profile to use different username + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with username pedroigor already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickReviewProfile(); + + this.updateProfileWithUsernamePage.assertCurrent(); + this.updateProfileWithUsernamePage.update("Test", "User", "testing-user@redhat.com", "testing-user"); + + assertFederatedUser("testing-user", "testing-user@redhat.com", "pedroigor"); + } + + /** + * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by email + */ + @Test + public void testLinkAccountByEmailVerification() throws Exception { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + loginIDP("pedroigor"); + + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickLinkAccount(); + + // Confirm linking account by email + this.idpLinkEmailPage.assertCurrent(); + Assert.assertEquals("An email with instructions to link " + ObjectUtil.capitalize(getProviderId()) + " account pedroigor with your " + APP_REALM_ID + " account has been sent to you.", this.idpLinkEmailPage.getMessage()); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + String linkFromMail = getVerificationEmailLink(message); + + driver.navigate().to(linkFromMail.trim()); + + // authenticated and redirected to app. User is linked with identity provider + assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor"); + } + + + /** + * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen) + */ + @Test + public void testLinkAccountByReauthenticationWithPassword() throws Exception { + // Remove smtp config. The reauthentication by username+password screen will be automatically used then + final Map smtpConfig = new HashMap<>(); + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF); + smtpConfig.putAll(realmWithBroker.getSmtpConfig()); + realmWithBroker.setSmtpConfig(Collections.emptyMap()); + } + + }, APP_REALM_ID); + + + loginIDP("pedroigor"); + + + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickLinkAccount(); + + // Login screen shown. Username is prefilled and disabled. Registration link and social buttons are not shown + Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle()); + Assert.assertEquals("pedroigor", this.loginPage.getUsername()); + Assert.assertFalse(this.loginPage.isUsernameInputEnabled()); + + Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage()); + + try { + this.loginPage.findSocialButton(getProviderId()); + Assert.fail("Not expected to see social button with " + getProviderId()); + } catch (NoSuchElementException expected) { + } + + try { + this.loginPage.clickRegister(); + Assert.fail("Not expected to see register link"); + } catch (NoSuchElementException expected) { + } + + // Use bad password first + this.loginPage.login("password1"); + Assert.assertEquals("Invalid username or password.", this.loginPage.getError()); + + // Use correct password now + this.loginPage.login("password"); + + // authenticated and redirected to app. User is linked with identity provider + assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor"); + + + // Restore smtp config + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + realmWithBroker.setSmtpConfig(smtpConfig); + } + + }, APP_REALM_ID); + } + + + /** + * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication (confirm password on login screen) + * and additionally he goes through "forget password" + */ + @Test + public void testLinkAccountByReauthentication_forgetPassword() throws Exception { + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW, + IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED); + + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF); + } + + }, APP_REALM_ID); + + loginIDP("pedroigor"); + + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickLinkAccount(); + + // Click "Forget password" on login page. Email sent directly because username is known + Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle()); + this.loginPage.resetPassword(); + + Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle()); + Assert.assertEquals("You should receive an email shortly with further instructions.", this.loginPage.getSuccessMessage()); + + // Click on link from email + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + String linkFromMail = getVerificationEmailLink(message); + + driver.navigate().to(linkFromMail.trim()); + + // Need to update password now + this.passwordUpdatePage.assertCurrent(); + this.passwordUpdatePage.changePassword("password", "password"); + + // authenticated and redirected to app. User is linked with identity provider + assertFederatedUser("pedroigor", "psilva@redhat.com", "pedroigor"); + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW, + IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE); + + } + + }, APP_REALM_ID); + } + + + protected void assertFederatedUser(String expectedUsername, String expectedEmail, String expectedFederatedUsername) { + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + UserModel federatedUser = getFederatedUser(); + + assertNotNull(federatedUser); + assertEquals(expectedUsername, federatedUser.getUsername()); + assertEquals(expectedEmail, federatedUser.getEmail()); + + RealmModel realmWithBroker = getRealm(); + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker); + assertEquals(1, federatedIdentities.size()); + + FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); + + assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); + assertEquals(expectedFederatedUsername, federatedIdentityModel.getUserName()); + } + + + protected void setExecutionRequirement(RealmModel realmWithBroker, String flowAlias, String authenticatorProvider, AuthenticationExecutionModel.Requirement requirement) { + AuthenticationFlowModel flowModel = realmWithBroker.getFlowByAlias(flowAlias); + List authExecutions = realmWithBroker.getAuthenticationExecutions(flowModel.getId()); + for (AuthenticationExecutionModel execution : authExecutions) { + if (execution.getAuthenticator().equals(authenticatorProvider)) { + execution.setRequirement(requirement); + realmWithBroker.updateAuthenticatorExecution(execution); + return; + } + } + + throw new IllegalStateException("Execution not found for flow " + flowAlias + " and authenticator " + authenticatorProvider); + } + +} 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 8af43b2b63..5248c38f5e 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 @@ -42,6 +42,7 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.services.Urls; import org.keycloak.testsuite.MailUtil; import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; import org.keycloak.testsuite.broker.util.UserSessionStatusServlet.UserSessionStatus; import org.keycloak.testsuite.pages.AccountFederatedIdentityPage; import org.keycloak.testsuite.pages.AccountPasswordPage; @@ -87,7 +88,7 @@ import static org.junit.Assert.fail; */ public abstract class AbstractIdentityProviderTest { - private static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build(); + protected static final URI BASE_URI = UriBuilder.fromUri("http://localhost:8081/auth").build(); @ClassRule public static BrokerKeyCloakRule brokerServerRule = new BrokerKeyCloakRule(); @@ -99,10 +100,10 @@ public abstract class AbstractIdentityProviderTest { protected WebDriver driver; @WebResource - private LoginPage loginPage; + protected LoginPage loginPage; @WebResource - private LoginUpdateProfilePage updateProfilePage; + protected LoginUpdateProfilePage updateProfilePage; @WebResource @@ -123,7 +124,7 @@ public abstract class AbstractIdentityProviderTest { @WebResource protected AccountFederatedIdentityPage accountFederatedIdentityPage; - private KeycloakSession session; + protected KeycloakSession session; @Before public void onBefore() { @@ -140,552 +141,7 @@ public abstract class AbstractIdentityProviderTest { brokerServerRule.stopSession(this.session, true); } - @Test - public void testSuccessfulAuthentication() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); - - UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true); - Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile")); - } - - @Test - public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); - - assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); - } - - @Test - public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); - - assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true); - } - - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); - } - - /** - * Test that verify email action is performed if email is provided and email trust is not enabled for the provider - * - * @throws MessagingException - * @throws IOException - */ - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException { - getRealm().setVerifyEmail(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - try { - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - identityProviderModel.setTrustEmail(false); - - UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false); - - // email is verified now - assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); - - } finally { - getRealm().setVerifyEmail(false); - } - } - - private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail, - boolean isProfileUpdateExpected) - throws IOException, MessagingException { - authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected); - - // verify email is sent - Assert.assertTrue(verifyEmailPage.isCurrent()); - - // read email to take verification link from - Assert.assertEquals(1, greenMail.getReceivedMessages().length); - MimeMessage message = greenMail.getReceivedMessages()[0]; - String verificationUrl = getVerificationEmailLink(message); - - driver.navigate().to(verificationUrl.trim()); - - // authenticated and redirected to app - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); - - UserModel federatedUser = getFederatedUser(); - - assertNotNull(federatedUser); - - doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - RealmModel realm = getRealm(); - - Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); - - assertEquals(1, federatedIdentities.size()); - - FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); - - assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); - assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName()); - - driver.navigate().to("http://localhost:8081/test-app/logout"); - driver.navigate().to("http://localhost:8081/test-app"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - return federatedUser; - } - - /** - * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later - */ - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() { - getRealm().setVerifyEmail(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - try { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false); - - assertTrue(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); - - } finally { - getRealm().setVerifyEmail(false); - } - } - - /** - * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider - */ - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() { - getRealm().setVerifyEmail(true); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - try { - identityProviderModel.setTrustEmail(true); - - UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); - - assertFalse(federatedUser.getRequiredActions().contains(RequiredAction.VERIFY_EMAIL.name())); - - } finally { - identityProviderModel.setTrustEmail(false); - getRealm().setVerifyEmail(false); - } - } - - /** - * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page - * - * @throws MessagingException - * @throws IOException - */ - @Test - public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException { - getRealm().setVerifyEmail(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - try { - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); - identityProviderModel.setTrustEmail(true); - - UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true); - Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile")); - } finally { - identityProviderModel.setTrustEmail(false); - getRealm().setVerifyEmail(false); - } - } - - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() { - - getRealm().setRegistrationEmailAsUsername(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - try { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - authenticateWithIdentityProvider(identityProviderModel, "test-user", false); - - // authenticated and redirected to app - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - // check correct user is created with email as username and bound to correct federated identity - RealmModel realm = getRealm(); - - UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm); - - assertNotNull(federatedUser); - - assertEquals("test-user@localhost", federatedUser.getUsername()); - - doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false); - - Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); - - assertEquals(1, federatedIdentities.size()); - - FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); - - assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); - - driver.navigate().to("http://localhost:8081/test-app/logout"); - driver.navigate().to("http://localhost:8081/test-app"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - } finally { - getRealm().setRegistrationEmailAsUsername(false); - } - } - - @Test - public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() { - - getRealm().setRegistrationEmailAsUsername(true); - brokerServerRule.stopSession(this.session, true); - this.session = brokerServerRule.startSession(); - - try { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - // check correct user is created with username from provider as email is not available - RealmModel realm = getRealm(); - UserModel federatedUser = getFederatedUser(); - assertNotNull(federatedUser); - - doAssertFederatedUserNoEmail(federatedUser); - - Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); - - assertEquals(1, federatedIdentities.size()); - - FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); - - assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); - revokeGrant(); - - driver.navigate().to("http://localhost:8081/test-app/logout"); - driver.navigate().to("http://localhost:8081/test-app"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - } finally { - getRealm().setRegistrationEmailAsUsername(false); - } - } - - protected void doAssertFederatedUserNoEmail(UserModel federatedUser) { - assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername()); - assertEquals(null, federatedUser.getEmail()); - assertEquals("Test", federatedUser.getFirstName()); - assertEquals("User", federatedUser.getLastName()); - } - - @Test - public void testDisabled() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - - identityProviderModel.setEnabled(false); - - this.driver.navigate().to("http://localhost:8081/test-app/"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - try { - this.driver.findElement(By.className(getProviderId())); - fail("Provider [" + getProviderId() + "] not disabled."); - } catch (NoSuchElementException nsee) { - - } - } - - @Test - public void testProviderOnLoginPage() { - // Provider button is available on login page - this.driver.navigate().to("http://localhost:8081/test-app/"); - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - loginPage.findSocialButton(getProviderId()); - } - - // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour - // @Test - public void testUserAlreadyExistsWhenUpdatingProfile() { - this.driver.navigate().to("http://localhost:8081/test-app/"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - // choose the identity provider - this.loginPage.clickSocial(getProviderId()); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); - - // log in to identity provider - this.loginPage.login("test-user", "password"); - - doAfterProviderAuthentication(); - - this.updateProfilePage.assertCurrent(); - this.updateProfilePage.update("Test", "User", "psilva@redhat.com"); - - WebElement element = this.driver.findElement(By.className("kc-feedback-text")); - - assertNotNull(element); - - assertEquals("Email already exists.", element.getText()); - - this.updateProfilePage.assertCurrent(); - this.updateProfilePage.update("Test", "User", "test-user@redhat.com"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); - - UserModel federatedUser = getFederatedUser(); - - assertNotNull(federatedUser); - } - - // TODO: Reenable and adjust to KEYCLOAK-1750 changed behaviour - // @Test - public void testUserAlreadyExistsWhenNotUpdatingProfile() { - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); - - this.driver.navigate().to("http://localhost:8081/test-app/"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - // choose the identity provider - this.loginPage.clickSocial(getProviderId()); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); - - // log in to identity provider - this.loginPage.login("pedroigor", "password"); - - doAfterProviderAuthentication(); - - WebElement element = this.driver.findElement(By.className("kc-feedback-text")); - - assertNotNull(element); - - assertEquals("User with email already exists. Please login to account management to link the account.", element.getText()); - } - - @Test - public void testAccountManagementLinkIdentity() { - // Login as pedroigor to account management - accountFederatedIdentityPage.realm("realm-with-broker"); - accountFederatedIdentityPage.open(); - assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); - loginPage.login("pedroigor", "password"); - assertTrue(accountFederatedIdentityPage.isCurrent()); - - // Link my "pedroigor" identity with "test-user" from brokered Keycloak - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); - accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias()); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); - this.loginPage.login("test-user", "password"); - doAfterProviderAuthentication(); - - // Assert identity linked in account management - assertTrue(accountFederatedIdentityPage.isCurrent()); - assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\"")); - - // Revoke grant in account mgmt - revokeGrant(); - - // Logout from account management - accountFederatedIdentityPage.logout(); - assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); - - // Assert I am logged immediately to account management due to previously linked "test-user" identity - loginPage.clickSocial(identityProviderModel.getAlias()); - doAfterProviderAuthentication(); - assertTrue(accountFederatedIdentityPage.isCurrent()); - assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\"")); - - // Unlink my "test-user" - accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias()); - assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\"")); - - // Revoke grant in account mgmt - 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")); - - // Try to login. Previous link is not valid anymore, so now it should try to register new user - this.loginPage.clickSocial(identityProviderModel.getAlias()); - this.loginPage.login("test-user", "password"); - doAfterProviderAuthentication(); - this.updateProfilePage.assertCurrent(); - } - - @Test(expected = NoSuchElementException.class) - public void testIdentityProviderNotAllowed() { - this.driver.navigate().to("http://localhost:8081/test-app/"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - driver.findElement(By.className("model-oidc-idp")); - } - - protected void configureClientRetrieveToken(String clientId) { - RealmModel realm = getRealm(); - RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); - ClientModel client = realm.getClientByClientId(clientId); - if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - } - - protected void configureUserRetrieveToken(String username) { - RealmModel realm = getRealm(); - UserModel user = session.users().getUserByUsername(username, realm); - RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); - if (user != null && !user.hasRole(readTokenRole)) { - user.grantRole(readTokenRole); - } - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - } - - protected void unconfigureClientRetrieveToken(String clientId) { - RealmModel realm = getRealm(); - RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); - ClientModel client = realm.getClientByClientId(clientId); - if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole); - - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - } - - protected void unconfigureUserRetrieveToken(String username) { - RealmModel realm = getRealm(); - UserModel user = session.users().getUserByUsername(username, realm); - RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); - if (user != null && user.hasRole(readTokenRole)) { - user.deleteRoleMapping(readTokenRole); - } - brokerServerRule.stopSession(session, true); - session = brokerServerRule.startSession(); - - } - - @Test - public void testTokenStorageAndRetrievalByApplication() { - setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); - IdentityProviderModel identityProviderModel = getIdentityProviderModel(); - - identityProviderModel.setStoreToken(true); - - authenticateWithIdentityProvider(identityProviderModel, "test-user", true); - - UserModel federatedUser = getFederatedUser(); - RealmModel realm = getRealm(); - Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); - - assertFalse(federatedIdentities.isEmpty()); - assertEquals(1, federatedIdentities.size()); - - FederatedIdentityModel identityModel = federatedIdentities.iterator().next(); - - assertNotNull(identityModel.getToken()); - - UserSessionStatus userSessionStatus = retrieveSessionStatus(); - String accessToken = userSessionStatus.getAccessTokenString(); - URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName()); - final String authHeader = "Bearer " + accessToken; - ClientRequestFilter authFilter = new ClientRequestFilter() { - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); - } - }; - Client client = ClientBuilder.newBuilder().register(authFilter).build(); - WebTarget tokenEndpoint = client.target(tokenEndpointUrl); - Response response = tokenEndpoint.request().get(); - assertEquals(Status.OK.getStatusCode(), response.getStatus()); - assertNotNull(response.readEntity(String.class)); - revokeGrant(); - - - driver.navigate().to("http://localhost:8081/test-app/logout"); - String currentUrl = this.driver.getCurrentUrl(); - System.out.println("after logout currentUrl: " + currentUrl); - assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - - unconfigureUserRetrieveToken(getProviderId() + ".test-user"); - loginIDP("test-user"); - //authenticateWithIdentityProvider(identityProviderModel, "test-user"); - assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl()); - - userSessionStatus = retrieveSessionStatus(); - accessToken = userSessionStatus.getAccessTokenString(); - final String authHeader2 = "Bearer " + accessToken; - ClientRequestFilter authFilter2 = new ClientRequestFilter() { - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2); - } - }; - client = ClientBuilder.newBuilder().register(authFilter2).build(); - tokenEndpoint = client.target(tokenEndpointUrl); - response = tokenEndpoint.request().get(); - - assertEquals(Status.FORBIDDEN.getStatusCode(), response.getStatus()); - - revokeGrant(); - driver.navigate().to("http://localhost:8081/test-app/logout"); - driver.navigate().to("http://localhost:8081/test-app"); - - assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); - } - - protected abstract void doAssertTokenRetrieval(String pageSource); - - private UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) { + protected UserModel assertSuccessfulAuthentication(IdentityProviderModel identityProviderModel, String username, String expectedEmail, boolean isProfileUpdateExpected) { authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected); // authenticated and redirected to app @@ -722,7 +178,16 @@ public abstract class AbstractIdentityProviderTest { return federatedUser; } - private void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) { + + + protected void doAssertFederatedUserNoEmail(UserModel federatedUser) { + assertEquals("kc-oidc-idp.test-user-noemail", federatedUser.getUsername()); + assertEquals(null, federatedUser.getEmail()); + assertEquals("Test", federatedUser.getFirstName()); + assertEquals("User", federatedUser.getLastName()); + } + + protected void authenticateWithIdentityProvider(IdentityProviderModel identityProviderModel, String username, boolean isProfileUpdateExpected) { loginIDP(username); @@ -738,7 +203,7 @@ public abstract class AbstractIdentityProviderTest { } - private void loginIDP(String username) { + protected void loginIDP(String username) { driver.navigate().to("http://localhost:8081/test-app"); assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); @@ -776,6 +241,7 @@ public abstract class AbstractIdentityProviderTest { protected abstract String getProviderId(); + protected IdentityProviderModel getIdentityProviderModel() { IdentityProviderModel identityProviderModel = getRealm().getIdentityProviderByAlias(getProviderId()); @@ -786,10 +252,16 @@ public abstract class AbstractIdentityProviderTest { return identityProviderModel; } - private RealmModel getRealm() { - return this.session.realms().getRealm("realm-with-broker"); + + protected RealmModel getRealm() { + return getRealm(this.session); } + protected RealmModel getRealm(KeycloakSession session) { + return session.realms().getRealm("realm-with-broker"); + } + + protected void doAssertFederatedUser(UserModel federatedUser, IdentityProviderModel identityProviderModel, String expectedEmail, boolean isProfileUpdateExpected) { if (isProfileUpdateExpected) { String userFirstName = "New first"; @@ -805,19 +277,6 @@ public abstract class AbstractIdentityProviderTest { } } - private UserSessionStatus retrieveSessionStatus() { - UserSessionStatus sessionStatus = null; - - try { - String pageSource = this.driver.getPageSource(); - - sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatus.class); - } catch (IOException ignore) { - ignore.printStackTrace(); - } - - return sessionStatus; - } private void removeTestUsers() { RealmModel realm = getRealm(); @@ -835,40 +294,60 @@ public abstract class AbstractIdentityProviderTest { } } } - - private String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException { - Multipart multipart = (Multipart) message.getContent(); - - final String textContentType = multipart.getBodyPart(0).getContentType(); - - assertEquals("text/plain; charset=UTF-8", textContentType); - - final String textBody = (String) multipart.getBodyPart(0).getContent(); - final String textVerificationUrl = MailUtil.getLink(textBody); - - final String htmlContentType = multipart.getBodyPart(1).getContentType(); - - assertEquals("text/html; charset=UTF-8", htmlContentType); - - final String htmlBody = (String) multipart.getBodyPart(1).getContent(); - final String htmlVerificationUrl = MailUtil.getLink(htmlBody); - - assertEquals(htmlVerificationUrl, textVerificationUrl); - return htmlVerificationUrl; - } - private void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) { + protected void setUpdateProfileFirstLogin(final String updateProfileFirstLogin) { KeycloakModelUtils.runJobInTransaction(this.session.getKeycloakSessionFactory(), new KeycloakSessionTask() { @Override public void run(KeycloakSession session) { - RealmModel realm = session.realms().getRealm("realm-with-broker"); - AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS); - reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin); - realm.updateAuthenticatorConfig(reviewProfileConfig); + RealmModel realm = getRealm(session); + setUpdateProfileFirstLogin(realm, updateProfileFirstLogin); } }); } + + protected void setUpdateProfileFirstLogin(RealmModel realm, String updateProfileFirstLogin) { + AuthenticatorConfigModel reviewProfileConfig = realm.getAuthenticatorConfigByAlias(DefaultAuthenticationFlows.IDP_REVIEW_PROFILE_CONFIG_ALIAS); + reviewProfileConfig.getConfig().put(IdpReviewProfileAuthenticatorFactory.UPDATE_PROFILE_ON_FIRST_LOGIN, updateProfileFirstLogin); + realm.updateAuthenticatorConfig(reviewProfileConfig); + } + + + protected UserSessionStatusServlet.UserSessionStatus retrieveSessionStatus() { + UserSessionStatusServlet.UserSessionStatus sessionStatus = null; + + try { + String pageSource = this.driver.getPageSource(); + + sessionStatus = JsonSerialization.readValue(pageSource.getBytes(), UserSessionStatusServlet.UserSessionStatus.class); + } catch (IOException ignore) { + ignore.printStackTrace(); + } + + return sessionStatus; + } + + protected String getVerificationEmailLink(MimeMessage message) throws IOException, MessagingException { + Multipart multipart = (Multipart) message.getContent(); + + final String textContentType = multipart.getBodyPart(0).getContentType(); + + assertEquals("text/plain; charset=UTF-8", textContentType); + + final String textBody = (String) multipart.getBodyPart(0).getContent(); + final String textVerificationUrl = MailUtil.getLink(textBody); + + final String htmlContentType = multipart.getBodyPart(1).getContentType(); + + assertEquals("text/html; charset=UTF-8", htmlContentType); + + final String htmlBody = (String) multipart.getBodyPart(1).getContent(); + final String htmlVerificationUrl = MailUtil.getLink(htmlBody); + + assertEquals(htmlVerificationUrl, textVerificationUrl); + + return htmlVerificationUrl; + } } 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 new file mode 100644 index 0000000000..1d2e5b68d4 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java @@ -0,0 +1,547 @@ +package org.keycloak.testsuite.broker; + +import java.io.IOException; +import java.net.URI; +import java.util.Set; + +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeMessage; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.models.ClientModel; +import org.keycloak.models.Constants; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.services.Urls; +import org.keycloak.testsuite.MailUtil; +import org.keycloak.testsuite.broker.util.UserSessionStatusServlet; +import org.openqa.selenium.By; +import org.openqa.selenium.NoSuchElementException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * @author pedroigor + */ +public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdentityProviderTest { + + @Test + public void testSuccessfulAuthentication() { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); + + UserModel user = assertSuccessfulAuthentication(identityProviderModel, "test-user", "new@email.com", true); + Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile")); + } + + @Test + public void testSuccessfulAuthenticationUpdateProfileOnMissing_nothingMissing() { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); + + assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); + } + + @Test + public void testSuccessfulAuthenticationUpdateProfileOnMissing_missingEmail() { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_MISSING); + + assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", "new@email.com", true); + } + + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile() { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); + } + + /** + * Test that verify email action is performed if email is provided and email trust is not enabled for the provider + * + * @throws MessagingException + * @throws IOException + */ + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled() throws IOException, MessagingException { + getRealm().setVerifyEmail(true); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + try { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + identityProviderModel.setTrustEmail(false); + + UserModel federatedUser = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "test-user@localhost", false); + + // email is verified now + assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name())); + + } finally { + getRealm().setVerifyEmail(false); + } + } + + private UserModel assertSuccessfulAuthenticationWithEmailVerification(IdentityProviderModel identityProviderModel, String username, String expectedEmail, + boolean isProfileUpdateExpected) + throws IOException, MessagingException { + authenticateWithIdentityProvider(identityProviderModel, username, isProfileUpdateExpected); + + // verify email is sent + Assert.assertTrue(verifyEmailPage.isCurrent()); + + // read email to take verification link from + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + MimeMessage message = greenMail.getReceivedMessages()[0]; + String verificationUrl = getVerificationEmailLink(message); + + driver.navigate().to(verificationUrl.trim()); + + // authenticated and redirected to app + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + + UserModel federatedUser = getFederatedUser(); + + assertNotNull(federatedUser); + + doAssertFederatedUser(federatedUser, identityProviderModel, expectedEmail, isProfileUpdateExpected); + + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + RealmModel realm = getRealm(); + + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); + + assertEquals(1, federatedIdentities.size()); + + FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); + + assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); + assertEquals(federatedUser.getUsername(), federatedIdentityModel.getIdentityProvider() + "." + federatedIdentityModel.getUserName()); + + driver.navigate().to("http://localhost:8081/test-app/logout"); + driver.navigate().to("http://localhost:8081/test-app"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + return federatedUser; + } + + /** + * Test for KEYCLOAK-1053 - verify email action is not performed if email is not provided, login is normal, but action stays in set to be performed later + */ + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile_emailNotProvided_emailVerifyEnabled() { + getRealm().setVerifyEmail(true); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + try { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user-noemail", null, false); + + assertTrue(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name())); + + } finally { + getRealm().setVerifyEmail(false); + } + } + + /** + * Test for KEYCLOAK-1372 - verify email action is not performed if email is provided but email trust is enabled for the provider + */ + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile_emailProvided_emailVerifyEnabled_emailTrustEnabled() { + getRealm().setVerifyEmail(true); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + try { + identityProviderModel.setTrustEmail(true); + + UserModel federatedUser = assertSuccessfulAuthentication(identityProviderModel, "test-user", "test-user@localhost", false); + + assertFalse(federatedUser.getRequiredActions().contains(UserModel.RequiredAction.VERIFY_EMAIL.name())); + + } finally { + identityProviderModel.setTrustEmail(false); + getRealm().setVerifyEmail(false); + } + } + + /** + * Test for KEYCLOAK-1372 - verify email action is performed if email is provided and email trust is enabled for the provider, but email is changed on First login update profile page + * + * @throws MessagingException + * @throws IOException + */ + @Test + public void testSuccessfulAuthentication_emailTrustEnabled_emailVerifyEnabled_emailUpdatedOnFirstLogin() throws IOException, MessagingException { + getRealm().setVerifyEmail(true); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + try { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); + identityProviderModel.setTrustEmail(true); + + UserModel user = assertSuccessfulAuthenticationWithEmailVerification(identityProviderModel, "test-user", "new@email.com", true); + Assert.assertEquals("617-666-7777", user.getFirstAttribute("mobile")); + } finally { + identityProviderModel.setTrustEmail(false); + getRealm().setVerifyEmail(false); + } + } + + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername() { + + getRealm().setRegistrationEmailAsUsername(true); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + try { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + authenticateWithIdentityProvider(identityProviderModel, "test-user", false); + + // authenticated and redirected to app + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + // check correct user is created with email as username and bound to correct federated identity + RealmModel realm = getRealm(); + + UserModel federatedUser = session.users().getUserByUsername("test-user@localhost", realm); + + assertNotNull(federatedUser); + + assertEquals("test-user@localhost", federatedUser.getUsername()); + + doAssertFederatedUser(federatedUser, identityProviderModel, "test-user@localhost", false); + + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); + + assertEquals(1, federatedIdentities.size()); + + FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); + + assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); + + driver.navigate().to("http://localhost:8081/test-app/logout"); + driver.navigate().to("http://localhost:8081/test-app"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + + } finally { + getRealm().setRegistrationEmailAsUsername(false); + } + } + + @Test + public void testSuccessfulAuthenticationWithoutUpdateProfile_newUser_emailAsUsername_emailNotProvided() { + + getRealm().setRegistrationEmailAsUsername(true); + brokerServerRule.stopSession(this.session, true); + this.session = brokerServerRule.startSession(); + + try { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_OFF); + + authenticateWithIdentityProvider(identityProviderModel, "test-user-noemail", false); + + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + // check correct user is created with username from provider as email is not available + RealmModel realm = getRealm(); + UserModel federatedUser = getFederatedUser(); + assertNotNull(federatedUser); + + doAssertFederatedUserNoEmail(federatedUser); + + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); + + assertEquals(1, federatedIdentities.size()); + + FederatedIdentityModel federatedIdentityModel = federatedIdentities.iterator().next(); + + assertEquals(getProviderId(), federatedIdentityModel.getIdentityProvider()); + revokeGrant(); + + driver.navigate().to("http://localhost:8081/test-app/logout"); + driver.navigate().to("http://localhost:8081/test-app"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + + } finally { + getRealm().setRegistrationEmailAsUsername(false); + } + } + + @Test + public void testDisabled() { + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + + identityProviderModel.setEnabled(false); + + this.driver.navigate().to("http://localhost:8081/test-app/"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + + try { + this.driver.findElement(By.className(getProviderId())); + fail("Provider [" + getProviderId() + "] not disabled."); + } catch (NoSuchElementException nsee) { + + } + } + + @Test + public void testProviderOnLoginPage() { + // Provider button is available on login page + this.driver.navigate().to("http://localhost:8081/test-app/"); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + loginPage.findSocialButton(getProviderId()); + } + + @Test + public void testAccountManagementLinkIdentity() { + // Login as pedroigor to account management + accountFederatedIdentityPage.realm("realm-with-broker"); + accountFederatedIdentityPage.open(); + assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); + loginPage.login("pedroigor", "password"); + assertTrue(accountFederatedIdentityPage.isCurrent()); + + // Link my "pedroigor" identity with "test-user" from brokered Keycloak + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); + accountFederatedIdentityPage.clickAddProvider(identityProviderModel.getAlias()); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + this.loginPage.login("test-user", "password"); + doAfterProviderAuthentication(); + + // Assert identity linked in account management + assertTrue(accountFederatedIdentityPage.isCurrent()); + assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\"")); + + // Revoke grant in account mgmt + revokeGrant(); + + // Logout from account management + accountFederatedIdentityPage.logout(); + assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); + + // Assert I am logged immediately to account management due to previously linked "test-user" identity + loginPage.clickSocial(identityProviderModel.getAlias()); + doAfterProviderAuthentication(); + assertTrue(accountFederatedIdentityPage.isCurrent()); + assertTrue(driver.getPageSource().contains("id=\"remove-" + identityProviderModel.getAlias() + "\"")); + + // Unlink my "test-user" + accountFederatedIdentityPage.clickRemoveProvider(identityProviderModel.getAlias()); + assertTrue(driver.getPageSource().contains("id=\"add-" + identityProviderModel.getAlias() + "\"")); + + // Revoke grant in account mgmt + 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")); + + // Try to login. Previous link is not valid anymore, so now it should try to register new user + this.loginPage.clickSocial(identityProviderModel.getAlias()); + this.loginPage.login("test-user", "password"); + doAfterProviderAuthentication(); + this.updateProfilePage.assertCurrent(); + } + + + // KEYCLOAK-1822 + @Test + public void testAccountManagementLinkedIdentityAlreadyExists() { + // Login as "test-user" through broker + IdentityProviderModel identityProvider = getIdentityProviderModel(); + assertSuccessfulAuthentication(identityProvider, "test-user", "test-user@localhost", false); + + // Login as pedroigor to account management + accountFederatedIdentityPage.realm("realm-with-broker"); + accountFederatedIdentityPage.open(); + assertTrue(driver.getTitle().equals("Log in to realm-with-broker")); + loginPage.login("pedroigor", "password"); + assertTrue(accountFederatedIdentityPage.isCurrent()); + + // Try to link my "pedroigor" identity with "test-user" from brokered Keycloak. + accountFederatedIdentityPage.clickAddProvider(identityProvider.getAlias()); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + this.loginPage.login("test-user", "password"); + doAfterProviderAuthentication(); + + // Error is displayed in account management because federated identity"test-user" already linked to local account "test-user" + assertTrue(accountFederatedIdentityPage.isCurrent()); + assertEquals("Federated identity returned by " + getProviderId() + " is already linked to another user.", accountFederatedIdentityPage.getError()); + } + + + @Test(expected = NoSuchElementException.class) + public void testIdentityProviderNotAllowed() { + this.driver.navigate().to("http://localhost:8081/test-app/"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + + driver.findElement(By.className("model-oidc-idp")); + } + + protected void configureClientRetrieveToken(String clientId) { + RealmModel realm = getRealm(); + RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); + ClientModel client = realm.getClientByClientId(clientId); + if (!client.hasScope(readTokenRole)) client.addScopeMapping(readTokenRole); + + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + } + + protected void configureUserRetrieveToken(String username) { + RealmModel realm = getRealm(); + UserModel user = session.users().getUserByUsername(username, realm); + RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); + if (user != null && !user.hasRole(readTokenRole)) { + user.grantRole(readTokenRole); + } + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + } + + protected void unconfigureClientRetrieveToken(String clientId) { + RealmModel realm = getRealm(); + RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); + ClientModel client = realm.getClientByClientId(clientId); + if (client.hasScope(readTokenRole)) client.deleteScopeMapping(readTokenRole); + + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + } + + protected void unconfigureUserRetrieveToken(String username) { + RealmModel realm = getRealm(); + UserModel user = session.users().getUserByUsername(username, realm); + RoleModel readTokenRole = realm.getClientByClientId(Constants.BROKER_SERVICE_CLIENT_ID).getRole(Constants.READ_TOKEN_ROLE); + if (user != null && user.hasRole(readTokenRole)) { + user.deleteRoleMapping(readTokenRole); + } + brokerServerRule.stopSession(session, true); + session = brokerServerRule.startSession(); + + } + + @Test + public void testTokenStorageAndRetrievalByApplication() { + setUpdateProfileFirstLogin(IdentityProviderRepresentation.UPFLM_ON); + IdentityProviderModel identityProviderModel = getIdentityProviderModel(); + + identityProviderModel.setStoreToken(true); + + authenticateWithIdentityProvider(identityProviderModel, "test-user", true); + + UserModel federatedUser = getFederatedUser(); + RealmModel realm = getRealm(); + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realm); + + assertFalse(federatedIdentities.isEmpty()); + assertEquals(1, federatedIdentities.size()); + + FederatedIdentityModel identityModel = federatedIdentities.iterator().next(); + + assertNotNull(identityModel.getToken()); + + UserSessionStatusServlet.UserSessionStatus userSessionStatus = retrieveSessionStatus(); + String accessToken = userSessionStatus.getAccessTokenString(); + URI tokenEndpointUrl = Urls.identityProviderRetrieveToken(BASE_URI, getProviderId(), realm.getName()); + final String authHeader = "Bearer " + accessToken; + ClientRequestFilter authFilter = new ClientRequestFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader); + } + }; + Client client = ClientBuilder.newBuilder().register(authFilter).build(); + WebTarget tokenEndpoint = client.target(tokenEndpointUrl); + Response response = tokenEndpoint.request().get(); + assertEquals(Response.Status.OK.getStatusCode(), response.getStatus()); + assertNotNull(response.readEntity(String.class)); + revokeGrant(); + + + driver.navigate().to("http://localhost:8081/test-app/logout"); + String currentUrl = this.driver.getCurrentUrl(); + System.out.println("after logout currentUrl: " + currentUrl); + assertTrue(currentUrl.startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + + unconfigureUserRetrieveToken(getProviderId() + ".test-user"); + loginIDP("test-user"); + //authenticateWithIdentityProvider(identityProviderModel, "test-user"); + assertEquals("http://localhost:8081/test-app", driver.getCurrentUrl()); + + userSessionStatus = retrieveSessionStatus(); + accessToken = userSessionStatus.getAccessTokenString(); + final String authHeader2 = "Bearer " + accessToken; + ClientRequestFilter authFilter2 = new ClientRequestFilter() { + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, authHeader2); + } + }; + client = ClientBuilder.newBuilder().register(authFilter2).build(); + tokenEndpoint = client.target(tokenEndpointUrl); + response = tokenEndpoint.request().get(); + + assertEquals(Response.Status.FORBIDDEN.getStatusCode(), response.getStatus()); + + revokeGrant(); + driver.navigate().to("http://localhost:8081/test-app/logout"); + driver.navigate().to("http://localhost:8081/test-app"); + + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/auth/realms/realm-with-broker/protocol/openid-connect/auth")); + } + + protected abstract void doAssertTokenRetrieval(String pageSource); + +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java new file mode 100644 index 0000000000..d6893632e6 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCFirstBrokerLoginTest.java @@ -0,0 +1,145 @@ +package org.keycloak.testsuite.broker; + +import java.util.Set; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.keycloak.authentication.authenticators.broker.IdpEmailVerificationAuthenticatorFactory; +import org.keycloak.models.AuthenticationExecutionModel; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.DefaultAuthenticationFlows; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.KeycloakServer; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.openqa.selenium.NoSuchElementException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * @author Marek Posolda + */ +public class OIDCFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest { + + private static final int PORT = 8082; + + @ClassRule + public static AbstractKeycloakRule samlServerRule = 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")); + server.importRealm(getClass().getResourceAsStream("/broker-test/test-broker-realm-with-saml.json")); + } + + @Override + protected String[] getTestRealms() { + return new String[] { "realm-with-oidc-identity-provider", "realm-with-saml-idp-basic" }; + } + }; + + @Override + protected String getProviderId() { + return "kc-oidc-idp"; + } + + + /** + * Tests that duplication is detected and user wants to link federatedIdentity with existing account. He will confirm link by reauthentication + * with different broker already linked to his account + */ + @Test + public void testLinkAccountByReauthenticationWithDifferentBroker() throws Exception { + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW, + IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.DISABLED); + + setUpdateProfileFirstLogin(realmWithBroker, IdentityProviderRepresentation.UPFLM_OFF); + } + + }, APP_REALM_ID); + + // First link "pedroigor" user with SAML broker and logout + driver.navigate().to("http://localhost:8081/test-app"); + this.loginPage.clickSocial("kc-saml-idp-basic"); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8082/auth/")); + Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle()); + this.loginPage.login("pedroigor", "password"); + + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickLinkAccount(); + + this.loginPage.login("password"); + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + driver.navigate().to("http://localhost:8081/test-app/logout"); + + + // login through OIDC broker now + loginIDP("pedroigor"); + + this.idpConfirmLinkPage.assertCurrent(); + Assert.assertEquals("User with email psilva@redhat.com already exists. How do you want to continue?", this.idpConfirmLinkPage.getMessage()); + this.idpConfirmLinkPage.clickLinkAccount(); + + // assert reauthentication with login page. On login page is link to kc-saml-idp-basic as user has it linked already + Assert.assertEquals("Log in to " + APP_REALM_ID, this.driver.getTitle()); + Assert.assertEquals("Authenticate as pedroigor to link your account with " + getProviderId(), this.loginPage.getSuccessMessage()); + + try { + this.loginPage.findSocialButton(getProviderId()); + Assert.fail("Not expected to see social button with " + getProviderId()); + } catch (NoSuchElementException expected) { + } + + // reauthenticate with SAML broker + this.loginPage.clickSocial("kc-saml-idp-basic"); + Assert.assertEquals("Log in to realm-with-saml-idp-basic", this.driver.getTitle()); + this.loginPage.login("pedroigor", "password"); + + + // authenticated and redirected to app. User is linked with identity provider + assertTrue(this.driver.getCurrentUrl().startsWith("http://localhost:8081/test-app")); + UserModel federatedUser = getFederatedUser(); + + assertNotNull(federatedUser); + assertEquals("pedroigor", federatedUser.getUsername()); + assertEquals("psilva@redhat.com", federatedUser.getEmail()); + + RealmModel realmWithBroker = getRealm(); + Set federatedIdentities = this.session.users().getFederatedIdentities(federatedUser, realmWithBroker); + assertEquals(2, federatedIdentities.size()); + + for (FederatedIdentityModel link : federatedIdentities) { + Assert.assertEquals("pedroigor", link.getUserName()); + Assert.assertTrue(link.getIdentityProvider().equals(getProviderId()) || link.getIdentityProvider().equals("kc-saml-idp-basic")); + } + + brokerServerRule.update(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel realmWithBroker) { + setExecutionRequirement(realmWithBroker, DefaultAuthenticationFlows.FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW, + IdpEmailVerificationAuthenticatorFactory.PROVIDER_ID, AuthenticationExecutionModel.Requirement.ALTERNATIVE); + + } + + }, APP_REALM_ID); + } + +} 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 0a7ee1602a..5bfdc1d75d 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 @@ -26,7 +26,7 @@ import static org.junit.Assert.fail; /** * @author pedroigor */ -public class OIDCKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest { +public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest { private static final int PORT = 8082; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java new file mode 100644 index 0000000000..66692d48db --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLFirstBrokerLoginTest.java @@ -0,0 +1,40 @@ +package org.keycloak.testsuite.broker; + +import org.junit.ClassRule; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.KeycloakServer; +import org.keycloak.testsuite.rule.AbstractKeycloakRule; + +/** + * @author Marek Posolda + */ +public class SAMLFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest { + + private static final int PORT = 8082; + + @ClassRule + public static AbstractKeycloakRule samlServerRule = 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-saml.json")); + } + + @Override + protected String[] getTestRealms() { + return new String[] { "realm-with-saml-idp-basic" }; + } + }; + + @Override + protected String getProviderId() { + return "kc-saml-idp-basic"; + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java index 7bbaa204d8..8be9dcc11c 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerBasicTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.fail; /** * @author pedroigor */ -public class SAMLKeyCloakServerBrokerBasicTest extends AbstractIdentityProviderTest { +public class SAMLKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityProviderTest { @ClassRule public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java index 249f2e02df..197749b170 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/SAMLKeyCloakServerBrokerWithSignatureTest.java @@ -24,7 +24,7 @@ import static org.junit.Assert.fail; /** * @author pedroigor */ -public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractIdentityProviderTest { +public class SAMLKeyCloakServerBrokerWithSignatureTest extends AbstractKeycloakIdentityProviderTest { @ClassRule public static AbstractKeycloakRule samlServerRule = new AbstractKeycloakRule() { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java index 950c182a3f..30d94fae78 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/LoginTest.java @@ -215,9 +215,10 @@ public class LoginTest { Assert.assertEquals("login-test", loginPage.getUsername()); Assert.assertEquals("", loginPage.getPassword()); - Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); + // KEYCLOAK-2024 + Assert.assertEquals("Invalid username or password.", loginPage.getError()); - events.expectLogin().user(userId).session((String) null).error("user_disabled") + events.expectLogin().user(userId).session((String) null).error("invalid_user_credentials") .detail(Details.USERNAME, "login-test") .removeDetail(Details.CONSENT) .assertEvent(); @@ -250,6 +251,7 @@ public class LoginTest { Assert.assertEquals("login-test", loginPage.getUsername()); Assert.assertEquals("", loginPage.getPassword()); + // KEYCLOAK-2024 Assert.assertEquals("Account is disabled, contact admin.", loginPage.getError()); events.expectLogin().user(userId).session((String) null).error("user_disabled") diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index 4e771dfefd..8c84581ae7 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -4,6 +4,7 @@ import org.junit.Assert; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; +import org.keycloak.Config; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.ModelDuplicateException; @@ -146,11 +147,11 @@ public class AdapterTest extends AbstractModelTest { Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim"))); List creds = user.getCredentialsDirectly(); Assert.assertEquals(creds.get(0).getHashIterations(), 1); - realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(200)")); + realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(200)")); Assert.assertTrue(userProvider.validCredentials(realmModel, user, UserCredentialModel.password("geheim"))); creds = user.getCredentialsDirectly(); Assert.assertEquals(creds.get(0).getHashIterations(), 200); - realmModel.setPasswordPolicy( new PasswordPolicy("hashIterations(1)")); + realmModel.setPasswordPolicy(new PasswordPolicy("hashIterations(1)")); } @Test @@ -797,6 +798,22 @@ public class AdapterTest extends AbstractModelTest { } + // KEYCLOAK-2026 + @Test + public void testMasterAdminClient() { + realmModel = realmManager.createRealm("foo-realm"); + ClientModel masterAdminClient = realmModel.getMasterAdminClient(); + Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId()); + + commit(); + + realmModel = realmManager.getRealmByName("foo-realm"); + masterAdminClient = realmModel.getMasterAdminClient(); + Assert.assertEquals(Config.getAdminRealm(), masterAdminClient.getRealm().getId()); + + realmManager.removeRealm(realmModel); + } + private KeyPair generateKeypair() throws NoSuchAlgorithmException { return KeyPairGenerator.getInstance("RSA").generateKeyPair(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java index 4c0279848f..54a5cbb4ba 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/AccountFederatedIdentityPage.java @@ -5,12 +5,17 @@ import javax.ws.rs.core.UriBuilder; import org.keycloak.services.Urls; import org.keycloak.testsuite.Constants; import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; /** * @author Marek Posolda */ public class AccountFederatedIdentityPage extends AbstractAccountPage { + @FindBy(className = "alert-error") + private WebElement errorMessage; + public AccountFederatedIdentityPage() {}; private String realmName = "test"; @@ -39,4 +44,8 @@ public class AccountFederatedIdentityPage extends AbstractAccountPage { public void clickRemoveProvider(String providerId) { driver.findElement(By.id("remove-" + providerId)).click(); } + + public String getError() { + return errorMessage.getText(); + } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java new file mode 100644 index 0000000000..83596a06a7 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpConfirmLinkPage.java @@ -0,0 +1,41 @@ +package org.keycloak.testsuite.pages; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Marek Posolda + */ +public class IdpConfirmLinkPage extends AbstractPage { + + @FindBy(id = "updateProfile") + private WebElement updateProfileButton; + + @FindBy(id = "linkAccount") + private WebElement linkAccountButton; + + @FindBy(className = "instruction") + private WebElement message; + + @Override + public boolean isCurrent() { + return driver.getTitle().equals("Account already exists"); + } + + public String getMessage() { + return message.getText(); + } + + public void clickReviewProfile() { + updateProfileButton.click(); + } + + public void clickLinkAccount() { + linkAccountButton.click(); + } + + @Override + public void open() throws Exception { + throw new UnsupportedOperationException(); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java new file mode 100644 index 0000000000..91387b5487 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/IdpLinkEmailPage.java @@ -0,0 +1,27 @@ +package org.keycloak.testsuite.pages; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.FindBy; + +/** + * @author Marek Posolda + */ +public class IdpLinkEmailPage extends AbstractPage { + + @FindBy(id = "instruction1") + private WebElement message; + + @Override + public boolean isCurrent() { + return driver.getTitle().startsWith("Link "); + } + + @Override + public void open() throws Exception { + throw new UnsupportedOperationException(); + } + + public String getMessage() { + return message.getText(); + } +} diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java index f32953375b..04e5dddfcd 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginPage.java @@ -112,6 +112,10 @@ public class LoginPage extends AbstractPage { return usernameInput.getAttribute("value"); } + public boolean isUsernameInputEnabled() { + return usernameInput.isEnabled(); + } + public String getPassword() { return passwordInput.getAttribute("value"); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java index d67862c792..c9038a4d6e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/pages/LoginUpdateProfilePage.java @@ -21,6 +21,8 @@ */ package org.keycloak.testsuite.pages; +import org.junit.Assert; +import org.openqa.selenium.By; import org.openqa.selenium.WebElement; import org.openqa.selenium.support.FindBy;