This commit is contained in:
Markus Backes 2015-11-17 09:32:41 +01:00
commit 5e329d7b29
163 changed files with 4678 additions and 6529 deletions

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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);
}
}
}

View file

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

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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);
}
}
}

View file

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

View file

@ -1,43 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.7.0.Final-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-connections-file</artifactId>
<name>Keycloak Connections File</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-single-file</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
</dependencies>
</project>

View file

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

View file

@ -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<KeycloakSession, FileConnectionProvider> allProviders = new HashMap<KeycloakSession, FileConnectionProvider>();
@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<RealmModel> realms = session.realms().getRealms();
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
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) {
}
}

View file

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

View file

@ -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<FileConnectionProvider> {
}

View file

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

View file

@ -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<String, RealmModel> allRealms = new HashMap<String, RealmModel>();
// realmId, userId, userModel
private final Map<String, Map<String,UserModel>> allUsers = new HashMap<String, Map<String,UserModel>>();
private String modelVersion;
public InMemoryModel() {
}
public void putRealm(String id, RealmModel realm) {
allRealms.put(id, realm);
allUsers.put(id, new HashMap<String, UserModel>());
}
public String getModelVersion() {
return modelVersion;
}
public void setModelVersion(String modelVersion) {
this.modelVersion = modelVersion;
}
public RealmModel getRealm(String id) {
return allRealms.get(id);
}
public Collection<RealmModel> 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<String, UserModel> realmUsers(String realmId) {
Map<String, UserModel> 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<UserModel> getUsers(String realmId) {
return realmUsers(realmId).values();
}
public boolean removeUser(String realmId, String userId) {
return (realmUsers(realmId).remove(userId) != null);
}
}

View file

@ -1,30 +0,0 @@
package org.keycloak.connections.file;
import org.keycloak.representations.idm.RealmRepresentation;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class Model {
private String modelVersion;
private List<RealmRepresentation> realms;
public String getModelVersion() {
return modelVersion;
}
public void setModelVersion(String modelVersion) {
this.modelVersion = modelVersion;
}
public List<RealmRepresentation> getRealms() {
return realms;
}
public void setRealms(List<RealmRepresentation> realms) {
this.realms = realms;
}
}

View file

@ -1 +0,0 @@
org.keycloak.connections.file.DefaultFileConnectionProviderFactory

View file

@ -1 +0,0 @@
org.keycloak.connections.file.FileConnectionSpi

View file

@ -58,6 +58,9 @@
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="GROUP_ROLE_MAPPING" constraintName="FK_GROUP_ROLE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
<addColumn tableName="CLIENT">
<column name="REGISTRATION_SECRET" type="VARCHAR(255)"/>
</addColumn>
</changeSet>
</databaseChangeLog>

View file

@ -17,7 +17,6 @@
<module>jpa-liquibase</module>
<module>infinispan</module>
<module>mongo</module>
<module>file</module>
<module>mongo-update</module>
<module>http-client</module>
</modules>

View file

@ -19,6 +19,7 @@ public class ClientRepresentation {
protected Boolean enabled;
protected String clientAuthenticatorType;
protected String secret;
protected String registrationAccessToken;
protected String[] defaultRoles;
protected List<String> redirectUris;
protected List<String> 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<String> getRedirectUris() {
return redirectUris;
}
@ -251,4 +260,5 @@ public class ClientRepresentation {
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
this.protocolMappers = protocolMappers;
}
}

View file

@ -14,6 +14,7 @@ import java.util.Map;
public class GroupRepresentation {
protected String id;
protected String name;
protected String path;
protected Map<String, List<String>> attributes;
protected List<String> realmRoles;
protected Map<String, List<String>> 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<String> getRealmRoles() {
return realmRoles;
}

View file

@ -36,10 +36,6 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-file</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-sessions-infinispan</artifactId>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-connections-file">
<resources>
<artifact name="${org.keycloak:keycloak-connections-file}"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="org.keycloak.keycloak-export-import-single-file"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.keycloak-model-file">
<resources>
<artifact name="${org.keycloak:keycloak-model-file}"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-connections-file"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -8,7 +8,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
<module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@ -33,7 +32,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
@ -70,4 +68,4 @@
<subsystem name="weld"/>
</exclude-subsystems>
</deployment>
</jboss-deployment-structure>
</jboss-deployment-structure>

View file

@ -18,7 +18,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
<module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@ -43,7 +42,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>

View file

@ -173,10 +173,6 @@
<maven-resource group="org.keycloak" artifact="keycloak-connections-jpa-liquibase"/>
</module-def>
<module-def name="org.keycloak.keycloak-connections-file">
<maven-resource group="org.keycloak" artifact="keycloak-connections-file"/>
</module-def>
<module-def name="org.keycloak.keycloak-connections-infinispan">
<maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
</module-def>
@ -224,11 +220,11 @@
<module-def name="org.keycloak.keycloak-social-facebook">
<maven-resource group="org.keycloak" artifact="keycloak-social-facebook"/>
</module-def>
<module-def name="org.keycloak.keycloak-social-linkedin">
<maven-resource group="org.keycloak" artifact="keycloak-social-linkedin"/>
</module-def>
<module-def name="org.keycloak.keycloak-social-stackoverflow">
<maven-resource group="org.keycloak" artifact="keycloak-social-stackoverflow"/>
</module-def>
@ -250,12 +246,6 @@
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
</module-def>
<!-- file -->
<module-def name="org.keycloak.keycloak-model-file">
<maven-resource group="org.keycloak" artifact="keycloak-model-file"/>
</module-def>
<!-- mongo -->
<module-def name="org.keycloak.keycloak-connections-mongo">

View file

@ -8,7 +8,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
<module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@ -33,7 +32,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
@ -70,4 +68,4 @@
<subsystem name="weld"/>
</exclude-subsystems>
</deployment>
</jboss-deployment-structure>
</jboss-deployment-structure>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-connections-file">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-export-import-api"/>
<module name="org.keycloak.keycloak-export-import-single-file"/>
<module name="org.codehaus.jackson.jackson-mapper-asl"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.keycloak.keycloak-model-file">
<resources>
<!-- Insert resources here -->
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-model-api"/>
<module name="org.keycloak.keycloak-connections-file"/>
<module name="org.jboss.logging"/>
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -18,7 +18,6 @@
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo" services="import"/>
<module name="org.keycloak.keycloak-connections-mongo-update" services="import"/>
<module name="org.keycloak.keycloak-connections-file" services="import"/>
<module name="org.keycloak.keycloak-common" services="import"/>
<module name="org.keycloak.keycloak-core" services="import"/>
<module name="org.keycloak.keycloak-email-api" services="import"/>
@ -43,7 +42,6 @@
<module name="org.keycloak.keycloak-model-api" services="import"/>
<module name="org.keycloak.keycloak-model-jpa" services="import"/>
<module name="org.keycloak.keycloak-model-mongo" services="import"/>
<module name="org.keycloak.keycloak-model-file" services="import"/>
<module name="org.keycloak.keycloak-model-sessions-infinispan" services="import"/>
<module name="org.keycloak.keycloak-saml-core" services="import"/>

View file

@ -114,6 +114,10 @@
<name>picketlink.version</name>
<value>${picketlink.version}</value>
</injection>
<injection>
<name>wildfly.version</name>
<value>${wildfly.version}</value>
</injection>
</injections>
<options>
<xmlTransformerType>saxon</xmlTransformerType>

View file

@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
</para>
</section>
<section>
<title>Modifying First Broker Login Flow</title>
<para>
First Broker Login flow is used during first login with some identity provider. Term <literal>First Login</literal> 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 <link linkend="identity-broker-first-login">Identity provider chapter</link>.
</para>
</section>
<section id="client_authentication">
<title>Authentication of clients</title>
<para>Keycloak actually supports pluggable authentication for <ulink url="http://openid.net/specs/openid-connect-core-1_0.html">OpenID Connect</ulink>

View file

@ -8,7 +8,7 @@
<para>
<orderedlist>
<listitem>
Create a new theme within the <literal>themes/admin/mytheme</literal> directory in your distribution.
Create a new theme within the <literal>themes/mytheme/admin</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@ -19,15 +19,15 @@ import=common/keycloak
]]></programlisting>
</listitem>
<listitem>
Copy the file <literal>themes/admin/base/resources/partials/user-attribute-entry.html</literal> into the
a mirror directory in your theme: <literal>themes/admin/mytheme/resources/partials/user-attribute-entry.html</literal>.
Copy the file <literal>themes/base/admin/resources/partials/user-attributes.html</literal> into the
a mirror directory in your theme: <literal>themes/mytheme/admin/resources/partials/user-attributes.html</literal>.
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.
</listitem>
<listitem>
In the <literal>user-attribute-entry.html</literal> file add your custom user attribute entry form item. For example
In the <literal>user-attributes.html</literal> file add your custom user attribute entry form item. For example
<programlisting><![CDATA[ <div class="form-group clearfix block">
<label class="col-sm-2 control-label" for="mobile">Mobile</label>
<div class="col-sm-6">
@ -52,7 +52,7 @@ import=common/keycloak
<para>
<orderedlist>
<listitem>
Create a new theme within the <literal>themes/login/mytheme</literal> directory in your distribution.
Create a new theme within the <literal>themes/mytheme/login</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@ -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]]></programlisting>
</listitem>
<listitem>
Copy the file <literal>themes/login/base/register.ftl</literal> into the
a mirror directory in your theme: <literal>themes/login/mytheme/register.ftl</literal>.
Copy the file <literal>themes/base/login/register.ftl</literal> into the
a mirror directory in your theme: <literal>themes/mytheme/login/register.ftl</literal>.
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.
<para>
<orderedlist>
<listitem>
Create a new theme within the <literal>themes/account/mytheme</literal> directory in your distribution.
Create a new theme within the <literal>themes/mytheme/account</literal> directory in your distribution.
Where <literal>mytheme</literal> is whatever you want to name your theme.
</listitem>
<listitem>
@ -113,8 +113,8 @@ import=common/keycloak
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]></programlisting>
</listitem>
<listitem>
Copy the file <literal>themes/account/base/account.ftl</literal> into the
a mirror directory in your theme: <literal>themes/account/mytheme/account.ftl</literal>.
Copy the file <literal>themes/base/account/account.ftl</literal> into the
a mirror directory in your theme: <literal>themes/mytheme/account/account.ftl</literal>.
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

View file

@ -66,7 +66,7 @@
</itemizedlist>
<section>
<section id="identity-broker-overview">
<title>Overview</title>
<para>
@ -127,10 +127,11 @@
<listitem>
<para>
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 <emphasis>identity federation</emphasis>.
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 <emphasis>account linking</emphasis>.
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 <emphasis>account linking</emphasis>. What exactly is done is configurable
and can be specified by setup of <link linkend="identity-broker-first-login">First Login Flow</link> .
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.
</para>
@ -210,7 +211,7 @@
<para>
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.
</para>
</listitem>
</varlistentry>
@ -274,6 +275,15 @@
be used by any other means.
</entry>
</row>
<row>
<entry>
<literal>Authenticate By Default</literal>
</entry>
<entry>
If enabled, Keycloak will automatically redirect to this identity provider even before displaying login screen.
In other words, steps 3 and 4 from <link linkend="identity-broker-overview">the base flow</link> are skipped.
</entry>
</row>
<row>
<entry>
<literal>Store Tokens</literal>
@ -293,20 +303,6 @@
to access any stored external tokens via the broker service.
</entry>
</row>
<row>
<entry>
<literal>Update Profile on First Login</literal>
</entry>
<entry>
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
<emphasis>update profile page</emphasis> asking for additional information in order to federate their identities.
When "On missing info", users will be presented with the <emphasis>update profile page</emphasis> 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.
</entry>
</row>
<row>
<entry>
<literal>Trust email</literal>
@ -326,6 +322,16 @@
You can put number into this field, providers with lower numbers are shown first.
</entry>
</row>
<row>
<entry>
<literal>First Login Flow</literal>
</entry>
<entry>
Alias of authentication flow, which is triggered during first login with this identity provider. Term <literal>First Login</literal>
means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
More details in <link linkend="identity-broker-first-login">First Login section</link>.
</entry>
</row>
</tbody>
</tgroup>
</table>
@ -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.
</para>
<section>
@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
<section>
<title>Automatically Select and Identity Provider</title>
<para>
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 <literal>Authenticate By Default</literal>, 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.
</para>
<para>
Applications can also automatically select an identity provider in order to authenticate an user.
Selection per application is preferred over <literal>Authenticate By Default</literal> option if you need more control
on when exactly is Identity provider automatically selected.
</para>
<para>
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({
</para>
</section>
<section id="identity-broker-first-login">
<title>First Login Flow</title>
<para>
When Keycloak successfully authenticates user through identity provider (step 8 in <link linkend="identity-broker-overview">Overview</link> chapter),
there can be two situations:
<orderedlist>
<listitem>
<para>
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 <link linkend="identity-broker-overview">Overview</link> chapter).
</para>
</listitem>
<listitem>
<para>
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...
</para>
</listitem>
</orderedlist>
</para>
<para>
Because we had various requirements what to do in second case, we changed the behaviour to be flexible and configurable
through <link linkend="auth_spi">Authentication Flows SPI</link>. In admin console in Identity provider settings, there is option
<literal>First Login Flow</literal>, which allows you to choose, which workflow will be used after "first login" with this identity provider account.
By default it points to <literal>first broker login</literal> flow, but you can configure and use your own flow and use different flows for different identity providers etc.
</para>
<para>
The flow itself is configured in admin console under <literal>Authentication</literal> tab. When you choose <literal>First Broker Login</literal> 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 <literal>required</literal>, 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 <link linkend="auth_spi">Authentication Flows SPI</link> for more details on how to do it.
</para>
<para>
For <literal>First Broker Login</literal> case, it might be useful if your Authenticator is subclass of <literal>org.keycloak.authentication.authenticators.broker.AbstractIdpAuthenticator</literal>
so you have access to all details about authenticated Identity provider account. But it's not a requirement.
</para>
<section>
<title>Default First Login Flow</title>
<para>
Let's describe the default behaviour provided by <literal>First Broker Login</literal> flow. There are those authenticators:
<variablelist>
<varlistentry>
<term>Review Profile</term>
<listitem>
<para>
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 <literal>Update Profile On First Login</literal> option.
When <literal>On</literal>, users will be always presented with the profile page asking for additional information
in order to federate their identities. When <literal>missing</literal>, 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 <literal>Off</literal>, the profile page won't be displayed, unless user clicks in later phase on <literal>Review profile info</literal>
link (page displayed in later phase by <literal>Confirm Link Existing Account</literal> authenticator)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Create User If Unique</term>
<listitem>
<para>
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 <literal>Handle Existing Account</literal> subflow.
If you always want to ensure that there is no duplicated account, you can mark this authenticator as <literal>REQUIRED</literal> .
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.
</para>
<para>
This authenticator also has config option <literal>Require Password Update After Registration</literal> .
When enabled, user is required to update password after account is created.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Confirm Link Existing Account</term>
<listitem>
<para>
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 <literal>Review Profile</literal> 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.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Verify Existing Account By Email</term>
<listitem>
<para>
This authenticator is <literal>ALTERNATIVE</literal> 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).
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Verify Existing Account By Re-authentication</term>
<listitem>
<para>
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.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
</section>
</section>
<section>
<title>Examples</title>
<para>

View file

@ -43,9 +43,9 @@
<section id="overlay_install">
<title>Install on existing WildFly 9.0.1.Final</title>
<title>Install on existing WildFly &wildfly.version;</title>
<para>
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
<literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak
run:
@ -62,11 +62,15 @@
<para>
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:
<programlisting>cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
<programlisting>
cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install.cli
</programlisting>
Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
<programlisting>cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting>
<programlisting>
cd &lt;WILDFLY_HOME&gt;/bin
./jboss-cli.sh -c --file=keycloak-install-ha.cli
</programlisting>
You may see exceptions in the server log, but after restarting the server they should be gone.
You can restart the server with:
<programlisting>&lt;WILDFLY_HOME&gt;/bin/jboss-cli.sh -c :reload</programlisting>
@ -75,7 +79,7 @@
<section>
<title>Install on existing JBoss EAP 6.4.0.GA</title>
<para>
Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
Same procedure as WildFly &wildfly.version;, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
</para>
</section>
<section>
@ -85,7 +89,7 @@
To install it first download <literal>keycloak-demo-&project.version;.zip</literal> or
<literal>keycloak-demo-&project.version;.tar.gz</literal>. Once downloaded extract it inside
<literal>keycloak-demo-&project.version;</literal> you'll find <literal>keycloak</literal> which contains
a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
and <literal>examples</literal> which contains everything you need to get started developing applications that use Keycloak.
</para>
<para>
@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is <literal>
settings you can specify before boot time. This is configured in the
<literal>standalone/configuration/keycloak-server.json</literal>.
By default the setting is like this:
<programlisting><![CDATA[
"connectionsHttpClient": {
"default": {
"disable-trust-manager": true
}
},
<programlisting><![CDATA[
"connectionsHttpClient": {
"default": {
"disable-trust-manager": true
}
},
]]></programlisting>
Possible configuration options are:
<variablelist>
@ -659,25 +663,25 @@ All configuration options are optional. Default value for directory is <literal>
to do with the <literal>keytool</literal> utility that comes with the Java jdk.
</para>
<para>
<programlisting>
$ 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
</programlisting>
<programlisting>
$ 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
</programlisting>
</para>
<para>
You should answer <literal>What is your first and last name ?</literal> question with
@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is <literal>
</para>
<para>
The first thing to do is generate a Certificate Request:
<programlisting>
$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
</programlisting>
<programlisting>
$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
</programlisting>
</para>
<para>
Where <literal>yourdomain</literal> is a DNS name for which this certificate is generated for.
Keytool generates the request:
<programlisting>
-----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-----
</programlisting>
<programlisting>
-----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-----
</programlisting>
</para>
<para>
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:
<programlisting>
$ keytool -import -keystore keycloak.jks -file root.crt -alias root
</programlisting>
<programlisting>
$ keytool -import -keystore keycloak.jks -file root.crt -alias root
</programlisting>
</para>
<para>
Last step is import your new CA generated certificate to your keystore:
<programlisting>
$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
</programlisting>
<programlisting>
$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
</programlisting>
</para>
</section>
</section>
@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is <literal>
</para>
<para>
To the <literal>security-realms</literal> element add:
<programlisting><![CDATA[<security-realm name="UndertowRealm">
<server-identities>
<ssl>
<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
</ssl>
</server-identities>
</security-realm>]]></programlisting>
<programlisting><![CDATA[
<security-realm name="UndertowRealm">
<server-identities>
<ssl>
<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
</ssl>
</server-identities>
</security-realm>
]]></programlisting>
</para>
<para>
Find the element <literal>&lt;server name="default-server"&gt;</literal> (it's a child element of <literal>&lt;subsystem xmlns="urn:jboss:domain:undertow:1.0"&gt;</literal>) and add:
<programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>
]]></programlisting>
<programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>]]></programlisting>
</para>
<para>
Check the <ulink url="https://docs.jboss.org/author/display/WFLY8/Undertow+(web)+subsystem+configuration">Wildfly Undertow</ulink> documentation for more information on fine tuning the socket connections.
@ -813,44 +818,20 @@ All configuration options are optional. Default value for directory is <literal>
</section>
<section>
<title>Adding Keycloak server in Domain Mode</title>
<title>Keycloak server in Domain Mode</title>
<para>
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.
</para>
<para>
To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the <literal>extensions</literal>
element add the Keycloak extension:
<programlisting><![CDATA[
<extensions>
...
<extension module="org.keycloak.keycloak-subsystem"/>
</extensions>
]]></programlisting>
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 <literal>profile</literal> element with <literal>name</literal> full:
<programlisting><![CDATA[
<profile name="full">
...
<subsystem xmlns="urn:jboss:domain:keycloak:1.0">
<auth-server name="main-auth-server">
<enabled>true</enabled>
<web-context>auth</web-context>
</auth-server>
</subsystem>
]]></programlisting>
THe server is also added to server profiles. By default two servers are started
in the main-server-group which uses the full profile.
</para>
<para>
To configure the server copy <literal>standalone/configuration/keycloak-server.json</literal> to
<literal>domain/servers/&lt;SERVER NAME&gt;/configuration</literal>. The configuration should be identical
You need to make sure <literal>domain/servers/&lt;SERVER NAME&gt;/configuration</literal> is identical
for all servers in a group.
</para>
<para>
Follow the <link linkend='clustering'>Clustering</link> 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.
</para>
<para>
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 <link linkend='providers'>Providers</link> and
@ -865,12 +846,12 @@ All configuration options are optional. Default value for directory is <literal>
</para>
<para>
To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
<programlisting><![CDATA[
<programlisting><![CDATA[
<subsystem xmlns="urn:jboss:domain:undertow:2.0">
<server name="default-server">
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
<location name="/" handler="welcome-content"/>
</host>
<server name="default-server">
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
<location name="/" handler="welcome-content"/>
</host>
]]></programlisting>
</para>
<para>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -37,7 +37,7 @@
<td>{{mapper.name}}</td>
<td>{{mapperTypes[mapper.protocolMapper].category}}</td>
<td>{{mapperTypes[mapper.protocolMapper].name}}</td>
<td><input type="checkbox" ng-model="mapper.isChecked"></td>
<td><input type="checkbox" ng-model="mapper.isChecked" id="{{mapper.protocolMapper}}"></td>
</tr>
<tr data-ng-show="mappers.length == 0">
<td>{{:: 'no-mappers-available' | translate}}</td>

View file

@ -0,0 +1,50 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
<li>{{group.name}}</li>
</ol>
<kc-tabs-group></kc-tabs-group>
<table class="table table-striped table-bordered">
<caption data-ng-show="users" class="hidden">Table of group members</caption>
<thead>
<tr>
<tr data-ng-show="searchLoaded && users.length > 0">
<th>Username</th>
<th>Last Name</th>
<th>First Name</th>
<th>Email</th>
<th>Actions</th>
</tr>
</tr>
</thead>
<tfoot data-ng-show="users && (users.length >= query.max || query.first > 0)">
<tr>
<td colspan="7">
<div class="table-nav">
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
<button data-ng-click="nextPage()" class="next" ng-disabled="users.length < query.max">Next page</button>
</div>
</td>
</tr>
</tfoot>
<tbody>
<tr ng-repeat="user in users">
<td><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{user.username}}</a></td>
<td>{{user.lastName}}</td>
<td>{{user.firstName}}</td>
<td>{{user.email}}</td>
<td class="kc-action-cell">
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">Edit</button>
</td>
</tr>
<tr data-ng-show="!users || users.length == 0">
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch != null">No group members</td>
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch == null">No group members</td>
</tr>
</tbody>
</table>
</div>
<kc-menu></kc-menu>

View file

@ -20,8 +20,8 @@
<tbody>
<tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
<td>{{requiredAction.name}}</td>
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)"></td>
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)"></td>
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
</tr>
<tr data-ng-show="requiredActions.length == 0">
<td>No required actions configured</td>

View file

@ -94,7 +94,6 @@
</div>
</div>
</div>
</div>
</form>
</div>

View file

@ -0,0 +1,85 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li>{{user.username}}</li>
</ol>
<kc-tabs-user></kc-tabs-user>
<form class="form-horizontal" name="realmForm" novalidate>
<div class="form-group" kc-read-only="!access.manageUsers">
<label class="col-md-1 control-label" class="control-label"></label>
<div class="col-md-8" >
<div class="row">
<div class="col-md-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="5">
<div class="form-inline">
<label class="control-label">Group Membership</label>
<kc-tooltip>Groups user is a member of. Select a listed group and click the Leave button to leave the group.</kc-tooltip>
<div class="pull-right" data-ng-show="access.manageUsers">
<button id="leaveGroups" class="btn btn-default" ng-click="leaveGroup()">Leave</button>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<select id="groupMembership" class="form-control" size=5
ng-model="selectedGroup"
ng-options="r.path for r in groupMemberships">
<option style="display:none" value="">select a type</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
<div class="col-md-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th class="kc-table-actions" colspan="5">
<div class="form-inline">
<label class="control-label">Available Groups</label>
<kc-tooltip>Groups a user can join. Select a group and click the join button.</kc-tooltip>
<div class="pull-right" data-ng-show="access.manageUsers">
<button id="joinGroup" class="btn btn-default" ng-click="joinGroup()">Join</button>
</div>
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<td> <div
tree-id="tree"
angular-treeview="true"
tree-model="groupList"
node-id="id"
node-label="name"
node-children="subGroups" >
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</form>
</div>
<kc-menu></kc-menu>

View file

@ -10,6 +10,7 @@
<li ng-class="{active: path[4] == 'user-attributes'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-attributes">Attributes</a></li>
<li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
<li ng-class="{active: path[4] == 'groups'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/groups">Groups</a></li>
<li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
<li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
<li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>

View file

@ -12,8 +12,8 @@
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="updateProfile" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="linkAccount" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
</div>
</form>

View file

@ -5,10 +5,10 @@
<#elseif section = "header">
${msg("emailLinkIdpTitle", idpAlias)}
<#elseif section = "form">
<p class="instruction">
<p id="instruction1" class="instruction">
${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
</p>
<p class="instruction">
<p id="instruction2" class="instruction">
${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
</p>
</#if>

View file

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

View file

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

View file

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

View file

@ -71,21 +71,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -70,21 +70,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -81,21 +81,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -67,21 +67,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

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

View file

@ -90,6 +90,9 @@ public class FilterSessionStore implements AdapterSessionStore {
MultivaluedHashMap<String, String> getParams() {
if (parameters != null) return parameters;
if (body == null) return new MultivaluedHashMap<String, String>();
String contentType = getContentType();
contentType = contentType.toLowerCase();
if (contentType.startsWith("application/x-www-form-urlencoded")) {

View file

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

View file

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

View file

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

View file

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

View file

@ -23,6 +23,9 @@ public class FormMessage {
private String message;
private Object[] parameters;
public FormMessage() {
}
/**
* Create message.
*

View file

@ -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<AuthenticationExecutionModel> result) {
List<AuthenticationExecutionModel> 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);
}
}
}
}

View file

@ -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<RoleModel> 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<String> redirectUris = clientModel.getRedirectUris();
if (redirectUris != null) {

View file

@ -737,6 +737,8 @@ public class RepresentationToModel {
KeycloakModelUtils.generateSecret(client);
}
client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
if (resourceRep.getAttributes() != null) {
for (Map.Entry<String, String> 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());

View file

@ -1,62 +0,0 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent>
<artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.7.0.Final-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>keycloak-model-file</artifactId>
<name>Keycloak Model File</name>
<description/>
<dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-single-file</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-file</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View file

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

View file

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

View file

@ -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<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
return null;
}
@Override
public List<UserModel> 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<FederatedIdentityModel> 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<UserModel> 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<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) {
List<UserModel> users = new ArrayList<>(inMemoryModel.getUsers(realm.getId()));
if (!includeServiceAccounts) {
users = filterServiceAccountUsers(users);
}
List<UserModel> sortedList = sortedSubList(users, firstResult, maxResults);
return sortedList;
}
private List<UserModel> filterServiceAccountUsers(List<UserModel> users) {
List<UserModel> result = new ArrayList<>();
for (UserModel user : users) {
if (user.getServiceAccountClientLink() == null) {
result.add(user);
}
}
return result;
}
protected List<UserModel> 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<UserModel> searchForUser(String search, RealmModel realm) {
return searchForUser(search, realm, -1, -1);
}
@Override
public List<UserModel> 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<UserModel> found = new ArrayList<UserModel>();
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<UserModel> searchForUserByAttributes(Map<String, String> 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<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
Pattern usernamePattern = null;
Pattern firstNamePattern = null;
Pattern lastNamePattern = null;
Pattern emailPattern = null;
for (Map.Entry<String, String> 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<UserModel> found = new ArrayList<UserModel>();
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<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
Collection<UserModel> users = inMemoryModel.getUsers(realm.getId());
List<UserModel> matchedUsers = new ArrayList<>();
for (UserModel user : users) {
List<String> vals = user.getAttribute(attrName);
if (vals.contains(attrValue)) {
matchedUsers.add(user);
}
}
return matchedUsers;
}
@Override
public Set<FederatedIdentityModel> getFederatedIdentities(UserModel userModel, RealmModel realm) {
UserEntity userEntity = ((UserAdapter)getUserById(userModel.getId(), realm)).getUserEntity();
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
if (linkEntities == null) {
return Collections.EMPTY_SET;
}
Set<FederatedIdentityModel> result = new HashSet<FederatedIdentityModel>();
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<FederatedIdentityEntity> 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<FederatedIdentityEntity> 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<UserModel> toBeRemoved = new HashSet<UserModel>();
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<UserCredentialModel> 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<FederatedIdentityEntity> 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
}
}

View file

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

View file

@ -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<String, RoleAdapter> allRoles = new HashMap<String, RoleAdapter>();
private final Map<String, RoleModel> allScopeMappings = new HashMap<String, RoleModel>();
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<String> getWebOrigins() {
Set<String> result = new HashSet<String>();
if (entity.getWebOrigins() != null) {
result.addAll(entity.getWebOrigins());
}
return result;
}
@Override
public void setWebOrigins(Set<String> webOrigins) {
List<String> result = new ArrayList<String>();
result.addAll(webOrigins);
entity.setWebOrigins(result);
}
@Override
public void addWebOrigin(String webOrigin) {
Set<String> webOrigins = getWebOrigins();
webOrigins.add(webOrigin);
setWebOrigins(webOrigins);
}
@Override
public void removeWebOrigin(String webOrigin) {
Set<String> webOrigins = getWebOrigins();
webOrigins.remove(webOrigin);
setWebOrigins(webOrigins);
}
@Override
public Set<String> getRedirectUris() {
Set<String> result = new HashSet<String>();
if (entity.getRedirectUris() != null) {
result.addAll(entity.getRedirectUris());
}
return result;
}
@Override
public void setRedirectUris(Set<String> redirectUris) {
List<String> result = new ArrayList<String>();
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<RoleModel> getScopeMappings() {
return new HashSet<RoleModel>(allScopeMappings.values());
}
@Override
public Set<RoleModel> getRealmScopeMappings() {
Set<RoleModel> allScopes = getScopeMappings();
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
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<String, String> getAttributes() {
Map<String, String> copy = new HashMap<String, String>();
copy.putAll(entity.getAttributes());
return copy;
}
@Override
public Set<ProtocolMapperModel> getProtocolMappers() {
Set<ProtocolMapperModel> result = new HashSet<ProtocolMapperModel>();
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<String, String> config = new HashMap<String, String>();
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<RoleModel> getRoles() {
return new HashSet(allRoles.values());
}
@Override
public boolean hasScope(RoleModel role) {
if (isFullScopeAllowed()) return true;
Set<RoleModel> 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<RoleModel> getClientScopeMappings(ClientModel client) {
Set<RoleModel> allScopes = client.getScopeMappings();
Set<RoleModel> appRoles = new HashSet<RoleModel>();
for (RoleModel role : allScopes) {
RoleAdapter roleAdapter = (RoleAdapter)role;
if (getId().equals(roleAdapter.getRoleEntity().getClientId())) {
appRoles.add(role);
}
}
return appRoles;
}
@Override
public List<String> getDefaultRoles() {
return entity.getDefaultRoles();
}
@Override
public void addDefaultRole(String name) {
RoleModel role = getRole(name);
if (role == null) {
addRole(name);
}
List<String> 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<String> roleNames = new ArrayList<String>();
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<String, Integer> getRegisteredNodes() {
return entity.getRegisteredNodes() == null ? Collections.<String, Integer>emptyMap() : Collections.unmodifiableMap(entity.getRegisteredNodes());
}
@Override
public void registerNode(String nodeHost, int registrationTime) {
if (entity.getRegisteredNodes() == null) {
entity.setRegisteredNodes(new HashMap<String, Integer>());
}
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();
}
}

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
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<String, List<String>>());
}
List<String> attrValues = new ArrayList<>();
attrValues.add(value);
group.getAttributes().put(name, attrValues);
}
@Override
public void setAttribute(String name, List<String> values) {
if (group.getAttributes() == null) {
group.setAttributes(new HashMap<String, List<String>>());
}
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<String> attrValues = group.getAttributes().get(name);
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
}
@Override
public List<String> getAttribute(String name) {
if (group.getAttributes()==null) return Collections.<String>emptyList();
List<String> attrValues = group.getAttributes().get(name);
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
}
@Override
public Map<String, List<String>> getAttributes() {
return group.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) group.getAttributes());
}
@Override
public boolean hasRole(RoleModel role) {
Set<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
public void grantRole(RoleModel role) {
if (group.getRoleIds() == null) {
group.setRoleIds(new LinkedList<String>());
}
if (group.getRoleIds().contains(role.getId())) {
return;
}
group.getRoleIds().add(role.getId());
}
@Override
public Set<RoleModel> getRoleMappings() {
if (group.getRoleIds() == null || group.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
Set<RoleModel> roles = new HashSet<>();
for (String id : group.getRoleIds()) {
roles.add(realm.getRoleById(id));
}
return roles;
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> allRoles = getRoleMappings();
// Filter to retrieve just realm roles
Set<RoleModel> realmRoles = new HashSet<RoleModel>();
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<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<RoleModel>();
Set<RoleModel> 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<GroupModel> getSubGroups() {
Set<GroupModel> 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);
}
}

View file

@ -1,26 +0,0 @@
package org.keycloak.models.file.adapter;
import org.keycloak.connections.file.InMemoryModel;
import org.keycloak.migration.MigrationModel;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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);
}
}

View file

@ -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<RoleModel> compositeRoles = new HashSet<RoleModel>();
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<String> compositeRoleIds = role.getCompositeRoleIds();
if (compositeRoleIds == null) compositeRoleIds = new ArrayList<String>();
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<RoleModel> toBeRemoved = new HashSet<RoleModel>();
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<String> compositeRoleIds = role.getCompositeRoleIds();
if (compositeRoleIds == null) return; // shouldn't happen
compositeRoleIds.remove(childRole.getId());
role.setCompositeRoleIds(compositeRoleIds);
}
@Override
public Set<RoleModel> 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<RoleModel> visited = new HashSet<RoleModel>();
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();
}
}

View file

@ -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<RoleModel> allRoles = new HashSet<RoleModel>();
private final Set<GroupModel> allGroups = new HashSet<GroupModel>();
public UserAdapter(RealmModel realm, UserEntity userEntity, InMemoryModel inMemoryModel) {
this.user = userEntity;
this.realm = realm;
if (userEntity.getFederatedIdentities() == null) {
userEntity.setFederatedIdentities(new ArrayList<FederatedIdentityEntity>());
}
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<String, List<String>>());
}
List<String> attrValues = new ArrayList<>();
attrValues.add(value);
user.getAttributes().put(name, attrValues);
}
@Override
public void setAttribute(String name, List<String> values) {
if (user.getAttributes() == null) {
user.setAttributes(new HashMap<String, List<String>>());
}
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<String> attrValues = user.getAttributes().get(name);
return (attrValues==null || attrValues.isEmpty()) ? null : attrValues.get(0);
}
@Override
public List<String> getAttribute(String name) {
if (user.getAttributes()==null) return Collections.<String>emptyList();
List<String> attrValues = user.getAttributes().get(name);
return (attrValues == null) ? Collections.<String>emptyList() : Collections.unmodifiableList(attrValues);
}
@Override
public Map<String, List<String>> getAttributes() {
return user.getAttributes()==null ? Collections.<String, List<String>>emptyMap() : Collections.unmodifiableMap((Map) user.getAttributes());
}
@Override
public Set<String> getRequiredActions() {
List<String> requiredActions = user.getRequiredActions();
if (requiredActions == null) requiredActions = new ArrayList<String>();
return new HashSet(requiredActions);
}
@Override
public void addRequiredAction(RequiredAction action) {
String actionName = action.name();
addRequiredAction(actionName);
}
@Override
public void addRequiredAction(String actionName) {
List<String> 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<String> 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<CredentialEntity> 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<CredentialEntity> 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<CredentialEntity> getCredentialEntities(UserEntity userEntity, String credType) {
List<CredentialEntity> credentialEntities = new ArrayList<CredentialEntity>();
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<CredentialEntity>() {
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<UserCredentialValueModel> getCredentialsDirectly() {
List<CredentialEntity> credentials = new ArrayList<CredentialEntity>(user.getCredentials());
List<UserCredentialValueModel> result = new ArrayList<UserCredentialValueModel>();
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<GroupModel> 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<RoleModel> roles = getRoleMappings();
return KeycloakModelUtils.hasRole(roles, role);
}
@Override
public void grantRole(RoleModel role) {
allRoles.add(role);
}
@Override
public Set<RoleModel> getRoleMappings() {
return Collections.unmodifiableSet(allRoles);
}
@Override
public Set<RoleModel> getRealmRoleMappings() {
Set<RoleModel> 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<RoleModel> realmRoles = new HashSet<RoleModel>();
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<RoleModel> getClientRoleMappings(ClientModel app) {
Set<RoleModel> result = new HashSet<RoleModel>();
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<UserConsentModel> 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()));
}
}

View file

@ -1 +0,0 @@
org.keycloak.models.file.FileRealmProviderFactory

View file

@ -1 +0,0 @@
org.keycloak.models.file.FileUserProviderFactory

View file

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

View file

@ -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<String, String> attributes = new HashMap<String, String>();
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;
}

View file

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

View file

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

View file

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

View file

@ -177,6 +177,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> 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();

View file

@ -1223,7 +1223,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> 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

View file

@ -453,7 +453,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
@Override
public Set<GroupModel> 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<GroupModel> groups = new HashSet<>();
for (String id : user.getGroupIds()) {
groups.add(realm.getGroupById(id));

View file

@ -29,7 +29,6 @@
<module>invalidation-cache</module>
<module>jpa</module>
<module>mongo</module>
<module>file</module>
<module>sessions-infinispan</module>
</modules>
</project>

20
pom.xml
View file

@ -48,8 +48,8 @@
<dom4j.version>1.6.1</dom4j.version>
<xml-apis.version>1.4.01</xml-apis.version>
<slf4j.version>1.7.7</slf4j.version>
<wildfly.version>9.0.1.Final</wildfly.version>
<wildfly.core.version>1.0.1.Final</wildfly.core.version>
<wildfly.version>9.0.2.Final</wildfly.version>
<wildfly.core.version>1.0.2.Final</wildfly.core.version>
<wildfly.build-tools.version>1.0.0.Final</wildfly.build-tools.version>
<!-- this is EAP 6.4 alpha, publicly available -->
@ -597,11 +597,6 @@
<artifactId>keycloak-broker-saml</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-infinispan</artifactId>
@ -838,7 +833,7 @@
<groupId>org.keycloak</groupId>
<artifactId>keycloak-wf9-server-subsystem</artifactId>
<version>${project.version}</version>
</dependency>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-subsystem</artifactId>
@ -959,11 +954,6 @@
<artifactId>keycloak-model-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-file</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
@ -1441,7 +1431,7 @@
<groupId>org.wildfly.build</groupId>
<artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
<version>${wildfly.build-tools.version}</version>
</plugin>
</plugin>
<plugin>
<groupId>org.wildfly.build</groupId>
<artifactId>wildfly-server-provisioning-maven-plugin</artifactId>
@ -1462,7 +1452,7 @@
<requireMavenVersion>
<version>3.1.1</version>
</requireMavenVersion>
</rules>
</rules>
</configuration>
</execution>
</executions>

View file

@ -59,21 +59,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -54,21 +54,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -69,21 +69,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

@ -69,21 +69,21 @@
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-security</artifactId>
<version>${jetty9.version}</version>
<scope>compile</scope>
<scope>provided</scope>
</dependency>
<dependency>

View file

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

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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;
}
}

View file

@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
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;
}
}

View file

@ -0,0 +1 @@
org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more