Merge branch 'master' of https://github.com/keycloak/keycloak
This commit is contained in:
commit
5e329d7b29
163 changed files with 4678 additions and 6529 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,18 +1,9 @@
|
|||
package org.keycloak.client.registration;
|
||||
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpRequest;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.common.util.Base64;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -23,160 +14,58 @@ import java.io.InputStream;
|
|||
*/
|
||||
public class ClientRegistration {
|
||||
|
||||
private String clientRegistrationUrl;
|
||||
private HttpClient httpClient;
|
||||
private Auth auth;
|
||||
private final String DEFAULT = "default";
|
||||
private final String INSTALLATION = "install";
|
||||
|
||||
public static ClientRegistrationBuilder create() {
|
||||
return new ClientRegistrationBuilder();
|
||||
private HttpUtil httpUtil;
|
||||
|
||||
public ClientRegistration(String authServerUrl, String realm) {
|
||||
httpUtil = new HttpUtil(HttpClients.createDefault(), HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
|
||||
}
|
||||
|
||||
private ClientRegistration() {
|
||||
public ClientRegistration(String authServerUrl, String realm, HttpClient httpClient) {
|
||||
httpUtil = new HttpUtil(httpClient, HttpUtil.getUrl(authServerUrl, "realms", realm, "clients"));
|
||||
}
|
||||
|
||||
public void close() throws ClientRegistrationException {
|
||||
if (httpUtil != null) {
|
||||
httpUtil.close();
|
||||
}
|
||||
httpUtil = null;
|
||||
}
|
||||
|
||||
public ClientRegistration auth(Auth auth) {
|
||||
httpUtil.setAuth(auth);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
InputStream resultStream = doPost(content);
|
||||
InputStream resultStream = httpUtil.doPost(content, DEFAULT);
|
||||
return deserialize(resultStream, ClientRepresentation.class);
|
||||
}
|
||||
|
||||
public ClientRepresentation get() throws ClientRegistrationException {
|
||||
if (auth instanceof ClientIdSecretAuth) {
|
||||
String clientId = ((ClientIdSecretAuth) auth).clientId;
|
||||
return get(clientId);
|
||||
} else {
|
||||
throw new ClientRegistrationException("Requires client authentication");
|
||||
}
|
||||
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
|
||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||
}
|
||||
|
||||
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = doGet(clientId);
|
||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
|
||||
InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
|
||||
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
|
||||
}
|
||||
|
||||
public void update(ClientRepresentation client) throws ClientRegistrationException {
|
||||
String content = serialize(client);
|
||||
doPut(content, client.getClientId());
|
||||
httpUtil.doPut(content, DEFAULT, client.getClientId());
|
||||
}
|
||||
|
||||
public void delete() throws ClientRegistrationException {
|
||||
if (auth instanceof ClientIdSecretAuth) {
|
||||
String clientId = ((ClientIdSecretAuth) auth).clientId;
|
||||
delete(clientId);
|
||||
} else {
|
||||
throw new ClientRegistrationException("Requires client authentication");
|
||||
}
|
||||
public void delete(ClientRepresentation client) throws ClientRegistrationException {
|
||||
delete(client.getClientId());
|
||||
}
|
||||
|
||||
public void delete(String clientId) throws ClientRegistrationException {
|
||||
doDelete(clientId);
|
||||
}
|
||||
|
||||
public void close() throws ClientRegistrationException {
|
||||
if (httpClient instanceof CloseableHttpClient) {
|
||||
try {
|
||||
((CloseableHttpClient) httpClient).close();
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to close http client", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream doPost(String content) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPost request = new HttpPost(clientRegistrationUrl);
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
auth.addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
InputStream responseStream = null;
|
||||
if (response.getEntity() != null) {
|
||||
responseStream = response.getEntity().getContent();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 201) {
|
||||
return responseStream;
|
||||
} else {
|
||||
responseStream.close();
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
private InputStream doGet(String endpoint) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpGet request = new HttpGet(clientRegistrationUrl + "/" + endpoint);
|
||||
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
|
||||
auth.addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
InputStream responseStream = null;
|
||||
if (response.getEntity() != null) {
|
||||
responseStream = response.getEntity().getContent();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 200) {
|
||||
return responseStream;
|
||||
} else if (response.getStatusLine().getStatusCode() == 404) {
|
||||
responseStream.close();
|
||||
return null;
|
||||
} else {
|
||||
responseStream.close();
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doPut(String content, String endpoint) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPut request = new HttpPut(clientRegistrationUrl + "/" + endpoint);
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
auth.addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void doDelete(String endpoint) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpDelete request = new HttpDelete(clientRegistrationUrl + "/" + endpoint);
|
||||
|
||||
auth.addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
httpUtil.doDelete(DEFAULT, clientId);
|
||||
}
|
||||
|
||||
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
|
||||
|
@ -195,81 +84,4 @@ public class ClientRegistration {
|
|||
}
|
||||
}
|
||||
|
||||
public static class ClientRegistrationBuilder {
|
||||
|
||||
private String realm;
|
||||
|
||||
private String authServerUrl;
|
||||
|
||||
private Auth auth;
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
public ClientRegistrationBuilder realm(String realm) {
|
||||
this.realm = realm;
|
||||
return this;
|
||||
}
|
||||
public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
|
||||
this.authServerUrl = authServerUrl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder auth(String token) {
|
||||
this.auth = new TokenAuth(token);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
|
||||
this.auth = new ClientIdSecretAuth(clientId, clientSecret);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
|
||||
this.httpClient = httpClient;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ClientRegistration build() {
|
||||
ClientRegistration clientRegistration = new ClientRegistration();
|
||||
clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
|
||||
|
||||
clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
|
||||
clientRegistration.auth = auth;
|
||||
|
||||
return clientRegistration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface Auth {
|
||||
void addAuth(HttpRequest httpRequest);
|
||||
}
|
||||
|
||||
public static class AuthorizationHeaderAuth implements Auth {
|
||||
private String credentials;
|
||||
|
||||
public AuthorizationHeaderAuth(String credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public void addAuth(HttpRequest httpRequest) {
|
||||
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
|
||||
}
|
||||
}
|
||||
|
||||
public static class TokenAuth extends AuthorizationHeaderAuth {
|
||||
public TokenAuth(String token) {
|
||||
super("Bearer " + token);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
|
||||
private String clientId;
|
||||
|
||||
public ClientIdSecretAuth(String clientId, String clientSecret) {
|
||||
super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
|
||||
this.clientId = clientId;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
package org.keycloak.client.registration;
|
||||
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.client.methods.*;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.keycloak.client.registration.Auth;
|
||||
import org.keycloak.client.registration.ClientRegistrationException;
|
||||
import org.keycloak.client.registration.HttpErrorException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
class HttpUtil {
|
||||
|
||||
private HttpClient httpClient;
|
||||
|
||||
private String baseUri;
|
||||
|
||||
private Auth auth;
|
||||
|
||||
HttpUtil(HttpClient httpClient, String baseUri) {
|
||||
this.httpClient = httpClient;
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
void setAuth(Auth auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
InputStream doPost(String content, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
InputStream responseStream = null;
|
||||
if (response.getEntity() != null) {
|
||||
responseStream = response.getEntity().getContent();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 201) {
|
||||
return responseStream;
|
||||
} else {
|
||||
responseStream.close();
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
InputStream doGet(String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
InputStream responseStream = null;
|
||||
if (response.getEntity() != null) {
|
||||
responseStream = response.getEntity().getContent();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() == 200) {
|
||||
return responseStream;
|
||||
} else if (response.getStatusLine().getStatusCode() == 404) {
|
||||
responseStream.close();
|
||||
return null;
|
||||
} else {
|
||||
responseStream.close();
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
void doPut(String content, String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
||||
|
||||
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||
request.setEntity(new StringEntity(content));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
void doDelete(String... path) throws ClientRegistrationException {
|
||||
try {
|
||||
HttpDelete request = new HttpDelete(getUrl(baseUri, path));
|
||||
|
||||
addAuth(request);
|
||||
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response.getEntity() != null) {
|
||||
response.getEntity().getContent().close();
|
||||
}
|
||||
|
||||
if (response.getStatusLine().getStatusCode() != 200) {
|
||||
throw new HttpErrorException(response.getStatusLine());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to send request", e);
|
||||
}
|
||||
}
|
||||
|
||||
void close() throws ClientRegistrationException {
|
||||
if (httpClient instanceof CloseableHttpClient) {
|
||||
try {
|
||||
((CloseableHttpClient) httpClient).close();
|
||||
} catch (IOException e) {
|
||||
throw new ClientRegistrationException("Failed to close http client", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String getUrl(String baseUri, String... path) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(baseUri);
|
||||
for (String p : path) {
|
||||
s.append('/');
|
||||
s.append(p);
|
||||
}
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
private void addAuth(HttpRequestBase request) {
|
||||
if (auth != null) {
|
||||
auth.addAuth(request);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -24,4 +24,8 @@ public class ObjectUtil {
|
|||
|
||||
return str1.equals(str2);
|
||||
}
|
||||
|
||||
public static String capitalize(String str) {
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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> {
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.connections.file.FileConnectionSpi
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import java.util.Map;
|
|||
public class GroupRepresentation {
|
||||
protected String id;
|
||||
protected String name;
|
||||
protected String path;
|
||||
protected Map<String, List<String>> attributes;
|
||||
protected List<String> realmRoles;
|
||||
protected Map<String, List<String>> clientRoles;
|
||||
|
@ -35,6 +36,14 @@ public class GroupRepresentation {
|
|||
this.name = name;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public void setPath(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public List<String> getRealmRoles() {
|
||||
return realmRoles;
|
||||
}
|
||||
|
|
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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"/>
|
||||
|
|
|
@ -114,6 +114,10 @@
|
|||
<name>picketlink.version</name>
|
||||
<value>${picketlink.version}</value>
|
||||
</injection>
|
||||
<injection>
|
||||
<name>wildfly.version</name>
|
||||
<value>${wildfly.version}</value>
|
||||
</injection>
|
||||
</injections>
|
||||
<options>
|
||||
<xmlTransformerType>saxon</xmlTransformerType>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -43,9 +43,9 @@
|
|||
|
||||
|
||||
<section id="overlay_install">
|
||||
<title>Install on existing WildFly 9.0.1.Final</title>
|
||||
<title>Install on existing WildFly &wildfly.version;</title>
|
||||
<para>
|
||||
Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download
|
||||
Keycloak can be installed into an existing WildFly &wildfly.version; server. To do this download
|
||||
<literal>keycloak-overlay-&project.version;.zip</literal> or <literal>keycloak-overlay-&project.version;.tar.gz</literal>.
|
||||
Once downloaded extract into the root directory of your WildFly installation. To start WildFly with Keycloak
|
||||
run:
|
||||
|
@ -62,11 +62,15 @@
|
|||
<para>
|
||||
To add Keycloak to other sever configurations (standalone.xml, standalone-ha.xml, etc.) start the server with
|
||||
the desired server-config. If you are running the server in standalone mode run:
|
||||
<programlisting>cd <WILDFLY_HOME>/bin
|
||||
./jboss-cli.sh -c --file=keycloak-install.cli</programlisting>
|
||||
<programlisting>
|
||||
cd <WILDFLY_HOME>/bin
|
||||
./jboss-cli.sh -c --file=keycloak-install.cli
|
||||
</programlisting>
|
||||
Or if you are running in clustering (HA) mode (by having used -c standalone-ha.xml) then run:
|
||||
<programlisting>cd <WILDFLY_HOME>/bin
|
||||
./jboss-cli.sh -c --file=keycloak-install-ha.cli</programlisting>
|
||||
<programlisting>
|
||||
cd <WILDFLY_HOME>/bin
|
||||
./jboss-cli.sh -c --file=keycloak-install-ha.cli
|
||||
</programlisting>
|
||||
You may see exceptions in the server log, but after restarting the server they should be gone.
|
||||
You can restart the server with:
|
||||
<programlisting><WILDFLY_HOME>/bin/jboss-cli.sh -c :reload</programlisting>
|
||||
|
@ -75,7 +79,7 @@
|
|||
<section>
|
||||
<title>Install on existing JBoss EAP 6.4.0.GA</title>
|
||||
<para>
|
||||
Same procedure as WildFly 9.0.1.Final, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
|
||||
Same procedure as WildFly &wildfly.version;, but download <literal>keycloak-overlay-eap6-&project.version;.zip</literal> or <literal>keycloak-overlay-eap6-&project.version;.tar.gz</literal>.
|
||||
</para>
|
||||
</section>
|
||||
<section>
|
||||
|
@ -85,7 +89,7 @@
|
|||
To install it first download <literal>keycloak-demo-&project.version;.zip</literal> or
|
||||
<literal>keycloak-demo-&project.version;.tar.gz</literal>. Once downloaded extract it inside
|
||||
<literal>keycloak-demo-&project.version;</literal> you'll find <literal>keycloak</literal> which contains
|
||||
a full WildFly 9.0.0.Final server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
|
||||
a full WildFly &wildfly.version; server with Keycloak Server and Adapters included. You'll also find <literal>docs</literal>
|
||||
and <literal>examples</literal> which contains everything you need to get started developing applications that use Keycloak.
|
||||
</para>
|
||||
<para>
|
||||
|
@ -437,12 +441,12 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
settings you can specify before boot time. This is configured in the
|
||||
<literal>standalone/configuration/keycloak-server.json</literal>.
|
||||
By default the setting is like this:
|
||||
<programlisting><![CDATA[
|
||||
"connectionsHttpClient": {
|
||||
"default": {
|
||||
"disable-trust-manager": true
|
||||
}
|
||||
},
|
||||
<programlisting><![CDATA[
|
||||
"connectionsHttpClient": {
|
||||
"default": {
|
||||
"disable-trust-manager": true
|
||||
}
|
||||
},
|
||||
]]></programlisting>
|
||||
Possible configuration options are:
|
||||
<variablelist>
|
||||
|
@ -659,25 +663,25 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
to do with the <literal>keytool</literal> utility that comes with the Java jdk.
|
||||
</para>
|
||||
<para>
|
||||
<programlisting>
|
||||
$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
|
||||
Enter keystore password: secret
|
||||
Re-enter new password: secret
|
||||
What is your first and last name?
|
||||
[Unknown]: localhost
|
||||
What is the name of your organizational unit?
|
||||
[Unknown]: Keycloak
|
||||
What is the name of your organization?
|
||||
[Unknown]: Red Hat
|
||||
What is the name of your City or Locality?
|
||||
[Unknown]: Westford
|
||||
What is the name of your State or Province?
|
||||
[Unknown]: MA
|
||||
What is the two-letter country code for this unit?
|
||||
[Unknown]: US
|
||||
Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct?
|
||||
[no]: yes
|
||||
</programlisting>
|
||||
<programlisting>
|
||||
$ keytool -genkey -alias localhost -keyalg RSA -keystore keycloak.jks -validity 10950
|
||||
Enter keystore password: secret
|
||||
Re-enter new password: secret
|
||||
What is your first and last name?
|
||||
[Unknown]: localhost
|
||||
What is the name of your organizational unit?
|
||||
[Unknown]: Keycloak
|
||||
What is the name of your organization?
|
||||
[Unknown]: Red Hat
|
||||
What is the name of your City or Locality?
|
||||
[Unknown]: Westford
|
||||
What is the name of your State or Province?
|
||||
[Unknown]: MA
|
||||
What is the two-letter country code for this unit?
|
||||
[Unknown]: US
|
||||
Is CN=localhost, OU=Keycloak, O=Test, L=Westford, ST=MA, C=US correct?
|
||||
[no]: yes
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
You should answer <literal>What is your first and last name ?</literal> question with
|
||||
|
@ -693,44 +697,44 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
</para>
|
||||
<para>
|
||||
The first thing to do is generate a Certificate Request:
|
||||
<programlisting>
|
||||
$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
|
||||
</programlisting>
|
||||
<programlisting>
|
||||
$ keytool -certreq -alias yourdomain -keystore keycloak.jks > keycloak.careq
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Where <literal>yourdomain</literal> is a DNS name for which this certificate is generated for.
|
||||
Keytool generates the request:
|
||||
<programlisting>
|
||||
-----BEGIN NEW CERTIFICATE REQUEST-----
|
||||
MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y
|
||||
ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY
|
||||
Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1
|
||||
29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as
|
||||
H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw
|
||||
Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35
|
||||
2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i
|
||||
n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf
|
||||
PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ
|
||||
9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X
|
||||
MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S
|
||||
vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8=
|
||||
-----END NEW CERTIFICATE REQUEST-----
|
||||
</programlisting>
|
||||
<programlisting>
|
||||
-----BEGIN NEW CERTIFICATE REQUEST-----
|
||||
MIIC2jCCAcICAQAwZTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAk1BMREwDwYDVQQHEwhXZXN0Zm9y
|
||||
ZDEQMA4GA1UEChMHUmVkIEhhdDEQMA4GA1UECxMHUmVkIEhhdDESMBAGA1UEAxMJbG9jYWxob3N0
|
||||
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr7kck2TaavlEOGbcpi9c0rncY4HhdzmY
|
||||
Ax2nZfq1eZEaIPqI5aTxwQZzzLDK9qbeAd8Ji79HzSqnRDxNYaZu7mAYhFKHgixsolE3o5Yfzbw1
|
||||
29Rvy+eUVe+WZxv5oo9wolVVpdSINIMEL2LaFhtX/c1dqiqYVpfnvFshZQaIg2nL8juzZcBjj4as
|
||||
H98gIS7khql/dkZKsw9NLvyxgJvp7PaXurX29fNf3ihG+oFrL22oFyV54BWWxXCKU/GPn61EGZGw
|
||||
Ft2qSIGLdctpMD1aJR2bcnlhEjZKDksjQZoQ5YMXaAGkcYkG6QkgrocDE2YXDbi7GIdf9MegVJ35
|
||||
2DQMpwIDAQABoDAwLgYJKoZIhvcNAQkOMSEwHzAdBgNVHQ4EFgQUQwlZJBA+fjiDdiVzaO9vrE/i
|
||||
n2swDQYJKoZIhvcNAQELBQADggEBAC5FRvMkhal3q86tHPBYWBuTtmcSjs4qUm6V6f63frhveWHf
|
||||
PzRrI1xH272XUIeBk0gtzWo0nNZnf0mMCtUBbHhhDcG82xolikfqibZijoQZCiGiedVjHJFtniDQ
|
||||
9bMDUOXEMQ7gHZg5q6mJfNG9MbMpQaUVEEFvfGEQQxbiFK7hRWU8S23/d80e8nExgQxdJWJ6vd0X
|
||||
MzzFK6j4Dj55bJVuM7GFmfdNC52pNOD5vYe47Aqh8oajHX9XTycVtPXl45rrWAH33ftbrS8SrZ2S
|
||||
vqIFQeuLL3BaHwpl3t7j2lMWcK1p80laAxEASib/fAwrRHpLHBXRcq6uALUOZl4Alt8=
|
||||
-----END NEW CERTIFICATE REQUEST-----
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Send this ca request to your CA. The CA will issue you a signed certificate and send it to you.
|
||||
Before you import your new cert, you must obtain and import the root certificate of the CA.
|
||||
You can download the cert from CA (ie.: root.crt) and import as follows:
|
||||
<programlisting>
|
||||
$ keytool -import -keystore keycloak.jks -file root.crt -alias root
|
||||
</programlisting>
|
||||
<programlisting>
|
||||
$ keytool -import -keystore keycloak.jks -file root.crt -alias root
|
||||
</programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Last step is import your new CA generated certificate to your keystore:
|
||||
<programlisting>
|
||||
$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
|
||||
</programlisting>
|
||||
<programlisting>
|
||||
$ keytool -import -alias yourdomain -keystore keycloak.jks -file your-certificate.cer
|
||||
</programlisting>
|
||||
</para>
|
||||
</section>
|
||||
</section>
|
||||
|
@ -744,18 +748,19 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
</para>
|
||||
<para>
|
||||
To the <literal>security-realms</literal> element add:
|
||||
<programlisting><![CDATA[<security-realm name="UndertowRealm">
|
||||
<server-identities>
|
||||
<ssl>
|
||||
<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
|
||||
</ssl>
|
||||
</server-identities>
|
||||
</security-realm>]]></programlisting>
|
||||
<programlisting><![CDATA[
|
||||
<security-realm name="UndertowRealm">
|
||||
<server-identities>
|
||||
<ssl>
|
||||
<keystore path="keycloak.jks" relative-to="jboss.server.config.dir" keystore-password="secret" />
|
||||
</ssl>
|
||||
</server-identities>
|
||||
</security-realm>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Find the element <literal><server name="default-server"></literal> (it's a child element of <literal><subsystem xmlns="urn:jboss:domain:undertow:1.0"></literal>) and add:
|
||||
<programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>
|
||||
]]></programlisting>
|
||||
<programlisting><![CDATA[<https-listener name="https" socket-binding="https" security-realm="UndertowRealm"/>]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
Check the <ulink url="https://docs.jboss.org/author/display/WFLY8/Undertow+(web)+subsystem+configuration">Wildfly Undertow</ulink> documentation for more information on fine tuning the socket connections.
|
||||
|
@ -813,44 +818,20 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<title>Adding Keycloak server in Domain Mode</title>
|
||||
<title>Keycloak server in Domain Mode</title>
|
||||
<para>
|
||||
In domain mode, you start the server with the "domain" command instead of the "standalone" command. In this case, the Keycloak subsystem is
|
||||
defined in domain/configuration/domain.xml instead of standalone/configuration.standalone.xml. Inside domain.xml, you will see more than one
|
||||
profile. A Keycloak subsystem can be defined in zero or more of those profiles.
|
||||
profile. The Keycloak subsystem is defined for all initial profiles.
|
||||
</para>
|
||||
<para>
|
||||
To enable Keycloak for a server profile edit domain/configuration/domain.xml. To the <literal>extensions</literal>
|
||||
element add the Keycloak extension:
|
||||
<programlisting><![CDATA[
|
||||
<extensions>
|
||||
...
|
||||
<extension module="org.keycloak.keycloak-subsystem"/>
|
||||
</extensions>
|
||||
]]></programlisting>
|
||||
Then you need to add the server to the required server profiles. By default WildFly starts two servers
|
||||
in the main-server-group which uses the full profile. To add Keycloak for this profile add the Keycloak
|
||||
subsystem to the <literal>profile</literal> element with <literal>name</literal> full:
|
||||
<programlisting><![CDATA[
|
||||
<profile name="full">
|
||||
...
|
||||
<subsystem xmlns="urn:jboss:domain:keycloak:1.0">
|
||||
<auth-server name="main-auth-server">
|
||||
<enabled>true</enabled>
|
||||
<web-context>auth</web-context>
|
||||
</auth-server>
|
||||
</subsystem>
|
||||
]]></programlisting>
|
||||
THe server is also added to server profiles. By default two servers are started
|
||||
in the main-server-group which uses the full profile.
|
||||
</para>
|
||||
<para>
|
||||
To configure the server copy <literal>standalone/configuration/keycloak-server.json</literal> to
|
||||
<literal>domain/servers/<SERVER NAME>/configuration</literal>. The configuration should be identical
|
||||
You need to make sure <literal>domain/servers/<SERVER NAME>/configuration</literal> is identical
|
||||
for all servers in a group.
|
||||
</para>
|
||||
<para>
|
||||
Follow the <link linkend='clustering'>Clustering</link> section of the documentation to configure Keycloak
|
||||
for clustering. In domain mode it doesn't make much sense to not configure Keycloak in cluster mode.
|
||||
</para>
|
||||
<para>
|
||||
To deploy custom providers and themes you should deploys these as modules and make sure the modules are
|
||||
available to all servers in the group. See <link linkend='providers'>Providers</link> and
|
||||
|
@ -865,12 +846,12 @@ All configuration options are optional. Default value for directory is <literal>
|
|||
</para>
|
||||
<para>
|
||||
To do this, add the <literal>default-web-module</literal> attribute in the Undertow subystem in standalone.xml.
|
||||
<programlisting><![CDATA[
|
||||
<programlisting><![CDATA[
|
||||
<subsystem xmlns="urn:jboss:domain:undertow:2.0">
|
||||
<server name="default-server">
|
||||
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
|
||||
<location name="/" handler="welcome-content"/>
|
||||
</host>
|
||||
<server name="default-server">
|
||||
<host name="default-host" alias="localhost" default-web-module="keycloak-server.war">
|
||||
<location name="/" handler="welcome-content"/>
|
||||
</host>
|
||||
]]></programlisting>
|
||||
</para>
|
||||
<para>
|
||||
|
|
|
@ -44,8 +44,7 @@ public interface Errors {
|
|||
|
||||
String NOT_ALLOWED = "not_allowed";
|
||||
|
||||
String FEDERATED_IDENTITY_EMAIL_EXISTS = "federated_identity_email_exists";
|
||||
String FEDERATED_IDENTITY_USERNAME_EXISTS = "federated_identity_username_exists";
|
||||
String FEDERATED_IDENTITY_EXISTS = "federated_identity_account_exists";
|
||||
String SSL_REQUIRED = "ssl_required";
|
||||
|
||||
String USER_SESSION_NOT_FOUND = "user_session_not_found";
|
||||
|
|
|
@ -48,6 +48,8 @@ public enum EventType {
|
|||
SEND_VERIFY_EMAIL_ERROR(true),
|
||||
SEND_RESET_PASSWORD(true),
|
||||
SEND_RESET_PASSWORD_ERROR(true),
|
||||
SEND_IDENTITY_PROVIDER_LINK(true),
|
||||
SEND_IDENTITY_PROVIDER_LINK_ERROR(true),
|
||||
RESET_PASSWORD(true),
|
||||
RESET_PASSWORD_ERROR(true),
|
||||
|
||||
|
@ -66,8 +68,6 @@ public enum EventType {
|
|||
IDENTITY_PROVIDER_RESPONSE_ERROR(false),
|
||||
IDENTITY_PROVIDER_RETRIEVE_TOKEN(false),
|
||||
IDENTITY_PROVIDER_RETRIEVE_TOKEN_ERROR(false),
|
||||
IDENTITY_PROVIDER_ACCCOUNT_LINKING(false),
|
||||
IDENTITY_PROVIDER_ACCCOUNT_LINKING_ERROR(false),
|
||||
IMPERSONATE(true),
|
||||
CUSTOM_REQUIRED_ACTION(true),
|
||||
CUSTOM_REQUIRED_ACTION_ERROR(true),
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -394,7 +394,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t
|
|||
trust-email=Trust Email
|
||||
trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
|
||||
gui-order.tooltip=Number defining order of the provider in GUI (eg. on Login page).
|
||||
first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider.
|
||||
first-broker-login-flow.tooltip=Alias of authentication flow, which is triggered after first login with this identity provider. Term 'First Login' means that there is not yet existing Keycloak account linked with the authenticated identity provider account.
|
||||
openid-connect-config=OpenID Connect Config
|
||||
openid-connect-config.tooltip=OIDC SP and external IDP configuration.
|
||||
authorization-url=Authorization URL
|
||||
|
|
|
@ -447,6 +447,21 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'UserRoleMappingCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users/:user/groups', {
|
||||
templateUrl : resourceUrl + '/partials/user-group-membership.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
user : function(UserLoader) {
|
||||
return UserLoader();
|
||||
},
|
||||
groups : function(GroupListLoader) {
|
||||
return GroupListLoader();
|
||||
}
|
||||
},
|
||||
controller : 'UserGroupMembershipCtrl'
|
||||
})
|
||||
.when('/realms/:realm/users/:user/sessions', {
|
||||
templateUrl : resourceUrl + '/partials/user-sessions.html',
|
||||
resolve : {
|
||||
|
@ -628,9 +643,21 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
group : function(GroupLoader) {
|
||||
return GroupLoader();
|
||||
}
|
||||
},
|
||||
},
|
||||
controller : 'GroupDetailCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups/:group/members', {
|
||||
templateUrl : resourceUrl + '/partials/group-members.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
group : function(GroupLoader) {
|
||||
return GroupLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GroupMembersCtrl'
|
||||
})
|
||||
.when('/realms/:realm/groups/:group/role-mappings', {
|
||||
templateUrl : resourceUrl + '/partials/group-role-mappings.html',
|
||||
resolve : {
|
||||
|
|
|
@ -877,7 +877,7 @@ module.controller('ClientDetailCtrl', function($scope, realm, client, $route, se
|
|||
$scope.viewImportDetails = function() {
|
||||
$modal.open({
|
||||
templateUrl: resourceUrl + '/partials/modal/view-object.html',
|
||||
controller: 'JsonModalCtrl',
|
||||
controller: 'ObjectModalCtrl',
|
||||
resolve: {
|
||||
object: function () {
|
||||
return $scope.client;
|
||||
|
|
|
@ -319,3 +319,50 @@ module.controller('GroupRoleMappingCtrl', function($scope, $http, realm, group,
|
|||
|
||||
|
||||
});
|
||||
|
||||
module.controller('GroupMembersCtrl', function($scope, realm, group, GroupMembership) {
|
||||
$scope.realm = realm;
|
||||
$scope.page = 0;
|
||||
|
||||
|
||||
$scope.query = {
|
||||
realm: realm.realm,
|
||||
groupId: group.id,
|
||||
max : 5,
|
||||
first : 0
|
||||
}
|
||||
|
||||
|
||||
$scope.firstPage = function() {
|
||||
$scope.query.first = 0;
|
||||
$scope.searchQuery();
|
||||
}
|
||||
|
||||
$scope.previousPage = function() {
|
||||
$scope.query.first -= parseInt($scope.query.max);
|
||||
if ($scope.query.first < 0) {
|
||||
$scope.query.first = 0;
|
||||
}
|
||||
$scope.searchQuery();
|
||||
}
|
||||
|
||||
$scope.nextPage = function() {
|
||||
$scope.query.first += parseInt($scope.query.max);
|
||||
$scope.searchQuery();
|
||||
}
|
||||
|
||||
$scope.searchQuery = function() {
|
||||
console.log("query.search: " + $scope.query.search);
|
||||
$scope.searchLoaded = false;
|
||||
|
||||
$scope.users = GroupMembership.query($scope.query, function() {
|
||||
console.log('search loaded');
|
||||
$scope.searchLoaded = true;
|
||||
$scope.lastSearch = $scope.query.search;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.searchQuery();
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -1090,3 +1090,64 @@ module.controller('UserFederationMapperCreateCtrl', function($scope, realm, prov
|
|||
|
||||
});
|
||||
|
||||
module.controller('UserGroupMembershipCtrl', function($scope, $route, realm, groups, user, UserGroupMembership, UserGroupMapping, Notifications, $location, Dialog) {
|
||||
$scope.realm = realm;
|
||||
$scope.user = user;
|
||||
$scope.groupList = groups;
|
||||
$scope.selectedGroup = null;
|
||||
$scope.tree = [];
|
||||
|
||||
UserGroupMembership.query({realm: realm.realm, userId: user.id}, function(data) {
|
||||
$scope.groupMemberships = data;
|
||||
|
||||
});
|
||||
|
||||
$scope.joinGroup = function() {
|
||||
if (!$scope.tree.currentNode) {
|
||||
Notifications.error('Please select a group to add');
|
||||
return;
|
||||
};
|
||||
UserGroupMapping.update({realm: realm.realm, userId: user.id, groupId: $scope.tree.currentNode.id}, function() {
|
||||
Notifications.success('Added group membership');
|
||||
$route.reload();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
$scope.leaveGroup = function() {
|
||||
UserGroupMapping.remove({realm: realm.realm, userId: user.id, groupId: $scope.selectedGroup.id}, function() {
|
||||
Notifications.success('Removed group membership');
|
||||
$route.reload();
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
var isLeaf = function(node) {
|
||||
return node.id != "realm" && (!node.subGroups || node.subGroups.length == 0);
|
||||
};
|
||||
|
||||
$scope.getGroupClass = function(node) {
|
||||
if (node.id == "realm") {
|
||||
return 'pficon pficon-users';
|
||||
}
|
||||
if (isLeaf(node)) {
|
||||
return 'normal';
|
||||
}
|
||||
if (node.subGroups.length && node.collapsed) return 'collapsed';
|
||||
if (node.subGroups.length && !node.collapsed) return 'expanded';
|
||||
return 'collapsed';
|
||||
|
||||
}
|
||||
|
||||
$scope.getSelectedClass = function(node) {
|
||||
if (node.selected) {
|
||||
return 'selected';
|
||||
} else if ($scope.cutNode && $scope.cutNode.id == node.id) {
|
||||
return 'cut';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -1512,6 +1512,34 @@ module.factory('GroupCompositeClientRoleMapping', function($resource) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('GroupMembership', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/groups/:groupId/members', {
|
||||
realm : '@realm',
|
||||
groupId : '@groupId'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
module.factory('UserGroupMembership', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups', {
|
||||
realm : '@realm',
|
||||
userId : '@userId'
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserGroupMapping', function($resource) {
|
||||
return $resource(authUrl + '/admin/realms/:realm/users/:userId/groups/:groupId', {
|
||||
realm : '@realm',
|
||||
userId : '@userId',
|
||||
groupId : '@groupId'
|
||||
}, {
|
||||
update : {
|
||||
method : 'PUT'
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
<td>{{mapper.name}}</td>
|
||||
<td>{{mapperTypes[mapper.protocolMapper].category}}</td>
|
||||
<td>{{mapperTypes[mapper.protocolMapper].name}}</td>
|
||||
<td><input type="checkbox" ng-model="mapper.isChecked"></td>
|
||||
<td><input type="checkbox" ng-model="mapper.isChecked" id="{{mapper.protocolMapper}}"></td>
|
||||
</tr>
|
||||
<tr data-ng-show="mappers.length == 0">
|
||||
<td>{{:: 'no-mappers-available' | translate}}</td>
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/groups">Groups</a></li>
|
||||
<li>{{group.name}}</li>
|
||||
</ol>
|
||||
<kc-tabs-group></kc-tabs-group>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<caption data-ng-show="users" class="hidden">Table of group members</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<tr data-ng-show="searchLoaded && users.length > 0">
|
||||
<th>Username</th>
|
||||
<th>Last Name</th>
|
||||
<th>First Name</th>
|
||||
<th>Email</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</tr>
|
||||
</thead>
|
||||
<tfoot data-ng-show="users && (users.length >= query.max || query.first > 0)">
|
||||
<tr>
|
||||
<td colspan="7">
|
||||
<div class="table-nav">
|
||||
<button data-ng-click="firstPage()" class="first" ng-disabled="query.first == 0">First page</button>
|
||||
<button data-ng-click="previousPage()" class="prev" ng-disabled="query.first == 0">Previous page</button>
|
||||
<button data-ng-click="nextPage()" class="next" ng-disabled="users.length < query.max">Next page</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
<tbody>
|
||||
<tr ng-repeat="user in users">
|
||||
<td><a href="#/realms/{{realm.realm}}/users/{{user.id}}">{{user.username}}</a></td>
|
||||
<td>{{user.lastName}}</td>
|
||||
<td>{{user.firstName}}</td>
|
||||
<td>{{user.email}}</td>
|
||||
<td class="kc-action-cell">
|
||||
<button class="btn btn-default btn-block btn-sm" kc-open="/realms/{{realm.realm}}/users/{{user.id}}">Edit</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr data-ng-show="!users || users.length == 0">
|
||||
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch != null">No group members</td>
|
||||
<td class="text-muted" data-ng-show="searchLoaded && users.length == 0 && lastSearch == null">No group members</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -20,8 +20,8 @@
|
|||
<tbody>
|
||||
<tr ng-repeat="requiredAction in requiredActions" data-ng-show="requiredActions.length > 0">
|
||||
<td>{{requiredAction.name}}</td>
|
||||
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)"></td>
|
||||
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)"></td>
|
||||
<td><input type="checkbox" ng-model="requiredAction.enabled" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.enabled"></td>
|
||||
<td><input type="checkbox" ng-model="requiredAction.defaultAction" ng-change="updateRequiredAction(requiredAction)" id="{{requiredAction.alias}}.defaultAction"></td>
|
||||
</tr>
|
||||
<tr data-ng-show="requiredActions.length == 0">
|
||||
<td>No required actions configured</td>
|
||||
|
|
|
@ -94,7 +94,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
|
||||
<li>{{user.username}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-user></kc-tabs-user>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate>
|
||||
<div class="form-group" kc-read-only="!access.manageUsers">
|
||||
<label class="col-md-1 control-label" class="control-label"></label>
|
||||
|
||||
<div class="col-md-8" >
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
<div class="form-inline">
|
||||
<label class="control-label">Group Membership</label>
|
||||
<kc-tooltip>Groups user is a member of. Select a listed group and click the Leave button to leave the group.</kc-tooltip>
|
||||
|
||||
<div class="pull-right" data-ng-show="access.manageUsers">
|
||||
<button id="leaveGroups" class="btn btn-default" ng-click="leaveGroup()">Leave</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<select id="groupMembership" class="form-control" size=5
|
||||
ng-model="selectedGroup"
|
||||
ng-options="r.path for r in groupMemberships">
|
||||
<option style="display:none" value="">select a type</option>
|
||||
</select>
|
||||
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="kc-table-actions" colspan="5">
|
||||
|
||||
<div class="form-inline">
|
||||
<label class="control-label">Available Groups</label>
|
||||
<kc-tooltip>Groups a user can join. Select a group and click the join button.</kc-tooltip>
|
||||
|
||||
<div class="pull-right" data-ng-show="access.manageUsers">
|
||||
<button id="joinGroup" class="btn btn-default" ng-click="joinGroup()">Join</button>
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td> <div
|
||||
tree-id="tree"
|
||||
angular-treeview="true"
|
||||
tree-model="groupList"
|
||||
node-id="id"
|
||||
node-label="name"
|
||||
node-children="subGroups" >
|
||||
</div>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
1
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
Normal file → Executable file
1
forms/common-themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user.html
Normal file → Executable file
|
@ -10,6 +10,7 @@
|
|||
<li ng-class="{active: path[4] == 'user-attributes'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-attributes">Attributes</a></li>
|
||||
<li ng-class="{active: path[4] == 'user-credentials'}" data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/user-credentials">Credentials</a></li>
|
||||
<li ng-class="{active: path[4] == 'role-mappings'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/role-mappings">Role Mappings</a></li>
|
||||
<li ng-class="{active: path[4] == 'groups'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/groups">Groups</a></li>
|
||||
<li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/consents">Consents</a></li>
|
||||
<li ng-class="{active: path[4] == 'sessions'}" ><a href="#/realms/{{realm.realm}}/users/{{user.id}}/sessions">Sessions</a></li>
|
||||
<li ng-class="{active: path[4] == 'federated-identity' || path[1] == 'federated-identity'}" data-ng-show="user.federatedIdentities != null"><a href="#/realms/{{realm.realm}}/users/{{user.id}}/federated-identity">Identity Provider Links</a></li>
|
||||
|
|
|
@ -12,8 +12,8 @@
|
|||
<form id="kc-register-form" class="${properties.kcFormClass!}" action="${url.loginAction}" method="post">
|
||||
|
||||
<div id="kc-form-buttons" class="${properties.kcFormGroupClass!}">
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="updateProfile" value="updateProfile">${msg("confirmLinkIdpReviewProfile")}</button>
|
||||
<button type="submit" class="${properties.kcButtonClass!} ${properties.kcButtonPrimaryClass!} ${properties.kcButtonLargeClass!}" name="submitAction" id="linkAccount" value="linkAccount">${msg("confirmLinkIdpContinue", idpAlias)}</button>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
<#elseif section = "header">
|
||||
${msg("emailLinkIdpTitle", idpAlias)}
|
||||
<#elseif section = "form">
|
||||
<p class="instruction">
|
||||
<p id="instruction1" class="instruction">
|
||||
${msg("emailLinkIdp1", idpAlias, brokerContext.username, realm.name)}
|
||||
</p>
|
||||
<p class="instruction">
|
||||
<p id="instruction2" class="instruction">
|
||||
${msg("emailLinkIdp2")} <a href="${url.firstBrokerLoginUrl}">${msg("doClickHere")}</a> ${msg("emailLinkIdp3")}
|
||||
</p>
|
||||
</#if>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -21,6 +21,7 @@ import javax.mail.internet.MimeMultipart;
|
|||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.email.freemarker.beans.EventBean;
|
||||
|
@ -89,7 +90,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
attributes.put("link", link);
|
||||
attributes.put("linkExpiration", expirationInMinutes);
|
||||
|
||||
String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
|
||||
String realmName = ObjectUtil.capitalize(realm.getName());
|
||||
attributes.put("realmName", realmName);
|
||||
|
||||
send("passwordResetSubject", "password-reset.ftl", attributes);
|
||||
|
@ -102,12 +103,12 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
attributes.put("link", link);
|
||||
attributes.put("linkExpiration", expirationInMinutes);
|
||||
|
||||
String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
|
||||
String realmName = ObjectUtil.capitalize(realm.getName());
|
||||
attributes.put("realmName", realmName);
|
||||
|
||||
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
|
||||
String idpAlias = brokerContext.getIdpConfig().getAlias();
|
||||
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
|
||||
idpAlias = ObjectUtil.capitalize(idpAlias);
|
||||
|
||||
attributes.put("identityProviderContext", brokerContext);
|
||||
attributes.put("identityProviderAlias", idpAlias);
|
||||
|
@ -123,7 +124,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
attributes.put("link", link);
|
||||
attributes.put("linkExpiration", expirationInMinutes);
|
||||
|
||||
String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
|
||||
String realmName = ObjectUtil.capitalize(realm.getName());
|
||||
attributes.put("realmName", realmName);
|
||||
|
||||
send("executeActionsSubject", "executeActions.ftl", attributes);
|
||||
|
@ -137,7 +138,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
attributes.put("link", link);
|
||||
attributes.put("linkExpiration", expirationInMinutes);
|
||||
|
||||
String realmName = realm.getName().substring(0, 1).toUpperCase() + realm.getName().substring(1);
|
||||
String realmName = ObjectUtil.capitalize(realm.getName());
|
||||
attributes.put("realmName", realmName);
|
||||
|
||||
send("emailVerificationSubject", "email-verification.ftl", attributes);
|
||||
|
@ -253,7 +254,7 @@ public class FreeMarkerEmailProvider implements EmailProvider {
|
|||
private String toCamelCase(EventType event){
|
||||
StringBuilder sb = new StringBuilder("event");
|
||||
for(String s : event.name().toString().toLowerCase().split("_")){
|
||||
sb.append(s.substring(0,1).toUpperCase()).append(s.substring(1));
|
||||
sb.append(ObjectUtil.capitalize(s));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.keycloak.OAuth2Constants;
|
|||
import org.keycloak.authentication.requiredactions.util.UpdateProfileContext;
|
||||
import org.keycloak.authentication.requiredactions.util.UserUpdateProfileContext;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.common.util.ObjectUtil;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.freemarker.BrowserSecurityHeaderSetup;
|
||||
|
@ -288,7 +289,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
case LOGIN_IDP_LINK_EMAIL:
|
||||
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
|
||||
String idpAlias = brokerContext.getIdpConfig().getAlias();
|
||||
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
|
||||
idpAlias = ObjectUtil.capitalize(idpAlias);
|
||||
|
||||
attributes.put("brokerContext", brokerContext);
|
||||
attributes.put("idpAlias", idpAlias);
|
||||
|
@ -470,7 +471,7 @@ public class FreeMarkerLoginFormsProvider implements LoginFormsProvider {
|
|||
public Response createIdpLinkEmailPage() {
|
||||
BrokeredIdentityContext brokerContext = (BrokeredIdentityContext) this.attributes.get(IDENTITY_PROVIDER_BROKER_CONTEXT);
|
||||
String idpAlias = brokerContext.getIdpConfig().getAlias();
|
||||
idpAlias = idpAlias.substring(0, 1).toUpperCase() + idpAlias.substring(1);
|
||||
idpAlias = ObjectUtil.capitalize(idpAlias);;
|
||||
setMessage(MessageType.WARNING, Messages.LINK_IDP, idpAlias);
|
||||
|
||||
return createResponse(LoginFormsPages.LOGIN_IDP_LINK_EMAIL);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package org.keycloak.adapters.osgi;
|
||||
|
||||
import java.net.URL;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.eclipse.jetty.security.ConstraintMapping;
|
||||
import org.eclipse.jetty.util.security.Constraint;
|
||||
|
@ -133,7 +133,8 @@ public class PaxWebIntegrationService {
|
|||
Constraint constraint = constraintMapping.getConstraint();
|
||||
String[] roles = constraint.getRoles();
|
||||
// name property is unavailable on constraint object :/
|
||||
String name = "Constraint-" + new Random().nextInt();
|
||||
|
||||
String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE);
|
||||
|
||||
int dataConstraint = constraint.getDataConstraint();
|
||||
String dataConstraintStr;
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
|
@ -25,8 +27,10 @@ public class DefaultAuthenticationFlows {
|
|||
|
||||
public static final String CLIENT_AUTHENTICATION_FLOW = "clients";
|
||||
public static final String FIRST_BROKER_LOGIN_FLOW = "first broker login";
|
||||
public static final String FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW = "Handle Existing Account";
|
||||
|
||||
public static final String IDP_REVIEW_PROFILE_CONFIG_ALIAS = "review profile config";
|
||||
public static final String IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS = "create unique user config";
|
||||
|
||||
public static void addFlows(RealmModel realm) {
|
||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm);
|
||||
|
@ -34,7 +38,7 @@ public class DefaultAuthenticationFlows {
|
|||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
||||
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, false);
|
||||
}
|
||||
public static void migrateFlows(RealmModel realm) {
|
||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
|
||||
|
@ -42,7 +46,7 @@ public class DefaultAuthenticationFlows {
|
|||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
||||
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(realm);
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm);
|
||||
if (realm.getFlowByAlias(FIRST_BROKER_LOGIN_FLOW) == null) firstBrokerLoginFlow(realm, true);
|
||||
}
|
||||
|
||||
public static void registrationFlow(RealmModel realm) {
|
||||
|
@ -320,7 +324,7 @@ public class DefaultAuthenticationFlows {
|
|||
realm.addAuthenticatorExecution(execution);
|
||||
}
|
||||
|
||||
public static void firstBrokerLoginFlow(RealmModel realm) {
|
||||
public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
|
||||
AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
|
||||
firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
|
||||
firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
|
||||
|
@ -347,7 +351,7 @@ public class DefaultAuthenticationFlows {
|
|||
|
||||
|
||||
AuthenticatorConfigModel createUserIfUniqueConfig = new AuthenticatorConfigModel();
|
||||
createUserIfUniqueConfig.setAlias("create unique user config");
|
||||
createUserIfUniqueConfig.setAlias(IDP_CREATE_UNIQUE_USER_CONFIG_ALIAS);
|
||||
config = new HashMap<>();
|
||||
config.put("require.password.update.after.registration", "false");
|
||||
createUserIfUniqueConfig.setConfig(config);
|
||||
|
@ -366,7 +370,7 @@ public class DefaultAuthenticationFlows {
|
|||
AuthenticationFlowModel linkExistingAccountFlow = new AuthenticationFlowModel();
|
||||
linkExistingAccountFlow.setTopLevel(false);
|
||||
linkExistingAccountFlow.setBuiltIn(true);
|
||||
linkExistingAccountFlow.setAlias("Handle Existing Account");
|
||||
linkExistingAccountFlow.setAlias(FIRST_BROKER_LOGIN_HANDLE_EXISTING_SUBFLOW);
|
||||
linkExistingAccountFlow.setDescription("Handle what to do if there is existing account with same email/username like authenticated identity provider");
|
||||
linkExistingAccountFlow.setProviderId("basic-flow");
|
||||
linkExistingAccountFlow = realm.addAuthenticationFlow(linkExistingAccountFlow);
|
||||
|
@ -421,10 +425,19 @@ public class DefaultAuthenticationFlows {
|
|||
execution = new AuthenticationExecutionModel();
|
||||
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
|
||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
|
||||
// TODO: read the requirement from browser authenticator
|
||||
// if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
|
||||
// execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
||||
// }
|
||||
|
||||
if (migrate) {
|
||||
// Try to read OTP requirement from browser flow
|
||||
AuthenticationFlowModel browserFlow = realm.getBrowserFlow();
|
||||
List<AuthenticationExecutionModel> browserExecutions = new LinkedList<>();
|
||||
KeycloakModelUtils.deepFindAuthenticationExecutions(realm, browserFlow, browserExecutions);
|
||||
for (AuthenticationExecutionModel browserExecution : browserExecutions) {
|
||||
if (browserExecution.getAuthenticator().equals("auth-otp-form")) {
|
||||
execution.setRequirement(browserExecution.getRequirement());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
execution.setAuthenticator("auth-otp-form");
|
||||
execution.setPriority(20);
|
||||
execution.setAuthenticatorFlow(false);
|
||||
|
|
|
@ -23,6 +23,9 @@ public class FormMessage {
|
|||
private String message;
|
||||
private Object[] parameters;
|
||||
|
||||
public FormMessage() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create message.
|
||||
*
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package org.keycloak.models.utils;
|
||||
|
||||
import org.bouncycastle.openssl.PEMWriter;
|
||||
import org.keycloak.models.AuthenticationExecutionModel;
|
||||
import org.keycloak.models.AuthenticationFlowModel;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.models.GroupModel;
|
||||
|
@ -16,6 +18,7 @@ import org.keycloak.models.UserCredentialModel;
|
|||
import org.keycloak.models.UserFederationMapperModel;
|
||||
import org.keycloak.models.UserFederationProviderModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||
import org.keycloak.common.util.CertificateUtils;
|
||||
import org.keycloak.common.util.PemUtils;
|
||||
|
@ -31,6 +34,7 @@ import java.security.PrivateKey;
|
|||
import java.security.PublicKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -386,4 +390,24 @@ public final class KeycloakModelUtils {
|
|||
realm.addDefaultRole(Constants.OFFLINE_ACCESS_ROLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively find all AuthenticationExecutionModel from specified flow or all it's subflows
|
||||
*
|
||||
* @param realm
|
||||
* @param flow
|
||||
* @param result input should be empty list. At the end will be all executions added to this list
|
||||
*/
|
||||
public static void deepFindAuthenticationExecutions(RealmModel realm, AuthenticationFlowModel flow, List<AuthenticationExecutionModel> result) {
|
||||
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
|
||||
for (AuthenticationExecutionModel execution : executions) {
|
||||
if (execution.isAuthenticatorFlow()) {
|
||||
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
|
||||
deepFindAuthenticationExecutions(realm, subFlow, result);
|
||||
} else {
|
||||
result.add(execution);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,8 +11,6 @@ import org.keycloak.models.IdentityProviderMapperModel;
|
|||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.ModelException;
|
||||
import org.keycloak.models.OTPPolicy;
|
||||
import org.keycloak.models.session.PersistentClientSessionModel;
|
||||
import org.keycloak.models.session.PersistentUserSessionModel;
|
||||
import org.keycloak.models.ProtocolMapperModel;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RequiredActionProviderModel;
|
||||
|
@ -47,7 +45,6 @@ import org.keycloak.representations.idm.UserSessionRepresentation;
|
|||
import org.keycloak.common.util.Time;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
|
@ -60,10 +57,25 @@ import java.util.Set;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ModelToRepresentation {
|
||||
public static void buildGroupPath(StringBuilder sb, GroupModel group) {
|
||||
if (group.getParent() != null) {
|
||||
buildGroupPath(sb, group.getParent());
|
||||
}
|
||||
sb.append('/').append(group.getName());
|
||||
}
|
||||
|
||||
public static String buildGroupPath(GroupModel group) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
buildGroupPath(sb, group);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
public static GroupRepresentation toRepresentation(GroupModel group, boolean full) {
|
||||
GroupRepresentation rep = new GroupRepresentation();
|
||||
rep.setId(group.getId());
|
||||
rep.setName(group.getName());
|
||||
rep.setPath(buildGroupPath(group));
|
||||
if (!full) return rep;
|
||||
// Role mappings
|
||||
Set<RoleModel> roles = group.getRoleMappings();
|
||||
|
@ -375,6 +387,7 @@ public class ModelToRepresentation {
|
|||
rep.setNotBefore(clientModel.getNotBefore());
|
||||
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
||||
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
|
||||
rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
|
||||
|
||||
Set<String> redirectUris = clientModel.getRedirectUris();
|
||||
if (redirectUris != null) {
|
||||
|
|
|
@ -737,6 +737,8 @@ public class RepresentationToModel {
|
|||
KeycloakModelUtils.generateSecret(client);
|
||||
}
|
||||
|
||||
client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
|
||||
|
||||
if (resourceRep.getAttributes() != null) {
|
||||
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
|
||||
client.setAttribute(entry.getKey(), entry.getValue());
|
||||
|
@ -813,6 +815,7 @@ public class RepresentationToModel {
|
|||
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
||||
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
|
||||
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
|
||||
if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
|
||||
resource.updateClient();
|
||||
|
||||
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.models.file.FileRealmProviderFactory
|
|
@ -1 +0,0 @@
|
|||
org.keycloak.models.file.FileUserProviderFactory
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -1223,7 +1223,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
|||
@Override
|
||||
public ClientModel getMasterAdminClient() {
|
||||
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
|
||||
return appData != null ? new ClientAdapter(session, this, appData, invocationContext) : null;
|
||||
if (appData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MongoRealmEntity masterRealm = getMongoStore().loadEntity(MongoRealmEntity.class, appData.getRealmId(), invocationContext);
|
||||
RealmModel masterRealmModel = new RealmAdapter(session, masterRealm, invocationContext);
|
||||
return new ClientAdapter(session, masterRealmModel, appData, invocationContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
<module>invalidation-cache</module>
|
||||
<module>jpa</module>
|
||||
<module>mongo</module>
|
||||
<module>file</module>
|
||||
<module>sessions-infinispan</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
20
pom.xml
20
pom.xml
|
@ -48,8 +48,8 @@
|
|||
<dom4j.version>1.6.1</dom4j.version>
|
||||
<xml-apis.version>1.4.01</xml-apis.version>
|
||||
<slf4j.version>1.7.7</slf4j.version>
|
||||
<wildfly.version>9.0.1.Final</wildfly.version>
|
||||
<wildfly.core.version>1.0.1.Final</wildfly.core.version>
|
||||
<wildfly.version>9.0.2.Final</wildfly.version>
|
||||
<wildfly.core.version>1.0.2.Final</wildfly.core.version>
|
||||
<wildfly.build-tools.version>1.0.0.Final</wildfly.build-tools.version>
|
||||
|
||||
<!-- this is EAP 6.4 alpha, publicly available -->
|
||||
|
@ -597,11 +597,6 @@
|
|||
<artifactId>keycloak-broker-saml</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-file</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||
|
@ -838,7 +833,7 @@
|
|||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-wf9-server-subsystem</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-subsystem</artifactId>
|
||||
|
@ -959,11 +954,6 @@
|
|||
<artifactId>keycloak-model-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-model-file</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
|
||||
|
@ -1441,7 +1431,7 @@
|
|||
<groupId>org.wildfly.build</groupId>
|
||||
<artifactId>wildfly-feature-pack-build-maven-plugin</artifactId>
|
||||
<version>${wildfly.build-tools.version}</version>
|
||||
</plugin>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.wildfly.build</groupId>
|
||||
<artifactId>wildfly-server-provisioning-maven-plugin</artifactId>
|
||||
|
@ -1462,7 +1452,7 @@
|
|||
<requireMavenVersion>
|
||||
<version>3.1.1</version>
|
||||
</requireMavenVersion>
|
||||
</rules>
|
||||
</rules>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.protocol.saml.clientregistration.EntityDescriptorClientRegistrationProviderFactory
|
|
@ -10,11 +10,12 @@ import org.keycloak.authentication.AuthenticationFlowContext;
|
|||
import org.keycloak.authentication.authenticators.broker.util.ExistingUserInfo;
|
||||
import org.keycloak.authentication.authenticators.broker.util.SerializedBrokeredIdentityContext;
|
||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.AuthenticatorConfigModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.FormMessage;
|
||||
import org.keycloak.services.messages.Messages;
|
||||
|
||||
/**
|
||||
|
@ -78,6 +79,15 @@ public class IdpCreateUserIfUniqueAuthenticator extends AbstractIdpAuthenticator
|
|||
.setError(Messages.FEDERATED_IDENTITY_EXISTS, duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
|
||||
.createErrorPage();
|
||||
context.challenge(challengeResponse);
|
||||
|
||||
if (context.getExecution().isRequired()) {
|
||||
context.getEvent()
|
||||
.user(duplication.getExistingUserId())
|
||||
.detail("existing_" + duplication.getDuplicateAttributeName(), duplication.getDuplicateAttributeValue())
|
||||
.removeDetail(Details.AUTH_METHOD)
|
||||
.removeDetail(Details.AUTH_TYPE)
|
||||
.error(Errors.FEDERATED_IDENTITY_EXISTS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ import org.keycloak.authentication.authenticators.broker.util.SerializedBrokered
|
|||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||
import org.keycloak.email.EmailException;
|
||||
import org.keycloak.email.EmailProvider;
|
||||
import org.keycloak.events.Details;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.login.LoginFormsProvider;
|
||||
import org.keycloak.models.ClientSessionModel;
|
||||
import org.keycloak.models.Constants;
|
||||
|
@ -52,6 +56,15 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
|||
String link = UriBuilder.fromUri(context.getActionUrl())
|
||||
.queryParam(Constants.KEY, clientSession.getNote(Constants.VERIFY_EMAIL_KEY))
|
||||
.build().toString();
|
||||
|
||||
EventBuilder event = context.getEvent().clone().event(EventType.SEND_IDENTITY_PROVIDER_LINK)
|
||||
.user(existingUser)
|
||||
.detail(Details.USERNAME, existingUser.getUsername())
|
||||
.detail(Details.EMAIL, existingUser.getEmail())
|
||||
.detail(Details.CODE_ID, clientSession.getId())
|
||||
.removeDetail(Details.AUTH_METHOD)
|
||||
.removeDetail(Details.AUTH_TYPE);
|
||||
|
||||
long expiration = TimeUnit.SECONDS.toMinutes(context.getRealm().getAccessCodeLifespanUserAction());
|
||||
try {
|
||||
|
||||
|
@ -60,15 +73,11 @@ public class IdpEmailVerificationAuthenticator extends AbstractIdpAuthenticator
|
|||
.setUser(existingUser)
|
||||
.setAttribute(EmailProvider.IDENTITY_PROVIDER_BROKER_CONTEXT, brokerContext)
|
||||
.sendConfirmIdentityBrokerLink(link, expiration);
|
||||
// event.clone().event(EventType.SEND_RESET_PASSWORD)
|
||||
// .user(user)
|
||||
// .detail(Details.USERNAME, username)
|
||||
// .detail(Details.EMAIL, user.getEmail()).detail(Details.CODE_ID, context.getClientSession().getId()).success();
|
||||
|
||||
event.success();
|
||||
} catch (EmailException e) {
|
||||
// event.clone().event(EventType.SEND_RESET_PASSWORD)
|
||||
// .detail(Details.USERNAME, username)
|
||||
// .user(user)
|
||||
// .error(Errors.EMAIL_SEND_FAILED);
|
||||
event.error(Errors.EMAIL_SEND_FAILED);
|
||||
|
||||
logger.error("Failed to send email to confirm identity broker linking", e);
|
||||
Response challenge = context.form()
|
||||
.setError(Messages.EMAIL_SENT_ERROR)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue