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 extends Provider> getProviderClass() {
- return FileConnectionProvider.class;
- }
-
- @Override
- public Class extends ProviderFactory> 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-liquibaseinfinispanmongo
- filemongo-updatehttp-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.keycloakkeycloak-model-jpa
-
- org.keycloak
- keycloak-model-file
- org.keycloakkeycloak-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 clientsKeycloak 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 @@
+
+
+
\ 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 @@
+
+
\ 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 @@