Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a739dd8eb3 | ||
|
|
07f27633ac | ||
|
|
e05afd2e48 | ||
|
|
58372b3151 | ||
|
|
6b70b5ddcd | ||
|
|
fd73d48e0e |
No files matched your search
+8
-10
@@ -1,21 +1,19 @@
|
|||||||
language: java
|
language: java
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- MAVEN_SKIP_RC=true
|
|
||||||
- MAVEN_OPTS="-Xms512m -Xmx2048m"
|
|
||||||
|
|
||||||
jdk:
|
jdk:
|
||||||
- oraclejdk8
|
- oraclejdk8
|
||||||
|
|
||||||
before_script:
|
cache:
|
||||||
- export MAVEN_SKIP_RC=true
|
directories:
|
||||||
|
- $HOME/.m2
|
||||||
|
|
||||||
install:
|
before_cache:
|
||||||
- mvn install -Pdistribution -DskipTests=true -B -V -q
|
- rm -rf $HOME/.m2/repository/org/keycloak
|
||||||
|
|
||||||
|
install: mvn install -Pdistribution -DskipTests=true -B -V
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- mvn test -B
|
- mvn test -B
|
||||||
- mvn -file testsuite/integration-arquillian test -B -Pno-console
|
- mvn -file testsuite/integration-arquillian test -B
|
||||||
|
|
||||||
sudo: false
|
sudo: false
|
||||||
@@ -7,7 +7,7 @@ Keycloak is an SSO Service for web apps and REST services. For more information
|
|||||||
Building
|
Building
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Ensure you have JDK 8 (or newer), Maven 3.2.1 (or newer) and Git installed
|
Ensure you have JDK 7 (or newer), Maven 3.2.1 (or newer) and Git installed
|
||||||
|
|
||||||
java -version
|
java -version
|
||||||
mvn -version
|
mvn -version
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -92,9 +92,4 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
|
|||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public IdentityProviderDataMarshaller getMarshaller() {
|
|
||||||
return new DefaultDataMarshaller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+1
-6
@@ -1,10 +1,10 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.IdentityProviderMapper;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
@@ -35,9 +35,4 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider
|
|||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -18,13 +18,9 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.models.IdentityProviderModel;
|
import org.keycloak.models.IdentityProviderModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -156,22 +152,6 @@ public class BrokeredIdentityContext {
|
|||||||
this.contextData = contextData;
|
this.contextData = contextData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the attribute, which will be available on "Update profile" page and in authenticators
|
|
||||||
public void setUserAttribute(String attributeName, String attributeValue) {
|
|
||||||
List<String> list = new ArrayList<>();
|
|
||||||
list.add(attributeValue);
|
|
||||||
getContextData().put(Constants.USER_ATTRIBUTES_PREFIX + attributeName, list);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUserAttribute(String attributeName) {
|
|
||||||
List<String> userAttribute = (List<String>) getContextData().get(Constants.USER_ATTRIBUTES_PREFIX + attributeName);
|
|
||||||
if (userAttribute == null || userAttribute.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return userAttribute.get(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFirstName() {
|
public String getFirstName() {
|
||||||
return firstName;
|
return firstName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
package org.keycloak.broker.provider;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.keycloak.common.util.Base64Url;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class DefaultDataMarshaller implements IdentityProviderDataMarshaller {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String serialize(Object value) {
|
|
||||||
if (value instanceof String) {
|
|
||||||
return (String) value;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
byte[] bytes = JsonSerialization.writeValueAsBytes(value);
|
|
||||||
return Base64Url.encode(bytes);
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
|
||||||
try {
|
|
||||||
if (clazz.equals(String.class)) {
|
|
||||||
return clazz.cast(serialized);
|
|
||||||
} else {
|
|
||||||
byte[] bytes = Base64Url.decode(serialized);
|
|
||||||
if (List.class.isAssignableFrom(clazz)) {
|
|
||||||
List list = JsonSerialization.readValue(bytes, List.class);
|
|
||||||
return clazz.cast(list);
|
|
||||||
} else {
|
|
||||||
return JsonSerialization.readValue(bytes, clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException ioe) {
|
|
||||||
throw new RuntimeException(ioe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -69,10 +69,10 @@ public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
context.setUserAttribute(attribute, attributeValue);
|
user.setSingleAttribute(attribute, attributeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package org.keycloak.broker.provider;
|
package org.keycloak.broker.provider;
|
||||||
|
|
||||||
|
import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
|
||||||
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.IdentityProviderMapperModel;
|
import org.keycloak.models.IdentityProviderMapperModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -17,7 +20,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
||||||
public static final String ROLE = "role";
|
public static final String ROLE = "role";
|
||||||
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
|
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ProviderConfigProperty property;
|
ProviderConfigProperty property;
|
||||||
@@ -29,6 +32,34 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
|||||||
configProperties.add(property);
|
configProperties.add(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static String[] parseRole(String role) {
|
||||||
|
int scopeIndex = role.lastIndexOf('.');
|
||||||
|
if (scopeIndex > -1) {
|
||||||
|
String appName = role.substring(0, scopeIndex);
|
||||||
|
role = role.substring(scopeIndex + 1);
|
||||||
|
String[] rtn = {appName, role};
|
||||||
|
return rtn;
|
||||||
|
} else {
|
||||||
|
String[] rtn = {null, role};
|
||||||
|
return rtn;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
|
||||||
|
String[] parsedRole = parseRole(roleName);
|
||||||
|
RoleModel role = null;
|
||||||
|
if (parsedRole[0] == null) {
|
||||||
|
role = realm.getRole(parsedRole[1]);
|
||||||
|
} else {
|
||||||
|
ClientModel client = realm.getClientByClientId(parsedRole[0]);
|
||||||
|
role = client.getRole(parsedRole[1]);
|
||||||
|
}
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ProviderConfigProperty> getConfigProperties() {
|
public List<ProviderConfigProperty> getConfigProperties() {
|
||||||
return configProperties;
|
return configProperties;
|
||||||
@@ -62,7 +93,7 @@ public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
|
|||||||
@Override
|
@Override
|
||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(ROLE);
|
String roleName = mapperModel.getConfig().get(ROLE);
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -67,7 +67,7 @@ public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProvide
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
|
||||||
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
|
||||||
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
context.getClientSession().setUserSessionNote(attribute, attributeValue);
|
||||||
|
|||||||
@@ -103,10 +103,4 @@ public interface IdentityProvider<C extends IdentityProviderModel> extends Provi
|
|||||||
*/
|
*/
|
||||||
Response export(UriInfo uriInfo, RealmModel realm, String format);
|
Response export(UriInfo uriInfo, RealmModel realm, String format);
|
||||||
|
|
||||||
/**
|
|
||||||
* Implementation of marshaller to serialize/deserialize attached data to Strings, which can be saved in clientSession
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
IdentityProviderDataMarshaller getMarshaller();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
-12
@@ -1,12 +0,0 @@
|
|||||||
package org.keycloak.broker.provider;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public interface IdentityProviderDataMarshaller {
|
|
||||||
|
|
||||||
String serialize(Object obj);
|
|
||||||
<T> T deserialize(String serialized, Class<T> clazz);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -20,10 +20,8 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
|||||||
String getDisplayType();
|
String getDisplayType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to determine what keycloak username and email to use to process the login request from the external IDP.
|
* Called to determine what keycloak username and email to use to process the login request from the external IDP
|
||||||
* It's called before "FirstBrokerLogin" flow, so can be used to map attributes to BrokeredIdentityContext ( BrokeredIdentityContext.setUserAttribute ),
|
* Usually used to map BrokeredIdentityContet.username or email.
|
||||||
* which will be available on "Review Profile" page and in authenticators during FirstBrokerLogin flow
|
|
||||||
*
|
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* @param realm
|
* @param realm
|
||||||
@@ -33,7 +31,7 @@ public interface IdentityProviderMapper extends Provider, ProviderFactory<Identi
|
|||||||
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after UserModel is created for first time for this user. Called after "FirstBrokerLogin" flow
|
* Called after UserModel is created for first time for this user.
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* @param realm
|
* @param realm
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
package org.keycloak.broker.provider.util;
|
package org.keycloak.broker.provider.util;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
@@ -27,9 +24,6 @@ public class SimpleHttp {
|
|||||||
private Map<String, String> headers;
|
private Map<String, String> headers;
|
||||||
private Map<String, String> params;
|
private Map<String, String> params;
|
||||||
|
|
||||||
private SSLSocketFactory sslFactory;
|
|
||||||
private HostnameVerifier hostnameVerifier;
|
|
||||||
|
|
||||||
protected SimpleHttp(String url, String method) {
|
protected SimpleHttp(String url, String method) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.method = method;
|
this.method = method;
|
||||||
@@ -59,15 +53,6 @@ public class SimpleHttp {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleHttp sslFactory(SSLSocketFactory factory) {
|
|
||||||
sslFactory = factory;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SimpleHttp hostnameVerifier(HostnameVerifier verifier) {
|
|
||||||
hostnameVerifier = verifier;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String asString() throws IOException {
|
public String asString() throws IOException {
|
||||||
boolean get = method.equals("GET");
|
boolean get = method.equals("GET");
|
||||||
@@ -100,7 +85,6 @@ public class SimpleHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
setupTruststoreIfApplicable(connection);
|
|
||||||
OutputStream os = null;
|
OutputStream os = null;
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
|
|
||||||
@@ -187,7 +171,6 @@ public class SimpleHttp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
setupTruststoreIfApplicable(connection);
|
|
||||||
OutputStream os = null;
|
OutputStream os = null;
|
||||||
InputStream is = null;
|
InputStream is = null;
|
||||||
|
|
||||||
@@ -252,13 +235,4 @@ public class SimpleHttp {
|
|||||||
return writer.toString();
|
return writer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupTruststoreIfApplicable(HttpURLConnection connection) {
|
|
||||||
if (connection instanceof HttpsURLConnection && sslFactory != null) {
|
|
||||||
HttpsURLConnection con = (HttpsURLConnection) connection;
|
|
||||||
con.setSSLSocketFactory(sslFactory);
|
|
||||||
if (hostnameVerifier != null) {
|
|
||||||
con.setHostnameVerifier(hostnameVerifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+2
-6
@@ -20,14 +20,13 @@ package org.keycloak.broker.oidc;
|
|||||||
import org.codehaus.jackson.JsonNode;
|
import org.codehaus.jackson.JsonNode;
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
import org.codehaus.jackson.map.ObjectMapper;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
import org.keycloak.broker.provider.AbstractIdentityProvider;
|
||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.connections.truststore.JSSETruststoreConfigurator;
|
|
||||||
import org.keycloak.events.Errors;
|
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;
|
||||||
@@ -248,15 +247,12 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
public SimpleHttp generateTokenRequest(String authorizationCode) {
|
||||||
JSSETruststoreConfigurator configurator = new JSSETruststoreConfigurator(session);
|
|
||||||
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
return SimpleHttp.doPost(getConfig().getTokenUrl())
|
||||||
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
.param(OAUTH2_PARAMETER_CODE, authorizationCode)
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
.param(OAUTH2_PARAMETER_CLIENT_ID, getConfig().getClientId())
|
||||||
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
.param(OAUTH2_PARAMETER_CLIENT_SECRET, getConfig().getClientSecret())
|
||||||
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
.param(OAUTH2_PARAMETER_REDIRECT_URI, uriInfo.getAbsolutePath().toString())
|
||||||
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE)
|
.param(OAUTH2_PARAMETER_GRANT_TYPE, OAUTH2_GRANT_TYPE_AUTHORIZATION_CODE);
|
||||||
.sslFactory(configurator.getSSLSocketFactory())
|
|
||||||
.hostnameVerifier(configurator.getHostnameVerifier());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+1
-8
@@ -5,7 +5,6 @@ import org.keycloak.broker.provider.BrokeredIdentityContext;
|
|||||||
import org.keycloak.constants.AdapterConstants;
|
import org.keycloak.constants.AdapterConstants;
|
||||||
import org.keycloak.events.EventBuilder;
|
import org.keycloak.events.EventBuilder;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
@@ -52,13 +51,7 @@ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
|
|||||||
@POST
|
@POST
|
||||||
@Path(AdapterConstants.K_LOGOUT)
|
@Path(AdapterConstants.K_LOGOUT)
|
||||||
public Response backchannelLogout(String input) {
|
public Response backchannelLogout(String input) {
|
||||||
JWSInput token = null;
|
JWSInput token = new JWSInput(input);
|
||||||
try {
|
|
||||||
token = new JWSInput(input);
|
|
||||||
} catch (JWSInputException e) {
|
|
||||||
logger.warn("Failed to verify logout request");
|
|
||||||
return Response.status(400).build();
|
|
||||||
}
|
|
||||||
PublicKey key = getExternalIdpKey();
|
PublicKey key = getExternalIdpKey();
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
if (!verify(token, key)) {
|
if (!verify(token, key)) {
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ 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.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.jose.jws.JWSInputException;
|
|
||||||
import org.keycloak.jose.jws.crypto.RSAProvider;
|
import org.keycloak.jose.jws.crypto.RSAProvider;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
@@ -43,7 +42,7 @@ import org.keycloak.services.messages.Messages;
|
|||||||
import org.keycloak.services.resources.IdentityBrokerService;
|
import org.keycloak.services.resources.IdentityBrokerService;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
import org.keycloak.common.util.PemUtils;
|
import org.keycloak.util.PemUtils;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
@@ -283,41 +282,40 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||||||
throw new IdentityBrokerException("No token from server.");
|
throw new IdentityBrokerException("No token from server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonWebToken token;
|
|
||||||
try {
|
try {
|
||||||
JWSInput jws = new JWSInput(encodedToken);
|
JWSInput jws = new JWSInput(encodedToken);
|
||||||
if (!verify(jws, key)) {
|
if (!verify(jws, key)) {
|
||||||
throw new IdentityBrokerException("token signature validation failed");
|
throw new IdentityBrokerException("token signature validation failed");
|
||||||
}
|
}
|
||||||
token = jws.readJsonContent(JsonWebToken.class);
|
JsonWebToken token = jws.readJsonContent(JsonWebToken.class);
|
||||||
} catch (JWSInputException e) {
|
|
||||||
throw new IdentityBrokerException("Invalid token", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
String iss = token.getIssuer();
|
String iss = token.getIssuer();
|
||||||
|
|
||||||
if (!token.hasAudience(getConfig().getClientId())) {
|
if (!token.hasAudience(getConfig().getClientId())) {
|
||||||
throw new IdentityBrokerException("Wrong audience from token.");
|
throw new IdentityBrokerException("Wrong audience from token.");
|
||||||
}
|
|
||||||
|
|
||||||
if (!token.isActive()) {
|
|
||||||
throw new IdentityBrokerException("Token is no longer valid");
|
|
||||||
}
|
|
||||||
|
|
||||||
String trustedIssuers = getConfig().getIssuer();
|
|
||||||
|
|
||||||
if (trustedIssuers != null) {
|
|
||||||
String[] issuers = trustedIssuers.split(",");
|
|
||||||
|
|
||||||
for (String trustedIssuer : issuers) {
|
|
||||||
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
if (!token.isActive()) {
|
||||||
|
throw new IdentityBrokerException("Token is no longer valid");
|
||||||
|
}
|
||||||
|
|
||||||
|
String trustedIssuers = getConfig().getIssuer();
|
||||||
|
|
||||||
|
if (trustedIssuers != null) {
|
||||||
|
String[] issuers = trustedIssuers.split(",");
|
||||||
|
|
||||||
|
for (String trustedIssuer : issuers) {
|
||||||
|
if (iss != null && iss.equals(trustedIssuer.trim())) {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IdentityBrokerException("Wrong issuer from token. Got: " + iss + " expected: " + getConfig().getIssuer());
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IdentityBrokerException("Could not decode token.", e);
|
||||||
}
|
}
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper
|
|||||||
} else if (value instanceof List) {
|
} else if (value instanceof List) {
|
||||||
List list = (List)value;
|
List list = (List)value;
|
||||||
for (Object val : list) {
|
for (Object val : list) {
|
||||||
if (valueEquals(desiredValue, val)) return true;
|
return valueEquals(desiredValue, val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
+8
-8
@@ -69,8 +69,8 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
|||||||
* @param user context to store profile data into
|
* @param user context to store profile data into
|
||||||
* @param profile to store into context
|
* @param profile to store into context
|
||||||
* @param provider identification of social provider to be used in log dump
|
* @param provider identification of social provider to be used in log dump
|
||||||
*
|
*
|
||||||
* @see #preprocessFederatedIdentity(KeycloakSession, RealmModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
* @see #importNewUser(KeycloakSession, RealmModel, UserModel, IdentityProviderMapperModel, BrokeredIdentityContext)
|
||||||
* @see BrokeredIdentityContext#getContextData()
|
* @see BrokeredIdentityContext#getContextData()
|
||||||
*/
|
*/
|
||||||
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
public static void storeUserProfileForMapper(BrokeredIdentityContext user, JsonNode profile, String provider) {
|
||||||
@@ -100,17 +100,17 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(CONF_USER_ATTRIBUTE);
|
||||||
if (attribute == null || attribute.trim().isEmpty()) {
|
if (attribute == null || attribute.trim().isEmpty()) {
|
||||||
logger.warnf("Attribute is not configured for mapper %s", mapperModel.getName());
|
logger.debug("Attribute is not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
attribute = attribute.trim();
|
attribute = attribute.trim();
|
||||||
|
|
||||||
String value = getJsonValue(mapperModel, context);
|
String value = getJsonValue(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
context.setUserAttribute(attribute, value);
|
user.setSingleAttribute(attribute, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,13 +123,13 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
|||||||
|
|
||||||
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
String jsonField = mapperModel.getConfig().get(CONF_JSON_FIELD);
|
||||||
if (jsonField == null || jsonField.trim().isEmpty()) {
|
if (jsonField == null || jsonField.trim().isEmpty()) {
|
||||||
logger.warnf("JSON field path is not configured for mapper %s", mapperModel.getName());
|
logger.debug("JSON field path is not configured");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
jsonField = jsonField.trim();
|
jsonField = jsonField.trim();
|
||||||
|
|
||||||
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
if (jsonField.startsWith(JSON_PATH_DELIMITER) || jsonField.endsWith(JSON_PATH_DELIMITER) || jsonField.startsWith("[")) {
|
||||||
logger.warnf("JSON field path is invalid %s", jsonField);
|
logger.debug("JSON field path is invalid " + jsonField);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +138,7 @@ public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityPr
|
|||||||
String value = getJsonValue(profileJsonNode, jsonField);
|
String value = getJsonValue(profileJsonNode, jsonField);
|
||||||
|
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
logger.debugf("User profile JSON value '%s' is not available.", jsonField);
|
logger.debug("User profile JSON value '" + jsonField + "' is not available.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import org.keycloak.models.KeycloakSession;
|
|||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -81,7 +80,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
|
|||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (hasClaimValue(mapperModel, context)) {
|
if (hasClaimValue(mapperModel, context)) {
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
@@ -91,7 +90,7 @@ public class ClaimToRoleMapper extends AbstractClaimMapper {
|
|||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (!hasClaimValue(mapperModel, context)) {
|
if (!hasClaimValue(mapperModel, context)) {
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.deleteRoleMapping(role);
|
user.deleteRoleMapping(role);
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -11,7 +11,6 @@ import org.keycloak.models.KeycloakSession;
|
|||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
@@ -86,7 +85,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
|||||||
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
|
||||||
//if (token == null) return;
|
//if (token == null) return;
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
String[] parseRole = KeycloakModelUtils.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
String[] parseRole = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
|
||||||
String externalRoleName = parseRole[1];
|
String externalRoleName = parseRole[1];
|
||||||
String claimName = null;
|
String claimName = null;
|
||||||
if (parseRole[0] == null) {
|
if (parseRole[0] == null) {
|
||||||
@@ -96,7 +95,7 @@ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
|
|||||||
}
|
}
|
||||||
Object claim = getClaimValue(token, claimName);
|
Object claim = getClaimValue(token, claimName);
|
||||||
if (valueEquals(externalRoleName, claim)) {
|
if (valueEquals(externalRoleName, claim)) {
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
return role;
|
return role;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,11 +70,11 @@ public class UserAttributeMapper extends AbstractClaimMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getClaimValue(mapperModel, context);
|
Object value = getClaimValue(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
context.setUserAttribute(attribute, value.toString());
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,10 @@ public class UsernameTemplateMapper extends AbstractClaimMapper {
|
|||||||
return "Username Template Importer";
|
return "Username Template Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-6
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -45,11 +45,6 @@
|
|||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package org.keycloak.broker.saml;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
import javax.xml.stream.XMLEventReader;
|
|
||||||
|
|
||||||
import org.keycloak.broker.provider.DefaultDataMarshaller;
|
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
|
||||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
|
||||||
import org.keycloak.saml.common.exceptions.ParsingException;
|
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
|
||||||
import org.keycloak.saml.common.util.StaxUtil;
|
|
||||||
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
|
||||||
import org.keycloak.saml.processing.core.parsers.util.SAMLParserUtil;
|
|
||||||
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLAssertionWriter;
|
|
||||||
import org.keycloak.saml.processing.core.saml.v2.writers.SAMLResponseWriter;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class SAMLDataMarshaller extends DefaultDataMarshaller {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String serialize(Object obj) {
|
|
||||||
|
|
||||||
// Lame impl, but hopefully sufficient for now. See if something better is needed...
|
|
||||||
if (obj.getClass().getName().startsWith("org.keycloak.dom.saml")) {
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (obj instanceof ResponseType) {
|
|
||||||
ResponseType responseType = (ResponseType) obj;
|
|
||||||
SAMLResponseWriter samlWriter = new SAMLResponseWriter(StaxUtil.getXMLStreamWriter(bos));
|
|
||||||
samlWriter.write(responseType);
|
|
||||||
} else if (obj instanceof AssertionType) {
|
|
||||||
AssertionType assertion = (AssertionType) obj;
|
|
||||||
SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
|
|
||||||
samlWriter.write(assertion);
|
|
||||||
} else if (obj instanceof AuthnStatementType) {
|
|
||||||
AuthnStatementType authnStatement = (AuthnStatementType) obj;
|
|
||||||
SAMLAssertionWriter samlWriter = new SAMLAssertionWriter(StaxUtil.getXMLStreamWriter(bos));
|
|
||||||
samlWriter.write(authnStatement, true);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Don't know how to serialize object of type " + obj.getClass().getName());
|
|
||||||
}
|
|
||||||
} catch (ProcessingException pe) {
|
|
||||||
throw new RuntimeException(pe);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new String(bos.toByteArray());
|
|
||||||
} else {
|
|
||||||
return super.serialize(obj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> T deserialize(String serialized, Class<T> clazz) {
|
|
||||||
if (clazz.getName().startsWith("org.keycloak.dom.saml")) {
|
|
||||||
String xmlString = serialized;
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (clazz.equals(ResponseType.class) || clazz.equals(AssertionType.class)) {
|
|
||||||
byte[] bytes = xmlString.getBytes();
|
|
||||||
InputStream is = new ByteArrayInputStream(bytes);
|
|
||||||
Object respType = new SAMLParser().parse(is);
|
|
||||||
return clazz.cast(respType);
|
|
||||||
} else if (clazz.equals(AuthnStatementType.class)) {
|
|
||||||
byte[] bytes = xmlString.getBytes();
|
|
||||||
InputStream is = new ByteArrayInputStream(bytes);
|
|
||||||
XMLEventReader xmlEventReader = new SAMLParser().createEventReader(is);
|
|
||||||
AuthnStatementType authnStatement = SAMLParserUtil.parseAuthnStatement(xmlEventReader);
|
|
||||||
return clazz.cast(authnStatement);
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Don't know how to deserialize object of type " + clazz.getName());
|
|
||||||
}
|
|
||||||
} catch (ParsingException pe) {
|
|
||||||
throw new RuntimeException(pe);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return super.deserialize(serialized, clazz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
package org.keycloak.broker.saml;
|
package org.keycloak.broker.saml;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
import org.keycloak.ClientConnection;
|
||||||
import org.keycloak.common.ClientConnection;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.common.VerificationException;
|
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.IdentityProvider;
|
import org.keycloak.broker.provider.IdentityProvider;
|
||||||
@@ -11,6 +10,7 @@ import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
|||||||
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
import org.keycloak.dom.saml.v2.assertion.AttributeType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
|
import org.keycloak.dom.saml.v2.assertion.EncryptedAssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
import org.keycloak.dom.saml.v2.assertion.SubjectType;
|
||||||
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
import org.keycloak.dom.saml.v2.protocol.LogoutRequestType;
|
||||||
@@ -24,29 +24,36 @@ import org.keycloak.events.EventType;
|
|||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
import org.keycloak.protocol.saml.SAML2LogoutResponseBuilder;
|
||||||
import org.keycloak.saml.SAML2LogoutResponseBuilder;
|
import org.keycloak.protocol.saml.SAMLRequestParser;
|
||||||
import org.keycloak.saml.SAMLRequestParser;
|
|
||||||
import org.keycloak.protocol.saml.SamlProtocol;
|
import org.keycloak.protocol.saml.SamlProtocol;
|
||||||
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
import org.keycloak.protocol.saml.SamlProtocolUtils;
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
|
import org.keycloak.saml.common.constants.JBossSAMLConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
import org.keycloak.saml.common.exceptions.ProcessingException;
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
import org.keycloak.saml.common.util.DocumentUtil;
|
||||||
|
import org.keycloak.saml.common.util.StaxParserUtil;
|
||||||
|
import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
|
||||||
|
import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
|
||||||
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;
|
import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
|
||||||
|
import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
|
||||||
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
|
||||||
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
import org.keycloak.saml.processing.web.util.PostBindingUtil;
|
||||||
import org.keycloak.services.ErrorPage;
|
import org.keycloak.services.ErrorPage;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.FormParam;
|
import javax.ws.rs.FormParam;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
@@ -54,7 +61,9 @@ import javax.ws.rs.core.MediaType;
|
|||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import javax.xml.namespace.QName;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -97,13 +106,6 @@ public class SAMLEndpoint {
|
|||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
|
||||||
@NoCache
|
|
||||||
@Path("descriptor")
|
|
||||||
public Response getSPDescriptor() {
|
|
||||||
return provider.export(uriInfo, realm, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
|
||||||
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
|
||||||
@@ -153,7 +155,7 @@ public class SAMLEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract String getBindingType();
|
protected abstract String getBindingType();
|
||||||
protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
|
protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
|
||||||
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
|
||||||
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
protected abstract SAMLDocumentHolder extractResponseDocument(String response);
|
||||||
protected PublicKey getIDPKey() {
|
protected PublicKey getIDPKey() {
|
||||||
@@ -186,7 +188,7 @@ public class SAMLEndpoint {
|
|||||||
}
|
}
|
||||||
if (config.isValidateSignature()) {
|
if (config.isValidateSignature()) {
|
||||||
try {
|
try {
|
||||||
verifySignature(GeneralConstants.SAML_REQUEST_KEY, holder);
|
verifySignature(holder);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.error("validation failed", e);
|
logger.error("validation failed", e);
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
@@ -245,18 +247,16 @@ public class SAMLEndpoint {
|
|||||||
builder.logoutRequestID(request.getID());
|
builder.logoutRequestID(request.getID());
|
||||||
builder.destination(config.getSingleLogoutServiceUrl());
|
builder.destination(config.getSingleLogoutServiceUrl());
|
||||||
builder.issuer(issuerURL);
|
builder.issuer(issuerURL);
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
builder.relayState(relayState);
|
||||||
.relayState(relayState);
|
|
||||||
if (config.isWantAuthnRequestsSigned()) {
|
if (config.isWantAuthnRequestsSigned()) {
|
||||||
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
.signatureAlgorithm(provider.getSignatureAlgorithm())
|
|
||||||
.signDocument();
|
.signDocument();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (config.isPostBindingResponse()) {
|
if (config.isPostBindingResponse()) {
|
||||||
return binding.postBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
|
return builder.postBinding().response();
|
||||||
} else {
|
} else {
|
||||||
return binding.redirectBinding(builder.buildDocument()).response(config.getSingleLogoutServiceUrl());
|
return builder.redirectBinding().response();
|
||||||
}
|
}
|
||||||
} catch (ConfigurationException e) {
|
} catch (ConfigurationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@@ -274,7 +274,7 @@ public class SAMLEndpoint {
|
|||||||
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder holder, ResponseType responseType, String relayState) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
AssertionType assertion = AssertionUtil.getAssertion(responseType, realm.getPrivateKey());
|
AssertionType assertion = getAssertion(responseType);
|
||||||
SubjectType subject = assertion.getSubject();
|
SubjectType subject = assertion.getSubject();
|
||||||
SubjectType.STSubType subType = subject.getSubType();
|
SubjectType.STSubType subType = subject.getSubType();
|
||||||
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
NameIDType subjectNameID = (NameIDType) subType.getBaseID();
|
||||||
@@ -334,6 +334,22 @@ public class SAMLEndpoint {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
|
||||||
|
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
|
||||||
|
|
||||||
|
if (assertions.isEmpty()) {
|
||||||
|
throw new IdentityBrokerException("No assertion from response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
|
||||||
|
EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
|
||||||
|
|
||||||
|
if (encryptedAssertion != null) {
|
||||||
|
decryptAssertion(responseType, realm.getPrivateKey());
|
||||||
|
|
||||||
|
}
|
||||||
|
return responseType.getAssertions().get(0).getAssertion();
|
||||||
|
}
|
||||||
|
|
||||||
public Response handleSamlResponse(String samlResponse, String relayState) {
|
public Response handleSamlResponse(String samlResponse, String relayState) {
|
||||||
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
|
||||||
@@ -347,7 +363,7 @@ public class SAMLEndpoint {
|
|||||||
}
|
}
|
||||||
if (config.isValidateSignature()) {
|
if (config.isValidateSignature()) {
|
||||||
try {
|
try {
|
||||||
verifySignature(GeneralConstants.SAML_RESPONSE_KEY, holder);
|
verifySignature(holder);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.error("validation failed", e);
|
logger.error("validation failed", e);
|
||||||
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
|
||||||
@@ -390,14 +406,43 @@ public class SAMLEndpoint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
|
||||||
|
SAML2Response saml2Response = new SAML2Response();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Document doc = saml2Response.convert(responseType);
|
||||||
|
Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
|
||||||
|
|
||||||
|
if (enc == null) {
|
||||||
|
throw new IdentityBrokerException("No encrypted assertion found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
|
||||||
|
Document newDoc = DocumentUtil.createDocument();
|
||||||
|
Node importedNode = newDoc.importNode(enc, true);
|
||||||
|
newDoc.appendChild(importedNode);
|
||||||
|
|
||||||
|
Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
|
||||||
|
SAMLParser parser = new SAMLParser();
|
||||||
|
|
||||||
|
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
|
||||||
|
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
|
||||||
|
.getNodeAsStream(decryptedDocumentElement)));
|
||||||
|
|
||||||
|
responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
|
||||||
|
|
||||||
|
return responseType;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IdentityBrokerException("Could not decrypt assertion.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class PostBinding extends Binding {
|
protected class PostBinding extends Binding {
|
||||||
@Override
|
@Override
|
||||||
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
|
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,9 +465,9 @@ public class SAMLEndpoint {
|
|||||||
|
|
||||||
protected class RedirectBinding extends Binding {
|
protected class RedirectBinding extends Binding {
|
||||||
@Override
|
@Override
|
||||||
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
|
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
|
||||||
PublicKey publicKey = getIDPKey();
|
PublicKey publicKey = getIDPKey();
|
||||||
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key);
|
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import org.keycloak.broker.provider.AbstractIdentityProvider;
|
|||||||
import org.keycloak.broker.provider.AuthenticationRequest;
|
import org.keycloak.broker.provider.AuthenticationRequest;
|
||||||
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
import org.keycloak.broker.provider.BrokeredIdentityContext;
|
||||||
import org.keycloak.broker.provider.IdentityBrokerException;
|
import org.keycloak.broker.provider.IdentityBrokerException;
|
||||||
import org.keycloak.broker.provider.IdentityProviderDataMarshaller;
|
|
||||||
import org.keycloak.broker.provider.util.SimpleHttp;
|
import org.keycloak.broker.provider.util.SimpleHttp;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
||||||
@@ -34,18 +33,20 @@ import org.keycloak.models.ClientSessionModel;
|
|||||||
import org.keycloak.models.FederatedIdentityModel;
|
import org.keycloak.models.FederatedIdentityModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder;
|
import org.keycloak.protocol.saml.SAML2AuthnRequestBuilder;
|
||||||
import org.keycloak.saml.SAML2AuthnRequestBuilder;
|
import org.keycloak.protocol.saml.SAML2LogoutRequestBuilder;
|
||||||
import org.keycloak.saml.SAML2LogoutRequestBuilder;
|
import org.keycloak.protocol.saml.SAML2NameIDPolicyBuilder;
|
||||||
import org.keycloak.saml.SAML2NameIDPolicyBuilder;
|
|
||||||
import org.keycloak.saml.SignatureAlgorithm;
|
|
||||||
import org.keycloak.saml.common.constants.GeneralConstants;
|
import org.keycloak.saml.common.constants.GeneralConstants;
|
||||||
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
import org.keycloak.saml.common.constants.JBossSAMLURIConstants;
|
||||||
|
import org.keycloak.saml.common.exceptions.ConfigurationException;
|
||||||
|
import org.keycloak.saml.common.exceptions.ParsingException;
|
||||||
|
import org.keycloak.saml.common.exceptions.ProcessingException;
|
||||||
|
|
||||||
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 javax.ws.rs.core.UriBuilder;
|
import javax.ws.rs.core.UriBuilder;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
|
import java.io.IOException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
@@ -91,8 +92,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
.issuer(issuerURL)
|
.issuer(issuerURL)
|
||||||
.forceAuthn(getConfig().isForceAuthn())
|
.forceAuthn(getConfig().isForceAuthn())
|
||||||
.protocolBinding(protocolBinding)
|
.protocolBinding(protocolBinding)
|
||||||
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat));
|
.nameIdPolicy(SAML2NameIDPolicyBuilder.format(nameIDPolicyFormat))
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
|
||||||
.relayState(request.getState());
|
.relayState(request.getState());
|
||||||
|
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
@@ -109,15 +109,14 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
|
|
||||||
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
KeyPair keypair = new KeyPair(publicKey, privateKey);
|
||||||
|
|
||||||
binding.signWith(keypair);
|
authnRequestBuilder.signWith(keypair);
|
||||||
binding.signatureAlgorithm(getSignatureAlgorithm());
|
authnRequestBuilder.signDocument();
|
||||||
binding.signDocument();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getConfig().isPostBindingAuthnRequest()) {
|
if (getConfig().isPostBindingAuthnRequest()) {
|
||||||
return binding.postBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
|
return authnRequestBuilder.postBinding().request();
|
||||||
} else {
|
} else {
|
||||||
return binding.redirectBinding(authnRequestBuilder.toDocument()).request(destinationUrl);
|
return authnRequestBuilder.redirectBinding().request();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IdentityBrokerException("Could not create authentication request.", e);
|
throw new IdentityBrokerException("Could not create authentication request.", e);
|
||||||
@@ -154,10 +153,9 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
String singleLogoutServiceUrl = getConfig().getSingleLogoutServiceUrl();
|
||||||
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
if (singleLogoutServiceUrl == null || singleLogoutServiceUrl.trim().equals("") || !getConfig().isBackchannelSupported()) return;
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
|
||||||
try {
|
try {
|
||||||
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
int status = SimpleHttp.doPost(singleLogoutServiceUrl)
|
||||||
.param(GeneralConstants.SAML_REQUEST_KEY, binding.postBinding(logoutBuilder.buildDocument()).encoded())
|
.param(GeneralConstants.SAML_REQUEST_KEY, logoutBuilder.postBinding().encoded())
|
||||||
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
|
.param(GeneralConstants.RELAY_STATE, userSession.getId()).asStatus();
|
||||||
boolean success = status >=200 && status < 400;
|
boolean success = status >=200 && status < 400;
|
||||||
if (!success) {
|
if (!success) {
|
||||||
@@ -180,8 +178,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
SAML2LogoutRequestBuilder logoutBuilder = buildLogoutRequest(userSession, uriInfo, realm, singleLogoutServiceUrl);
|
||||||
JaxrsSAML2BindingBuilder binding = buildLogoutBinding(userSession, realm);
|
return logoutBuilder.postBinding().request();
|
||||||
return binding.postBinding(logoutBuilder.buildDocument()).request(singleLogoutServiceUrl);
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
@@ -195,19 +192,13 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
.issuer(getEntityId(uriInfo, realm))
|
.issuer(getEntityId(uriInfo, realm))
|
||||||
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
.sessionIndex(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SESSION_INDEX))
|
||||||
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
.userPrincipal(userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT), userSession.getNote(SAMLEndpoint.SAML_FEDERATED_SUBJECT_NAMEFORMAT))
|
||||||
.destination(singleLogoutServiceUrl);
|
.destination(singleLogoutServiceUrl)
|
||||||
return logoutBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private JaxrsSAML2BindingBuilder buildLogoutBinding(UserSessionModel userSession, RealmModel realm) {
|
|
||||||
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder()
|
|
||||||
.relayState(userSession.getId());
|
.relayState(userSession.getId());
|
||||||
if (getConfig().isWantAuthnRequestsSigned()) {
|
if (getConfig().isWantAuthnRequestsSigned()) {
|
||||||
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
logoutBuilder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
|
||||||
.signatureAlgorithm(getSignatureAlgorithm())
|
|
||||||
.signDocument();
|
.signDocument();
|
||||||
}
|
}
|
||||||
return binding;
|
return logoutBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -229,7 +220,7 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
|
|
||||||
|
|
||||||
String descriptor =
|
String descriptor =
|
||||||
"<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
|
"<EntityDescriptor entityID=\"" + getEntityId(uriInfo, realm) + "\">\n" +
|
||||||
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
|
" <SPSSODescriptor AuthnRequestsSigned=\"" + getConfig().isWantAuthnRequestsSigned() + "\"\n" +
|
||||||
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol urn:oasis:names:tc:SAML:1.1:protocol http://schemas.xmlsoap.org/ws/2003/07/secext\">\n" +
|
||||||
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
" <NameIDFormat>" + getConfig().getNameIDPolicyFormat() + "\n" +
|
||||||
@@ -254,18 +245,4 @@ public class SAMLIdentityProvider extends AbstractIdentityProvider<SAMLIdentityP
|
|||||||
"</EntityDescriptor>\n";
|
"</EntityDescriptor>\n";
|
||||||
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
return Response.ok(descriptor, MediaType.APPLICATION_XML_TYPE).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public SignatureAlgorithm getSignatureAlgorithm() {
|
|
||||||
String alg = getConfig().getSignatureAlgorithm();
|
|
||||||
if (alg != null) {
|
|
||||||
SignatureAlgorithm algorithm = SignatureAlgorithm.valueOf(alg);
|
|
||||||
if (algorithm != null) return algorithm;
|
|
||||||
}
|
|
||||||
return SignatureAlgorithm.RSA_SHA256;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IdentityProviderDataMarshaller getMarshaller() {
|
|
||||||
return new SAMLDataMarshaller();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -87,14 +87,6 @@ public class SAMLIdentityProviderConfig extends IdentityProviderModel {
|
|||||||
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
getConfig().put("wantAuthnRequestsSigned", String.valueOf(wantAuthnRequestsSigned));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSignatureAlgorithm() {
|
|
||||||
return getConfig().get("signatureAlgorithm");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSignatureAlgorithm(String signatureAlgorithm) {
|
|
||||||
getConfig().put("signatureAlgorithm", signatureAlgorithm);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getEncryptionPublicKey() {
|
public String getEncryptionPublicKey() {
|
||||||
return getConfig().get("encryptionPublicKey");
|
return getConfig().get("encryptionPublicKey");
|
||||||
}
|
}
|
||||||
|
|||||||
+60
-64
@@ -73,78 +73,74 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory
|
|||||||
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
|
List<EntityDescriptorType.EDTChoiceType> choiceType = entityType.getChoiceType();
|
||||||
|
|
||||||
if (!choiceType.isEmpty()) {
|
if (!choiceType.isEmpty()) {
|
||||||
IDPSSODescriptorType idpDescriptor = null;
|
EntityDescriptorType.EDTChoiceType edtChoiceType = choiceType.get(0);
|
||||||
|
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
|
||||||
|
|
||||||
//Metadata documents can contain multiple Descriptors (See ADFS metadata documents) such as RoleDescriptor, SPSSODescriptor, IDPSSODescriptor.
|
if (!descriptors.isEmpty()) {
|
||||||
//So we need to loop through to find the IDPSSODescriptor.
|
EntityDescriptorType.EDTDescriptorChoiceType edtDescriptorChoiceType = descriptors.get(0);
|
||||||
for(EntityDescriptorType.EDTChoiceType edtChoiceType : entityType.getChoiceType()) {
|
IDPSSODescriptorType idpDescriptor = edtDescriptorChoiceType.getIdpDescriptor();
|
||||||
List<EntityDescriptorType.EDTDescriptorChoiceType> descriptors = edtChoiceType.getDescriptors();
|
|
||||||
|
|
||||||
if(!descriptors.isEmpty() && descriptors.get(0).getIdpDescriptor() != null) {
|
if (idpDescriptor != null) {
|
||||||
idpDescriptor = descriptors.get(0).getIdpDescriptor();
|
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
|
||||||
}
|
String singleSignOnServiceUrl = null;
|
||||||
}
|
boolean postBinding = false;
|
||||||
|
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
|
||||||
if (idpDescriptor != null) {
|
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||||
SAMLIdentityProviderConfig samlIdentityProviderConfig = new SAMLIdentityProviderConfig();
|
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||||
String singleSignOnServiceUrl = null;
|
postBinding = true;
|
||||||
boolean postBinding = false;
|
break;
|
||||||
for (EndpointType endpoint : idpDescriptor.getSingleSignOnService()) {
|
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||||
if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
||||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
|
||||||
postBinding = true;
|
|
||||||
break;
|
|
||||||
} else if (endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
|
||||||
singleSignOnServiceUrl = endpoint.getLocation().toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String singleLogoutServiceUrl = null;
|
|
||||||
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
|
||||||
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
|
||||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
|
||||||
break;
|
|
||||||
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
|
||||||
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
|
||||||
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
|
||||||
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
|
||||||
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
|
||||||
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
|
||||||
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
|
||||||
|
|
||||||
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
|
|
||||||
String defaultCertificate = null;
|
|
||||||
|
|
||||||
if (keyDescriptor != null) {
|
|
||||||
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
|
|
||||||
Element keyInfo = keyDescriptorType.getKeyInfo();
|
|
||||||
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
|
||||||
|
|
||||||
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
|
||||||
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
|
||||||
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
|
||||||
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
|
||||||
} else if (keyDescriptorType.getUse() == null) {
|
|
||||||
defaultCertificate = x509KeyInfo.getTextContent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
String singleLogoutServiceUrl = null;
|
||||||
|
for (EndpointType endpoint : idpDescriptor.getSingleLogoutService()) {
|
||||||
|
if (postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_POST_BINDING.get())) {
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
} else if (!postBinding && endpoint.getBinding().toString().equals(JBossSAMLURIConstants.SAML_HTTP_REDIRECT_BINDING.get())){
|
||||||
|
singleLogoutServiceUrl = endpoint.getLocation().toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (defaultCertificate != null) {
|
}
|
||||||
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
samlIdentityProviderConfig.setSingleLogoutServiceUrl(singleLogoutServiceUrl);
|
||||||
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
samlIdentityProviderConfig.setSingleSignOnServiceUrl(singleSignOnServiceUrl);
|
||||||
|
samlIdentityProviderConfig.setWantAuthnRequestsSigned(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
samlIdentityProviderConfig.setValidateSignature(idpDescriptor.isWantAuthnRequestsSigned());
|
||||||
|
samlIdentityProviderConfig.setPostBindingResponse(postBinding);
|
||||||
|
samlIdentityProviderConfig.setPostBindingAuthnRequest(postBinding);
|
||||||
|
|
||||||
|
List<KeyDescriptorType> keyDescriptor = idpDescriptor.getKeyDescriptor();
|
||||||
|
String defaultCertificate = null;
|
||||||
|
|
||||||
|
if (keyDescriptor != null) {
|
||||||
|
for (KeyDescriptorType keyDescriptorType : keyDescriptor) {
|
||||||
|
Element keyInfo = keyDescriptorType.getKeyInfo();
|
||||||
|
Element x509KeyInfo = DocumentUtil.getChildElement(keyInfo, new QName("dsig", "X509Certificate"));
|
||||||
|
|
||||||
|
if (KeyTypes.SIGNING.equals(keyDescriptorType.getUse())) {
|
||||||
|
samlIdentityProviderConfig.setSigningCertificate(x509KeyInfo.getTextContent());
|
||||||
|
} else if (KeyTypes.ENCRYPTION.equals(keyDescriptorType.getUse())) {
|
||||||
|
samlIdentityProviderConfig.setEncryptionPublicKey(x509KeyInfo.getTextContent());
|
||||||
|
} else if (keyDescriptorType.getUse() == null) {
|
||||||
|
defaultCertificate = x509KeyInfo.getTextContent();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
if (defaultCertificate != null) {
|
||||||
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
|
if (samlIdentityProviderConfig.getSigningCertificate() == null) {
|
||||||
}
|
samlIdentityProviderConfig.setSigningCertificate(defaultCertificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return samlIdentityProviderConfig.getConfig();
|
if (samlIdentityProviderConfig.getEncryptionPublicKey() == null) {
|
||||||
|
samlIdentityProviderConfig.setEncryptionPublicKey(defaultCertificate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return samlIdentityProviderConfig.getConfig();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ParsingException pe) {
|
} catch (ParsingException pe) {
|
||||||
|
|||||||
+2
-3
@@ -15,7 +15,6 @@ import org.keycloak.models.KeycloakSession;
|
|||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
|
||||||
import org.keycloak.provider.ProviderConfigProperty;
|
import org.keycloak.provider.ProviderConfigProperty;
|
||||||
import org.keycloak.representations.JsonWebToken;
|
import org.keycloak.representations.JsonWebToken;
|
||||||
|
|
||||||
@@ -96,7 +95,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
|||||||
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (isAttributePresent(mapperModel, context)) {
|
if (isAttributePresent(mapperModel, context)) {
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.grantRole(role);
|
user.grantRole(role);
|
||||||
}
|
}
|
||||||
@@ -126,7 +125,7 @@ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
|
|||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
|
||||||
if (!isAttributePresent(mapperModel, context)) {
|
if (!isAttributePresent(mapperModel, context)) {
|
||||||
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName);
|
RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
|
||||||
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
|
||||||
user.deleteRoleMapping(role);
|
user.deleteRoleMapping(role);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,11 +80,11 @@ public class UserAttributeMapper extends AbstractIdentityProviderMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
|
||||||
Object value = getAttribute(mapperModel, context);
|
Object value = getAttribute(mapperModel, context);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
context.setUserAttribute(attribute, value.toString());
|
user.setSingleAttribute(attribute, value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
|
|||||||
return "Username Template Importer";
|
return "Username Template Importer";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
package org.keycloak.broker.saml;
|
|
||||||
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.keycloak.dom.saml.v2.assertion.AssertionType;
|
|
||||||
import org.keycloak.dom.saml.v2.assertion.AuthnStatementType;
|
|
||||||
import org.keycloak.dom.saml.v2.assertion.NameIDType;
|
|
||||||
import org.keycloak.dom.saml.v2.protocol.ResponseType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class SAMLDataMarshallerTest {
|
|
||||||
|
|
||||||
private static final String TEST_RESPONSE = "<samlp:Response xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_4804cf50-cd96-4b92-823e-89adaa0c78ba\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.920Z\" Destination=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\" InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><samlp:Status><samlp:StatusCode Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"></samlp:StatusCode></samlp:Status><saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion></samlp:Response>";
|
|
||||||
|
|
||||||
private static final String TEST_ASSERTION = "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9\" Version=\"2.0\" IssueInstant=\"2015-11-06T11:00:33.911Z\"><saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">http://localhost:8082/auth/realms/realm-with-saml-idp-basic</saml:Issuer><saml:Subject><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified\">test-user</saml:NameID><saml:SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><saml:SubjectConfirmationData InResponseTo=\"ID_c6b90123-f0bb-4c5c-bf9d-388d5bbe467a\" NotOnOrAfter=\"2015-11-06T11:05:31.911Z\" Recipient=\"http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint\"></saml:SubjectConfirmationData></saml:SubjectConfirmation></saml:Subject><saml:Conditions NotBefore=\"2015-11-06T11:00:31.911Z\" NotOnOrAfter=\"2015-11-06T11:01:31.911Z\"><saml:AudienceRestriction><saml:Audience>http://localhost:8081/auth/realms/realm-with-broker</saml:Audience></saml:AudienceRestriction></saml:Conditions><saml:AuthnStatement AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement><saml:AttributeStatement><saml:Attribute Name=\"mobile\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">617-666-7777</saml:AttributeValue></saml:Attribute><saml:Attribute Name=\"urn:oid:1.2.840.113549.1.9.1\" FriendlyName=\"email\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">test-user@localhost</saml:AttributeValue></saml:Attribute></saml:AttributeStatement><saml:AttributeStatement><saml:Attribute Name=\"Role\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:basic\"><saml:AttributeValue xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xsi:type=\"xs:string\">manager</saml:AttributeValue></saml:Attribute></saml:AttributeStatement></saml:Assertion>";
|
|
||||||
|
|
||||||
private static final String TEST_AUTHN_TYPE = "<saml:AuthnStatement xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" AuthnInstant=\"2015-11-06T11:00:33.923Z\" SessionIndex=\"fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5\"><saml:AuthnContext><saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:unspecified</saml:AuthnContextClassRef></saml:AuthnContext></saml:AuthnStatement>";
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseResponse() throws Exception {
|
|
||||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
|
||||||
ResponseType responseType = serializer.deserialize(TEST_RESPONSE, ResponseType.class);
|
|
||||||
|
|
||||||
// test ResponseType
|
|
||||||
Assert.assertEquals(responseType.getID(), "ID_4804cf50-cd96-4b92-823e-89adaa0c78ba");
|
|
||||||
Assert.assertEquals(responseType.getDestination(), "http://localhost:8081/auth/realms/realm-with-broker/broker/kc-saml-idp-basic/endpoint");
|
|
||||||
Assert.assertEquals(responseType.getIssuer().getValue(), "http://localhost:8082/auth/realms/realm-with-saml-idp-basic");
|
|
||||||
Assert.assertEquals(responseType.getAssertions().get(0).getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
|
|
||||||
|
|
||||||
// back to String
|
|
||||||
String serialized = serializer.serialize(responseType);
|
|
||||||
Assert.assertEquals(TEST_RESPONSE, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseAssertion() throws Exception {
|
|
||||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
|
||||||
AssertionType assertion = serializer.deserialize(TEST_ASSERTION, AssertionType.class);
|
|
||||||
|
|
||||||
// test assertion
|
|
||||||
Assert.assertEquals(assertion.getID(), "ID_29b196c2-d641-45c8-a423-8ed8e54d4cf9");
|
|
||||||
Assert.assertEquals(((NameIDType) assertion.getSubject().getSubType().getBaseID()).getValue(), "test-user");
|
|
||||||
|
|
||||||
// back to String
|
|
||||||
String serialized = serializer.serialize(assertion);
|
|
||||||
Assert.assertEquals(TEST_ASSERTION, serialized);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseAuthnType() throws Exception {
|
|
||||||
SAMLDataMarshaller serializer = new SAMLDataMarshaller();
|
|
||||||
AuthnStatementType authnStatement = serializer.deserialize(TEST_AUTHN_TYPE, AuthnStatementType.class);
|
|
||||||
|
|
||||||
// test authnStatement
|
|
||||||
Assert.assertEquals(authnStatement.getSessionIndex(), "fa0f4fd3-8a11-44f4-9acb-ee30c5bb8fe5");
|
|
||||||
|
|
||||||
// back to String
|
|
||||||
String serialized = serializer.serialize(authnStatement);
|
|
||||||
Assert.assertEquals(TEST_AUTHN_TYPE, serialized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +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-client-registration-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.8.0.CR3</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-client-registration-api</artifactId>
|
|
||||||
<name>Keycloak Client Registration API</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
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.ClientInitialAccessPresentation;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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(ClientInitialAccessPresentation initialAccess) {
|
|
||||||
return new BearerTokenAuth(initialAccess.getToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Auth token(ClientRepresentation client) {
|
|
||||||
return new BearerTokenAuth(client.getRegistrationAccessToken());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Auth token(OIDCClientRepresentation 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-186
@@ -1,186 +0,0 @@
|
|||||||
package org.keycloak.client.registration;
|
|
||||||
|
|
||||||
import org.apache.http.client.HttpClient;
|
|
||||||
import org.apache.http.impl.client.HttpClients;
|
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
|
||||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
|
||||||
import org.keycloak.representations.adapters.config.AdapterConfig;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class ClientRegistration {
|
|
||||||
|
|
||||||
public static final ObjectMapper outputMapper = new ObjectMapper();
|
|
||||||
static {
|
|
||||||
outputMapper.getSerializationConfig().addMixInAnnotations(ClientRepresentation.class, ClientRepresentationMixIn.class);
|
|
||||||
outputMapper.getSerializationConfig().addMixInAnnotations(OIDCClientRepresentation.class, OIDCClientRepresentationMixIn.class);
|
|
||||||
outputMapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final String JSON = "application/json";
|
|
||||||
private final String XML = "application/xml";
|
|
||||||
|
|
||||||
private final String DEFAULT = "default";
|
|
||||||
private final String INSTALLATION = "install";
|
|
||||||
private final String OIDC = "openid-connect";
|
|
||||||
private final String SAML = "saml2-entity-descriptor";
|
|
||||||
|
|
||||||
private HttpUtil httpUtil;
|
|
||||||
|
|
||||||
public static ClientRegistrationBuilder create() {
|
|
||||||
return new ClientRegistrationBuilder();
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientRegistration(HttpUtil httpUtil) {
|
|
||||||
this.httpUtil = httpUtil;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void close() throws ClientRegistrationException {
|
|
||||||
if (httpUtil != null) {
|
|
||||||
httpUtil.close();
|
|
||||||
}
|
|
||||||
httpUtil = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistration auth(Auth auth) {
|
|
||||||
httpUtil.setAuth(auth);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRepresentation create(ClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
String content = serialize(client);
|
|
||||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, DEFAULT);
|
|
||||||
return deserialize(resultStream, ClientRepresentation.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRepresentation get(String clientId) throws ClientRegistrationException {
|
|
||||||
InputStream resultStream = httpUtil.doGet(JSON, DEFAULT, clientId);
|
|
||||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AdapterConfig getAdapterConfig(String clientId) throws ClientRegistrationException {
|
|
||||||
InputStream resultStream = httpUtil.doGet(JSON, INSTALLATION, clientId);
|
|
||||||
return resultStream != null ? deserialize(resultStream, AdapterConfig.class) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRepresentation update(ClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
String content = serialize(client);
|
|
||||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, DEFAULT, client.getClientId());
|
|
||||||
return resultStream != null ? deserialize(resultStream, ClientRepresentation.class) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(ClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
delete(client.getClientId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(String clientId) throws ClientRegistrationException {
|
|
||||||
httpUtil.doDelete(DEFAULT, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OIDCClientRegistration oidc() {
|
|
||||||
return new OIDCClientRegistration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public SAMLClientRegistration saml() {
|
|
||||||
return new SAMLClientRegistration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String serialize(Object obj) throws ClientRegistrationException {
|
|
||||||
try {
|
|
||||||
return outputMapper.writeValueAsString(obj);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ClientRegistrationException("Failed to write json object", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> T deserialize(InputStream inputStream, Class<T> clazz) throws ClientRegistrationException {
|
|
||||||
try {
|
|
||||||
return JsonSerialization.readValue(inputStream, clazz);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ClientRegistrationException("Failed to read json object", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OIDCClientRegistration {
|
|
||||||
|
|
||||||
public OIDCClientRepresentation create(OIDCClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
String content = serialize(client);
|
|
||||||
InputStream resultStream = httpUtil.doPost(content, JSON, JSON, OIDC);
|
|
||||||
return deserialize(resultStream, OIDCClientRepresentation.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public OIDCClientRepresentation get(String clientId) throws ClientRegistrationException {
|
|
||||||
InputStream resultStream = httpUtil.doGet(JSON, OIDC, clientId);
|
|
||||||
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OIDCClientRepresentation update(OIDCClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
String content = serialize(client);
|
|
||||||
InputStream resultStream = httpUtil.doPut(content, JSON, JSON, OIDC, client.getClientId());
|
|
||||||
return resultStream != null ? deserialize(resultStream, OIDCClientRepresentation.class) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(OIDCClientRepresentation client) throws ClientRegistrationException {
|
|
||||||
delete(client.getClientId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void delete(String clientId) throws ClientRegistrationException {
|
|
||||||
httpUtil.doDelete(OIDC, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SAMLClientRegistration {
|
|
||||||
|
|
||||||
public ClientRepresentation create(String entityDescriptor) throws ClientRegistrationException {
|
|
||||||
InputStream resultStream = httpUtil.doPost(entityDescriptor, XML, JSON, SAML);
|
|
||||||
return deserialize(resultStream, ClientRepresentation.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ClientRegistrationBuilder {
|
|
||||||
|
|
||||||
private String url;
|
|
||||||
private HttpClient httpClient;
|
|
||||||
|
|
||||||
ClientRegistrationBuilder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder url(String realmUrl) {
|
|
||||||
url = realmUrl;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder url(String authUrl, String realm) {
|
|
||||||
url = HttpUtil.getUrl(authUrl, "realms", realm, "clients");
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationBuilder httpClient(HttpClient httpClient) {
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistration build() {
|
|
||||||
if (url == null) {
|
|
||||||
throw new IllegalStateException("url not configured");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpClient == null) {
|
|
||||||
httpClient = HttpClients.createDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ClientRegistration(new HttpUtil(httpClient, url));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-16
@@ -1,16 +0,0 @@
|
|||||||
package org.keycloak.client.registration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class ClientRegistrationException extends Exception {
|
|
||||||
|
|
||||||
public ClientRegistrationException(String s, Throwable throwable) {
|
|
||||||
super(s, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientRegistrationException(String s) {
|
|
||||||
super(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-13
@@ -1,13 +0,0 @@
|
|||||||
package org.keycloak.client.registration;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
abstract class ClientRepresentationMixIn {
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
String registrationAccessToken;
|
|
||||||
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
package org.keycloak.client.registration;
|
|
||||||
|
|
||||||
import org.apache.http.StatusLine;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class HttpErrorException extends IOException {
|
|
||||||
|
|
||||||
private StatusLine statusLine;
|
|
||||||
|
|
||||||
public HttpErrorException(StatusLine statusLine) {
|
|
||||||
this.statusLine = statusLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StatusLine getStatusLine() {
|
|
||||||
return statusLine;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
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 contentType, String acceptType, String... path) throws ClientRegistrationException {
|
|
||||||
try {
|
|
||||||
HttpPost request = new HttpPost(getUrl(baseUri, path));
|
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
|
||||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
|
||||||
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 acceptType, String... path) throws ClientRegistrationException {
|
|
||||||
try {
|
|
||||||
HttpGet request = new HttpGet(getUrl(baseUri, path));
|
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
|
||||||
|
|
||||||
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 contentType, String acceptType, String... path) throws ClientRegistrationException {
|
|
||||||
try {
|
|
||||||
HttpPut request = new HttpPut(getUrl(baseUri, path));
|
|
||||||
|
|
||||||
request.setHeader(HttpHeaders.CONTENT_TYPE, contentType);
|
|
||||||
request.setHeader(HttpHeaders.ACCEPT, acceptType);
|
|
||||||
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() != 204) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-22
@@ -1,22 +0,0 @@
|
|||||||
package org.keycloak.client.registration;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.annotate.JsonIgnore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
abstract class OIDCClientRepresentationMixIn {
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private Integer client_id_issued_at;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private Integer client_secret_expires_at;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private String registration_client_uri;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private String registration_access_token;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,34 +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-client-registration-parent</artifactId>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<version>1.7.0.Final-SNAPSHOT</version>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-client-registration-cli</artifactId>
|
|
||||||
<name>Keycloak Client Registration CLI</name>
|
|
||||||
<description/>
|
|
||||||
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-core</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-client-registration-api</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.aesh</groupId>
|
|
||||||
<artifactId>aesh</artifactId>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
-72
@@ -1,72 +0,0 @@
|
|||||||
package org.keycloak.client.registration.cli;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.parser.CommandLineParserException;
|
|
||||||
import org.jboss.aesh.console.AeshConsole;
|
|
||||||
import org.jboss.aesh.console.AeshConsoleBuilder;
|
|
||||||
import org.jboss.aesh.console.Prompt;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandNotFoundException;
|
|
||||||
import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
|
|
||||||
import org.jboss.aesh.console.settings.Settings;
|
|
||||||
import org.jboss.aesh.console.settings.SettingsBuilder;
|
|
||||||
import org.jboss.aesh.terminal.Color;
|
|
||||||
import org.jboss.aesh.terminal.TerminalColor;
|
|
||||||
import org.jboss.aesh.terminal.TerminalString;
|
|
||||||
import org.keycloak.client.registration.Auth;
|
|
||||||
import org.keycloak.client.registration.ClientRegistration;
|
|
||||||
import org.keycloak.client.registration.cli.commands.CreateCommand;
|
|
||||||
import org.keycloak.client.registration.cli.commands.ExitCommand;
|
|
||||||
import org.keycloak.client.registration.cli.commands.SetupCommand;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class ClientRegistrationCLI {
|
|
||||||
|
|
||||||
private static ClientRegistration reg;
|
|
||||||
|
|
||||||
public static void main(String[] args) throws CommandLineParserException, CommandNotFoundException {
|
|
||||||
reg = ClientRegistration.create().url("http://localhost:8080/auth/realms/master").build();
|
|
||||||
reg.auth(Auth.token("..."));
|
|
||||||
|
|
||||||
Context context = new Context();
|
|
||||||
|
|
||||||
List<Command> commands = new LinkedList<>();
|
|
||||||
commands.add(new SetupCommand(context));
|
|
||||||
commands.add(new CreateCommand(context));
|
|
||||||
commands.add(new ExitCommand(context));
|
|
||||||
|
|
||||||
SettingsBuilder builder = new SettingsBuilder().logging(true);
|
|
||||||
builder.enableMan(true).readInputrc(false);
|
|
||||||
|
|
||||||
Settings settings = builder.create();
|
|
||||||
|
|
||||||
AeshCommandRegistryBuilder commandRegistryBuilder = new AeshCommandRegistryBuilder();
|
|
||||||
for (Command c : commands) {
|
|
||||||
commandRegistryBuilder.command(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
AeshConsole aeshConsole = new AeshConsoleBuilder()
|
|
||||||
.commandRegistry(commandRegistryBuilder.create())
|
|
||||||
.settings(settings)
|
|
||||||
.prompt(new Prompt(new TerminalString("[clientreg]$ ",
|
|
||||||
new TerminalColor(Color.GREEN, Color.DEFAULT, Color.Intensity.BRIGHT))))
|
|
||||||
.create();
|
|
||||||
|
|
||||||
aeshConsole.start();
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
if (args.length > 0) {
|
|
||||||
CommandContainer command = registry.getCommand(args[0], null);
|
|
||||||
ParserGenerator.parseAndPopulate(command, args[0], Arrays.copyOfRange(args, 1, args.length));
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//commandInvocation.getCommandRegistry().getAllCommandNames()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
-37
@@ -1,37 +0,0 @@
|
|||||||
package org.keycloak.client.registration.cli;
|
|
||||||
|
|
||||||
import org.codehaus.jackson.map.ObjectMapper;
|
|
||||||
import org.codehaus.jackson.map.SerializationConfig;
|
|
||||||
import org.codehaus.jackson.map.annotate.JsonSerialize;
|
|
||||||
import org.keycloak.client.registration.ClientRegistration;
|
|
||||||
import org.keycloak.util.SystemPropertiesJsonParserFactory;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
public class Context {
|
|
||||||
|
|
||||||
private static final ObjectMapper mapper = new ObjectMapper(new SystemPropertiesJsonParserFactory());
|
|
||||||
static {
|
|
||||||
mapper.setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
|
|
||||||
mapper.enable(SerializationConfig.Feature.INDENT_OUTPUT);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClientRegistration reg;
|
|
||||||
|
|
||||||
public ClientRegistration getReg() {
|
|
||||||
return reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReg(ClientRegistration reg) {
|
|
||||||
this.reg = reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T readJson(InputStream bytes, Class<T> type) throws IOException {
|
|
||||||
return mapper.readValue(bytes, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-64
@@ -1,64 +0,0 @@
|
|||||||
package org.keycloak.client.registration.cli.commands;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.jboss.aesh.io.Resource;
|
|
||||||
import org.keycloak.client.registration.ClientRegistrationException;
|
|
||||||
import org.keycloak.client.registration.cli.Context;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@CommandDefinition(name="create", description = "[OPTIONS] FILE")
|
|
||||||
public class CreateCommand implements Command {
|
|
||||||
|
|
||||||
@Option(shortName = 'h', hasValue = false, description = "display this help and exit")
|
|
||||||
private boolean help;
|
|
||||||
|
|
||||||
@Arguments(description = "files or directories thats listed")
|
|
||||||
private List<Resource> arguments;
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public CreateCommand(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
|
||||||
System.out.println(help);
|
|
||||||
|
|
||||||
|
|
||||||
if(help) {
|
|
||||||
commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
|
|
||||||
if(arguments != null) {
|
|
||||||
for(Resource f : arguments) {
|
|
||||||
System.out.println(f.getAbsolutePath());
|
|
||||||
ClientRepresentation rep = JsonSerialization.readValue(f.read(), ClientRepresentation.class);
|
|
||||||
try {
|
|
||||||
context.getReg().create(rep);
|
|
||||||
} catch (ClientRegistrationException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// reg.create();
|
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-29
@@ -1,29 +0,0 @@
|
|||||||
package org.keycloak.client.registration.cli.commands;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.Context;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@CommandDefinition(name="exit", description = "Exit the program")
|
|
||||||
public class ExitCommand implements Command {
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public ExitCommand(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
|
||||||
commandInvocation.stop();
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
-48
@@ -1,48 +0,0 @@
|
|||||||
package org.keycloak.client.registration.cli.commands;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.jboss.aesh.io.Resource;
|
|
||||||
import org.keycloak.client.registration.ClientRegistrationException;
|
|
||||||
import org.keycloak.client.registration.cli.Context;
|
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
|
||||||
import org.keycloak.util.JsonSerialization;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
|
||||||
*/
|
|
||||||
@CommandDefinition(name="setup", description = "")
|
|
||||||
public class SetupCommand implements Command {
|
|
||||||
|
|
||||||
@Option(shortName = 'h', hasValue = false, description = "display this help and exit")
|
|
||||||
private boolean help;
|
|
||||||
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
public SetupCommand(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws IOException, InterruptedException {
|
|
||||||
System.out.println(help);
|
|
||||||
|
|
||||||
if(help) {
|
|
||||||
commandInvocation.getShell().out().println(commandInvocation.getHelpInfo("create"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private String promptForUsername(CommandInvocation invocation) throws InterruptedException {
|
|
||||||
invocation.print("username: ");
|
|
||||||
return invocation.getInputLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,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.8.0.CR3</version>
|
|
||||||
</parent>
|
|
||||||
|
|
||||||
<name>Keycloak Client Registration Parent</name>
|
|
||||||
<description/>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<artifactId>keycloak-client-registration-parent</artifactId>
|
|
||||||
<packaging>pom</packaging>
|
|
||||||
|
|
||||||
<modules>
|
|
||||||
<module>api</module>
|
|
||||||
<!--<module>cli</module>-->
|
|
||||||
</modules>
|
|
||||||
</project>
|
|
||||||
@@ -1,94 +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.8.0.CR3</version>
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
|
||||||
</parent>
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
|
|
||||||
<artifactId>keycloak-common</artifactId>
|
|
||||||
<name>Keycloak Common</name>
|
|
||||||
<packaging>jar</packaging>
|
|
||||||
<description>Common library and dependencies shared with server and all adapters</description>
|
|
||||||
|
|
||||||
<properties>
|
|
||||||
<timestamp>${maven.build.timestamp}</timestamp>
|
|
||||||
<maven.build.timestamp.format>yyyy-MM-dd HH:mm</maven.build.timestamp.format>
|
|
||||||
<keycloak.osgi.export>
|
|
||||||
org.keycloak.common.*
|
|
||||||
</keycloak.osgi.export>
|
|
||||||
<keycloak.osgi.import>
|
|
||||||
*;resolution:=optional
|
|
||||||
</keycloak.osgi.import>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.bouncycastle</groupId>
|
|
||||||
<artifactId>bcpkix-jdk15on</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>junit</groupId>
|
|
||||||
<artifactId>junit</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>true</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Adding OSGI metadata to the JAR without changing the packaging type. -->
|
|
||||||
<plugin>
|
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>
|
|
||||||
</archive>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.felix</groupId>
|
|
||||||
<artifactId>maven-bundle-plugin</artifactId>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>bundle-manifest</id>
|
|
||||||
<phase>process-classes</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>manifest</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
<configuration>
|
|
||||||
<instructions>
|
|
||||||
<Bundle-ClassPath>.</Bundle-ClassPath>
|
|
||||||
<Bundle-Name>${project.name}</Bundle-Name>
|
|
||||||
<Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
|
|
||||||
<Import-Package>${keycloak.osgi.import}</Import-Package>
|
|
||||||
<Export-Package>${keycloak.osgi.export}</Export-Package>
|
|
||||||
</instructions>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
</project>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package org.keycloak.common;
|
|
||||||
|
|
||||||
import org.keycloak.common.util.Time;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.Properties;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
|
||||||
* @version $Revision: 1 $
|
|
||||||
*/
|
|
||||||
public class Version {
|
|
||||||
public static final String UNKNOWN = "UNKNOWN";
|
|
||||||
public static String NAME;
|
|
||||||
public static String NAME_HTML;
|
|
||||||
public static String VERSION;
|
|
||||||
public static String RESOURCES_VERSION;
|
|
||||||
public static String BUILD_TIME;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Properties props = new Properties();
|
|
||||||
InputStream is = Version.class.getResourceAsStream("/keycloak-version.properties");
|
|
||||||
try {
|
|
||||||
props.load(is);
|
|
||||||
Version.NAME = props.getProperty("name");
|
|
||||||
Version.NAME_HTML = props.getProperty("name-html");
|
|
||||||
Version.VERSION = props.getProperty("version");
|
|
||||||
Version.BUILD_TIME = props.getProperty("build-time");
|
|
||||||
Version.RESOURCES_VERSION = Version.VERSION.toLowerCase();
|
|
||||||
if (Version.RESOURCES_VERSION.endsWith("-snapshot")) {
|
|
||||||
Version.RESOURCES_VERSION = Version.RESOURCES_VERSION.replace("-snapshot", "-" + Time.currentTime());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
Version.VERSION= Version.UNKNOWN;
|
|
||||||
Version.BUILD_TIME= Version.UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,4 +0,0 @@
|
|||||||
name=${product.name}
|
|
||||||
name-html=${product.name-html}
|
|
||||||
version=${product.version}
|
|
||||||
build-time=${product.build-time}
|
|
||||||
@@ -4,19 +4,23 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>keycloak-connections-truststore</artifactId>
|
<artifactId>keycloak-connections-file</artifactId>
|
||||||
<name>Keycloak Truststore</name>
|
<name>Keycloak Connections File</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>keycloak-export-import-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-export-import-single-file</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
@@ -26,10 +30,14 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.codehaus.jackson</groupId>
|
||||||
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
<scope>provided</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
/*
|
||||||
|
* 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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+10
-13
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
* as indicated by the @author tags. All rights reserved.
|
* as indicated by the @author tags. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -14,21 +14,18 @@
|
|||||||
* License for the specific language governing permissions and limitations under
|
* License for the specific language governing permissions and limitations under
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.subsystem.saml.as7.logging;
|
package org.keycloak.connections.file;
|
||||||
|
|
||||||
import org.jboss.logging.MessageBundle;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.jboss.logging.Messages;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.provider.Provider;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface to be fleshed out later when error messages are fully externalized.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
*
|
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
|
|
||||||
*/
|
*/
|
||||||
@MessageBundle(projectCode = "TLIP")
|
public interface FileConnectionProvider extends Provider, KeycloakTransaction {
|
||||||
public interface KeycloakMessages {
|
|
||||||
|
|
||||||
/**
|
InMemoryModel getModel();
|
||||||
* The messages
|
|
||||||
*/
|
void sessionClosed(KeycloakSession session);
|
||||||
KeycloakMessages MESSAGES = Messages.getBundle(KeycloakMessages.class);
|
|
||||||
}
|
}
|
||||||
+5
-7
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2016 Red Hat Inc. and/or its affiliates and other contributors
|
* Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
|
||||||
* as indicated by the @author tags. All rights reserved.
|
* as indicated by the @author tags. All rights reserved.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||||
@@ -14,14 +14,12 @@
|
|||||||
* License for the specific language governing permissions and limitations under
|
* License for the specific language governing permissions and limitations under
|
||||||
* the License.
|
* the License.
|
||||||
*/
|
*/
|
||||||
|
package org.keycloak.connections.file;
|
||||||
|
|
||||||
package org.keycloak.partialimport;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enum for actions taken by PartialImport.
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
*
|
|
||||||
* @author Stan Silvert ssilvert@redhat.com (C) 2016 Red Hat Inc.
|
|
||||||
*/
|
*/
|
||||||
public enum Action {
|
public interface FileConnectionProviderFactory extends ProviderFactory<FileConnectionProvider> {
|
||||||
ADDED, SKIPPED, OVERWRITTEN
|
|
||||||
}
|
}
|
||||||
+7
-6
@@ -1,13 +1,13 @@
|
|||||||
package org.keycloak.connections.truststore;
|
package org.keycloak.connections.file;
|
||||||
|
|
||||||
import org.keycloak.provider.Provider;
|
import org.keycloak.provider.Provider;
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ProviderFactory;
|
||||||
import org.keycloak.provider.Spi;
|
import org.keycloak.provider.Spi;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author Stan Silvert ssilvert@redhat.com (C) 2015 Red Hat Inc.
|
||||||
*/
|
*/
|
||||||
public class TruststoreSpi implements Spi {
|
public class FileConnectionSpi implements Spi {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isInternal() {
|
public boolean isInternal() {
|
||||||
@@ -16,16 +16,17 @@ public class TruststoreSpi implements Spi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "truststore";
|
return "connectionsFile";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends Provider> getProviderClass() {
|
public Class<? extends Provider> getProviderClass() {
|
||||||
return TruststoreProvider.class;
|
return FileConnectionProvider.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
public Class<? extends ProviderFactory> getProviderFactoryClass() {
|
||||||
return TruststoreProviderFactory.class;
|
return FileConnectionProviderFactory.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
+109
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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 +1 @@
|
|||||||
|
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
org.keycloak.connections.file.FileConnectionSpi
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -22,10 +22,6 @@
|
|||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-api</artifactId>
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.keycloak</groupId>
|
|
||||||
<artifactId>keycloak-connections-truststore</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
<artifactId>httpclient</artifactId>
|
<artifactId>httpclient</artifactId>
|
||||||
|
|||||||
+40
-62
@@ -10,11 +10,10 @@ import org.apache.http.entity.ContentType;
|
|||||||
import org.apache.http.impl.client.CloseableHttpClient;
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
import org.keycloak.connections.truststore.TruststoreProvider;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.common.util.EnvUtil;
|
import org.keycloak.util.EnvUtil;
|
||||||
import org.keycloak.common.util.KeystoreUtil;
|
import org.keycloak.util.KeystoreUtil;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -29,12 +28,9 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||||||
private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
|
private static final Logger logger = Logger.getLogger(DefaultHttpClientFactory.class);
|
||||||
|
|
||||||
private volatile CloseableHttpClient httpClient;
|
private volatile CloseableHttpClient httpClient;
|
||||||
private Config.Scope config;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public HttpClientProvider create(KeycloakSession session) {
|
public HttpClientProvider create(KeycloakSession session) {
|
||||||
lazyInit(session);
|
|
||||||
|
|
||||||
return new HttpClientProvider() {
|
return new HttpClientProvider() {
|
||||||
@Override
|
@Override
|
||||||
public HttpClient getHttpClient() {
|
public HttpClient getHttpClient() {
|
||||||
@@ -78,9 +74,7 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
try {
|
try {
|
||||||
if (httpClient != null) {
|
httpClient.close();
|
||||||
httpClient.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -93,62 +87,46 @@ public class DefaultHttpClientFactory implements HttpClientFactory {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(Config.Scope config) {
|
public void init(Config.Scope config) {
|
||||||
this.config = config;
|
long socketTimeout = config.getLong("socket-timeout-millis", -1L);
|
||||||
}
|
long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
|
||||||
|
int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
|
||||||
|
int connectionPoolSize = config.getInt("connection-pool-size", 200);
|
||||||
|
boolean disableTrustManager = config.getBoolean("disable-trust-manager", false);
|
||||||
|
boolean disableCookies = config.getBoolean("disable-cookies", true);
|
||||||
|
String hostnameVerificationPolicy = config.get("hostname-verification-policy", "WILDCARD");
|
||||||
|
HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = HttpClientBuilder.HostnameVerificationPolicy.valueOf(hostnameVerificationPolicy);
|
||||||
|
String truststore = config.get("truststore");
|
||||||
|
String truststorePassword = config.get("truststore-password");
|
||||||
|
String clientKeystore = config.get("client-keystore");
|
||||||
|
String clientKeystorePassword = config.get("client-keystore-password");
|
||||||
|
String clientPrivateKeyPassword = config.get("client-key-password");
|
||||||
|
|
||||||
private void lazyInit(KeycloakSession session) {
|
HttpClientBuilder builder = new HttpClientBuilder();
|
||||||
if (httpClient == null) {
|
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
||||||
synchronized(this) {
|
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
|
||||||
if (httpClient == null) {
|
.maxPooledPerRoute(maxPooledPerRoute)
|
||||||
long socketTimeout = config.getLong("socket-timeout-millis", -1L);
|
.connectionPoolSize(connectionPoolSize)
|
||||||
long establishConnectionTimeout = config.getLong("establish-connection-timeout-millis", -1L);
|
.hostnameVerification(hostnamePolicy)
|
||||||
int maxPooledPerRoute = config.getInt("max-pooled-per-route", 0);
|
.disableCookies(disableCookies);
|
||||||
int connectionPoolSize = config.getInt("connection-pool-size", 200);
|
if (disableTrustManager) builder.disableTrustManager();
|
||||||
boolean disableCookies = config.getBoolean("disable-cookies", true);
|
if (truststore != null) {
|
||||||
String clientKeystore = config.get("client-keystore");
|
truststore = EnvUtil.replace(truststore);
|
||||||
String clientKeystorePassword = config.get("client-keystore-password");
|
try {
|
||||||
String clientPrivateKeyPassword = config.get("client-key-password");
|
builder.trustStore(KeystoreUtil.loadKeyStore(truststore, truststorePassword));
|
||||||
|
} catch (Exception e) {
|
||||||
TruststoreProvider truststoreProvider = session.getProvider(TruststoreProvider.class);
|
throw new RuntimeException("Failed to load truststore", e);
|
||||||
boolean disableTrustManager = truststoreProvider == null || truststoreProvider.getTruststore() == null;
|
|
||||||
if (disableTrustManager) {
|
|
||||||
logger.warn("Truststore is disabled");
|
|
||||||
}
|
|
||||||
HttpClientBuilder.HostnameVerificationPolicy hostnamePolicy = disableTrustManager ? null
|
|
||||||
: HttpClientBuilder.HostnameVerificationPolicy.valueOf(truststoreProvider.getPolicy().name());
|
|
||||||
|
|
||||||
HttpClientBuilder builder = new HttpClientBuilder();
|
|
||||||
builder.socketTimeout(socketTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.establishConnectionTimeout(establishConnectionTimeout, TimeUnit.MILLISECONDS)
|
|
||||||
.maxPooledPerRoute(maxPooledPerRoute)
|
|
||||||
.connectionPoolSize(connectionPoolSize)
|
|
||||||
.disableCookies(disableCookies);
|
|
||||||
|
|
||||||
if (disableTrustManager) {
|
|
||||||
// TODO: is it ok to do away with disabling trust manager?
|
|
||||||
//builder.disableTrustManager();
|
|
||||||
} else {
|
|
||||||
builder.hostnameVerification(hostnamePolicy);
|
|
||||||
try {
|
|
||||||
builder.trustStore(truststoreProvider.getTruststore());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to load truststore", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientKeystore != null) {
|
|
||||||
clientKeystore = EnvUtil.replace(clientKeystore);
|
|
||||||
try {
|
|
||||||
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
|
||||||
builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to load keystore", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
httpClient = builder.build();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (clientKeystore != null) {
|
||||||
|
clientKeystore = EnvUtil.replace(clientKeystore);
|
||||||
|
try {
|
||||||
|
KeyStore clientCertKeystore = KeystoreUtil.loadKeyStore(clientKeystore, clientKeystorePassword);
|
||||||
|
builder.keyStore(clientCertKeystore, clientPrivateKeyPassword);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to load keystore", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpClient = builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+1
-1
@@ -145,7 +145,7 @@ public class HttpClientBuilder {
|
|||||||
* Disable cookie management.
|
* Disable cookie management.
|
||||||
*/
|
*/
|
||||||
public HttpClientBuilder disableCookies(boolean disable) {
|
public HttpClientBuilder disableCookies(boolean disable) {
|
||||||
this.disableCookies = disable;
|
this.disableTrustManager = disable;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -27,6 +27,5 @@
|
|||||||
<artifactId>infinispan-core</artifactId>
|
<artifactId>infinispan-core</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
-1
@@ -126,7 +126,6 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
|||||||
|
|
||||||
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
|
||||||
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
-1
@@ -11,7 +11,6 @@ public interface InfinispanConnectionProvider extends Provider {
|
|||||||
static final String REALM_CACHE_NAME = "realms";
|
static final String REALM_CACHE_NAME = "realms";
|
||||||
static final String USER_CACHE_NAME = "users";
|
static final String USER_CACHE_NAME = "users";
|
||||||
static final String SESSION_CACHE_NAME = "sessions";
|
static final String SESSION_CACHE_NAME = "sessions";
|
||||||
static final String OFFLINE_SESSION_CACHE_NAME = "offlineSessions";
|
|
||||||
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
|
||||||
|
|
||||||
<K, V> Cache<K, V> getCache(String name);
|
<K, V> Cache<K, V> getCache(String name);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
|
||||||
<changeSet author="mposolda@redhat.com" id="1.6.0">
|
|
||||||
|
|
||||||
<addColumn tableName="REALM">
|
|
||||||
<column name="OFFLINE_SESSION_IDLE_TIMEOUT" type="INT" defaultValueNumeric="0"/>
|
|
||||||
<column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<addColumn tableName="KEYCLOAK_ROLE">
|
|
||||||
<column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<addColumn tableName="CLIENT">
|
|
||||||
<column name="ROOT_URL" type="VARCHAR(255)"/>
|
|
||||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_USER_SESSION">
|
|
||||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="LAST_SESSION_REFRESH" type="INT"/>
|
|
||||||
<column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="DATA" type="CLOB"/>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_CLIENT_SESSION">
|
|
||||||
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="OFFLINE" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="TIMESTAMP" type="INT"/>
|
|
||||||
<column name="DATA" type="CLOB"/>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION"/>
|
|
||||||
<addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE" constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION"/>
|
|
||||||
</changeSet>
|
|
||||||
</databaseChangeLog>
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
|
||||||
<changeSet author="mposolda@redhat.com" id="1.6.1_from15">
|
|
||||||
|
|
||||||
<preConditions onFail="MARK_RAN" onFailMessage="Upgrading from 1.6.0 version. Skipped 1.6.1_from15 changeSet and marked as ran">
|
|
||||||
<not>
|
|
||||||
<changeSetExecuted id="1.6.0" author="mposolda@redhat.com" changeLogFile="META-INF/jpa-changelog-1.6.0.xml" />
|
|
||||||
</not>
|
|
||||||
</preConditions>
|
|
||||||
|
|
||||||
<addColumn tableName="REALM">
|
|
||||||
<column name="OFFLINE_SESSION_IDLE_TIMEOUT" type="INT" defaultValueNumeric="0"/>
|
|
||||||
<column name="REVOKE_REFRESH_TOKEN" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<addColumn tableName="KEYCLOAK_ROLE">
|
|
||||||
<column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<addColumn tableName="CLIENT">
|
|
||||||
<column name="ROOT_URL" type="VARCHAR(255)"/>
|
|
||||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_USER_SESSION">
|
|
||||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="LAST_SESSION_REFRESH" type="INT"/>
|
|
||||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="DATA" type="CLOB"/>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<createTable tableName="OFFLINE_CLIENT_SESSION">
|
|
||||||
<column name="CLIENT_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_SESSION_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="CLIENT_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="TIMESTAMP" type="INT"/>
|
|
||||||
<column name="DATA" type="CLOB"/>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_US_SES_PK2" tableName="OFFLINE_USER_SESSION"/>
|
|
||||||
<addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION"/>
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
<!-- Just for the update from 1.6.0 -->
|
|
||||||
<changeSet author="mposolda@redhat.com" id="1.6.1_from16">
|
|
||||||
|
|
||||||
<preConditions onFail="MARK_RAN" onFailMessage="Upgrading from 1.5.0 or older version. Skipped 1.6.1_from16 changeSet and marked as ran">
|
|
||||||
<changeSetExecuted id="1.6.0" author="mposolda@redhat.com" changeLogFile="META-INF/jpa-changelog-1.6.0.xml" />
|
|
||||||
</preConditions>
|
|
||||||
|
|
||||||
<dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_US_SES_PK" tableName="OFFLINE_USER_SESSION" />
|
|
||||||
<dropPrimaryKey constraintName="CONSTRAINT_OFFLINE_CL_SES_PK" tableName="OFFLINE_CLIENT_SESSION" />
|
|
||||||
|
|
||||||
<addColumn tableName="OFFLINE_USER_SESSION">
|
|
||||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
<update tableName="OFFLINE_USER_SESSION">
|
|
||||||
<column name="OFFLINE_FLAG" value="1"/>
|
|
||||||
</update>
|
|
||||||
<dropColumn tableName="OFFLINE_USER_SESSION" columnName="OFFLINE" />
|
|
||||||
|
|
||||||
<addColumn tableName="OFFLINE_CLIENT_SESSION">
|
|
||||||
<column name="OFFLINE_FLAG" type="VARCHAR(4)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
<update tableName="OFFLINE_CLIENT_SESSION">
|
|
||||||
<column name="OFFLINE_FLAG" value="1"/>
|
|
||||||
</update>
|
|
||||||
<dropColumn tableName="OFFLINE_CLIENT_SESSION" columnName="OFFLINE" />
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="USER_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_US_SES_PK2" tableName="OFFLINE_USER_SESSION"/>
|
|
||||||
<addPrimaryKey columnNames="CLIENT_SESSION_ID, OFFLINE_FLAG" constraintName="CONSTRAINT_OFFL_CL_SES_PK2" tableName="OFFLINE_CLIENT_SESSION"/>
|
|
||||||
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
<changeSet author="mposolda@redhat.com" id="1.6.1">
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
</databaseChangeLog>
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
|
||||||
<changeSet author="bburke@redhat.com" id="1.7.0">
|
|
||||||
<createTable tableName="KEYCLOAK_GROUP">
|
|
||||||
<column name="ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="NAME" type="VARCHAR(255)"/>
|
|
||||||
<column name="PARENT_GROUP" type="VARCHAR(36)"/>
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="GROUP_ROLE_MAPPING">
|
|
||||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="GROUP_ATTRIBUTE">
|
|
||||||
<column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="NAME" type="VARCHAR(255)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="VALUE" type="VARCHAR(255)"/>
|
|
||||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="USER_GROUP_MEMBERSHIP">
|
|
||||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USER_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="REALM_DEFAULT_GROUPS">
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="GROUP_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addColumn tableName="IDENTITY_PROVIDER">
|
|
||||||
<column name="FIRST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="true"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<addColumn tableName="REALM">
|
|
||||||
<column name="ACCESS_TOKEN_LIFE_IMPLICIT" type="INT" defaultValueNumeric="0"/>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<dropDefaultValue tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD" />
|
|
||||||
<dropColumn tableName="IDENTITY_PROVIDER" columnName="UPDATE_PROFILE_FIRST_LGN_MD"/>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP" tableName="KEYCLOAK_GROUP"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="KEYCLOAK_GROUP" constraintName="FK_GROUP_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_GROUP_ATTRIBUTE_PK" tableName="GROUP_ATTRIBUTE"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="GROUP_ATTRIBUTE" constraintName="FK_GROUP_ATTRIBUTE_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="GROUP_ID, USER_ID" constraintName="CONSTRAINT_USER_GROUP" tableName="USER_GROUP_MEMBERSHIP"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_GROUP_MEMBERSHIP" constraintName="FK_USER_GROUP_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="ROLE_ID, GROUP_ID" constraintName="CONSTRAINT_GROUP_ROLE" tableName="GROUP_ROLE_MAPPING"/>
|
|
||||||
<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"/>
|
|
||||||
|
|
||||||
<addUniqueConstraint columnNames="GROUP_ID" constraintName="CON_GROUP_ID_DEF_GROUPS" tableName="REALM_DEFAULT_GROUPS"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="GROUP_ID" baseTableName="REALM_DEFAULT_GROUPS" constraintName="FK_DEF_GROUPS_GROUP" referencedColumnNames="ID" referencedTableName="KEYCLOAK_GROUP"/>
|
|
||||||
|
|
||||||
<addColumn tableName="CLIENT">
|
|
||||||
<column name="REGISTRATION_TOKEN" type="VARCHAR(255)"/>
|
|
||||||
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
|
|
||||||
<update tableName="CLIENT">
|
|
||||||
<column name="STANDARD_FLOW_ENABLED" valueBoolean="false"/>
|
|
||||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" valueBoolean="true"/>
|
|
||||||
<where>DIRECT_GRANTS_ONLY = :value</where>
|
|
||||||
<whereParams>
|
|
||||||
<param valueBoolean="true" />
|
|
||||||
</whereParams>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
<dropDefaultValue tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY" />
|
|
||||||
<dropColumn tableName="CLIENT" columnName="DIRECT_GRANTS_ONLY"/>
|
|
||||||
|
|
||||||
<modifyDataType tableName="REALM" columnName="PASSWORD_POLICY" newDataType="VARCHAR(2550)"/>
|
|
||||||
|
|
||||||
<!-- Sybase specific hacks -->
|
|
||||||
<modifySql dbms="sybase">
|
|
||||||
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
|
|
||||||
</modifySql>
|
|
||||||
|
|
||||||
</changeSet>
|
|
||||||
</databaseChangeLog>
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
||||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
|
|
||||||
<changeSet author="mposolda@redhat.com" id="1.8.0">
|
|
||||||
|
|
||||||
<addColumn tableName="IDENTITY_PROVIDER">
|
|
||||||
<column name="POST_BROKER_LOGIN_FLOW_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="true"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
<createTable tableName="CLIENT_TEMPLATE">
|
|
||||||
<column name="ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="NAME" type="VARCHAR(255)"/>
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)"/>
|
|
||||||
<column name="DESCRIPTION" type="VARCHAR(255)"/>
|
|
||||||
<column name="PROTOCOL" type="VARCHAR(255)"/>
|
|
||||||
<column name="FULL_SCOPE_ALLOWED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="CONSENT_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="STANDARD_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="true">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="IMPLICIT_FLOW_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="DIRECT_ACCESS_GRANTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="FRONTCHANNEL_LOGOUT" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="BEARER_ONLY" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="PUBLIC_CLIENT" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="CLIENT_TEMPLATE_ATTRIBUTES">
|
|
||||||
<column name="TEMPLATE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="VALUE" type="VARCHAR(2048)"/>
|
|
||||||
<column name="NAME" type="VARCHAR(255)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
<createTable tableName="TEMPLATE_SCOPE_MAPPING">
|
|
||||||
<column name="TEMPLATE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="ROLE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<dropNotNullConstraint tableName="PROTOCOL_MAPPER" columnName="CLIENT_ID" columnDataType="VARCHAR(36)"/>
|
|
||||||
<addColumn tableName="CLIENT">
|
|
||||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="true"/>
|
|
||||||
</column>
|
|
||||||
<column name="USE_TEMPLATE_CONFIG" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USE_TEMPLATE_SCOPE" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="USE_TEMPLATE_MAPPERS" type="BOOLEAN" defaultValueBoolean="false">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
<addColumn tableName="PROTOCOL_MAPPER">
|
|
||||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="true"/>
|
|
||||||
</column>
|
|
||||||
</addColumn>
|
|
||||||
<createTable tableName="REALM_CLIENT_TEMPLATE">
|
|
||||||
<column name="CLIENT_TEMPLATE_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
<column name="REALM_ID" type="VARCHAR(36)">
|
|
||||||
<constraints nullable="false"/>
|
|
||||||
</column>
|
|
||||||
</createTable>
|
|
||||||
|
|
||||||
<addPrimaryKey columnNames="ID" constraintName="PK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addUniqueConstraint columnNames="REALM_ID,NAME" constraintName="UK_CLI_TEMPLATE" tableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="CLIENT_TEMPLATE" constraintName="FK_REALM_CLI_TMPLT" referencedColumnNames="ID" referencedTableName="REALM"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="PROTOCOL_MAPPER" constraintName="FK_CLI_TMPLT_MAPPER" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="CLIENT" constraintName="FK_CLI_TMPLT_CLIENT" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_RLM" referencedColumnNames="ID" referencedTableName="REALM"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="CLIENT_TEMPLATE_ID" baseTableName="REALM_CLIENT_TEMPLATE" constraintName="FK_RLM_CLI_TMPLT_CLI" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addPrimaryKey columnNames="TEMPLATE_ID, ROLE_ID" constraintName="PK_TEMPLATE_SCOPE" tableName="TEMPLATE_SCOPE_MAPPING"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="ROLE_ID" baseTableName="TEMPLATE_SCOPE_MAPPING" constraintName="FK_TEMPL_SCOPE_ROLE" referencedColumnNames="ID" referencedTableName="KEYCLOAK_ROLE"/>
|
|
||||||
<addPrimaryKey columnNames="TEMPLATE_ID, NAME" constraintName="PK_CL_TMPL_ATTR" tableName="CLIENT_TEMPLATE_ATTRIBUTES"/>
|
|
||||||
<addForeignKeyConstraint baseColumnNames="TEMPLATE_ID" baseTableName="CLIENT_TEMPLATE_ATTRIBUTES" constraintName="FK_CL_TEMPL_ATTR_TEMPL" referencedColumnNames="ID" referencedTableName="CLIENT_TEMPLATE"/>
|
|
||||||
|
|
||||||
<update tableName="CREDENTIAL">
|
|
||||||
<column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
|
|
||||||
<where>TYPE in ('password-history', 'password') AND ALGORITHM is NULL</where>
|
|
||||||
</update>
|
|
||||||
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
<changeSet id="1.8.0-2" author="keycloak">
|
|
||||||
<dropDefaultValue tableName="CREDENTIAL" columnName="ALGORITHM" columnDataType="VARCHAR(36)"/>
|
|
||||||
|
|
||||||
<update tableName="CREDENTIAL">
|
|
||||||
<column name="ALGORITHM" type="VARCHAR(36)" value="pbkdf2" />
|
|
||||||
<where>TYPE in ('password-history', 'password') AND ALGORITHM = 'HmacSHA1'</where>
|
|
||||||
</update>
|
|
||||||
</changeSet>
|
|
||||||
|
|
||||||
</databaseChangeLog>
|
|
||||||
@@ -9,7 +9,4 @@
|
|||||||
<include file="META-INF/jpa-changelog-1.3.0.xml"/>
|
<include file="META-INF/jpa-changelog-1.3.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-1.4.0.xml"/>
|
<include file="META-INF/jpa-changelog-1.4.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-1.5.0.xml"/>
|
<include file="META-INF/jpa-changelog-1.5.0.xml"/>
|
||||||
<include file="META-INF/jpa-changelog-1.6.1.xml"/>
|
|
||||||
<include file="META-INF/jpa-changelog-1.7.0.xml"/>
|
|
||||||
<include file="META-INF/jpa-changelog-1.8.0.xml"/>
|
|
||||||
</databaseChangeLog>
|
</databaseChangeLog>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.hibernate.javax.persistence</groupId>
|
<groupId>org.hibernate.javax.persistence</groupId>
|
||||||
<artifactId>${hibernate.javax.persistence.artifactId}</artifactId>
|
<artifactId>hibernate-jpa-2.1-api</artifactId>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|||||||
+14
-16
@@ -1,5 +1,18 @@
|
|||||||
package org.keycloak.connections.jpa;
|
package org.keycloak.connections.jpa;
|
||||||
|
|
||||||
|
import org.hibernate.ejb.AvailableSettings;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
||||||
|
import org.keycloak.connections.jpa.util.JpaUtils;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
import javax.persistence.EntityManagerFactory;
|
||||||
|
import javax.persistence.Persistence;
|
||||||
|
import javax.sql.DataSource;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.DatabaseMetaData;
|
import java.sql.DatabaseMetaData;
|
||||||
import java.sql.DriverManager;
|
import java.sql.DriverManager;
|
||||||
@@ -9,25 +22,10 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.naming.InitialContext;
|
|
||||||
import javax.persistence.EntityManager;
|
|
||||||
import javax.persistence.EntityManagerFactory;
|
|
||||||
import javax.persistence.Persistence;
|
|
||||||
import javax.sql.DataSource;
|
|
||||||
|
|
||||||
import org.hibernate.ejb.AvailableSettings;
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
|
|
||||||
import org.keycloak.connections.jpa.util.JpaUtils;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
public class DefaultJpaConnectionProviderFactory implements JpaConnectionProviderFactory {
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
private static final Logger logger = Logger.getLogger(DefaultJpaConnectionProviderFactory.class);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
package org.keycloak.connections.jpa;
|
package org.keycloak.connections.jpa;
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface JpaConnectionProviderFactory extends ProviderFactory<JpaConnectionProvider> {
|
public interface JpaConnectionProviderFactory extends ServerInfoAwareProviderFactory<JpaConnectionProvider> {
|
||||||
|
|
||||||
}
|
}
|
||||||
+1
-1
@@ -12,7 +12,7 @@ public interface JpaUpdaterProvider extends Provider {
|
|||||||
|
|
||||||
public String FIRST_VERSION = "1.0.0.Final";
|
public String FIRST_VERSION = "1.0.0.Final";
|
||||||
|
|
||||||
public String LAST_VERSION = "1.8.0";
|
public String LAST_VERSION = "1.5.0";
|
||||||
|
|
||||||
public String getCurrentVersionSql(String defaultSchema);
|
public String getCurrentVersionSql(String defaultSchema);
|
||||||
|
|
||||||
|
|||||||
@@ -29,14 +29,6 @@
|
|||||||
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
<class>org.keycloak.models.jpa.entities.AuthenticationExecutionEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
<class>org.keycloak.models.jpa.entities.AuthenticatorConfigEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.GroupEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class>
|
|
||||||
<class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class>
|
|
||||||
|
|
||||||
<!-- JpaAuditProviders -->
|
<!-- JpaAuditProviders -->
|
||||||
<class>org.keycloak.events.jpa.EventEntity</class>
|
<class>org.keycloak.events.jpa.EventEntity</class>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+1
-3
@@ -28,9 +28,7 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider {
|
|||||||
Update1_2_0_Beta1.class,
|
Update1_2_0_Beta1.class,
|
||||||
Update1_2_0_CR1.class,
|
Update1_2_0_CR1.class,
|
||||||
Update1_3_0.class,
|
Update1_3_0.class,
|
||||||
Update1_4_0.class,
|
Update1_4_0.class
|
||||||
Update1_7_0.class,
|
|
||||||
Update1_8_0.class
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ public abstract class Update {
|
|||||||
o.append(f, 1);
|
o.append(f, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
col.createIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
col.ensureIndex(o, new BasicDBObject("unique", unique).append("sparse", sparse));
|
||||||
log.debugv("Created index {0}, fields={1}, unique={2}, sparse={3}", name, Arrays.toString(fields), unique, sparse);
|
log.debugv("Created index {0}, fields={1}, unique={2}, sparse={3}", name, Arrays.toString(fields), unique, sparse);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ public class Update1_0_0_Final extends Update {
|
|||||||
@Override
|
@Override
|
||||||
public void update(KeycloakSession session) throws ClassNotFoundException {
|
public void update(KeycloakSession session) throws ClassNotFoundException {
|
||||||
DBCollection realmsCollection = db.getCollection("realms");
|
DBCollection realmsCollection = db.getCollection("realms");
|
||||||
realmsCollection.createIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
realmsCollection.ensureIndex(new BasicDBObject("name", 1), new BasicDBObject("unique", true));
|
||||||
|
|
||||||
DefaultMongoUpdaterProvider.log.debugv("Created collection {0}", "realms");
|
DefaultMongoUpdaterProvider.log.debugv("Created collection {0}", "realms");
|
||||||
|
|
||||||
|
|||||||
-39
@@ -1,39 +0,0 @@
|
|||||||
package org.keycloak.connections.mongo.updater.impl.updates;
|
|
||||||
|
|
||||||
import com.mongodb.BasicDBObject;
|
|
||||||
import com.mongodb.DBCollection;
|
|
||||||
import com.mongodb.DBCursor;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class Update1_7_0 extends Update {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "1.7.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(KeycloakSession session) throws ClassNotFoundException {
|
|
||||||
DBCollection clients = db.getCollection("clients");
|
|
||||||
DBCursor clientsCursor = clients.find();
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (clientsCursor.hasNext()) {
|
|
||||||
BasicDBObject client = (BasicDBObject) clientsCursor.next();
|
|
||||||
|
|
||||||
boolean directGrantsOnly = client.getBoolean("directGrantsOnly", false);
|
|
||||||
client.append("standardFlowEnabled", !directGrantsOnly);
|
|
||||||
client.append("implicitFlowEnabled", false);
|
|
||||||
client.append("directAccessGrantsEnabled", directGrantsOnly);
|
|
||||||
client.removeField("directGrantsOnly");
|
|
||||||
|
|
||||||
clients.save(client);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
clientsCursor.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
package org.keycloak.connections.mongo.updater.impl.updates;
|
|
||||||
|
|
||||||
import com.mongodb.BasicDBList;
|
|
||||||
import com.mongodb.BasicDBObject;
|
|
||||||
import com.mongodb.DBCollection;
|
|
||||||
import com.mongodb.WriteResult;
|
|
||||||
import org.keycloak.hash.Pbkdf2PasswordHashProvider;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.UserCredentialModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
|
||||||
*/
|
|
||||||
public class Update1_8_0 extends Update {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "1.8.0";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void update(KeycloakSession session) {
|
|
||||||
BasicDBList orArgs = new BasicDBList();
|
|
||||||
orArgs.add(new BasicDBObject("type", UserCredentialModel.PASSWORD));
|
|
||||||
orArgs.add(new BasicDBObject("type", UserCredentialModel.PASSWORD_HISTORY));
|
|
||||||
|
|
||||||
BasicDBObject elemMatch = new BasicDBObject("$or", orArgs);
|
|
||||||
elemMatch.put("algorithm", new BasicDBObject("$exists", false));
|
|
||||||
|
|
||||||
BasicDBObject query = new BasicDBObject("credentials", new BasicDBObject("$elemMatch", elemMatch));
|
|
||||||
|
|
||||||
BasicDBObject update = new BasicDBObject("$set", new BasicDBObject("credentials.$.algorithm", Pbkdf2PasswordHashProvider.ID));
|
|
||||||
|
|
||||||
DBCollection users = db.getCollection("users");
|
|
||||||
|
|
||||||
// Not sure how to do in single query
|
|
||||||
int countModified = 1;
|
|
||||||
while (countModified > 0) {
|
|
||||||
WriteResult wr = users.update(query, update, false, true);
|
|
||||||
countModified = wr.getN();
|
|
||||||
log.debugf("%d credentials modified in current iteration during upgrade to 1.8", countModified);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|||||||
+34
-62
@@ -1,12 +1,10 @@
|
|||||||
package org.keycloak.connections.mongo;
|
package org.keycloak.connections.mongo;
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import com.mongodb.DB;
|
||||||
import java.net.UnknownHostException;
|
import com.mongodb.MongoClient;
|
||||||
import java.util.Collections;
|
import com.mongodb.MongoClientOptions;
|
||||||
import java.util.LinkedHashMap;
|
import com.mongodb.MongoCredential;
|
||||||
import java.util.Map;
|
import com.mongodb.ServerAddress;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.Config;
|
import org.keycloak.Config;
|
||||||
@@ -16,32 +14,27 @@ import org.keycloak.connections.mongo.impl.context.TransactionMongoStoreInvocati
|
|||||||
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
|
import org.keycloak.connections.mongo.updater.MongoUpdaterProvider;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
|
||||||
|
|
||||||
import com.mongodb.DB;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import com.mongodb.MongoClient;
|
import java.lang.reflect.Method;
|
||||||
import com.mongodb.MongoClientOptions;
|
import java.net.UnknownHostException;
|
||||||
import com.mongodb.MongoClientURI;
|
import java.util.Collections;
|
||||||
import com.mongodb.MongoCredential;
|
import java.util.LinkedHashMap;
|
||||||
import com.mongodb.ServerAddress;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory, ServerInfoAwareProviderFactory {
|
public class DefaultMongoConnectionFactoryProvider implements MongoConnectionProviderFactory {
|
||||||
|
|
||||||
// TODO Make it dynamic
|
// TODO Make it dynamic
|
||||||
private String[] entities = new String[]{
|
private String[] entities = new String[]{
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoUserEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity",
|
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoClientEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoClientTemplateEntity",
|
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity",
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoOnlineUserSessionEntity",
|
|
||||||
"org.keycloak.models.mongo.keycloak.entities.MongoOfflineUserSessionEntity",
|
|
||||||
"org.keycloak.models.entities.IdentityProviderEntity",
|
"org.keycloak.models.entities.IdentityProviderEntity",
|
||||||
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
|
"org.keycloak.models.entities.ClientIdentityProviderMappingEntity",
|
||||||
"org.keycloak.models.entities.RequiredCredentialEntity",
|
"org.keycloak.models.entities.RequiredCredentialEntity",
|
||||||
@@ -55,8 +48,6 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
"org.keycloak.models.entities.AuthenticationFlowEntity",
|
||||||
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
"org.keycloak.models.entities.AuthenticatorConfigEntity",
|
||||||
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
"org.keycloak.models.entities.RequiredActionProviderEntity",
|
||||||
"org.keycloak.models.entities.PersistentUserSessionEntity",
|
|
||||||
"org.keycloak.models.entities.PersistentClientSessionEntity",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);
|
||||||
@@ -154,53 +145,31 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
* @throws UnknownHostException
|
* @throws UnknownHostException
|
||||||
*/
|
*/
|
||||||
protected MongoClient createMongoClient() throws UnknownHostException {
|
protected MongoClient createMongoClient() throws UnknownHostException {
|
||||||
operationalInfo = new LinkedHashMap<>();
|
String host = config.get("host", ServerAddress.defaultHost());
|
||||||
|
int port = config.getInt("port", ServerAddress.defaultPort());
|
||||||
String dbName = config.get("db", "keycloak");
|
String dbName = config.get("db", "keycloak");
|
||||||
|
|
||||||
String uriString = config.get("uri");
|
String user = config.get("user");
|
||||||
if (uriString != null) {
|
String password = config.get("password");
|
||||||
MongoClientURI uri = new MongoClientURI(uriString);
|
|
||||||
MongoClient client = new MongoClient(uri);
|
|
||||||
|
|
||||||
StringBuilder hostsBuilder = new StringBuilder();
|
MongoClientOptions clientOptions = getClientOptions();
|
||||||
for (int i=0 ; i<uri.getHosts().size() ; i++) {
|
|
||||||
if (i!=0) {
|
|
||||||
hostsBuilder.append(", ");
|
|
||||||
}
|
|
||||||
hostsBuilder.append(uri.getHosts().get(i));
|
|
||||||
}
|
|
||||||
String hosts = hostsBuilder.toString();
|
|
||||||
|
|
||||||
operationalInfo.put("mongoHosts", hosts);
|
MongoClient client;
|
||||||
operationalInfo.put("mongoDatabaseName", dbName);
|
if (user != null && password != null) {
|
||||||
operationalInfo.put("mongoUser", uri.getUsername());
|
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
||||||
|
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
||||||
logger.debugv("Initialized mongo model. host(s): %s, db: %s", uri.getHosts(), dbName);
|
|
||||||
return client;
|
|
||||||
} else {
|
} else {
|
||||||
String host = config.get("host", ServerAddress.defaultHost());
|
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
||||||
int port = config.getInt("port", ServerAddress.defaultPort());
|
|
||||||
|
|
||||||
String user = config.get("user");
|
|
||||||
String password = config.get("password");
|
|
||||||
|
|
||||||
MongoClientOptions clientOptions = getClientOptions();
|
|
||||||
|
|
||||||
MongoClient client;
|
|
||||||
if (user != null && password != null) {
|
|
||||||
MongoCredential credential = MongoCredential.createMongoCRCredential(user, dbName, password.toCharArray());
|
|
||||||
client = new MongoClient(new ServerAddress(host, port), Collections.singletonList(credential), clientOptions);
|
|
||||||
} else {
|
|
||||||
client = new MongoClient(new ServerAddress(host, port), clientOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
|
||||||
operationalInfo.put("mongoDatabaseName", dbName);
|
|
||||||
operationalInfo.put("mongoUser", user);
|
|
||||||
|
|
||||||
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
|
||||||
return client;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
operationalInfo = new LinkedHashMap<>();
|
||||||
|
operationalInfo.put("mongoServerAddress", client.getAddress().toString());
|
||||||
|
operationalInfo.put("mongoDatabaseName", dbName);
|
||||||
|
operationalInfo.put("mongoUser", user);
|
||||||
|
operationalInfo.put("mongoDriverVersion", client.getVersion());
|
||||||
|
|
||||||
|
logger.debugv("Initialized mongo model. host: %s, port: %d, db: %s", host, port, dbName);
|
||||||
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected MongoClientOptions getClientOptions() {
|
protected MongoClientOptions getClientOptions() {
|
||||||
@@ -212,6 +181,9 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
|
|||||||
checkIntOption("socketTimeout", builder);
|
checkIntOption("socketTimeout", builder);
|
||||||
checkBooleanOption("socketKeepAlive", builder);
|
checkBooleanOption("socketKeepAlive", builder);
|
||||||
checkBooleanOption("autoConnectRetry", builder);
|
checkBooleanOption("autoConnectRetry", builder);
|
||||||
|
if (config.getLong("maxAutoConnectRetryTime") != null) {
|
||||||
|
builder.maxAutoConnectRetryTime(config.getLong("maxAutoConnectRetryTime"));
|
||||||
|
}
|
||||||
if(config.getBoolean("ssl", false)) {
|
if(config.getBoolean("ssl", false)) {
|
||||||
builder.socketFactory(SSLSocketFactory.getDefault());
|
builder.socketFactory(SSLSocketFactory.getDefault());
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package org.keycloak.connections.mongo;
|
package org.keycloak.connections.mongo;
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
import org.keycloak.provider.ServerInfoAwareProviderFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public interface MongoConnectionProviderFactory extends ProviderFactory<MongoConnectionProvider> {
|
public interface MongoConnectionProviderFactory extends ServerInfoAwareProviderFactory<MongoConnectionProvider> {
|
||||||
}
|
}
|
||||||
+9
-2
@@ -1,6 +1,13 @@
|
|||||||
package org.keycloak.connections.mongo.impl;
|
package org.keycloak.connections.mongo.impl;
|
||||||
|
|
||||||
import com.mongodb.*;
|
import com.mongodb.BasicDBList;
|
||||||
|
import com.mongodb.BasicDBObject;
|
||||||
|
import com.mongodb.DB;
|
||||||
|
import com.mongodb.DBCollection;
|
||||||
|
import com.mongodb.DBCursor;
|
||||||
|
import com.mongodb.DBObject;
|
||||||
|
import com.mongodb.MongoException;
|
||||||
|
import com.mongodb.WriteResult;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.connections.mongo.api.MongoCollection;
|
import org.keycloak.connections.mongo.api.MongoCollection;
|
||||||
import org.keycloak.connections.mongo.api.MongoEntity;
|
import org.keycloak.connections.mongo.api.MongoEntity;
|
||||||
@@ -126,7 +133,7 @@ public class MongoStoreImpl implements MongoStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static ModelException convertException(MongoException e) {
|
public static ModelException convertException(MongoException e) {
|
||||||
if (e instanceof DuplicateKeyException) {
|
if (e instanceof MongoException.DuplicateKey) {
|
||||||
return new ModelDuplicateException(e);
|
return new ModelDuplicateException(e);
|
||||||
} else {
|
} else {
|
||||||
return new ModelException(e);
|
return new ModelException(e);
|
||||||
|
|||||||
+2
-1
@@ -9,10 +9,11 @@ import org.keycloak.connections.mongo.api.types.MapperRegistry;
|
|||||||
import org.keycloak.connections.mongo.impl.EntityInfo;
|
import org.keycloak.connections.mongo.impl.EntityInfo;
|
||||||
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
|
import org.keycloak.connections.mongo.impl.MongoStoreImpl;
|
||||||
import org.keycloak.models.utils.reflection.Property;
|
import org.keycloak.models.utils.reflection.Property;
|
||||||
import org.keycloak.common.util.reflections.Types;
|
import org.keycloak.util.reflections.Types;
|
||||||
|
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -3,7 +3,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>keycloak-parent</artifactId>
|
<artifactId>keycloak-parent</artifactId>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<version>1.8.0.CR3</version>
|
<version>1.5.1.Final</version>
|
||||||
</parent>
|
</parent>
|
||||||
<name>Connections Parent</name>
|
<name>Connections Parent</name>
|
||||||
<description/>
|
<description/>
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
<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>
|
||||||
<module>truststore</module>
|
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
-31
@@ -1,31 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import java.security.KeyStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class FileTruststoreProvider implements TruststoreProvider {
|
|
||||||
|
|
||||||
private final HostnameVerificationPolicy policy;
|
|
||||||
private final KeyStore truststore;
|
|
||||||
|
|
||||||
FileTruststoreProvider(KeyStore truststore, HostnameVerificationPolicy policy) {
|
|
||||||
this.policy = policy;
|
|
||||||
this.truststore = truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HostnameVerificationPolicy getPolicy() {
|
|
||||||
return policy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyStore getTruststore() {
|
|
||||||
return truststore;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-102
@@ -1,102 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
import org.keycloak.Config;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class FileTruststoreProviderFactory implements TruststoreProviderFactory {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(FileTruststoreProviderFactory.class);
|
|
||||||
|
|
||||||
private TruststoreProvider provider;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TruststoreProvider create(KeycloakSession session) {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void init(Config.Scope config) {
|
|
||||||
|
|
||||||
String storepath = config.get("file");
|
|
||||||
String pass = config.get("password");
|
|
||||||
String policy = config.get("hostname-verification-policy");
|
|
||||||
Boolean disabled = config.getBoolean("disabled", null);
|
|
||||||
|
|
||||||
// if "truststore" . "file" is not configured then it is disabled
|
|
||||||
if (storepath == null && pass == null && policy == null && disabled == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if explicitly disabled
|
|
||||||
if (disabled != null && disabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HostnameVerificationPolicy verificationPolicy = null;
|
|
||||||
KeyStore truststore = null;
|
|
||||||
|
|
||||||
if (storepath == null) {
|
|
||||||
throw new RuntimeException("Attribute 'file' missing in 'truststore':'file' configuration");
|
|
||||||
}
|
|
||||||
if (pass == null) {
|
|
||||||
throw new RuntimeException("Attribute 'password' missing in 'truststore':'file' configuration");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
truststore = loadStore(storepath, pass == null ? null :pass.toCharArray());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to initialize TruststoreProviderFactory: " + new File(storepath).getAbsolutePath(), e);
|
|
||||||
}
|
|
||||||
if (policy == null) {
|
|
||||||
verificationPolicy = HostnameVerificationPolicy.WILDCARD;
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
verificationPolicy = HostnameVerificationPolicy.valueOf(policy);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Invalid value for 'hostname-verification-policy': " + policy + " (must be one of: ANY, WILDCARD, STRICT)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
provider = new FileTruststoreProvider(truststore, verificationPolicy);
|
|
||||||
TruststoreProviderSingleton.set(provider);
|
|
||||||
log.debug("File trustore provider initialized: " + new File(storepath).getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyStore loadStore(String path, char[] password) throws Exception {
|
|
||||||
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
||||||
InputStream is = new FileInputStream(path);
|
|
||||||
try {
|
|
||||||
ks.load(is, password);
|
|
||||||
return ks;
|
|
||||||
} finally {
|
|
||||||
try {
|
|
||||||
is.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void postInit(KeycloakSessionFactory factory) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getId() {
|
|
||||||
return "file";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
public enum HostnameVerificationPolicy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hostname verification is not done on the server's certificate
|
|
||||||
*/
|
|
||||||
ANY,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows wildcards in subdomain names i.e. *.foo.com
|
|
||||||
*/
|
|
||||||
WILDCARD,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CN must match hostname connecting to
|
|
||||||
*/
|
|
||||||
STRICT
|
|
||||||
}
|
|
||||||
-106
@@ -1,106 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
|
|
||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class JSSETruststoreConfigurator {
|
|
||||||
|
|
||||||
private TruststoreProvider provider;
|
|
||||||
private volatile javax.net.ssl.SSLSocketFactory sslFactory;
|
|
||||||
private volatile TrustManager[] tm;
|
|
||||||
|
|
||||||
public JSSETruststoreConfigurator(KeycloakSession session) {
|
|
||||||
KeycloakSessionFactory factory = session.getKeycloakSessionFactory();
|
|
||||||
TruststoreProviderFactory truststoreFactory = (TruststoreProviderFactory) factory.getProviderFactory(TruststoreProvider.class, "file");
|
|
||||||
|
|
||||||
provider = truststoreFactory.create(session);
|
|
||||||
if (provider != null && provider.getTruststore() == null) {
|
|
||||||
provider = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSSETruststoreConfigurator(TruststoreProvider provider) {
|
|
||||||
this.provider = provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public javax.net.ssl.SSLSocketFactory getSSLSocketFactory() {
|
|
||||||
if (provider == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sslFactory == null) {
|
|
||||||
synchronized(this) {
|
|
||||||
if (sslFactory == null) {
|
|
||||||
try {
|
|
||||||
SSLContext sslctx = SSLContext.getInstance("TLS");
|
|
||||||
sslctx.init(null, getTrustManagers(), null);
|
|
||||||
sslFactory = sslctx.getSocketFactory();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to initialize SSLContext: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sslFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrustManager[] getTrustManagers() {
|
|
||||||
if (provider == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tm == null) {
|
|
||||||
synchronized (this) {
|
|
||||||
if (tm == null) {
|
|
||||||
TrustManagerFactory tmf = null;
|
|
||||||
try {
|
|
||||||
tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
tmf.init(provider.getTruststore());
|
|
||||||
tm = tmf.getTrustManagers();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to initialize TrustManager: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostnameVerifier getHostnameVerifier() {
|
|
||||||
if (provider == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HostnameVerificationPolicy policy = provider.getPolicy();
|
|
||||||
switch (policy) {
|
|
||||||
case ANY:
|
|
||||||
return new HostnameVerifier() {
|
|
||||||
@Override
|
|
||||||
public boolean verify(String s, SSLSession sslSession) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
case WILDCARD:
|
|
||||||
return new BrowserCompatHostnameVerifier();
|
|
||||||
case STRICT:
|
|
||||||
return new StrictHostnameVerifier();
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unknown policy: " + policy.name());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public TruststoreProvider getProvider() {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-87
@@ -1,87 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Using this class is ugly, but it is the only way to push our truststore to the default LDAP client implementation.
|
|
||||||
* <p>
|
|
||||||
* This SSLSocketFactory can only use truststore configured by TruststoreProvider after the ProviderFactory was
|
|
||||||
* initialized using standard Spi load / init mechanism. That will only happen if "truststore" provider is configured
|
|
||||||
* in keycloak-server.json.
|
|
||||||
* <p>
|
|
||||||
* If TruststoreProvider is not available this SSLSocketFactory will delegate all operations to javax.net.ssl.SSLSocketFactory.getDefault().
|
|
||||||
*
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class SSLSocketFactory extends javax.net.ssl.SSLSocketFactory {
|
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(SSLSocketFactory.class);
|
|
||||||
|
|
||||||
private static SSLSocketFactory instance;
|
|
||||||
|
|
||||||
private final javax.net.ssl.SSLSocketFactory sslsf;
|
|
||||||
|
|
||||||
private SSLSocketFactory() {
|
|
||||||
|
|
||||||
TruststoreProvider provider = TruststoreProviderSingleton.get();
|
|
||||||
javax.net.ssl.SSLSocketFactory sf = null;
|
|
||||||
if (provider != null) {
|
|
||||||
sf = new JSSETruststoreConfigurator(provider).getSSLSocketFactory();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sf == null) {
|
|
||||||
log.info("No truststore provider found - using default SSLSocketFactory");
|
|
||||||
sf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
sslsf = sf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static synchronized SSLSocketFactory getDefault() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new SSLSocketFactory();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getDefaultCipherSuites() {
|
|
||||||
return sslsf.getDefaultCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] getSupportedCipherSuites() {
|
|
||||||
return sslsf.getSupportedCipherSuites();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException {
|
|
||||||
return sslsf.createSocket(socket, host, port, autoClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port) throws IOException {
|
|
||||||
return sslsf.createSocket(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
|
||||||
return sslsf.createSocket(host, port, localHost, localPort);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
|
||||||
return sslsf.createSocket(host, port);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
|
||||||
return sslsf.createSocket(address, port, localAddress, localPort);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
-15
@@ -1,15 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import org.keycloak.provider.Provider;
|
|
||||||
|
|
||||||
import java.security.KeyStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public interface TruststoreProvider extends Provider {
|
|
||||||
|
|
||||||
HostnameVerificationPolicy getPolicy();
|
|
||||||
|
|
||||||
KeyStore getTruststore();
|
|
||||||
}
|
|
||||||
-9
@@ -1,9 +0,0 @@
|
|||||||
package org.keycloak.connections.truststore;
|
|
||||||
|
|
||||||
import org.keycloak.provider.ProviderFactory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public interface TruststoreProviderFactory extends ProviderFactory<TruststoreProvider> {
|
|
||||||
}
|
|
||||||
Loaded 100 of 3425 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user