resolve conflicts
This commit is contained in:
commit
41331111da
118 changed files with 2137 additions and 5693 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;
|
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.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.apache.http.impl.client.HttpClients;
|
||||||
|
import org.keycloak.representations.adapters.config.AdapterConfig;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.common.util.Base64;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -23,160 +14,59 @@ import java.io.InputStream;
|
||||||
*/
|
*/
|
||||||
public class ClientRegistration {
|
public class ClientRegistration {
|
||||||
|
|
||||||
private String clientRegistrationUrl;
|
private final String DEFAULT = "default";
|
||||||
private HttpClient httpClient;
|
private final String INSTALLATION = "install";
|
||||||
private Auth auth;
|
|
||||||
|
|
||||||
public static ClientRegistrationBuilder create() {
|
private HttpUtil httpUtil;
|
||||||
return new ClientRegistrationBuilder();
|
|
||||||
|
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 {
|
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
String content = serialize(client);
|
String content = serialize(client);
|
||||||
InputStream resultStream = doPost(content);
|
InputStream resultStream = httpUtil.doPost(content, DEFAULT);
|
||||||
return deserialize(resultStream, ClientRepresentation.class);
|
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 {
|
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
||||||
InputStream resultStream = doGet(clientId);
|
InputStream resultStream = httpUtil.doGet(DEFAULT, clientId);
|
||||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void update(ClientRepresentation client) throws ClientRegistrationException {
|
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
|
||||||
String content = serialize(client);
|
InputStream resultStream = httpUtil.doGet(INSTALLATION, clientId);
|
||||||
doPut(content, client.getClientId());
|
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete() throws ClientRegistrationException {
|
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
if (auth instanceof ClientIdSecretAuth) {
|
String content = serialize(client);
|
||||||
String clientId = ((ClientIdSecretAuth) auth).clientId;
|
InputStream resultStream = httpUtil.doPut(content, DEFAULT, client.getClientId());
|
||||||
delete(clientId);
|
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
||||||
} else {
|
}
|
||||||
throw new ClientRegistrationException("Requires client authentication");
|
|
||||||
}
|
public void delete(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
|
delete(client.getClientId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(String clientId) throws ClientRegistrationException {
|
public void delete(String clientId) throws ClientRegistrationException {
|
||||||
doDelete(clientId);
|
httpUtil.doDelete(DEFAULT, 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
|
private String serialize(ClientRepresentation client) throws ClientRegistrationException {
|
||||||
|
@ -195,81 +85,4 @@ public class ClientRegistration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ClientRegistrationBuilder {
|
|
||||||
|
|
||||||
private String realm;
|
|
||||||
|
|
||||||
private String authServerUrl;
|
|
||||||
|
|
||||||
private Auth auth;
|
|
||||||
|
|
||||||
private HttpClient httpClient;
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder realm(String realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
public ClientRegistrationBuilder authServerUrl(String authServerUrl) {
|
|
||||||
this.authServerUrl = authServerUrl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder auth(String token) {
|
|
||||||
this.auth = new TokenAuth(token);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder auth(String clientId, String clientSecret) {
|
|
||||||
this.auth = new ClientIdSecretAuth(clientId, clientSecret);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistration build() {
|
|
||||||
ClientRegistration clientRegistration = new ClientRegistration();
|
|
||||||
clientRegistration.clientRegistrationUrl = authServerUrl + "/realms/" + realm + "/client-registration/default";
|
|
||||||
|
|
||||||
clientRegistration.httpClient = httpClient != null ? httpClient : HttpClients.createDefault();
|
|
||||||
clientRegistration.auth = auth;
|
|
||||||
|
|
||||||
return clientRegistration;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Auth {
|
|
||||||
void addAuth(HttpRequest httpRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AuthorizationHeaderAuth implements Auth {
|
|
||||||
private String credentials;
|
|
||||||
|
|
||||||
public AuthorizationHeaderAuth(String credentials) {
|
|
||||||
this.credentials = credentials;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAuth(HttpRequest httpRequest) {
|
|
||||||
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, credentials);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TokenAuth extends AuthorizationHeaderAuth {
|
|
||||||
public TokenAuth(String token) {
|
|
||||||
super("Bearer " + token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ClientIdSecretAuth extends AuthorizationHeaderAuth {
|
|
||||||
private String clientId;
|
|
||||||
|
|
||||||
public ClientIdSecretAuth(String clientId, String clientSecret) {
|
|
||||||
super("Basic " + Base64.encodeBytes((clientId + ":" + clientSecret).getBytes()));
|
|
||||||
this.clientId = clientId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,171 @@
|
||||||
|
package org.keycloak.client.registration;
|
||||||
|
|
||||||
|
import org.apache.http.HttpHeaders;
|
||||||
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.HttpClient;
|
||||||
|
import org.apache.http.client.methods.*;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.keycloak.client.registration.Auth;
|
||||||
|
import org.keycloak.client.registration.ClientRegistrationException;
|
||||||
|
import org.keycloak.client.registration.HttpErrorException;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
class HttpUtil {
|
||||||
|
|
||||||
|
private HttpClient httpClient;
|
||||||
|
|
||||||
|
private String baseUri;
|
||||||
|
|
||||||
|
private Auth auth;
|
||||||
|
|
||||||
|
HttpUtil(HttpClient httpClient, String baseUri) {
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
this.baseUri = baseUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAuth(Auth auth) {
|
||||||
|
this.auth = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream doPost(String content, String... path) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
|
addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
InputStream responseStream = null;
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
responseStream = response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == 201) {
|
||||||
|
return responseStream;
|
||||||
|
} else {
|
||||||
|
responseStream.close();
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream doGet(String... path) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
|
||||||
|
addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
InputStream responseStream = null;
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
responseStream = response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return responseStream;
|
||||||
|
} else if (response.getStatusLine().getStatusCode() == 404) {
|
||||||
|
responseStream.close();
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (responseStream != null) {
|
||||||
|
responseStream.close();
|
||||||
|
}
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream doPut(String content, String... path) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
||||||
|
|
||||||
|
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||||
|
request.setHeader(HttpHeaders.ACCEPT, "application/json");
|
||||||
|
request.setEntity(new StringEntity(content));
|
||||||
|
|
||||||
|
addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream responseStream = null;
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
responseStream = response.getEntity().getContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() == 200) {
|
||||||
|
return responseStream;
|
||||||
|
} else {
|
||||||
|
if (responseStream != null) {
|
||||||
|
responseStream.close();
|
||||||
|
}
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void doDelete(String... path) throws ClientRegistrationException {
|
||||||
|
try {
|
||||||
|
HttpDelete request = new HttpDelete(getUrl(baseUri, path));
|
||||||
|
|
||||||
|
addAuth(request);
|
||||||
|
|
||||||
|
HttpResponse response = httpClient.execute(request);
|
||||||
|
if (response.getEntity() != null) {
|
||||||
|
response.getEntity().getContent().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getStatusLine().getStatusCode() != 200) {
|
||||||
|
throw new HttpErrorException(response.getStatusLine());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to send request", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() throws ClientRegistrationException {
|
||||||
|
if (httpClient instanceof CloseableHttpClient) {
|
||||||
|
try {
|
||||||
|
((CloseableHttpClient) httpClient).close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ClientRegistrationException("Failed to close http client", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getUrl(String baseUri, String... path) {
|
||||||
|
StringBuilder s = new StringBuilder();
|
||||||
|
s.append(baseUri);
|
||||||
|
for (String p : path) {
|
||||||
|
s.append('/');
|
||||||
|
s.append(p);
|
||||||
|
}
|
||||||
|
return s.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addAuth(HttpRequestBase request) {
|
||||||
|
if (auth != null) {
|
||||||
|
auth.addAuth(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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="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"/>
|
<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>
|
</changeSet>
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
|
@ -17,7 +17,6 @@
|
||||||
<module>jpa-liquibase</module>
|
<module>jpa-liquibase</module>
|
||||||
<module>infinispan</module>
|
<module>infinispan</module>
|
||||||
<module>mongo</module>
|
<module>mongo</module>
|
||||||
<module>file</module>
|
|
||||||
<module>mongo-update</module>
|
<module>mongo-update</module>
|
||||||
<module>http-client</module>
|
<module>http-client</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class ClientRepresentation {
|
||||||
protected Boolean enabled;
|
protected Boolean enabled;
|
||||||
protected String clientAuthenticatorType;
|
protected String clientAuthenticatorType;
|
||||||
protected String secret;
|
protected String secret;
|
||||||
|
protected String registrationAccessToken;
|
||||||
protected String[] defaultRoles;
|
protected String[] defaultRoles;
|
||||||
protected List<String> redirectUris;
|
protected List<String> redirectUris;
|
||||||
protected List<String> webOrigins;
|
protected List<String> webOrigins;
|
||||||
|
@ -124,6 +125,14 @@ public class ClientRepresentation {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRegistrationAccessToken() {
|
||||||
|
return registrationAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistrationAccessToken(String registrationAccessToken) {
|
||||||
|
this.registrationAccessToken = registrationAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getRedirectUris() {
|
public List<String> getRedirectUris() {
|
||||||
return redirectUris;
|
return redirectUris;
|
||||||
}
|
}
|
||||||
|
@ -251,4 +260,5 @@ public class ClientRepresentation {
|
||||||
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
|
public void setProtocolMappers(List<ProtocolMapperRepresentation> protocolMappers) {
|
||||||
this.protocolMappers = protocolMappers;
|
this.protocolMappers = protocolMappers;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
4
dependencies/server-all/pom.xml
vendored
4
dependencies/server-all/pom.xml
vendored
|
@ -36,10 +36,6 @@
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-jpa</artifactId>
|
<artifactId>keycloak-model-jpa</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-model-file</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-sessions-infinispan</artifactId>
|
<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-jpa-liquibase" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-connections-mongo" 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-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-common" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-core" services="import"/>
|
<module name="org.keycloak.keycloak-core" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-email-api" 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-api" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-jpa" 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-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-model-sessions-infinispan" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
<module name="org.keycloak.keycloak-connections-jpa-liquibase" services="import"/>
|
<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" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-connections-mongo-update" 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-common" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-core" services="import"/>
|
<module name="org.keycloak.keycloak-core" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-email-api" 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-api" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-jpa" 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-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-model-sessions-infinispan" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-saml-core" services="import"/>
|
<module name="org.keycloak.keycloak-saml-core" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" 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"/>
|
<maven-resource group="org.keycloak" artifact="keycloak-connections-jpa-liquibase"/>
|
||||||
</module-def>
|
</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">
|
<module-def name="org.keycloak.keycloak-connections-infinispan">
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
|
<maven-resource group="org.keycloak" artifact="keycloak-connections-infinispan"/>
|
||||||
</module-def>
|
</module-def>
|
||||||
|
@ -250,12 +246,6 @@
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
|
<maven-resource group="org.keycloak" artifact="keycloak-saml-protocol"/>
|
||||||
</module-def>
|
</module-def>
|
||||||
|
|
||||||
<!-- file -->
|
|
||||||
|
|
||||||
<module-def name="org.keycloak.keycloak-model-file">
|
|
||||||
<maven-resource group="org.keycloak" artifact="keycloak-model-file"/>
|
|
||||||
</module-def>
|
|
||||||
|
|
||||||
<!-- mongo -->
|
<!-- mongo -->
|
||||||
|
|
||||||
<module-def name="org.keycloak.keycloak-connections-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-jpa-liquibase" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-connections-mongo" 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-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-common" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-core" services="import"/>
|
<module name="org.keycloak.keycloak-core" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-email-api" 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-api" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-jpa" 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-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-model-sessions-infinispan" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
<module name="org.keycloak.keycloak-saml-protocol" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
<module name="org.keycloak.keycloak-services" export="true" services="import"/>
|
||||||
|
|
|
@ -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-jpa-liquibase" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-connections-mongo" 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-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-common" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-core" services="import"/>
|
<module name="org.keycloak.keycloak-core" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-email-api" 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-api" services="import"/>
|
||||||
<module name="org.keycloak.keycloak-model-jpa" 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-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-model-sessions-infinispan" services="import"/>
|
||||||
|
|
||||||
<module name="org.keycloak.keycloak-saml-core" services="import"/>
|
<module name="org.keycloak.keycloak-saml-core" services="import"/>
|
||||||
|
|
|
@ -866,6 +866,14 @@ public class SecretQuestionRequiredActionFactory implements RequiredActionFactor
|
||||||
</para>
|
</para>
|
||||||
</section>
|
</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">
|
<section id="client_authentication">
|
||||||
<title>Authentication of clients</title>
|
<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>
|
<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>
|
<para>
|
||||||
<orderedlist>
|
<orderedlist>
|
||||||
<listitem>
|
<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.
|
Where <literal>mytheme</literal> is whatever you want to name your theme.
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
@ -19,15 +19,15 @@ import=common/keycloak
|
||||||
]]></programlisting>
|
]]></programlisting>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
Copy the file <literal>themes/admin/base/resources/partials/user-attribute-entry.html</literal> into the
|
Copy the file <literal>themes/base/admin/resources/partials/user-attributes.html</literal> into the
|
||||||
a mirror directory in your theme: <literal>themes/admin/mytheme/resources/partials/user-attribute-entry.html</literal>.
|
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 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
|
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
|
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.
|
of creating a new theme, you can.
|
||||||
</listitem>
|
</listitem>
|
||||||
<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">
|
<programlisting><![CDATA[ <div class="form-group clearfix block">
|
||||||
<label class="col-sm-2 control-label" for="mobile">Mobile</label>
|
<label class="col-sm-2 control-label" for="mobile">Mobile</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
@ -52,7 +52,7 @@ import=common/keycloak
|
||||||
<para>
|
<para>
|
||||||
<orderedlist>
|
<orderedlist>
|
||||||
<listitem>
|
<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.
|
Where <literal>mytheme</literal> is whatever you want to name your theme.
|
||||||
</listitem>
|
</listitem>
|
||||||
<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>
|
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/login.css ../patternfly/lib/zocial/zocial.css css/login.css]]></programlisting>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
Copy the file <literal>themes/login/base/register.ftl</literal> into the
|
Copy the file <literal>themes/base/login/register.ftl</literal> into the
|
||||||
a mirror directory in your theme: <literal>themes/login/mytheme/register.ftl</literal>.
|
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 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
|
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
|
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>
|
<para>
|
||||||
<orderedlist>
|
<orderedlist>
|
||||||
<listitem>
|
<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.
|
Where <literal>mytheme</literal> is whatever you want to name your theme.
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
|
@ -113,8 +113,8 @@ import=common/keycloak
|
||||||
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]></programlisting>
|
styles= ../patternfly/lib/patternfly/css/patternfly.css ../patternfly/css/account.css css/account.css]]></programlisting>
|
||||||
</listitem>
|
</listitem>
|
||||||
<listitem>
|
<listitem>
|
||||||
Copy the file <literal>themes/account/base/account.ftl</literal> into the
|
Copy the file <literal>themes/base/account/account.ftl</literal> into the
|
||||||
a mirror directory in your theme: <literal>themes/account/mytheme/account.ftl</literal>.
|
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 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
|
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
|
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>
|
</itemizedlist>
|
||||||
|
|
||||||
<section>
|
<section id="identity-broker-overview">
|
||||||
<title>Overview</title>
|
<title>Overview</title>
|
||||||
|
|
||||||
<para>
|
<para>
|
||||||
|
@ -127,10 +127,11 @@
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Now Keycloak is going to check if the response from the identity provider is valid. If valid, it will create an user
|
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>.
|
(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
|
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>.
|
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
|
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.
|
the requested resource in the service provider.
|
||||||
</para>
|
</para>
|
||||||
|
@ -210,7 +211,7 @@
|
||||||
<para>
|
<para>
|
||||||
Social providers allows you to enable social authentication to your realm.
|
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.
|
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>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
@ -274,6 +275,15 @@
|
||||||
be used by any other means.
|
be used by any other means.
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</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>
|
<row>
|
||||||
<entry>
|
<entry>
|
||||||
<literal>Store Tokens</literal>
|
<literal>Store Tokens</literal>
|
||||||
|
@ -293,20 +303,6 @@
|
||||||
to access any stored external tokens via the broker service.
|
to access any stored external tokens via the broker service.
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</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>
|
<row>
|
||||||
<entry>
|
<entry>
|
||||||
<literal>Trust email</literal>
|
<literal>Trust email</literal>
|
||||||
|
@ -326,6 +322,16 @@
|
||||||
You can put number into this field, providers with lower numbers are shown first.
|
You can put number into this field, providers with lower numbers are shown first.
|
||||||
</entry>
|
</entry>
|
||||||
</row>
|
</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>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
@ -340,8 +346,8 @@
|
||||||
Forcing users to register to your realm when they want to access applications is hard.
|
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.
|
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.
|
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
|
Keycloak provides built-in support for the most common social networks out there, such as Google, Facebook, Twitter,
|
||||||
even Github.
|
Github, LinkedId and StackOverflow.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -1211,7 +1217,13 @@ Authorization: Bearer {keycloak_access_token}]]></programlisting>
|
||||||
<section>
|
<section>
|
||||||
<title>Automatically Select and Identity Provider</title>
|
<title>Automatically Select and Identity Provider</title>
|
||||||
<para>
|
<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>
|
||||||
<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.
|
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>
|
</para>
|
||||||
</section>
|
</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>
|
<section>
|
||||||
<title>Examples</title>
|
<title>Examples</title>
|
||||||
<para>
|
<para>
|
||||||
|
|
|
@ -116,7 +116,7 @@ public class KerberosFederationProvider implements UserFederationProvider {
|
||||||
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
// KerberosUsernamePasswordAuthenticator.isUserAvailable is an overhead, so avoid it for now
|
||||||
|
|
||||||
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
String kerberosPrincipal = local.getUsername() + "@" + kerberosConfig.getKerberosRealm();
|
||||||
return kerberosPrincipal.equals(local.getFirstAttribute(KERBEROS_PRINCIPAL));
|
return kerberosPrincipal.equalsIgnoreCase(local.getFirstAttribute(KERBEROS_PRINCIPAL));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -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.
|
federatedIdentityRemovingLastProviderMessage=You can''t remove last federated identity as you don''t have password.
|
||||||
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
|
identityProviderRedirectErrorMessage=Failed to redirect to identity provider.
|
||||||
identityProviderRemovedMessage=Identity provider removed successfully.
|
identityProviderRemovedMessage=Identity provider removed successfully.
|
||||||
|
identityProviderAlreadyLinkedMessage=Federated identity returned by {0} is already linked to another user.
|
||||||
|
|
||||||
accountDisabledMessage=Account is disabled, contact admin.
|
accountDisabledMessage=Account is disabled, contact admin.
|
||||||
|
|
||||||
|
|
|
@ -270,7 +270,10 @@ client-certificate-import=Client Certificate Import
|
||||||
import-client-certificate=Import Client Certificate
|
import-client-certificate=Import Client Certificate
|
||||||
jwt-import.key-alias.tooltip=Archive alias for your certificate.
|
jwt-import.key-alias.tooltip=Archive alias for your certificate.
|
||||||
secret=Secret
|
secret=Secret
|
||||||
regenerate-secret=Regenerate Secret
|
regenerate-secret=Regenerate Secretsecret=Secret
|
||||||
|
registrationAccessToken=Registration access token
|
||||||
|
registrationAccessToken.regenerate=Regenerate registration access token
|
||||||
|
registrationAccessToken.tooltip=The registration access token provides access for clients to the client registration service.
|
||||||
add-role=Add Role
|
add-role=Add Role
|
||||||
role-name=Role Name
|
role-name=Role Name
|
||||||
composite=Composite
|
composite=Composite
|
||||||
|
@ -394,7 +397,7 @@ update-profile-on-first-login.tooltip=Define conditions under which a user has t
|
||||||
trust-email=Trust Email
|
trust-email=Trust Email
|
||||||
trust-email.tooltip=If enabled then email provided by this provider is not verified even if verification is enabled for the realm.
|
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).
|
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=OpenID Connect Config
|
||||||
openid-connect-config.tooltip=OIDC SP and external IDP configuration.
|
openid-connect-config.tooltip=OIDC SP and external IDP configuration.
|
||||||
authorization-url=Authorization URL
|
authorization-url=Authorization URL
|
||||||
|
|
|
@ -30,7 +30,7 @@ module.controller('ClientRoleListCtrl', function($scope, $location, realm, clien
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client) {
|
module.controller('ClientCredentialsCtrl', function($scope, $location, realm, client, clientAuthenticatorProviders, clientConfigProperties, Client, ClientRegistrationAccessToken, Notifications) {
|
||||||
$scope.realm = realm;
|
$scope.realm = realm;
|
||||||
$scope.client = angular.copy(client);
|
$scope.client = angular.copy(client);
|
||||||
$scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
|
$scope.clientAuthenticatorProviders = clientAuthenticatorProviders;
|
||||||
|
@ -68,6 +68,17 @@ module.controller('ClientCredentialsCtrl', function($scope, $location, realm, cl
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
$scope.regenerateRegistrationAccessToken = function() {
|
||||||
|
var secret = ClientRegistrationAccessToken.update({ realm : $scope.realm.realm, client : $scope.client.id },
|
||||||
|
function(data) {
|
||||||
|
Notifications.success('The registration access token has been updated.');
|
||||||
|
$scope.client['registrationAccessToken'] = data.registrationAccessToken;
|
||||||
|
},
|
||||||
|
function() {
|
||||||
|
Notifications.error('Failed to update the registration access token');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) {
|
module.controller('ClientSecretCtrl', function($scope, $location, ClientSecret, Notifications) {
|
||||||
|
|
|
@ -981,6 +981,17 @@ module.factory('ClientSecret', function($resource) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.factory('ClientRegistrationAccessToken', function($resource) {
|
||||||
|
return $resource(authUrl + '/admin/realms/:realm/clients/:client/registration-access-token', {
|
||||||
|
realm : '@realm',
|
||||||
|
client : '@client'
|
||||||
|
}, {
|
||||||
|
update : {
|
||||||
|
method : 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
module.factory('ClientOrigins', function($resource) {
|
module.factory('ClientOrigins', function($resource) {
|
||||||
return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', {
|
return $resource(authUrl + '/admin/realms/:realm/clients/:client/allowed-origins', {
|
||||||
realm : '@realm',
|
realm : '@realm',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
|
<form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-show="currentAuthenticatorConfigProperties.length > 0" data-ng-controller="ClientGenericCredentialsCtrl">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<kc-provider-config realm="realm" config="client.attributes" properties="currentAuthenticatorConfigProperties"></kc-provider-config>
|
<kc-provider-config realm="realm" config="client.attributes" properties="currentAuthenticatorConfigProperties"></kc-provider-config>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<form class="form-horizontal" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
|
<form class="form-horizontal no-margin-top" name="keyForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSignedJWTCtrl">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
|
<label class="col-md-2 control-label" for="signingCert">{{:: 'certificate' | translate}}</label>
|
||||||
<kc-tooltip>{{:: 'certificate.tooltip' | translate}}</kc-tooltip>
|
<kc-tooltip>{{:: 'certificate.tooltip' | translate}}</kc-tooltip>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div>
|
<div>
|
||||||
<form class="form-horizontal" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
|
<form class="form-horizontal no-margin-top" name="credentialForm" novalidate kc-read-only="!access.manageClients" data-ng-controller="ClientSecretCtrl">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-md-2 control-label" for="secret">{{:: 'secret' | translate}}</label>
|
<label class="col-md-2 control-label" for="secret">{{:: 'secret' | translate}}</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
|
|
@ -28,6 +28,11 @@
|
||||||
<div data-ng-include="resourceUrl + '/partials/' + clientAuthenticatorConfigPartial">
|
<div data-ng-include="resourceUrl + '/partials/' + clientAuthenticatorConfigPartial">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<div data-ng-include="resourceUrl + '/partials/client-registration-access-token.html'">
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<kc-menu></kc-menu>
|
<kc-menu></kc-menu>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div>
|
||||||
|
<form class="form-horizontal" name="registrationAccessTokenForm" novalidate kc-read-only="!access.manageClients">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-2 control-label" for="registrationAccessToken">{{:: 'registrationAccessToken' | translate}}</label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input readonly kc-select-action="click" class="form-control" type="text" id="registrationAccessToken" name="registrationAccessToken" data-ng-model="client.registrationAccessToken">
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6" data-ng-show="access.manageClients">
|
||||||
|
<button type="submit" data-ng-click="regenerateRegistrationAccessToken()" class="btn btn-default">{{:: 'registrationAccessToken.regenerate' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<kc-tooltip>{{:: 'registrationAccessToken.tooltip' | translate}}</kc-tooltip>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -185,7 +185,6 @@ resetCredentialNotAllowedMessage=Reset Credential not allowed
|
||||||
|
|
||||||
permissionNotApprovedMessage=Permission not approved.
|
permissionNotApprovedMessage=Permission not approved.
|
||||||
noRelayStateInResponseMessage=No relay state in response from identity provider.
|
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.
|
insufficientPermissionMessage=Insufficient permissions to link identities.
|
||||||
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
|
couldNotProceedWithAuthenticationRequestMessage=Could not proceed with authentication request to identity provider.
|
||||||
couldNotObtainTokenMessage=Could not obtain token from identity provider.
|
couldNotObtainTokenMessage=Could not obtain token from identity provider.
|
||||||
|
|
|
@ -22,6 +22,11 @@ table {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-margin-top {
|
||||||
|
margin-top: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*********** Loading ***********/
|
/*********** Loading ***********/
|
||||||
|
|
||||||
|
|
|
@ -71,21 +71,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -70,21 +70,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -81,21 +81,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -67,21 +67,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -90,6 +90,9 @@ public class FilterSessionStore implements AdapterSessionStore {
|
||||||
|
|
||||||
MultivaluedHashMap<String, String> getParams() {
|
MultivaluedHashMap<String, String> getParams() {
|
||||||
if (parameters != null) return parameters;
|
if (parameters != null) return parameters;
|
||||||
|
|
||||||
|
if (body == null) return new MultivaluedHashMap<String, String>();
|
||||||
|
|
||||||
String contentType = getContentType();
|
String contentType = getContentType();
|
||||||
contentType = contentType.toLowerCase();
|
contentType = contentType.toLowerCase();
|
||||||
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
if (contentType.startsWith("application/x-www-form-urlencoded")) {
|
||||||
|
|
|
@ -90,6 +90,9 @@ public interface ClientModel extends RoleContainerModel {
|
||||||
String getSecret();
|
String getSecret();
|
||||||
public void setSecret(String secret);
|
public void setSecret(String secret);
|
||||||
|
|
||||||
|
String getRegistrationSecret();
|
||||||
|
void setRegistrationSecret(String registrationSecret);
|
||||||
|
|
||||||
boolean isFullScopeAllowed();
|
boolean isFullScopeAllowed();
|
||||||
void setFullScopeAllowed(boolean value);
|
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.HttpHeaders;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,6 +13,8 @@ import java.util.Locale;
|
||||||
*/
|
*/
|
||||||
public interface KeycloakContext {
|
public interface KeycloakContext {
|
||||||
|
|
||||||
|
URI getAuthServerUrl();
|
||||||
|
|
||||||
String getContextPath();
|
String getContextPath();
|
||||||
|
|
||||||
UriInfo getUri();
|
UriInfo getUri();
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String clientAuthenticatorType;
|
private String clientAuthenticatorType;
|
||||||
private String secret;
|
private String secret;
|
||||||
|
private String registrationSecret;
|
||||||
private String protocol;
|
private String protocol;
|
||||||
private int notBefore;
|
private int notBefore;
|
||||||
private boolean publicClient;
|
private boolean publicClient;
|
||||||
|
@ -90,6 +91,14 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRegistrationSecret() {
|
||||||
|
return registrationSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistrationSecret(String registrationSecret) {
|
||||||
|
this.registrationSecret = registrationSecret;
|
||||||
|
}
|
||||||
|
|
||||||
public int getNotBefore() {
|
public int getNotBefore() {
|
||||||
return notBefore;
|
return notBefore;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
@ -36,7 +38,7 @@ public class DefaultAuthenticationFlows {
|
||||||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||||
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
||||||
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(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) {
|
public static void migrateFlows(RealmModel realm) {
|
||||||
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
|
if (realm.getFlowByAlias(BROWSER_FLOW) == null) browserFlow(realm, true);
|
||||||
|
@ -44,7 +46,7 @@ public class DefaultAuthenticationFlows {
|
||||||
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
if (realm.getFlowByAlias(REGISTRATION_FLOW) == null) registrationFlow(realm);
|
||||||
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
if (realm.getFlowByAlias(RESET_CREDENTIALS_FLOW) == null) resetCredentialsFlow(realm);
|
||||||
if (realm.getFlowByAlias(CLIENT_AUTHENTICATION_FLOW) == null) clientAuthFlow(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) {
|
public static void registrationFlow(RealmModel realm) {
|
||||||
|
@ -322,7 +324,7 @@ public class DefaultAuthenticationFlows {
|
||||||
realm.addAuthenticatorExecution(execution);
|
realm.addAuthenticatorExecution(execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void firstBrokerLoginFlow(RealmModel realm) {
|
public static void firstBrokerLoginFlow(RealmModel realm, boolean migrate) {
|
||||||
AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
|
AuthenticationFlowModel firstBrokerLogin = new AuthenticationFlowModel();
|
||||||
firstBrokerLogin.setAlias(FIRST_BROKER_LOGIN_FLOW);
|
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");
|
firstBrokerLogin.setDescription("Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account");
|
||||||
|
@ -423,10 +425,19 @@ public class DefaultAuthenticationFlows {
|
||||||
execution = new AuthenticationExecutionModel();
|
execution = new AuthenticationExecutionModel();
|
||||||
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
|
execution.setParentFlow(verifyByReauthenticationAccountFlow.getId());
|
||||||
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
|
execution.setRequirement(AuthenticationExecutionModel.Requirement.OPTIONAL);
|
||||||
// TODO: read the requirement from browser authenticator
|
|
||||||
// if (migrate && hasCredentialType(realm, RequiredCredentialModel.TOTP.getType())) {
|
if (migrate) {
|
||||||
// execution.setRequirement(AuthenticationExecutionModel.Requirement.REQUIRED);
|
// 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.setAuthenticator("auth-otp-form");
|
||||||
execution.setPriority(20);
|
execution.setPriority(20);
|
||||||
execution.setAuthenticatorFlow(false);
|
execution.setAuthenticatorFlow(false);
|
||||||
|
|
|
@ -23,6 +23,9 @@ public class FormMessage {
|
||||||
private String message;
|
private String message;
|
||||||
private Object[] parameters;
|
private Object[] parameters;
|
||||||
|
|
||||||
|
public FormMessage() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create message.
|
* Create message.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package org.keycloak.models.utils;
|
package org.keycloak.models.utils;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.PEMWriter;
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.keycloak.common.util.Base64Url;
|
||||||
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
import org.keycloak.models.AuthenticationFlowModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.GroupModel;
|
import org.keycloak.models.GroupModel;
|
||||||
|
@ -17,6 +19,7 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserFederationMapperModel;
|
import org.keycloak.models.UserFederationMapperModel;
|
||||||
import org.keycloak.models.UserFederationProviderModel;
|
import org.keycloak.models.UserFederationProviderModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationExecutionRepresentation;
|
||||||
import org.keycloak.representations.idm.CertificateRepresentation;
|
import org.keycloak.representations.idm.CertificateRepresentation;
|
||||||
import org.keycloak.common.util.CertificateUtils;
|
import org.keycloak.common.util.CertificateUtils;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.common.util.PemUtils;
|
||||||
|
@ -30,9 +33,11 @@ import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -45,6 +50,8 @@ import java.util.UUID;
|
||||||
*/
|
*/
|
||||||
public final class KeycloakModelUtils {
|
public final class KeycloakModelUtils {
|
||||||
|
|
||||||
|
private static final int RANDOM_PASSWORD_BYTES = 32;
|
||||||
|
|
||||||
private KeycloakModelUtils() {
|
private KeycloakModelUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,12 +183,22 @@ public final class KeycloakModelUtils {
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserCredentialModel generateSecret(ClientModel app) {
|
public static UserCredentialModel generateSecret(ClientModel client) {
|
||||||
UserCredentialModel secret = UserCredentialModel.generateSecret();
|
UserCredentialModel secret = UserCredentialModel.generateSecret();
|
||||||
app.setSecret(secret.getValue());
|
client.setSecret(secret.getValue());
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void generateRegistrationAccessToken(ClientModel client) {
|
||||||
|
client.setRegistrationSecret(generatePassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generatePassword() {
|
||||||
|
byte[] buf = new byte[RANDOM_PASSWORD_BYTES];
|
||||||
|
new SecureRandom().nextBytes(buf);
|
||||||
|
return Base64Url.encode(buf);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getDefaultClientAuthenticatorType() {
|
public static String getDefaultClientAuthenticatorType() {
|
||||||
return "client-secret";
|
return "client-secret";
|
||||||
}
|
}
|
||||||
|
@ -389,6 +406,26 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively find all AuthenticationExecutionModel from specified flow or all it's subflows
|
||||||
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param flow
|
||||||
|
* @param result input should be empty list. At the end will be all executions added to this list
|
||||||
|
*/
|
||||||
|
public static void deepFindAuthenticationExecutions(RealmModel realm, AuthenticationFlowModel flow, List<AuthenticationExecutionModel> result) {
|
||||||
|
List<AuthenticationExecutionModel> executions = realm.getAuthenticationExecutions(flow.getId());
|
||||||
|
for (AuthenticationExecutionModel execution : executions) {
|
||||||
|
if (execution.isAuthenticatorFlow()) {
|
||||||
|
AuthenticationFlowModel subFlow = realm.getAuthenticationFlowById(execution.getFlowId());
|
||||||
|
deepFindAuthenticationExecutions(realm, subFlow, result);
|
||||||
|
} else {
|
||||||
|
result.add(execution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String resolveFirstAttribute(GroupModel group, String name) {
|
public static String resolveFirstAttribute(GroupModel group, String name) {
|
||||||
String value = group.getFirstAttribute(name);
|
String value = group.getFirstAttribute(name);
|
||||||
if (value != null) return value;
|
if (value != null) return value;
|
||||||
|
|
|
@ -11,8 +11,6 @@ import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.OTPPolicy;
|
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.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredActionProviderModel;
|
import org.keycloak.models.RequiredActionProviderModel;
|
||||||
|
@ -46,14 +44,7 @@ import org.keycloak.representations.idm.UserRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.common.util.Time;
|
import org.keycloak.common.util.Time;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
@ -301,19 +292,50 @@ public class ModelToRepresentation {
|
||||||
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
|
public static void exportAuthenticationFlows(RealmModel realm, RealmRepresentation rep) {
|
||||||
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
|
rep.setAuthenticationFlows(new LinkedList<AuthenticationFlowRepresentation>());
|
||||||
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
|
rep.setAuthenticatorConfig(new LinkedList<AuthenticatorConfigRepresentation>());
|
||||||
for (AuthenticationFlowModel model : realm.getAuthenticationFlows()) {
|
|
||||||
|
List<AuthenticationFlowModel> authenticationFlows = new ArrayList<>(realm.getAuthenticationFlows());
|
||||||
|
//ensure consistent ordering of authenticationFlows.
|
||||||
|
Collections.sort(authenticationFlows, new Comparator<AuthenticationFlowModel>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AuthenticationFlowModel left, AuthenticationFlowModel right) {
|
||||||
|
return left.getAlias().compareTo(right.getAlias());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (AuthenticationFlowModel model : authenticationFlows) {
|
||||||
AuthenticationFlowRepresentation flowRep = toRepresentation(realm, model);
|
AuthenticationFlowRepresentation flowRep = toRepresentation(realm, model);
|
||||||
rep.getAuthenticationFlows().add(flowRep);
|
rep.getAuthenticationFlows().add(flowRep);
|
||||||
}
|
}
|
||||||
for (AuthenticatorConfigModel model : realm.getAuthenticatorConfigs()) {
|
|
||||||
|
List<AuthenticatorConfigModel> authenticatorConfigs = new ArrayList<>(realm.getAuthenticatorConfigs());
|
||||||
|
//ensure consistent ordering of authenticatorConfigs.
|
||||||
|
Collections.sort(authenticatorConfigs, new Comparator<AuthenticatorConfigModel>() {
|
||||||
|
@Override
|
||||||
|
public int compare(AuthenticatorConfigModel left, AuthenticatorConfigModel right) {
|
||||||
|
return left.getAlias().compareTo(right.getAlias());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (AuthenticatorConfigModel model : authenticatorConfigs) {
|
||||||
rep.getAuthenticatorConfig().add(toRepresentation(model));
|
rep.getAuthenticatorConfig().add(toRepresentation(model));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void exportRequiredActions(RealmModel realm, RealmRepresentation rep) {
|
public static void exportRequiredActions(RealmModel realm, RealmRepresentation rep) {
|
||||||
|
|
||||||
rep.setRequiredActions(new LinkedList<RequiredActionProviderRepresentation>());
|
rep.setRequiredActions(new LinkedList<RequiredActionProviderRepresentation>());
|
||||||
for (RequiredActionProviderModel model : realm.getRequiredActionProviders()) {
|
|
||||||
|
List<RequiredActionProviderModel> requiredActionProviders = realm.getRequiredActionProviders();
|
||||||
|
//ensure consistent ordering of requiredActionProviders.
|
||||||
|
Collections.sort(requiredActionProviders, new Comparator<RequiredActionProviderModel>() {
|
||||||
|
@Override
|
||||||
|
public int compare(RequiredActionProviderModel left, RequiredActionProviderModel right) {
|
||||||
|
return left.getAlias().compareTo(right.getAlias());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (RequiredActionProviderModel model : requiredActionProviders) {
|
||||||
RequiredActionProviderRepresentation action = toRepresentation(model);
|
RequiredActionProviderRepresentation action = toRepresentation(model);
|
||||||
rep.getRequiredActions().add(action);
|
rep.getRequiredActions().add(action);
|
||||||
}
|
}
|
||||||
|
@ -396,6 +418,7 @@ public class ModelToRepresentation {
|
||||||
rep.setNotBefore(clientModel.getNotBefore());
|
rep.setNotBefore(clientModel.getNotBefore());
|
||||||
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
rep.setNodeReRegistrationTimeout(clientModel.getNodeReRegistrationTimeout());
|
||||||
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
|
rep.setClientAuthenticatorType(clientModel.getClientAuthenticatorType());
|
||||||
|
rep.setRegistrationAccessToken(clientModel.getRegistrationSecret());
|
||||||
|
|
||||||
Set<String> redirectUris = clientModel.getRedirectUris();
|
Set<String> redirectUris = clientModel.getRedirectUris();
|
||||||
if (redirectUris != null) {
|
if (redirectUris != null) {
|
||||||
|
|
|
@ -797,6 +797,8 @@ public class RepresentationToModel {
|
||||||
KeycloakModelUtils.generateSecret(client);
|
KeycloakModelUtils.generateSecret(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.setRegistrationSecret(resourceRep.getRegistrationAccessToken());
|
||||||
|
|
||||||
if (resourceRep.getAttributes() != null) {
|
if (resourceRep.getAttributes() != null) {
|
||||||
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
|
for (Map.Entry<String, String> entry : resourceRep.getAttributes().entrySet()) {
|
||||||
client.setAttribute(entry.getKey(), entry.getValue());
|
client.setAttribute(entry.getKey(), entry.getValue());
|
||||||
|
@ -873,6 +875,7 @@ public class RepresentationToModel {
|
||||||
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
if (rep.isSurrogateAuthRequired() != null) resource.setSurrogateAuthRequired(rep.isSurrogateAuthRequired());
|
||||||
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
|
if (rep.getNodeReRegistrationTimeout() != null) resource.setNodeReRegistrationTimeout(rep.getNodeReRegistrationTimeout());
|
||||||
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
|
if (rep.getClientAuthenticatorType() != null) resource.setClientAuthenticatorType(rep.getClientAuthenticatorType());
|
||||||
|
if (rep.getRegistrationAccessToken() != null) resource.setRegistrationSecret(rep.getRegistrationAccessToken());
|
||||||
resource.updateClient();
|
resource.updateClient();
|
||||||
|
|
||||||
if (rep.getProtocol() != null) resource.setProtocol(rep.getProtocol());
|
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();
|
getDelegateForUpdate();
|
||||||
updated.setSecret(secret);
|
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() {
|
public boolean isPublicClient() {
|
||||||
if (updated != null) return updated.isPublicClient();
|
if (updated != null) return updated.isPublicClient();
|
||||||
|
|
|
@ -31,6 +31,7 @@ public class CachedClient implements Serializable {
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String clientAuthenticatorType;
|
private String clientAuthenticatorType;
|
||||||
private String secret;
|
private String secret;
|
||||||
|
private String registrationSecret;
|
||||||
private String protocol;
|
private String protocol;
|
||||||
private Map<String, String> attributes = new HashMap<String, String>();
|
private Map<String, String> attributes = new HashMap<String, String>();
|
||||||
private boolean publicClient;
|
private boolean publicClient;
|
||||||
|
@ -57,6 +58,7 @@ public class CachedClient implements Serializable {
|
||||||
id = model.getId();
|
id = model.getId();
|
||||||
clientAuthenticatorType = model.getClientAuthenticatorType();
|
clientAuthenticatorType = model.getClientAuthenticatorType();
|
||||||
secret = model.getSecret();
|
secret = model.getSecret();
|
||||||
|
registrationSecret = model.getRegistrationSecret();
|
||||||
clientId = model.getClientId();
|
clientId = model.getClientId();
|
||||||
name = model.getName();
|
name = model.getName();
|
||||||
description = model.getDescription();
|
description = model.getDescription();
|
||||||
|
@ -129,6 +131,10 @@ public class CachedClient implements Serializable {
|
||||||
return secret;
|
return secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRegistrationSecret() {
|
||||||
|
return registrationSecret;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isPublicClient() {
|
public boolean isPublicClient() {
|
||||||
return publicClient;
|
return publicClient;
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,16 @@ public class ClientAdapter implements ClientModel {
|
||||||
entity.setSecret(secret);
|
entity.setSecret(secret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRegistrationSecret() {
|
||||||
|
return entity.getRegistrationSecret();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegistrationSecret(String registrationSecret) {
|
||||||
|
entity.setRegistrationSecret(registrationSecret);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean validateSecret(String secret) {
|
public boolean validateSecret(String secret) {
|
||||||
return secret.equals(entity.getSecret());
|
return secret.equals(entity.getSecret());
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.keycloak.models.jpa;
|
package org.keycloak.models.jpa;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||||
import org.keycloak.common.enums.SslRequired;
|
import org.keycloak.common.enums.SslRequired;
|
||||||
import org.keycloak.models.AuthenticationExecutionModel;
|
import org.keycloak.models.AuthenticationExecutionModel;
|
||||||
|
@ -1195,8 +1196,13 @@ public class RealmAdapter implements RealmModel {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ClientModel getMasterAdminClient() {
|
public ClientModel getMasterAdminClient() {
|
||||||
ClientEntity client = realm.getMasterAdminClient();
|
ClientEntity masterAdminClient = realm.getMasterAdminClient();
|
||||||
return client!=null ? new ClientAdapter(this, em, session, realm.getMasterAdminClient()) : null;
|
if (masterAdminClient == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
RealmAdapter masterRealm = new RealmAdapter(session, em, masterAdminClient.getRealm());
|
||||||
|
return new ClientAdapter(masterRealm, em, session, masterAdminClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -42,6 +42,8 @@ public class ClientEntity {
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
@Column(name="SECRET")
|
@Column(name="SECRET")
|
||||||
private String secret;
|
private String secret;
|
||||||
|
@Column(name="REGISTRATION_SECRET")
|
||||||
|
private String registrationSecret;
|
||||||
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
|
@Column(name="CLIENT_AUTHENTICATOR_TYPE")
|
||||||
private String clientAuthenticatorType;
|
private String clientAuthenticatorType;
|
||||||
@Column(name="NOT_BEFORE")
|
@Column(name="NOT_BEFORE")
|
||||||
|
@ -201,6 +203,14 @@ public class ClientEntity {
|
||||||
this.secret = secret;
|
this.secret = secret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRegistrationSecret() {
|
||||||
|
return registrationSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRegistrationSecret(String registrationSecret) {
|
||||||
|
this.registrationSecret = registrationSecret;
|
||||||
|
}
|
||||||
|
|
||||||
public int getNotBefore() {
|
public int getNotBefore() {
|
||||||
return notBefore;
|
return notBefore;
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,6 +177,17 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
|
||||||
updateMongoEntity();
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getRegistrationSecret() {
|
||||||
|
return getMongoEntity().getRegistrationSecret();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRegistrationSecret(String registrationSecretsecret) {
|
||||||
|
getMongoEntity().setRegistrationSecret(registrationSecretsecret);
|
||||||
|
updateMongoEntity();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPublicClient() {
|
public boolean isPublicClient() {
|
||||||
return getMongoEntity().isPublicClient();
|
return getMongoEntity().isPublicClient();
|
||||||
|
|
|
@ -1230,7 +1230,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
|
||||||
@Override
|
@Override
|
||||||
public ClientModel getMasterAdminClient() {
|
public ClientModel getMasterAdminClient() {
|
||||||
MongoClientEntity appData = getMongoStore().loadEntity(MongoClientEntity.class, realm.getMasterAdminClient(), invocationContext);
|
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
|
@Override
|
||||||
|
|
|
@ -453,7 +453,7 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Set<GroupModel> getGroups() {
|
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<>();
|
Set<GroupModel> groups = new HashSet<>();
|
||||||
for (String id : user.getGroupIds()) {
|
for (String id : user.getGroupIds()) {
|
||||||
groups.add(realm.getGroupById(id));
|
groups.add(realm.getGroupById(id));
|
||||||
|
|
|
@ -29,7 +29,6 @@
|
||||||
<module>invalidation-cache</module>
|
<module>invalidation-cache</module>
|
||||||
<module>jpa</module>
|
<module>jpa</module>
|
||||||
<module>mongo</module>
|
<module>mongo</module>
|
||||||
<module>file</module>
|
|
||||||
<module>sessions-infinispan</module>
|
<module>sessions-infinispan</module>
|
||||||
</modules>
|
</modules>
|
||||||
</project>
|
</project>
|
||||||
|
|
10
pom.xml
10
pom.xml
|
@ -597,11 +597,6 @@
|
||||||
<artifactId>keycloak-broker-saml</artifactId>
|
<artifactId>keycloak-broker-saml</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-connections-file</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-connections-infinispan</artifactId>
|
<artifactId>keycloak-connections-infinispan</artifactId>
|
||||||
|
@ -959,11 +954,6 @@
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-model-file</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
|
<artifactId>keycloak-invalidation-cache-infinispan</artifactId>
|
||||||
|
|
|
@ -59,21 +59,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -54,21 +54,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -69,21 +69,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -69,21 +69,21 @@
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-util</artifactId>
|
<artifactId>jetty-util</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-security</artifactId>
|
<artifactId>jetty-security</artifactId>
|
||||||
<version>${jetty9.version}</version>
|
<version>${jetty9.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -33,6 +33,8 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
public class EntityDescriptorDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||||
|
|
||||||
|
public static final String ID = "saml2-entity-descriptor";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported(String description) {
|
public boolean isSupported(String description) {
|
||||||
description = description.trim();
|
description = description.trim();
|
||||||
|
@ -161,7 +163,7 @@ public class EntityDescriptorDescriptionConverter implements ClientDescriptionCo
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
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
|
|
@ -8,6 +8,7 @@ import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.services.clientregistration.oidc.DescriptionConverter;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -17,6 +18,8 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
public class OIDCClientDescriptionConverter implements ClientDescriptionConverter, ClientDescriptionConverterFactory {
|
||||||
|
|
||||||
|
public static final String ID = "openid-connect";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSupported(String description) {
|
public boolean isSupported(String description) {
|
||||||
description = description.trim();
|
description = description.trim();
|
||||||
|
@ -26,15 +29,8 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
|
||||||
@Override
|
@Override
|
||||||
public ClientRepresentation convertToInternal(String description) {
|
public ClientRepresentation convertToInternal(String description) {
|
||||||
try {
|
try {
|
||||||
OIDCClientRepresentation oidcRep = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
|
OIDCClientRepresentation clientOIDC = JsonSerialization.readValue(description, OIDCClientRepresentation.class);
|
||||||
|
return DescriptionConverter.toInternal(clientOIDC);
|
||||||
ClientRepresentation client = new ClientRepresentation();
|
|
||||||
client.setClientId(KeycloakModelUtils.generateId());
|
|
||||||
client.setName(oidcRep.getClientName());
|
|
||||||
client.setRedirectUris(oidcRep.getRedirectUris());
|
|
||||||
client.setBaseUrl(oidcRep.getClientUri());
|
|
||||||
|
|
||||||
return client;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -59,7 +55,7 @@ public class OIDCClientDescriptionConverter implements ClientDescriptionConverte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return "openid-connect";
|
return ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ public class LogoutEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private ClientModel authorizeClient() {
|
private ClientModel authorizeClient() {
|
||||||
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event, realm).getClient();
|
ClientModel client = AuthorizeClientUtil.authorizeClient(session, event).getClient();
|
||||||
|
|
||||||
if (client.isBearerOnly()) {
|
if (client.isBearerOnly()) {
|
||||||
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException("invalid_client", "Bearer-only not allowed", Response.Status.BAD_REQUEST);
|
||||||
|
|
|
@ -145,7 +145,7 @@ public class TokenEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkClient() {
|
private void checkClient() {
|
||||||
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
|
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event);
|
||||||
client = clientAuth.getClient();
|
client = clientAuth.getClient();
|
||||||
clientAuthAttributes = clientAuth.getClientAuthAttributes();
|
clientAuthAttributes = clientAuth.getClientAuthAttributes();
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,48 @@ public class OIDCClientRepresentation {
|
||||||
@JsonProperty("redirect_uris")
|
@JsonProperty("redirect_uris")
|
||||||
private List<String> redirectUris;
|
private List<String> redirectUris;
|
||||||
|
|
||||||
|
@JsonProperty("token_endpoint_auth_method")
|
||||||
|
private String tokenEndpointAuthMethod;
|
||||||
|
|
||||||
|
@JsonProperty("grant_types")
|
||||||
|
private String grantTypes;
|
||||||
|
|
||||||
|
@JsonProperty("response_types")
|
||||||
|
private String responseTypes;
|
||||||
|
|
||||||
@JsonProperty("client_name")
|
@JsonProperty("client_name")
|
||||||
private String clientName;
|
private String clientName;
|
||||||
|
|
||||||
@JsonProperty("client_uri")
|
@JsonProperty("client_uri")
|
||||||
private String clientUri;
|
private String clientUri;
|
||||||
|
|
||||||
|
@JsonProperty("logo_uri")
|
||||||
|
private String logoUri;
|
||||||
|
|
||||||
|
@JsonProperty("scope")
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
@JsonProperty("contacts")
|
||||||
|
private String contacts;
|
||||||
|
|
||||||
|
@JsonProperty("tos_uri")
|
||||||
|
private String tos_uri;
|
||||||
|
|
||||||
|
@JsonProperty("policy_uri")
|
||||||
|
private String policy_uri;
|
||||||
|
|
||||||
|
@JsonProperty("jwks_uri")
|
||||||
|
private String jwks_uri;
|
||||||
|
|
||||||
|
@JsonProperty("jwks")
|
||||||
|
private String jwks;
|
||||||
|
|
||||||
|
@JsonProperty("software_id")
|
||||||
|
private String softwareId;
|
||||||
|
|
||||||
|
@JsonProperty("software_version")
|
||||||
|
private String softwareVersion;
|
||||||
|
|
||||||
public List<String> getRedirectUris() {
|
public List<String> getRedirectUris() {
|
||||||
return redirectUris;
|
return redirectUris;
|
||||||
}
|
}
|
||||||
|
@ -26,6 +62,30 @@ public class OIDCClientRepresentation {
|
||||||
this.redirectUris = redirectUris;
|
this.redirectUris = redirectUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTokenEndpointAuthMethod() {
|
||||||
|
return tokenEndpointAuthMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTokenEndpointAuthMethod(String tokenEndpointAuthMethod) {
|
||||||
|
this.tokenEndpointAuthMethod = tokenEndpointAuthMethod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGrantTypes() {
|
||||||
|
return grantTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setGrantTypes(String grantTypes) {
|
||||||
|
this.grantTypes = grantTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getResponseTypes() {
|
||||||
|
return responseTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResponseTypes(String responseTypes) {
|
||||||
|
this.responseTypes = responseTypes;
|
||||||
|
}
|
||||||
|
|
||||||
public String getClientName() {
|
public String getClientName() {
|
||||||
return clientName;
|
return clientName;
|
||||||
}
|
}
|
||||||
|
@ -42,4 +102,76 @@ public class OIDCClientRepresentation {
|
||||||
this.clientUri = clientUri;
|
this.clientUri = clientUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getLogoUri() {
|
||||||
|
return logoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLogoUri(String logoUri) {
|
||||||
|
this.logoUri = logoUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getScope() {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScope(String scope) {
|
||||||
|
this.scope = scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getContacts() {
|
||||||
|
return contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setContacts(String contacts) {
|
||||||
|
this.contacts = contacts;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTos_uri() {
|
||||||
|
return tos_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTos_uri(String tos_uri) {
|
||||||
|
this.tos_uri = tos_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPolicy_uri() {
|
||||||
|
return policy_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPolicy_uri(String policy_uri) {
|
||||||
|
this.policy_uri = policy_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJwks_uri() {
|
||||||
|
return jwks_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwks_uri(String jwks_uri) {
|
||||||
|
this.jwks_uri = jwks_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getJwks() {
|
||||||
|
return jwks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setJwks(String jwks) {
|
||||||
|
this.jwks = jwks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftwareId() {
|
||||||
|
return softwareId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoftwareId(String softwareId) {
|
||||||
|
this.softwareId = softwareId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSoftwareVersion() {
|
||||||
|
return softwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSoftwareVersion(String softwareVersion) {
|
||||||
|
this.softwareVersion = softwareVersion;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,18 +19,8 @@ import javax.ws.rs.core.Response;
|
||||||
*/
|
*/
|
||||||
public class AuthorizeClientUtil {
|
public class AuthorizeClientUtil {
|
||||||
|
|
||||||
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event, RealmModel realm) {
|
public static ClientAuthResult authorizeClient(KeycloakSession session, EventBuilder event) {
|
||||||
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
|
AuthenticationProcessor processor = getAuthenticationProcessor(session, event);
|
||||||
String flowId = clientAuthFlow.getId();
|
|
||||||
|
|
||||||
AuthenticationProcessor processor = new AuthenticationProcessor();
|
|
||||||
processor.setFlowId(flowId)
|
|
||||||
.setConnection(session.getContext().getConnection())
|
|
||||||
.setEventBuilder(event)
|
|
||||||
.setRealm(realm)
|
|
||||||
.setSession(session)
|
|
||||||
.setUriInfo(session.getContext().getUri())
|
|
||||||
.setRequest(session.getContext().getContextObject(HttpRequest.class));
|
|
||||||
|
|
||||||
Response response = processor.authenticateClient();
|
Response response = processor.authenticateClient();
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
|
@ -45,6 +35,24 @@ public class AuthorizeClientUtil {
|
||||||
return new ClientAuthResult(client, processor.getClientAuthAttributes());
|
return new ClientAuthResult(client, processor.getClientAuthAttributes());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static AuthenticationProcessor getAuthenticationProcessor(KeycloakSession session, EventBuilder event) {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
|
||||||
|
AuthenticationFlowModel clientAuthFlow = realm.getClientAuthenticationFlow();
|
||||||
|
String flowId = clientAuthFlow.getId();
|
||||||
|
|
||||||
|
AuthenticationProcessor processor = new AuthenticationProcessor();
|
||||||
|
processor.setFlowId(flowId)
|
||||||
|
.setConnection(session.getContext().getConnection())
|
||||||
|
.setEventBuilder(event)
|
||||||
|
.setRealm(realm)
|
||||||
|
.setSession(session)
|
||||||
|
.setUriInfo(session.getContext().getUri())
|
||||||
|
.setRequest(session.getContext().getContextObject(HttpRequest.class));
|
||||||
|
|
||||||
|
return processor;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ClientAuthResult {
|
public static class ClientAuthResult {
|
||||||
|
|
||||||
private final ClientModel client;
|
private final ClientModel client;
|
||||||
|
|
|
@ -10,6 +10,7 @@ import org.keycloak.services.util.LocaleHelper;
|
||||||
|
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.net.URI;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +30,13 @@ public class DefaultKeycloakContext implements KeycloakContext {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public URI getAuthServerUrl() {
|
||||||
|
UriInfo uri = getUri();
|
||||||
|
KeycloakApplication keycloakApplication = getContextObject(KeycloakApplication.class);
|
||||||
|
return keycloakApplication.getBaseUri(uri);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getContextPath() {
|
public String getContextPath() {
|
||||||
KeycloakApplication app = getContextObject(KeycloakApplication.class);
|
KeycloakApplication app = getContextObject(KeycloakApplication.class);
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.UnauthorizedException;
|
||||||
|
import org.keycloak.authentication.AuthenticationProcessor;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
|
import org.keycloak.services.ForbiddenException;
|
||||||
|
import org.keycloak.services.managers.ClientManager;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
|
||||||
|
import javax.ws.rs.*;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AdapterInstallationClientRegistrationProvider implements ClientRegistrationProvider {
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
private EventBuilder event;
|
||||||
|
private ClientRegAuth auth;
|
||||||
|
|
||||||
|
public AdapterInstallationClientRegistrationProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("{clientId}")
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public Response get(@PathParam("clientId") String clientId) {
|
||||||
|
event.event(EventType.CLIENT_INFO);
|
||||||
|
|
||||||
|
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||||
|
|
||||||
|
if (auth.isAuthenticated()) {
|
||||||
|
auth.requireView(client);
|
||||||
|
} else {
|
||||||
|
authenticateClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientManager clientManager = new ClientManager(new RealmManager(session));
|
||||||
|
Object rep = clientManager.toInstallationRepresentation(session.getContext().getRealm(), client, session.getContext().getAuthServerUrl());
|
||||||
|
|
||||||
|
event.client(client.getClientId()).success();
|
||||||
|
return Response.ok(rep).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuth(ClientRegAuth auth) {
|
||||||
|
this.auth = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEvent(EventBuilder event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void authenticateClient(ClientModel client) {
|
||||||
|
if (client.isPublicClient()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticationProcessor processor = AuthorizeClientUtil.getAuthenticationProcessor(session, event);
|
||||||
|
|
||||||
|
Response response = processor.authenticateClient();
|
||||||
|
if (response != null) {
|
||||||
|
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientModel authClient = processor.getClient();
|
||||||
|
if (client == null) {
|
||||||
|
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authClient.getClientId().equals(client.getClientId())) {
|
||||||
|
event.client(client.getClientId()).error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AdapterInstallationClientRegistrationProviderFactory implements ClientRegistrationProviderFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientRegistrationProvider create(KeycloakSession session) {
|
||||||
|
return new AdapterInstallationClientRegistrationProvider(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "install";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.UnauthorizedException;
|
||||||
|
import org.keycloak.events.Errors;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.models.*;
|
||||||
|
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
import org.keycloak.services.ForbiddenException;
|
||||||
|
import org.keycloak.services.managers.AppAuthManager;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class ClientRegAuth {
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
private EventBuilder event;
|
||||||
|
|
||||||
|
private String token;
|
||||||
|
private AccessToken.Access bearerRealmAccess;
|
||||||
|
|
||||||
|
private boolean authenticated = false;
|
||||||
|
private boolean registrationAccessToken = false;
|
||||||
|
|
||||||
|
public ClientRegAuth(KeycloakSession session, EventBuilder event) {
|
||||||
|
this.session = session;
|
||||||
|
this.event = event;
|
||||||
|
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init() {
|
||||||
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
|
||||||
|
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
||||||
|
if (authorizationHeader == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] split = authorizationHeader.split(" ");
|
||||||
|
if (!split[0].equalsIgnoreCase("bearer")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (split[1].indexOf('.') == -1) {
|
||||||
|
token = split[1];
|
||||||
|
authenticated = true;
|
||||||
|
registrationAccessToken = true;
|
||||||
|
} else {
|
||||||
|
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
|
||||||
|
bearerRealmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
|
||||||
|
authenticated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return authenticated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRegistrationAccessToken() {
|
||||||
|
return registrationAccessToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requireCreate() {
|
||||||
|
if (!authenticated) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bearerRealmAccess != null) {
|
||||||
|
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requireView(ClientModel client) {
|
||||||
|
if (!authenticated) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bearerRealmAccess != null) {
|
||||||
|
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (token != null) {
|
||||||
|
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void requireUpdate(ClientModel client) {
|
||||||
|
if (!authenticated) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (client == null) {
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bearerRealmAccess != null) {
|
||||||
|
if (bearerRealmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS) || bearerRealmAccess.isUserInRole(AdminRoles.VIEW_CLIENTS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (token != null) {
|
||||||
|
if (client.getRegistrationSecret() != null && client.getRegistrationSecret().equals(token)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event.error(Errors.NOT_ALLOWED);
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import org.keycloak.provider.Provider;
|
||||||
*/
|
*/
|
||||||
public interface ClientRegistrationProvider extends Provider {
|
public interface ClientRegistrationProvider extends Provider {
|
||||||
|
|
||||||
void setRealm(RealmModel realm);
|
void setAuth(ClientRegAuth auth);
|
||||||
|
|
||||||
void setEvent(EventBuilder event);
|
void setEvent(EventBuilder event);
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,50 @@
|
||||||
package org.keycloak.services.clientregistration;
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
|
||||||
|
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class ClientRegistrationService {
|
public class ClientRegistrationService {
|
||||||
|
|
||||||
private RealmModel realm;
|
|
||||||
|
|
||||||
private EventBuilder event;
|
private EventBuilder event;
|
||||||
|
|
||||||
@Context
|
@Context
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
|
|
||||||
public ClientRegistrationService(RealmModel realm, EventBuilder event) {
|
public ClientRegistrationService(EventBuilder event) {
|
||||||
this.realm = realm;
|
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Path("{provider}")
|
@Path("{provider}")
|
||||||
public Object getProvider(@PathParam("provider") String providerId) {
|
public Object getProvider(@PathParam("provider") String providerId) {
|
||||||
|
checkSsl();
|
||||||
|
|
||||||
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
|
ClientRegistrationProvider provider = session.getProvider(ClientRegistrationProvider.class, providerId);
|
||||||
provider.setRealm(realm);
|
|
||||||
|
if (provider == null) {
|
||||||
|
throw new NotFoundException("Client registration provider not found");
|
||||||
|
}
|
||||||
|
|
||||||
provider.setEvent(event);
|
provider.setEvent(event);
|
||||||
|
provider.setAuth(new ClientRegAuth(session, event));
|
||||||
return provider;
|
return provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkSsl() {
|
||||||
|
if (!session.getContext().getUri().getBaseUri().getScheme().equals("https")) {
|
||||||
|
if (session.getContext().getRealm().getSslRequired().isRequired(session.getContext().getConnection())) {
|
||||||
|
throw new ErrorResponseException("invalid_request", "HTTPS required", Response.Status.FORBIDDEN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,17 @@
|
||||||
package org.keycloak.services.clientregistration;
|
package org.keycloak.services.clientregistration;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.events.Errors;
|
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.models.*;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.RepresentationToModel;
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil;
|
|
||||||
import org.keycloak.representations.AccessToken;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.services.ErrorResponse;
|
import org.keycloak.services.ErrorResponse;
|
||||||
import org.keycloak.services.ForbiddenException;
|
|
||||||
import org.keycloak.services.managers.AppAuthManager;
|
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
import javax.ws.rs.core.MediaType;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
@ -26,31 +21,29 @@ import java.net.URI;
|
||||||
*/
|
*/
|
||||||
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
|
public class DefaultClientRegistrationProvider implements ClientRegistrationProvider {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DefaultClientRegistrationProvider.class);
|
|
||||||
|
|
||||||
private KeycloakSession session;
|
private KeycloakSession session;
|
||||||
private EventBuilder event;
|
private EventBuilder event;
|
||||||
private RealmModel realm;
|
private ClientRegAuth auth;
|
||||||
|
|
||||||
public DefaultClientRegistrationProvider(KeycloakSession session) {
|
public DefaultClientRegistrationProvider(KeycloakSession session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public Response create(ClientRepresentation client) {
|
public Response create(ClientRepresentation client) {
|
||||||
event.event(EventType.CLIENT_REGISTER);
|
event.event(EventType.CLIENT_REGISTER);
|
||||||
|
|
||||||
authenticate(true, null);
|
auth.requireCreate();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ClientModel clientModel = RepresentationToModel.createClient(session, realm, client, true);
|
ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
|
||||||
|
KeycloakModelUtils.generateRegistrationAccessToken(clientModel);
|
||||||
|
|
||||||
client = ModelToRepresentation.toRepresentation(clientModel);
|
client = ModelToRepresentation.toRepresentation(clientModel);
|
||||||
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
|
URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
|
||||||
|
|
||||||
logger.infov("Created client {0}", client.getClientId());
|
|
||||||
|
|
||||||
event.client(client.getClientId()).success();
|
event.client(client.getClientId()).success();
|
||||||
return Response.created(uri).entity(client).build();
|
return Response.created(uri).entity(client).build();
|
||||||
} catch (ModelDuplicateException e) {
|
} catch (ModelDuplicateException e) {
|
||||||
|
@ -64,10 +57,14 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
|
||||||
public Response get(@PathParam("clientId") String clientId) {
|
public Response get(@PathParam("clientId") String clientId) {
|
||||||
event.event(EventType.CLIENT_INFO);
|
event.event(EventType.CLIENT_INFO);
|
||||||
|
|
||||||
ClientModel client = authenticate(false, clientId);
|
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||||
if (client == null) {
|
auth.requireView(client);
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
|
||||||
|
if (auth.isRegistrationAccessToken()) {
|
||||||
|
KeycloakModelUtils.generateRegistrationAccessToken(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
event.client(client.getClientId()).success();
|
||||||
return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
|
return Response.ok(ModelToRepresentation.toRepresentation(client)).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,13 +74,19 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
|
||||||
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
|
public Response update(@PathParam("clientId") String clientId, ClientRepresentation rep) {
|
||||||
event.event(EventType.CLIENT_UPDATE).client(clientId);
|
event.event(EventType.CLIENT_UPDATE).client(clientId);
|
||||||
|
|
||||||
ClientModel client = authenticate(false, clientId);
|
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||||
|
auth.requireUpdate(client);
|
||||||
|
|
||||||
RepresentationToModel.updateClient(rep, client);
|
RepresentationToModel.updateClient(rep, client);
|
||||||
|
|
||||||
logger.infov("Updated client {0}", rep.getClientId());
|
if (auth.isRegistrationAccessToken()) {
|
||||||
|
KeycloakModelUtils.generateRegistrationAccessToken(client);
|
||||||
|
}
|
||||||
|
|
||||||
event.success();
|
rep = ModelToRepresentation.toRepresentation(client);
|
||||||
return Response.status(Response.Status.OK).build();
|
|
||||||
|
event.client(client.getClientId()).success();
|
||||||
|
return Response.ok(rep).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DELETE
|
@DELETE
|
||||||
|
@ -91,9 +94,11 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
|
||||||
public Response delete(@PathParam("clientId") String clientId) {
|
public Response delete(@PathParam("clientId") String clientId) {
|
||||||
event.event(EventType.CLIENT_DELETE).client(clientId);
|
event.event(EventType.CLIENT_DELETE).client(clientId);
|
||||||
|
|
||||||
ClientModel client = authenticate(false, clientId);
|
ClientModel client = session.getContext().getRealm().getClientByClientId(clientId);
|
||||||
if (realm.removeClient(client.getId())) {
|
auth.requireUpdate(client);
|
||||||
event.success();
|
|
||||||
|
if (session.getContext().getRealm().removeClient(client.getId())) {
|
||||||
|
event.client(client.getClientId()).success();
|
||||||
return Response.ok().build();
|
return Response.ok().build();
|
||||||
} else {
|
} else {
|
||||||
return Response.status(Response.Status.NOT_FOUND).build();
|
return Response.status(Response.Status.NOT_FOUND).build();
|
||||||
|
@ -101,50 +106,8 @@ public class DefaultClientRegistrationProvider implements ClientRegistrationProv
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void setAuth(ClientRegAuth auth) {
|
||||||
|
this.auth = auth;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ClientModel authenticate(boolean create, String clientId) {
|
|
||||||
String authorizationHeader = session.getContext().getRequestHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
|
|
||||||
|
|
||||||
boolean bearer = authorizationHeader != null && authorizationHeader.split(" ")[0].equalsIgnoreCase("Bearer");
|
|
||||||
|
|
||||||
if (bearer) {
|
|
||||||
AuthenticationManager.AuthResult authResult = new AppAuthManager().authenticateBearerToken(session, realm);
|
|
||||||
AccessToken.Access realmAccess = authResult.getToken().getResourceAccess(Constants.REALM_MANAGEMENT_CLIENT_ID);
|
|
||||||
if (realmAccess != null) {
|
|
||||||
if (realmAccess.isUserInRole(AdminRoles.MANAGE_CLIENTS)) {
|
|
||||||
return create ? null : realm.getClientByClientId(clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (create && realmAccess.isUserInRole(AdminRoles.CREATE_CLIENT)) {
|
|
||||||
return create ? null : realm.getClientByClientId(clientId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (!create) {
|
|
||||||
ClientModel client;
|
|
||||||
|
|
||||||
try {
|
|
||||||
AuthorizeClientUtil.ClientAuthResult clientAuth = AuthorizeClientUtil.authorizeClient(session, event, realm);
|
|
||||||
client = clientAuth.getClient();
|
|
||||||
|
|
||||||
if (client != null && !client.isPublicClient() && client.getClientId().equals(clientId)) {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
} catch (Throwable t) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
event.error(Errors.NOT_ALLOWED);
|
|
||||||
|
|
||||||
throw new ForbiddenException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRealm(RealmModel realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -152,4 +115,8 @@ this.realm = realm;
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
package org.keycloak.services.clientregistration;
|
|
||||||
|
|
||||||
import org.keycloak.events.EventBuilder;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.RealmModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
|
|
||||||
|
|
||||||
private KeycloakSession session;
|
|
||||||
private RealmModel realm;
|
|
||||||
private EventBuilder event;
|
|
||||||
|
|
||||||
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
|
||||||
this.session = session;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setRealm(RealmModel realm) {
|
|
||||||
this.realm = realm;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEvent(EventBuilder event) {
|
|
||||||
this.event = event;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.keycloak.services.clientregistration.oidc;
|
||||||
|
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class DescriptionConverter {
|
||||||
|
|
||||||
|
public static ClientRepresentation toInternal(OIDCClientRepresentation clientOIDC) {
|
||||||
|
ClientRepresentation client = new ClientRepresentation();
|
||||||
|
client.setClientId(KeycloakModelUtils.generateId());
|
||||||
|
client.setName(clientOIDC.getClientName());
|
||||||
|
client.setRedirectUris(clientOIDC.getRedirectUris());
|
||||||
|
client.setBaseUrl(clientOIDC.getClientUri());
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OIDCClientResponseRepresentation toExternalResponse(ClientRepresentation client) {
|
||||||
|
OIDCClientResponseRepresentation response = new OIDCClientResponseRepresentation();
|
||||||
|
response.setClientId(client.getClientId());
|
||||||
|
|
||||||
|
response.setClientName(client.getName());
|
||||||
|
response.setClientUri(client.getBaseUrl());
|
||||||
|
|
||||||
|
response.setClientSecret(client.getSecret());
|
||||||
|
response.setClientSecretExpiresAt(0);
|
||||||
|
|
||||||
|
response.setRedirectUris(client.getRedirectUris());
|
||||||
|
|
||||||
|
response.setRegistrationAccessToken(client.getRegistrationAccessToken());
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package org.keycloak.services.clientregistration.oidc;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.common.util.Time;
|
||||||
|
import org.keycloak.events.EventBuilder;
|
||||||
|
import org.keycloak.events.EventType;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.ModelDuplicateException;
|
||||||
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
|
import org.keycloak.models.utils.RepresentationToModel;
|
||||||
|
import org.keycloak.protocol.oidc.OIDCClientDescriptionConverter;
|
||||||
|
import org.keycloak.protocol.oidc.representations.OIDCClientRepresentation;
|
||||||
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
import org.keycloak.services.ErrorResponse;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegAuth;
|
||||||
|
import org.keycloak.services.clientregistration.ClientRegistrationProvider;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
|
import javax.ws.rs.Produces;
|
||||||
|
import javax.ws.rs.core.MediaType;
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class OIDCClientRegistrationProvider implements ClientRegistrationProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(OIDCClientRegistrationProvider.class);
|
||||||
|
|
||||||
|
private KeycloakSession session;
|
||||||
|
private EventBuilder event;
|
||||||
|
private ClientRegAuth auth;
|
||||||
|
|
||||||
|
public OIDCClientRegistrationProvider(KeycloakSession session) {
|
||||||
|
this.session = session;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @POST
|
||||||
|
// @Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
// @Produces(MediaType.APPLICATION_JSON)
|
||||||
|
// public Response create(OIDCClientRepresentation clientOIDC) {
|
||||||
|
// event.event(EventType.CLIENT_REGISTER);
|
||||||
|
//
|
||||||
|
// auth.requireCreate();
|
||||||
|
//
|
||||||
|
// ClientRepresentation client = DescriptionConverter.toInternal(clientOIDC);
|
||||||
|
//
|
||||||
|
// try {
|
||||||
|
// ClientModel clientModel = RepresentationToModel.createClient(session, session.getContext().getRealm(), client, true);
|
||||||
|
//
|
||||||
|
// client = ModelToRepresentation.toRepresentation(clientModel);
|
||||||
|
//
|
||||||
|
// String registrationAccessToken = TokenGenerator.createRegistrationAccessToken();
|
||||||
|
//
|
||||||
|
// clientModel.setRegistrationSecret(registrationAccessToken);
|
||||||
|
//
|
||||||
|
// URI uri = session.getContext().getUri().getAbsolutePathBuilder().path(clientModel.getId()).build();
|
||||||
|
//
|
||||||
|
// logger.infov("Created client {0}", client.getClientId());
|
||||||
|
//
|
||||||
|
// event.client(client.getClientId()).success();
|
||||||
|
//
|
||||||
|
// OIDCClientResponseRepresentation response = DescriptionConverter.toExternalResponse(client);
|
||||||
|
//
|
||||||
|
// response.setClientName(client.getName());
|
||||||
|
// response.setClientUri(client.getBaseUrl());
|
||||||
|
//
|
||||||
|
// response.setClientSecret(client.getSecret());
|
||||||
|
// response.setClientSecretExpiresAt(0);
|
||||||
|
//
|
||||||
|
// response.setRedirectUris(client.getRedirectUris());
|
||||||
|
//
|
||||||
|
// response.setRegistrationAccessToken(registrationAccessToken);
|
||||||
|
// response.setRegistrationClientUri(uri.toString());
|
||||||
|
//
|
||||||
|
// return Response.created(uri).entity(response).build();
|
||||||
|
// } catch (ModelDuplicateException e) {
|
||||||
|
// return ErrorResponse.exists("Client " + client.getClientId() + " already exists");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuth(ClientRegAuth auth) {
|
||||||
|
this.auth = auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setEvent(EventBuilder event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue