resolve conflicts

This commit is contained in:
Bill Burke 2015-11-18 09:39:19 -05:00
commit 41331111da
118 changed files with 2137 additions and 5693 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,59 @@ 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 = doGet(clientId);
InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
public void update(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
doPut(content, client.getClientId());
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
}
public void delete() throws ClientRegistrationException {
if (auth instanceof ClientIdSecretAuth) {
String clientId = ((ClientIdSecretAuth) auth).clientId;
delete(clientId);
} else {
throw new ClientRegistrationException("Requires client authentication");
}
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
String content = serialize(client);
InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
}
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 +85,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,171 @@
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 {
if (responseStream != null) {
responseStream.close();
}
throw new HttpErrorException(response.getStatusLine());
}
} catch (IOException e) {
throw new ClientRegistrationException("Failed to send request", e);
}
}
InputStream 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();
}
InputStream responseStream = null;
if (response.getEntity() != null) {
responseStream = response.getEntity().getContent();
}
if (response.getStatusLine().getStatusCode() == 200) {
return responseStream;
} else {
if (responseStream != null) {
responseStream.close();
}
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

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

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

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

@ -116,7 +116,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL));
}
@Override

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

@ -270,7 +270,10 @@ client-certificate-import=Client Certificate Import
import-client-certificate=Import Client Certificate
jwt-import.key-alias.tooltip=Archive alias for your certificate.
secret=Secret
regenerate-secret=Regenerate Secret
regenerate-secret=Regenerate Secretsecret=Secret
registrationAccessToken=Registration access token
registrationAccessToken.regenerate=Regenerate registration access token
registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
add-role=Add Role
role-name=Role Name
composite=Composite
@ -394,7 +397,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

@ -30,7 +30,7 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien
});
});
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client) {
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) {
$scope.realm = realm;
$scope.client = angular.copy(client);
$scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
@ -68,6 +68,17 @@ module.controller('ClientCredentialsCtrl', function($scope, $location, realm, cl
}
}, true);
$scope.regenerateRegistrationAccessToken = function() {
var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id },
function(data) {
Notifications.success('The registration access token has been updated.');
$scope.client['registrationAccessToken'] = data.registrationAccessToken;
},
function() {
Notifications.error('Failed to update the registration access token');
}
);
};
});
module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) {

View file

@ -981,6 +981,17 @@ module.factory('ClientSecret', function($resource) {
});
});
module.factory('ClientRegistrationAccessToken', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clients/:client/registration-access-token', {
realm : '@realm',
client : '@client'
}, {
update : {
method : 'POST'
}
});
});
module.factory('ClientOrigins', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', {
realm : '@realm',

View file

@ -1,5 +1,5 @@
<div>
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
<form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
<fieldset>
<kc-provider-config realm="realm" config="client.attributes" properties="currentAuthenticatorConfigProperties"></kc-provider-config>
</fieldset>

View file

@ -1,5 +1,5 @@
<div>
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
<form class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
<div class="form-group">
<label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
<kc-tooltip>{{:: 'certificate.tooltip' | translate}}</kc-tooltip>

View file

@ -1,5 +1,5 @@
<div>
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
<form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
<div class="form-group">
<label class="col-md-2 control-label" for="secret">{{:: 'secret' | translate}}</label>
<div class="col-sm-6">

View file

@ -28,6 +28,11 @@
<div data-ng-include="resourceUrl + '/partials/' + clientAuthenticatorConfigPartial">
</div>
<hr/>
<div data-ng-include="resourceUrl + '/partials/client-registration-access-token.html'">
</div>
</div>
<kc-menu></kc-menu>

View file

@ -0,0 +1,18 @@
<div>
<form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!access.manageClients">
<div class="form-group">
<label class="col-md-2 control-label" for="registrationAccessToken">{{:: 'registrationAccessToken' | translate}}</label>
<div class="col-sm-6">
<div class="row">
<div class="col-sm-6">
<input readonly kc-select-action="click" class="form-control" type="text" id="registrationAccessToken" name="registrationAccessToken" data-ng-model="client.registrationAccessToken">
</div>
<div class="col-sm-6" data-ng-show="access.manageClients">
<button type="submit" data-ng-click="regenerateRegistrationAccessToken()" class="btn btn-default">{{:: 'registrationAccessToken.regenerate' | translate}}</button>
</div>
</div>
</div>
<kc-tooltip>{{:: 'registrationAccessToken.tooltip' | translate}}</kc-tooltip>
</div>
</form>
</div>

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

@ -22,6 +22,11 @@ table {
margin-top: 20px;
}
.no-margin-top {
margin-top: 0px !important;
}
/*********** Loading ***********/

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

@ -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;
@ -36,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);
@ -44,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) {
@ -322,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");
@ -423,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,7 +1,9 @@
package org.keycloak.models.utils;
import org.bouncycastle.openssl.PEMWriter;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.common.util.Base64Url;
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;
@ -17,6 +19,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;
@ -30,9 +33,11 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -45,6 +50,8 @@ import java.util.UUID;
*/
public final class KeycloakModelUtils {
private static final int RANDOM_PASSWORD_BYTES = 32;
private KeycloakModelUtils() {
}
@ -176,12 +183,22 @@ public final class KeycloakModelUtils {
return rep;
}
public static UserCredentialModel generateSecret(ClientModel app) {
public static UserCredentialModel generateSecret(ClientModel client) {
UserCredentialModel secret = UserCredentialModel.generateSecret();
app.setSecret(secret.getValue());
client.setSecret(secret.getValue());
return secret;
}
public static void generateRegistrationAccessToken(ClientModel client) {
client.setRegistrationSecret(generatePassword());
}
public static String generatePassword() {
byte[] buf = new byte[RANDOM_PASSWORD_BYTES];
new SecureRandom().nextBytes(buf);
return Base64Url.encode(buf);
}
public static String getDefaultClientAuthenticatorType() {
return "client-secret";
}
@ -389,6 +406,26 @@ public final class KeycloakModelUtils {
}
}
/**
* 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);
}
}
}
public static String resolveFirstAttribute(GroupModel group, String name) {
String value = group.getFirstAttribute(name);
if (value != null) return value;

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;
@ -46,14 +44,7 @@ import org.keycloak.representations.idm.UserRepresentation;
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;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -301,19 +292,50 @@ public class ModelToRepresentation {
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
for (AuthenticationFlowModel model : realm.getAuthenticationFlows()) {
List<AuthenticationFlowModel> authenticationFlows = new ArrayList<>(realm.getAuthenticationFlows());
//ensure consistent ordering of authenticationFlows.
Collections.sort(authenticationFlows, new Comparator<AuthenticationFlowModel>() {
@Override
public int compare(AuthenticationFlowModel left, AuthenticationFlowModel right) {
return left.getAlias().compareTo(right.getAlias());
}
});
for (AuthenticationFlowModel model : authenticationFlows) {
AuthenticationFlowRepresentation flowRep = toRepresentation(realm, model);
rep.getAuthenticationFlows().add(flowRep);
}
for (AuthenticatorConfigModel model : realm.getAuthenticatorConfigs()) {
List<AuthenticatorConfigModel> authenticatorConfigs = new ArrayList<>(realm.getAuthenticatorConfigs());
//ensure consistent ordering of authenticatorConfigs.
Collections.sort(authenticatorConfigs, new Comparator<AuthenticatorConfigModel>() {
@Override
public int compare(AuthenticatorConfigModel left, AuthenticatorConfigModel right) {
return left.getAlias().compareTo(right.getAlias());
}
});
for (AuthenticatorConfigModel model : authenticatorConfigs) {
rep.getAuthenticatorConfig().add(toRepresentation(model));
}
}
public static void exportRequiredActions(RealmModel realm, RealmRepresentation rep) {
rep.setRequiredActions(new LinkedList<RequiredActionProviderRepresentation>());
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
List<RequiredActionProviderModel> requiredActionProviders = realm.getRequiredActionProviders();
//ensure consistent ordering of requiredActionProviders.
Collections.sort(requiredActionProviders, new Comparator<RequiredActionProviderModel>() {
@Override
public int compare(RequiredActionProviderModel left, RequiredActionProviderModel right) {
return left.getAlias().compareTo(right.getAlias());
}
});
for (RequiredActionProviderModel model : requiredActionProviders) {
RequiredActionProviderRepresentation action = toRepresentation(model);
rep.getRequiredActions().add(action);
}
@ -396,6 +418,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

@ -797,6 +797,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());
@ -873,6 +875,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

@ -1230,7 +1230,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>

16
pom.xml
View file

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

@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
@ -17,6 +18,8 @@ import java.io.IOException;
*/
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
public static final String ID = "openid-connect";
@Override
public boolean isSupported(String description) {
description = description.trim();
@ -26,15 +29,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public ClientRepresentation convertToInternal(String description) {
try {
OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
ClientRepresentation client = new ClientRepresentation();
client.setClientId(KeycloakModelUtils.generateId());
client.setName(oidcRep.getClientName());
client.setRedirectUris(oidcRep.getRedirectUris());
client.setBaseUrl(oidcRep.getClientUri());
return client;
OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
return DescriptionConverter.toInternal(clientOIDC);
} catch (IOException e) {
throw new RuntimeException(e);
}
@ -59,7 +55,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
@Override
public String getId() {
return "openid-connect";
return ID;
}
}

View file

@ -190,7 +190,7 @@ public class LogoutEndpoint {
}
private ClientModel authorizeClient() {
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
if (client.isBearerOnly()) {
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);

View file

@ -145,7 +145,7 @@ public class TokenEndpoint {
}
private void checkClient() {
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
client = clientAuth.getClient();
clientAuthAttributes = clientAuth.getClientAuthAttributes();

View file

@ -12,12 +12,48 @@ public class OIDCClientRepresentation {
@JsonProperty("redirect_uris")
private List<String> redirectUris;
@JsonProperty("token_endpoint_auth_method")
private String tokenEndpointAuthMethod;
@JsonProperty("grant_types")
private String grantTypes;
@JsonProperty("response_types")
private String responseTypes;
@JsonProperty("client_name")
private String clientName;
@JsonProperty("client_uri")
private String clientUri;
@JsonProperty("logo_uri")
private String logoUri;
@JsonProperty("scope")
private String scope;
@JsonProperty("contacts")
private String contacts;
@JsonProperty("tos_uri")
private String tos_uri;
@JsonProperty("policy_uri")
private String policy_uri;
@JsonProperty("jwks_uri")
private String jwks_uri;
@JsonProperty("jwks")
private String jwks;
@JsonProperty("software_id")
private String softwareId;
@JsonProperty("software_version")
private String softwareVersion;
public List<String> getRedirectUris() {
return redirectUris;
}
@ -26,6 +62,30 @@ public class OIDCClientRepresentation {
this.redirectUris = redirectUris;
}
public String getTokenEndpointAuthMethod() {
return tokenEndpointAuthMethod;
}
public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) {
this.tokenEndpointAuthMethod = tokenEndpointAuthMethod;
}
public String getGrantTypes() {
return grantTypes;
}
public void setGrantTypes(String grantTypes) {
this.grantTypes = grantTypes;
}
public String getResponseTypes() {
return responseTypes;
}
public void setResponseTypes(String responseTypes) {
this.responseTypes = responseTypes;
}
public String getClientName() {
return clientName;
}
@ -42,4 +102,76 @@ public class OIDCClientRepresentation {
this.clientUri = clientUri;
}
public String getLogoUri() {
return logoUri;
}
public void setLogoUri(String logoUri) {
this.logoUri = logoUri;
}
public String getScope() {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getContacts() {
return contacts;
}
public void setContacts(String contacts) {
this.contacts = contacts;
}
public String getTos_uri() {
return tos_uri;
}
public void setTos_uri(String tos_uri) {
this.tos_uri = tos_uri;
}
public String getPolicy_uri() {
return policy_uri;
}
public void setPolicy_uri(String policy_uri) {
this.policy_uri = policy_uri;
}
public String getJwks_uri() {
return jwks_uri;
}
public void setJwks_uri(String jwks_uri) {
this.jwks_uri = jwks_uri;
}
public String getJwks() {
return jwks;
}
public void setJwks(String jwks) {
this.jwks = jwks;
}
public String getSoftwareId() {
return softwareId;
}
public void setSoftwareId(String softwareId) {
this.softwareId = softwareId;
}
public String getSoftwareVersion() {
return softwareVersion;
}
public void setSoftwareVersion(String softwareVersion) {
this.softwareVersion = softwareVersion;
}
}

View file

@ -19,18 +19,8 @@ import javax.ws.rs.core.Response;
*/
public class AuthorizeClientUtil {
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
String flowId = clientAuthFlow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setFlowId(flowId)
.setConnection(session.getContext().getConnection())
.setEventBuilder(event)
.setRealm(realm)
.setSession(session)
.setUriInfo(session.getContext().getUri())
.setRequest(session.getContext().getContextObject(HttpRequest.class));
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
Response response = processor.authenticateClient();
if (response != null) {
@ -45,6 +35,24 @@ public class AuthorizeClientUtil {
return new ClientAuthResult(client, processor.getClientAuthAttributes());
}
public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) {
RealmModel realm = session.getContext().getRealm();
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
String flowId = clientAuthFlow.getId();
AuthenticationProcessor processor = new AuthenticationProcessor();
processor.setFlowId(flowId)
.setConnection(session.getContext().getConnection())
.setEventBuilder(event)
.setRealm(realm)
.setSession(session)
.setUriInfo(session.getContext().getUri())
.setRequest(session.getContext().getContextObject(HttpRequest.class));
return processor;
}
public static class ClientAuthResult {
private final ClientModel client;

View file

@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.Locale;
/**
@ -29,6 +30,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
this.session = session;
}
@Override
public URI getAuthServerUrl() {
UriInfo uri = getUri();
KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
return keycloakApplication.getBaseUri(uri);
}
@Override
public String getContextPath() {
KeycloakApplication app = getContextObject(KeycloakApplication.class);

View file

@ -0,0 +1,92 @@
package org.keycloak.services.clientregistration;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.authentication.AuthenticationProcessor;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.ClientManager;
import org.keycloak.services.managers.RealmManager;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private EventBuilder event;
private ClientRegAuth auth;
public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@GET
@Path("{clientId}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
if (auth.isAuthenticated()) {
auth.requireView(client);
} else {
authenticateClient(client);
}
ClientManager clientManager = new ClientManager(new RealmManager(session));
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
event.client(client.getClientId()).success();
return Response.ok(rep).build();
}
@Override
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
@Override
public void close() {
}
private void authenticateClient(ClientModel client) {
if (client.isPublicClient()) {
return;
}
AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
Response response = processor.authenticateClient();
if (response != null) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
ClientModel authClient = processor.getClient();
if (client == null) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (!authClient.getClientId().equals(client.getClientId())) {
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
}
}

View file

@ -0,0 +1,34 @@
package org.keycloak.services.clientregistration;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdapterInstallationClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
@Override
public ClientRegistrationProvider create(KeycloakSession session) {
return new AdapterInstallationClientRegistrationProvider(session);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "install";
}
}

View file

@ -0,0 +1,134 @@
package org.keycloak.services.clientregistration;
import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.*;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.core.HttpHeaders;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegAuth {
private KeycloakSession session;
private EventBuilder event;
private String token;
private AccessToken.Access bearerRealmAccess;
private boolean authenticated = false;
private boolean registrationAccessToken = false;
public ClientRegAuth(KeycloakSession session, EventBuilder event) {
this.session = session;
this.event = event;
init();
}
private void init() {
RealmModel realm = session.getContext().getRealm();
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authorizationHeader == null) {
return;
}
String[] split = authorizationHeader.split(" ");
if (!split[0].equalsIgnoreCase("bearer")) {
return;
}
if (split[1].indexOf('.') == -1) {
token = split[1];
authenticated = true;
registrationAccessToken = true;
} else {
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
authenticated = true;
}
}
public boolean isAuthenticated() {
return authenticated;
}
public boolean isRegistrationAccessToken() {
return registrationAccessToken;
}
public void requireCreate() {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
public void requireView(ClientModel client) {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (client == null) {
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
return;
}
} else if (token != null) {
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
public void requireUpdate(ClientModel client) {
if (!authenticated) {
event.error(Errors.NOT_ALLOWED);
throw new UnauthorizedException();
}
if (client == null) {
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
if (bearerRealmAccess != null) {
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
return;
}
} else if (token != null) {
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
return;
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
}

View file

@ -9,7 +9,7 @@ import org.keycloak.provider.Provider;
*/
public interface ClientRegistrationProvider extends Provider {
void setRealm(RealmModel realm);
void setAuth(ClientRegAuth auth);
void setEvent(EventBuilder event);

View file

@ -1,37 +1,50 @@
package org.keycloak.services.clientregistration;
import org.jboss.resteasy.spi.NotFoundException;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.ErrorResponseException;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class ClientRegistrationService {
private RealmModel realm;
private EventBuilder event;
@Context
private KeycloakSession session;
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
this.realm = realm;
public ClientRegistrationService(EventBuilder event) {
this.event = event;
}
@Path("{provider}")
public Object getProvider(@PathParam("provider") String providerId) {
checkSsl();
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
provider.setRealm(realm);
if (provider == null) {
throw new NotFoundException("Client registration provider not found");
}
provider.setEvent(event);
provider.setAuth(new ClientRegAuth(session, event));
return provider;
}
private void checkSsl() {
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) {
if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
}
}
}
}

View file

@ -1,22 +1,17 @@
package org.keycloak.services.clientregistration;
import org.jboss.logging.Logger;
import org.keycloak.events.Errors;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.*;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.*;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.net.URI;
@ -26,31 +21,29 @@ import java.net.URI;
*/
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private RealmModel realm;
private ClientRegAuth auth;
public DefaultClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response create(ClientRepresentation client) {
event.event(EventType.CLIENT_REGISTER);
authenticate(true, null);
auth.requireCreate();
try {
ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
client = ModelToRepresentation.toRepresentation(clientModel);
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
logger.infov("Created client {0}", client.getClientId());
event.client(client.getClientId()).success();
return Response.created(uri).entity(client).build();
} catch (ModelDuplicateException e) {
@ -64,10 +57,14 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response get(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_INFO);
ClientModel client = authenticate(false, clientId);
if (client == null) {
return Response.status(Response.Status.NOT_FOUND).build();
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireView(client);
if (auth.isRegistrationAccessToken()) {
KeycloakModelUtils.generateRegistrationAccessToken(client);
}
event.client(client.getClientId()).success();
return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
}
@ -77,13 +74,19 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
event.event(EventType.CLIENT_UPDATE).client(clientId);
ClientModel client = authenticate(false, clientId);
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
RepresentationToModel.updateClient(rep, client);
logger.infov("Updated client {0}", rep.getClientId());
if (auth.isRegistrationAccessToken()) {
KeycloakModelUtils.generateRegistrationAccessToken(client);
}
event.success();
return Response.status(Response.Status.OK).build();
rep = ModelToRepresentation.toRepresentation(client);
event.client(client.getClientId()).success();
return Response.ok(rep).build();
}
@DELETE
@ -91,9 +94,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
public Response delete(@PathParam("clientId") String clientId) {
event.event(EventType.CLIENT_DELETE).client(clientId);
ClientModel client = authenticate(false, clientId);
if (realm.removeClient(client.getId())) {
event.success();
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
auth.requireUpdate(client);
if (session.getContext().getRealm().removeClient(client.getId())) {
event.client(client.getClientId()).success();
return Response.ok().build();
} else {
return Response.status(Response.Status.NOT_FOUND).build();
@ -101,50 +106,8 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
}
@Override
public void close() {
}
private ClientModel authenticate(boolean create, String clientId) {
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
if (bearer) {
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
if (realmAccess != null) {
if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
return create ? null : realm.getClientByClientId(clientId);
}
if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
return create ? null : realm.getClientByClientId(clientId);
}
}
} else if (!create) {
ClientModel client;
try {
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
client = clientAuth.getClient();
if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) {
return client;
}
} catch (Throwable t) {
}
}
event.error(Errors.NOT_ALLOWED);
throw new ForbiddenException();
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
@ -152,4 +115,8 @@ this.realm = realm;
this.event = event;
}
@Override
public void close() {
}
}

View file

@ -1,34 +0,0 @@
package org.keycloak.services.clientregistration;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private KeycloakSession session;
private RealmModel realm;
private EventBuilder event;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
@Override
public void close() {
}
@Override
public void setRealm(RealmModel realm) {
this.realm = realm;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
}

View file

@ -0,0 +1,38 @@
package org.keycloak.services.clientregistration.oidc;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class DescriptionConverter {
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
ClientRepresentation client = new ClientRepresentation();
client.setClientId(KeycloakModelUtils.generateId());
client.setName(clientOIDC.getClientName());
client.setRedirectUris(clientOIDC.getRedirectUris());
client.setBaseUrl(clientOIDC.getClientUri());
return client;
}
public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
response.setClientId(client.getClientId());
response.setClientName(client.getName());
response.setClientUri(client.getBaseUrl());
response.setClientSecret(client.getSecret());
response.setClientSecretExpiresAt(0);
response.setRedirectUris(client.getRedirectUris());
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
return response;
}
}

View file

@ -0,0 +1,99 @@
package org.keycloak.services.clientregistration.oidc;
import org.jboss.logging.Logger;
import org.keycloak.common.util.Time;
import org.keycloak.events.EventBuilder;
import org.keycloak.events.EventType;
import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.utils.ModelToRepresentation;
import org.keycloak.models.utils.RepresentationToModel;
import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.clientregistration.ClientRegAuth;
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
import 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 OIDCClientRegistrationProvider implements ClientRegistrationProvider {
private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
private KeycloakSession session;
private EventBuilder event;
private ClientRegAuth auth;
public OIDCClientRegistrationProvider(KeycloakSession session) {
this.session = session;
}
// @POST
// @Consumes(MediaType.APPLICATION_JSON)
// @Produces(MediaType.APPLICATION_JSON)
// public Response create(OIDCClientRepresentation clientOIDC) {
// event.event(EventType.CLIENT_REGISTER);
//
// auth.requireCreate();
//
// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
//
// try {
// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
//
// client = ModelToRepresentation.toRepresentation(clientModel);
//
// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
//
// clientModel.setRegistrationSecret(registrationAccessToken);
//
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
//
// logger.infov("Created client {0}", client.getClientId());
//
// event.client(client.getClientId()).success();
//
// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
//
// response.setClientName(client.getName());
// response.setClientUri(client.getBaseUrl());
//
// response.setClientSecret(client.getSecret());
// response.setClientSecretExpiresAt(0);
//
// response.setRedirectUris(client.getRedirectUris());
//
// response.setRegistrationAccessToken(registrationAccessToken);
// response.setRegistrationClientUri(uri.toString());
//
// return Response.created(uri).entity(response).build();
// } catch (ModelDuplicateException e) {
// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
// }
// }
@Override
public void close() {
}
@Override
public void setAuth(ClientRegAuth auth) {
this.auth = auth;
}
@Override
public void setEvent(EventBuilder event) {
this.event = event;
}
}

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