Compare commits

..
51 Commits
Author SHA1 Message Date
Stian Thorgersen 25322e3a7c Bump version 2015-06-18 12:29:03 +02:00
Stian Thorgersen ba6a67910b Merge pull request #1400 from stianst/1.3.x
1.3.x
2015-06-17 19:30:39 +02:00
Stian Thorgersen b435ed2a7a KEYCLOAK-1476 2015-06-17 19:28:58 +02:00
Stian Thorgersen 36d52c4436 KEYCLOAK-1476 NotSerializableException: org.keycloak.models.cache.entities.CachedClientRole 2015-06-17 19:25:41 +02:00
Stian Thorgersen de84c73468 Merge pull request #1399 from mstruk/KEYCLOAK-1476
KEYCLOAK-1476 NotSerializableException: org.keycloak.models.cache.ent…
2015-06-17 19:25:15 +02:00
Marko Strukelj 12e1c3435e KEYCLOAK-1476 NotSerializableException: org.keycloak.models.cache.entities.CachedClientRole 2015-06-17 17:55:58 +02:00
Stian Thorgersen 6ede13ae34 Merge pull request #1398 from stianst/1.3.x
KEYCLOAK-1479
2015-06-17 12:54:10 +01:00
Stian Thorgersen aceb32997b KEYCLOAK-1479 2015-06-17 13:52:31 +02:00
Marek Posolda 9c4df5a34a Merge pull request #1396 from mposolda/1.3.x
Reduce liquibase logging during test to debug
2015-06-17 12:14:29 +02:00
mposolda 629a20c82f Reduce liquibase logging during test to debug 2015-06-17 12:13:00 +02:00
Stian Thorgersen 51d02352a1 Merge pull request #1395 from mstruk/examples-docs
KEYCLOAK-1442 Fix examples/demo-template/README.md
2015-06-17 06:28:52 +01:00
Marko Strukelj 9254e04e9c KEYCLOAK-1442 Fix examples/demo-template/README.md
- fixed all examples READMEs
2015-06-17 01:02:53 +02:00
Stian Thorgersen cc1056fd2d Merge pull request #1394 from stianst/1.3.x
1.3.x
2015-06-16 14:58:12 +01:00
Stian Thorgersen 0b79337afc Fix demo-dist build prob 2015-06-16 15:57:14 +02:00
Stian Thorgersen 3b5bdae426 Make it possible to specify ldif file for LDAPEmbeddedServer 2015-06-16 15:57:14 +02:00
Stian Thorgersen 77b4b0ef8e Fix demo-dist build prob 2015-06-16 15:57:14 +02:00
Stian Thorgersen 2021143fce Merge pull request #1393 from mstruk/KEYCLOAK-1448
KEYCLOAK-1448 Cannot configure an authenticator for method KEYCLOAK
2015-06-16 14:21:24 +01:00
Marko Strukelj 2b25a1aaa2 KEYCLOAK-1448 Cannot configure an authenticator for method KEYCLOAK 2015-06-16 15:15:04 +02:00
Stian Thorgersen 3c83b791d9 Merge pull request #1391 from stianst/1.3.x
Add missing ExampleDS
2015-06-16 13:40:51 +01:00
Stian Thorgersen 44a1225de3 Add missing ExampleDS 2015-06-16 14:40:04 +02:00
Stian Thorgersen 5c2e27c87d Merge pull request #1387 from stianst/1.3.x
Fixes
2015-06-16 13:03:07 +01:00
Stian Thorgersen 5ecc62be95 KEYCLOAK-1446
Field ... of subresource ... will not be injected according to spec
2015-06-16 14:02:03 +02:00
Stian Thorgersen ff3853dfee Updated clustering docs 2015-06-16 13:32:13 +02:00
Stian Thorgersen 571a190b45 KEYCLOAK-1468
Infinispan cache not available in JNDI
2015-06-16 13:32:13 +02:00
Stian Thorgersen 23fab43db0 Recover server-overlay name 2015-06-16 13:32:13 +02:00
Bill Burke f8ac2501f7 remove auth flow setup 2015-06-16 06:12:15 -04:00
Bill Burke 6116885166 remove auth flow migration 2015-06-16 06:04:21 -04:00
Stian Thorgersen 1a8adaf857 Merge pull request #1385 from stianst/1.3.x
Fix event-store example
2015-06-16 06:21:45 +01:00
Stian Thorgersen e4a6ccf596 Fix event-store example 2015-06-15 20:25:12 +02:00
Stian Thorgersen 97f48ba66c Merge pull request #1381 from stianst/1.3.x
Added wildfly extension to dist
2015-06-15 19:13:44 +01:00
Stian Thorgersen bf667dba65 Added wildfly extension to dist 2015-06-15 20:13:06 +02:00
Stian Thorgersen 90b321726b Merge pull request #1379 from stianst/1.3.x
Fixes
2015-06-15 15:04:05 +01:00
Stian Thorgersen 9e5704a181 Update keycloak-server.json 2015-06-15 15:49:39 +02:00
Stian Thorgersen 41c850999a KEYCLOAK-1449 Demo bundle doesn't include subsystem configuration 2015-06-15 15:01:35 +02:00
Stian Thorgersen e793d8f2e1 Merge pull request #1377 from stianst/1.3.x
Fixes
2015-06-15 14:00:05 +01:00
Stian Thorgersen afa0a3a0b4 Fix CDI examples 2015-06-15 14:21:12 +02:00
Stian Thorgersen 3b2f84ecf5 KEYCLOAK-1447 LinkageError deploying oauth-client-cdi to WildFly 8.2.0.Final 2015-06-15 13:22:21 +02:00
Stian Thorgersen 964106fa42 Update adapter installation docs 2015-06-15 09:04:04 +02:00
Stian Thorgersen 05fca83d5a Renamed eap6 server overlay poms 2015-06-15 09:04:04 +02:00
Stian Thorgersen dac5e51875 Merge pull request #1375 from stianst/1.3.x
Fix testsuite/docker-cluster deps
2015-06-15 06:41:25 +01:00
Stian Thorgersen aa36572b27 Fix testsuite/docker-cluster deps 2015-06-15 07:40:04 +02:00
Stian Thorgersen e2e4cd4d86 Merge pull request #1370 from stianst/1.3.x
KEYCLOAK-1411
2015-06-15 06:32:33 +01:00
Marek Posolda 92b3d5fcc6 Merge pull request #1373 from mposolda/1.3.x
KEYCLOAK-1260 Upgrade h2 version to fix locking issues in SAMLKeyCloa…
2015-06-12 19:55:49 +02:00
mposolda 1f1eef69ff KEYCLOAK-1260 Upgrade h2 version to fix locking issues in SAMLKeyCloakServerBrokerWithSignatureTest 2015-06-12 19:40:56 +02:00
Marek Posolda a9d0a938ba Merge pull request #1372 from mposolda/1.3.x
KEYCLOAK-1298 Fix constraint names to be uppercased
2015-06-12 19:15:42 +02:00
mposolda c0d8df381a KEYCLOAK-1298 Fix constraint names to be uppercased 2015-06-12 18:59:49 +02:00
Marek Posolda 984e474d01 Merge pull request #1371 from mposolda/1.3.x
Fix Oracle together with Sybase
2015-06-12 17:19:07 +02:00
mposolda 33eca6e50c Enable liquibase trace logging with JPA profile 2015-06-12 16:59:04 +02:00
Stian Thorgersen b9d69561dc Update server and adapter install docs 2015-06-12 16:13:51 +02:00
mposolda 8dc3cbd294 Fix Oracle together with Sybase 2015-06-12 16:08:21 +02:00
Stian Thorgersen c93addc3a9 KEYCLOAK-1411
DefaultCacheUserProvider addUser returns UserModel instance which is not cached/managed by the cache
2015-06-12 15:55:40 +02:00
3741 changed files with 177857 additions and 290964 deletions

No files matched your search

-20
View File
@@ -1,20 +0,0 @@
* text=auto
*.html text eol=lf
*.java text eol=lf
*.js text eol=lf
*.json text eol=lf
*.jsp text eol=lf
*.md text eol=lf
*.properties text eol=lf
*.svg text auto
*.xml text eol=lf
*.xsl text eol=lf
*.png binary
*.jpg binary
*.gif binary
*.ttf binary
*.eot binary
*.otf binary
*.woff binary
+5 -16
View File
@@ -1,21 +1,10 @@
language: java language: java
env: cache:
global: directories:
- MAVEN_SKIP_RC=true - $HOME/.m2
- MAVEN_OPTS="-Xms512m -Xmx2048m"
jdk: before_cache:
- oraclejdk8 - rm -rf $HOME/.m2/repository/org/keycloak
before_script:
- export MAVEN_SKIP_RC=true
install:
- mvn install -Pdistribution -DskipTests=true -B -V -q
script:
- mvn test -B
- mvn -file testsuite/integration-arquillian test -B -Pno-console
sudo: false sudo: false
+1 -1
View File
@@ -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
+50 -50
View File
@@ -1,50 +1,50 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-broker-core</artifactId> <artifactId>keycloak-broker-core</artifactId>
<name>Keycloak Broker Core</name> <name>Keycloak Broker Core</name>
<description/> <description/>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId> <artifactId>keycloak-events-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.resteasy</groupId> <groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId> <artifactId>resteasy-jaxrs</artifactId>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>log4j</groupId> <groupId>log4j</groupId>
<artifactId>log4j</artifactId> <artifactId>log4j</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
</exclusion> </exclusion>
<exclusion> <exclusion>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId> <artifactId>slf4j-simple</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>
@@ -58,11 +58,6 @@ public abstract class AbstractIdentityProvider<C extends IdentityProviderModel>
return null; return null;
} }
@Override
public Response performLogin(AuthenticationRequest request) {
return null;
}
@Override @Override
public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) { public Response keycloakInitiatedBrowserLogout(UserSessionModel userSession, UriInfo uriInfo, RealmModel realm) {
return null; return null;
@@ -92,9 +87,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,43 +1,38 @@
package org.keycloak.broker.provider; package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.broker.provider.IdentityProviderMapper;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.UserModel; import org.keycloak.models.RealmModel;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper { public abstract class AbstractIdentityProviderMapper implements IdentityProviderMapper {
@Override @Override
public void close() { public void close() {
} }
@Override @Override
public IdentityProviderMapper create(KeycloakSession session) { public IdentityProviderMapper create(KeycloakSession session) {
return null; return null;
} }
@Override @Override
public void init(org.keycloak.Config.Scope config) { public void init(org.keycloak.Config.Scope config) {
} }
@Override @Override
public void postInit(KeycloakSessionFactory factory) { public void postInit(KeycloakSessionFactory factory) {
} }
@Override @Override
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) {
}
}
@@ -17,14 +17,9 @@
*/ */
package org.keycloak.broker.provider; package org.keycloak.broker.provider;
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;
/** /**
@@ -48,7 +43,6 @@ public class BrokeredIdentityContext {
private IdentityProviderModel idpConfig; private IdentityProviderModel idpConfig;
private IdentityProvider idp; private IdentityProvider idp;
private Map<String, Object> contextData = new HashMap<>(); private Map<String, Object> contextData = new HashMap<>();
private ClientSessionModel clientSession;
public BrokeredIdentityContext(String id) { public BrokeredIdentityContext(String id) {
if (id == null) { if (id == null) {
@@ -156,22 +150,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;
} }
@@ -188,14 +166,6 @@ public class BrokeredIdentityContext {
this.lastName = lastName; this.lastName = lastName;
} }
public ClientSessionModel getClientSession() {
return clientSession;
}
public void setClientSession(ClientSessionModel clientSession) {
this.clientSession = clientSession;
}
public void setName(String name) { public void setName(String name) {
if (name != null) { if (name != null) {
int i = name.lastIndexOf(' '); int i = name.lastIndexOf(' ');
@@ -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);
}
}
}
@@ -1,89 +0,0 @@
package org.keycloak.broker.provider;
import org.keycloak.models.ClientModel;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedAttributeMapper extends AbstractIdentityProviderMapper {
public static final String ATTRIBUTE = "attribute";
public static final String ATTRIBUTE_VALUE = "attribute.value";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE);
property.setLabel("User Attribute");
property.setHelpText("Name of user attribute you want to hardcode");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE_VALUE);
property.setLabel("User Attribute Value");
property.setHelpText("Value you want to hardcode");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getDisplayCategory() {
return "Attribute Importer";
}
@Override
public String getDisplayType() {
return "Hardcoded Attribute";
}
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
public static final String PROVIDER_ID = "hardcoded-attribute-idp-mapper";
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
context.setUserAttribute(attribute, attributeValue);
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
user.setSingleAttribute(attribute, attributeValue);
}
@Override
public String getHelpText() {
return "When user is imported from provider, hardcode a value to a specific user attribute.";
}
}
@@ -1,79 +1,110 @@
package org.keycloak.broker.provider; package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.models.KeycloakSession; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.RealmModel; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.RoleModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.UserModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import java.util.ArrayList; import org.keycloak.models.UserModel;
import java.util.List; import org.keycloak.provider.ProviderConfigProperty;
/** import java.util.ArrayList;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> import java.util.List;
* @version $Revision: 1 $
*/ /**
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper { * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
public static final String ROLE = "role"; * @version $Revision: 1 $
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<>(); */
public class HardcodedRoleMapper extends AbstractIdentityProviderMapper {
static { public static final String ROLE = "role";
ProviderConfigProperty property; protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
property = new ProviderConfigProperty();
property.setName(ROLE); static {
property.setLabel("Role"); ProviderConfigProperty property;
property.setHelpText("Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); property = new ProviderConfigProperty();
property.setType(ProviderConfigProperty.ROLE_TYPE); property.setName(ROLE);
configProperties.add(property); property.setLabel("Role");
} property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
property.setType(ProviderConfigProperty.STRING_TYPE);
@Override configProperties.add(property);
public List<ProviderConfigProperty> getConfigProperties() { }
return configProperties;
}
@Override public static String[] parseRole(String role) {
public String getDisplayCategory() { int scopeIndex = role.indexOf('.');
return "Role Importer"; if (scopeIndex > -1) {
} String appName = role.substring(0, scopeIndex);
role = role.substring(scopeIndex + 1);
@Override String[] rtn = {appName, role};
public String getDisplayType() { return rtn;
return "Hardcoded Role"; } else {
} String[] rtn = {null, role};
return rtn;
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
}
}
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
public static RoleModel getRoleFromString(RealmModel realm, String roleName) {
@Override String[] parsedRole = parseRole(roleName);
public String getId() { RoleModel role = null;
return PROVIDER_ID; if (parsedRole[0] == null) {
} role = realm.getRole(parsedRole[1]);
} else {
@Override ClientModel client = realm.getClientByClientId(parsedRole[0]);
public String[] getCompatibleProviders() { role = client.getRole(parsedRole[1]);
return COMPATIBLE_PROVIDERS; }
} return role;
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { @Override
String roleName = mapperModel.getConfig().get(ROLE); public List<ProviderConfigProperty> getConfigProperties() {
RoleModel role = KeycloakModelUtils.getRoleFromString(realm, roleName); return configProperties;
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName); }
user.grantRole(role);
} @Override
public String getDisplayCategory() {
@Override return "Role Importer";
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { }
} @Override
public String getDisplayType() {
@Override return "Hardcoded Role";
public String getHelpText() { }
return "When user is imported from provider, hardcode a role mapping for it.";
} public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
}
public static final String PROVIDER_ID = "oidc-hardcoded-role-idp-mapper";
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String roleName = mapperModel.getConfig().get(ROLE);
RoleModel role = getRoleFromString(realm, roleName);
if (role == null) throw new IdentityBrokerException("Unable to find role: " + roleName);
user.grantRole(role);
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
}
@Override
public String getHelpText() {
return "When user is imported from provider, hardcode a role mapping for it.";
}
}
@@ -1,87 +0,0 @@
package org.keycloak.broker.provider;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class HardcodedUserSessionAttributeMapper extends AbstractIdentityProviderMapper {
public static final String ATTRIBUTE = "attribute";
public static final String ATTRIBUTE_VALUE = "attribute.value";
protected static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE);
property.setLabel("User Session Attribute");
property.setHelpText("Name of user session attribute you want to hardcode");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE_VALUE);
property.setLabel("User Session Attribute Value");
property.setHelpText("Value you want to hardcode");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public String getDisplayCategory() {
return "Attribute Importer";
}
@Override
public String getDisplayType() {
return "Hardcoded User Session Attribute";
}
public static final String[] COMPATIBLE_PROVIDERS = {ANY_PROVIDER};
public static final String PROVIDER_ID = "hardcoded-user-session-attribute-idp-mapper";
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
}
@Override
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
context.getClientSession().setUserSessionNote(attribute, attributeValue);
}
@Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(ATTRIBUTE);
String attributeValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
context.getClientSession().setUserSessionNote(attribute, attributeValue);
}
@Override
public String getHelpText() {
return "When user is imported from provider, hardcode a value to a specific user session attribute.";
}
}
@@ -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();
} }
@@ -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);
}
@@ -1,58 +1,56 @@
package org.keycloak.broker.provider; package org.keycloak.broker.provider;
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.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.provider.ConfiguredProvider; import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.Provider; import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.ProviderFactory;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider { public interface IdentityProviderMapper extends Provider, ProviderFactory<IdentityProviderMapper>,ConfiguredProvider {
public static final String ANY_PROVIDER = "*"; public static final String ANY_PROVIDER = "*";
String[] getCompatibleProviders(); String[] getCompatibleProviders();
String getDisplayCategory(); String getDisplayCategory();
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 realm
* @param session * @param mapperModel
* @param realm * @param context
* @param mapperModel */
* @param context 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 UserModel is created for first time for this user. Called after "FirstBrokerLogin" flow * @param session
* * @param realm
* @param session * @param user
* @param realm * @param mapperModel
* @param user * @param context
* @param mapperModel */
* @param context void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
*/
void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context); /**
* Called when this user has logged in before and has already been imported.
/** *
* Called when this user has logged in before and has already been imported. * @param session
* * @param realm
* @param session * @param user
* @param realm * @param mapperModel
* @param user * @param context
* @param mapperModel */
* @param context void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
*/
void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context);
}
}
@@ -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,3 +1 @@
org.keycloak.broker.provider.HardcodedRoleMapper org.keycloak.broker.provider.HardcodedRoleMapper
org.keycloak.broker.provider.HardcodedAttributeMapper
org.keycloak.broker.provider.HardcodedUserSessionAttributeMapper
@@ -1,2 +1,2 @@
org.keycloak.broker.provider.IdentityProviderSpi org.keycloak.broker.provider.IdentityProviderSpi
org.keycloak.broker.provider.IdentityProviderMapperSpi org.keycloak.broker.provider.IdentityProviderMapperSpi
+58 -58
View File
@@ -1,58 +1,58 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-broker-oidc</artifactId> <artifactId>keycloak-broker-oidc</artifactId>
<name>Keycloak Broker - OpenID Connect Identity Provider</name> <name>Keycloak Broker - OpenID Connect Identity Provider</name>
<description/> <description/>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-core</artifactId> <artifactId>keycloak-broker-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId> <artifactId>jackson-core-asl</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.codehaus.jackson</groupId> <groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId> <artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId> <artifactId>keycloak-events-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>
@@ -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,121 +1,114 @@
package org.keycloak.broker.oidc; package org.keycloak.broker.oidc;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import org.keycloak.broker.provider.BrokeredIdentityContext; 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; import org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.adapters.action.AdminAction;
import org.keycloak.representations.adapters.action.AdminAction; import org.keycloak.representations.adapters.action.LogoutAction;
import org.keycloak.representations.adapters.action.LogoutAction; import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.util.JsonSerialization;
import org.keycloak.util.JsonSerialization;
import javax.ws.rs.POST;
import javax.ws.rs.POST; import javax.ws.rs.Path;
import javax.ws.rs.Path; import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response; import java.io.IOException;
import java.io.IOException; import java.security.PublicKey;
import java.security.PublicKey;
/**
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $
* @version $Revision: 1 $ */
*/ public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
public class KeycloakOIDCIdentityProvider extends OIDCIdentityProvider {
public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
public static final String VALIDATED_ACCESS_TOKEN = "VALIDATED_ACCESS_TOKEN";
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) {
public KeycloakOIDCIdentityProvider(OIDCIdentityProviderConfig config) { super(config);
super(config); }
}
@Override
@Override public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) {
public Object callback(RealmModel realm, AuthenticationCallback callback, EventBuilder event) { return new KeycloakEndpoint(callback, realm, event);
return new KeycloakEndpoint(callback, realm, event); }
}
@Override
@Override protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) {
protected void processAccessTokenResponse(BrokeredIdentityContext context, PublicKey idpKey, AccessTokenResponse response) { JsonWebToken access = validateToken(idpKey, response.getToken());
JsonWebToken access = validateToken(idpKey, response.getToken()); context.getContextData().put(VALIDATED_ACCESS_TOKEN, access);
context.getContextData().put(VALIDATED_ACCESS_TOKEN, access); }
}
protected class KeycloakEndpoint extends OIDCEndpoint {
protected class KeycloakEndpoint extends OIDCEndpoint { public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) {
public KeycloakEndpoint(AuthenticationCallback callback, RealmModel realm, EventBuilder event) { super(callback, realm, event);
super(callback, realm, event); }
}
@POST
@POST @Path(AdapterConstants.K_LOGOUT)
@Path(AdapterConstants.K_LOGOUT) public Response backchannelLogout(String input) {
public Response backchannelLogout(String input) { JWSInput token = new JWSInput(input);
JWSInput token = null; PublicKey key = getExternalIdpKey();
try { if (key != null) {
token = new JWSInput(input); if (!verify(token, key)) {
} catch (JWSInputException e) { logger.warn("Failed to verify logout request");
logger.warn("Failed to verify logout request"); return Response.status(400).build();
return Response.status(400).build(); }
} }
PublicKey key = getExternalIdpKey(); LogoutAction action = null;
if (key != null) { try {
if (!verify(token, key)) { action = JsonSerialization.readValue(token.getContent(), LogoutAction.class);
logger.warn("Failed to verify logout request"); } catch (IOException e) {
return Response.status(400).build(); throw new RuntimeException(e);
} }
} if (!validateAction(action)) return Response.status(400).build();
LogoutAction action = null; if (action.getKeycloakSessionIds() != null) {
try { for (String sessionId : action.getKeycloakSessionIds()) {
action = JsonSerialization.readValue(token.getContent(), LogoutAction.class); String brokerSessionId = getConfig().getAlias() + "." + sessionId;
} catch (IOException e) { UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
throw new RuntimeException(e); if (userSession != null
} && userSession.getState() != UserSessionModel.State.LOGGING_OUT
if (!validateAction(action)) return Response.status(400).build(); && userSession.getState() != UserSessionModel.State.LOGGED_OUT
if (action.getKeycloakSessionIds() != null) { ) {
for (String sessionId : action.getKeycloakSessionIds()) { AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
String brokerSessionId = getConfig().getAlias() + "." + sessionId; }
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId); }
if (userSession != null
&& userSession.getState() != UserSessionModel.State.LOGGING_OUT }
&& userSession.getState() != UserSessionModel.State.LOGGED_OUT return Response.ok().build();
) { }
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
} protected boolean validateAction(AdminAction action) {
} if (!action.validate()) {
logger.warn("admin request failed, not validated" + action.getAction());
} return false;
return Response.ok().build(); }
} if (action.isExpired()) {
logger.warn("admin request failed, expired token");
protected boolean validateAction(AdminAction action) { return false;
if (!action.validate()) { }
logger.warn("admin request failed, not validated" + action.getAction()); if (!getConfig().getClientId().equals(action.getResource())) {
return false; logger.warn("Resource name does not match");
} return false;
if (action.isExpired()) {
logger.warn("admin request failed, expired token"); }
return false; return true;
} }
if (!getConfig().getClientId().equals(action.getResource())) {
logger.warn("Resource name does not match"); @Override
return false; public SimpleHttp generateTokenRequest(String authorizationCode) {
return super.generateTokenRequest(authorizationCode)
} .param(AdapterConstants.CLIENT_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
return true;
} }
@Override
public SimpleHttp generateTokenRequest(String authorizationCode) {
return super.generateTokenRequest(authorizationCode) }
.param(AdapterConstants.CLIENT_SESSION_STATE, "n/a"); // hack to get backchannel logout to work
} }
}
}
@@ -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,41 @@ 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 aud = token.getAudience();
String iss = token.getIssuer();
if (!token.hasAudience(getConfig().getClientId())) { if (aud != null && !aud.equals(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
@@ -1,97 +1,97 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.JsonWebToken;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper { public abstract class AbstractClaimMapper extends AbstractIdentityProviderMapper {
public static final String CLAIM = "claim"; public static final String CLAIM = "claim";
public static final String CLAIM_VALUE = "claim.value"; public static final String CLAIM_VALUE = "claim.value";
public static Object getClaimValue(JsonWebToken token, String claim) { public static Object getClaimValue(JsonWebToken token, String claim) {
String[] split = claim.split("\\."); String[] split = claim.split("\\.");
Map<String, Object> jsonObject = token.getOtherClaims(); Map<String, Object> jsonObject = token.getOtherClaims();
for (int i = 0; i < split.length; i++) { for (int i = 0; i < split.length; i++) {
if (i == split.length - 1) { if (i == split.length - 1) {
return jsonObject.get(split[i]); return jsonObject.get(split[i]);
} else { } else {
Object val = jsonObject.get(split[i]); Object val = jsonObject.get(split[i]);
if (!(val instanceof Map)) return null; if (!(val instanceof Map)) return null;
jsonObject = (Map<String, Object>)val; jsonObject = (Map<String, Object>)val;
} }
} }
return null; return null;
} }
public static Object getClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public static Object getClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String claim = mapperModel.getConfig().get(CLAIM); String claim = mapperModel.getConfig().get(CLAIM);
return getClaimValue(context, claim); return getClaimValue(context, claim);
} }
public static Object getClaimValue(BrokeredIdentityContext context, String claim) { public static Object getClaimValue(BrokeredIdentityContext context, String claim) {
{ // search access token { // search access token
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN); JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ACCESS_TOKEN);
if (token != null) { if (token != null) {
Object value = getClaimValue(token, claim); Object value = getClaimValue(token, claim);
if (value != null) return value; if (value != null) return value;
} }
} }
{ // search ID Token { // search ID Token
JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN); JsonWebToken token = (JsonWebToken)context.getContextData().get(KeycloakOIDCIdentityProvider.VALIDATED_ID_TOKEN);
if (token != null) { if (token != null) {
Object value = getClaimValue(token, claim); Object value = getClaimValue(token, claim);
if (value != null) return value; if (value != null) return value;
} }
} }
return null; return null;
} }
protected boolean hasClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { protected boolean hasClaimValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
Object value = getClaimValue(mapperModel, context); Object value = getClaimValue(mapperModel, context);
String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE); String desiredValue = mapperModel.getConfig().get(CLAIM_VALUE);
return valueEquals(desiredValue, value); return valueEquals(desiredValue, value);
} }
public boolean valueEquals(String desiredValue, Object value) { public boolean valueEquals(String desiredValue, Object value) {
if (value instanceof String) { if (value instanceof String) {
if (desiredValue.equals(value)) return true; if (desiredValue.equals(value)) return true;
} else if (value instanceof Double) { } else if (value instanceof Double) {
try { try {
if (Double.valueOf(desiredValue).equals(value)) return true; if (Double.valueOf(desiredValue).equals(value)) return true;
} catch (Exception e) { } catch (Exception e) {
} }
} else if (value instanceof Integer) { } else if (value instanceof Integer) {
try { try {
if (Integer.valueOf(desiredValue).equals(value)) return true; if (Integer.valueOf(desiredValue).equals(value)) return true;
} catch (Exception e) { } catch (Exception e) {
} }
} else if (value instanceof Boolean) { } else if (value instanceof Boolean) {
try { try {
if (Boolean.valueOf(desiredValue).equals(value)) return true; if (Boolean.valueOf(desiredValue).equals(value)) return true;
} catch (Exception e) { } catch (Exception e) {
} }
} 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;
} }
} }
@@ -1,206 +1,206 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.broker.oidc.OIDCIdentityProvider;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
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.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
/** /**
* Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user * Abstract class for Social Provider mappers which allow mapping of JSON user profile field into Keycloak user
* attribute. Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who * attribute. Concrete mapper classes with own ID and provider mapping must be implemented for each social provider who
* uses {@link JsonNode} user profile. * uses {@link JsonNode} user profile.
* *
* @author Vlastimil Elias (velias at redhat dot com) * @author Vlastimil Elias (velias at redhat dot com)
*/ */
public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper { public abstract class AbstractJsonUserAttributeMapper extends AbstractIdentityProviderMapper {
protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class); protected static final Logger logger = Logger.getLogger(AbstractJsonUserAttributeMapper.class);
protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump"); protected static final Logger LOGGER_DUMP_USER_PROFILE = Logger.getLogger("org.keycloak.social.user_profile_dump");
private static final String JSON_PATH_DELIMITER = "."; private static final String JSON_PATH_DELIMITER = ".";
/** /**
* Config param where name of mapping source JSON User Profile field is stored. * Config param where name of mapping source JSON User Profile field is stored.
*/ */
public static final String CONF_JSON_FIELD = "jsonField"; public static final String CONF_JSON_FIELD = "jsonField";
/** /**
* Config param where name of mapping target USer attribute is stored. * Config param where name of mapping target USer attribute is stored.
*/ */
public static final String CONF_USER_ATTRIBUTE = "userAttribute"; public static final String CONF_USER_ATTRIBUTE = "userAttribute";
/** /**
* Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored. * Key in {@link BrokeredIdentityContext#getContextData()} where {@link JsonNode} with user profile is stored.
*/ */
public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO; public static final String CONTEXT_JSON_NODE = OIDCIdentityProvider.USER_INFO;
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { static {
ProviderConfigProperty property; ProviderConfigProperty property;
ProviderConfigProperty property1; ProviderConfigProperty property1;
property1 = new ProviderConfigProperty(); property1 = new ProviderConfigProperty();
property1.setName(CONF_JSON_FIELD); property1.setName(CONF_JSON_FIELD);
property1.setLabel("Social Profile JSON Field Path"); property1.setLabel("Social Profile JSON Field Path");
property1.setHelpText("Path of field in Social provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. Eg. 'contact.address[0].country'."); property1.setHelpText("Path of field in Social provider User Profile JSON data to get value from. You can use dot notation for nesting and square brackets for array index. Eg. 'contact.address[0].country'.");
property1.setType(ProviderConfigProperty.STRING_TYPE); property1.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property1); configProperties.add(property1);
property = new ProviderConfigProperty(); property = new ProviderConfigProperty();
property.setName(CONF_USER_ATTRIBUTE); property.setName(CONF_USER_ATTRIBUTE);
property.setLabel("User Attribute Name"); property.setLabel("User Attribute Name");
property.setHelpText("User attribute name to store information into."); property.setHelpText("User attribute name to store information into.");
property.setType(ProviderConfigProperty.STRING_TYPE); property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property); configProperties.add(property);
} }
/** /**
* Store used profile JsonNode into user context for later use by this mapper. Profile data are dumped into special logger if enabled also to allow investigation of the structure. * Store used profile JsonNode into user context for later use by this mapper. Profile data are dumped into special logger if enabled also to allow investigation of the structure.
* *
* @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) {
user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile); user.getContextData().put(AbstractJsonUserAttributeMapper.CONTEXT_JSON_NODE, profile);
if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled()) if (LOGGER_DUMP_USER_PROFILE.isDebugEnabled())
LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile); LOGGER_DUMP_USER_PROFILE.debug("User Profile JSON Data for provider "+provider+": " + profile);
} }
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; return configProperties;
} }
@Override @Override
public String getDisplayCategory() { public String getDisplayCategory() {
return "Attribute Importer"; return "Attribute Importer";
} }
@Override @Override
public String getDisplayType() { public String getDisplayType() {
return "Attribute Importer"; return "Attribute Importer";
} }
@Override @Override
public String getHelpText() { public String getHelpText() {
return "Import user profile information if it exists in Social provider JSON data into the specified user attribute."; return "Import user profile information if it exists in Social provider JSON data into the specified user attribute.";
} }
@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.setAttribute(attribute, value);
} }
} }
@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) {
// we do not update user profile from social provider // we do not update user profile from social provider
} }
protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { protected static String getJsonValue(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
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;
} }
JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE); JsonNode profileJsonNode = (JsonNode) context.getContextData().get(CONTEXT_JSON_NODE);
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;
} }
protected static String getJsonValue(JsonNode baseNode, String fieldPath) { protected static String getJsonValue(JsonNode baseNode, String fieldPath) {
logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode); logger.debug("Going to process JsonNode path " + fieldPath + " on data " + baseNode);
if (baseNode != null) { if (baseNode != null) {
int idx = fieldPath.indexOf(JSON_PATH_DELIMITER); int idx = fieldPath.indexOf(JSON_PATH_DELIMITER);
String currentFieldName = fieldPath; String currentFieldName = fieldPath;
if (idx > 0) { if (idx > 0) {
currentFieldName = fieldPath.substring(0, idx).trim(); currentFieldName = fieldPath.substring(0, idx).trim();
if (currentFieldName.isEmpty()) { if (currentFieldName.isEmpty()) {
logger.debug("JSON path is invalid " + fieldPath); logger.debug("JSON path is invalid " + fieldPath);
return null; return null;
} }
} }
String currentNodeName = currentFieldName; String currentNodeName = currentFieldName;
int arrayIndex = -1; int arrayIndex = -1;
if (currentFieldName.endsWith("]")) { if (currentFieldName.endsWith("]")) {
int bi = currentFieldName.indexOf("["); int bi = currentFieldName.indexOf("[");
if (bi == -1) { if (bi == -1) {
logger.debug("Invalid array index construct in " + currentFieldName); logger.debug("Invalid array index construct in " + currentFieldName);
return null; return null;
} }
try { try {
String is = currentFieldName.substring(bi+1, currentFieldName.length() - 1).trim(); String is = currentFieldName.substring(bi+1, currentFieldName.length() - 1).trim();
arrayIndex = Integer.parseInt(is); arrayIndex = Integer.parseInt(is);
} catch (Exception e) { } catch (Exception e) {
logger.debug("Invalid array index construct in " + currentFieldName); logger.debug("Invalid array index construct in " + currentFieldName);
return null; return null;
} }
currentNodeName = currentFieldName.substring(0,bi).trim(); currentNodeName = currentFieldName.substring(0,bi).trim();
} }
JsonNode currentNode = baseNode.get(currentNodeName); JsonNode currentNode = baseNode.get(currentNodeName);
if (arrayIndex > -1 && currentNode.isArray()) { if (arrayIndex > -1 && currentNode.isArray()) {
logger.debug("Going to take array node at index " + arrayIndex); logger.debug("Going to take array node at index " + arrayIndex);
currentNode = currentNode.get(arrayIndex); currentNode = currentNode.get(arrayIndex);
} }
if (currentNode == null) { if (currentNode == null) {
logger.debug("JsonNode not found for name " + currentFieldName); logger.debug("JsonNode not found for name " + currentFieldName);
return null; return null;
} }
if (idx < 0) { if (idx < 0) {
if (!currentNode.isValueNode()) { if (!currentNode.isValueNode()) {
logger.debug("JsonNode is not value node for name " + currentFieldName); logger.debug("JsonNode is not value node for name " + currentFieldName);
return null; return null;
} }
String ret = currentNode.asText(); String ret = currentNode.asText();
if (ret != null && !ret.trim().isEmpty()) if (ret != null && !ret.trim().isEmpty())
return ret.trim(); return ret.trim();
} else { } else {
return getJsonValue(currentNode, fieldPath.substring(idx + 1)); return getJsonValue(currentNode, fieldPath.substring(idx + 1));
} }
} }
return null; return null;
} }
} }
@@ -1,106 +1,105 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.HardcodedRoleMapper; import org.keycloak.broker.provider.HardcodedRoleMapper;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
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; import java.util.List;
import java.util.List;
/**
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $
* @version $Revision: 1 $ */
*/ public class ClaimToRoleMapper extends AbstractClaimMapper {
public class ClaimToRoleMapper extends AbstractClaimMapper {
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static {
static { ProviderConfigProperty property;
ProviderConfigProperty property; ProviderConfigProperty property1;
ProviderConfigProperty property1; property1 = new ProviderConfigProperty();
property1 = new ProviderConfigProperty(); property1.setName(CLAIM);
property1.setName(CLAIM); property1.setLabel("Claim");
property1.setLabel("Claim"); property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'."); property1.setType(ProviderConfigProperty.STRING_TYPE);
property1.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property1);
configProperties.add(property1); property1 = new ProviderConfigProperty();
property1 = new ProviderConfigProperty(); property1.setName(CLAIM_VALUE);
property1.setName(CLAIM_VALUE); property1.setLabel("Claim Value");
property1.setLabel("Claim Value"); property1.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array.");
property1.setHelpText("Value the claim must have. If the claim is an array, then the value must be contained in the array."); property1.setType(ProviderConfigProperty.STRING_TYPE);
property1.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property1);
configProperties.add(property1); property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(HardcodedRoleMapper.ROLE);
property.setName(HardcodedRoleMapper.ROLE); property.setLabel("Role");
property.setLabel("Role"); property.setHelpText("Role to grant to user if claim is present. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
property.setHelpText("Role to grant to user if claim is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.ROLE_TYPE); configProperties.add(property);
configProperties.add(property); }
}
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
public static final String PROVIDER_ID = "oidc-role-idp-mapper";
@Override
@Override public List<ProviderConfigProperty> getConfigProperties() {
public List<ProviderConfigProperty> getConfigProperties() { return configProperties;
return configProperties; }
}
@Override
@Override public String getId() {
public String getId() { return PROVIDER_ID;
return PROVIDER_ID; }
}
@Override
@Override public String[] getCompatibleProviders() {
public String[] getCompatibleProviders() { return COMPATIBLE_PROVIDERS;
return COMPATIBLE_PROVIDERS; }
}
@Override
@Override public String getDisplayCategory() {
public String getDisplayCategory() { return "Role Importer";
return "Role Importer"; }
}
@Override
@Override public String getDisplayType() {
public String getDisplayType() { return "Claim to Role";
return "Claim to Role"; }
}
@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(HardcodedRoleMapper.ROLE);
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE); if (hasClaimValue(mapperModel, context)) {
if (hasClaimValue(mapperModel, context)) { RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.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); }
} }
}
@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) { String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE); if (!hasClaimValue(mapperModel, context)) {
if (!hasClaimValue(mapperModel, context)) { RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.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); }
}
}
}
@Override
@Override public String getHelpText() {
public String getHelpText() { return "If a claim exists, grant the user the specified realm or application role.";
return "If a claim exists, grant the user the specified realm or application role."; }
}
}
}
@@ -1,119 +1,118 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProvider;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.HardcodedRoleMapper; import org.keycloak.broker.provider.HardcodedRoleMapper;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
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 org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.JsonWebToken;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List;
/**
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $
* @version $Revision: 1 $ */
*/ public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
public class ExternalKeycloakRoleToRoleMapper extends AbstractClaimMapper {
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); private static final String EXTERNAL_ROLE = "external.role";
private static final String EXTERNAL_ROLE = "external.role";
static {
static { ProviderConfigProperty property;
ProviderConfigProperty property; ProviderConfigProperty property1;
ProviderConfigProperty property1; property1 = new ProviderConfigProperty();
property1 = new ProviderConfigProperty(); property1.setName(EXTERNAL_ROLE);
property1.setName(EXTERNAL_ROLE); property1.setLabel("External role");
property1.setLabel("External role"); property1.setHelpText("External role to check for. To reference an application role the syntax is appname.approle, i.e. myapp.myrole.");
property1.setHelpText("External role to check for. To reference an application role the syntax is appname.approle, i.e. myapp.myrole."); property1.setType(ProviderConfigProperty.STRING_TYPE);
property1.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property1);
configProperties.add(property1); property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(HardcodedRoleMapper.ROLE);
property.setName(HardcodedRoleMapper.ROLE); property.setLabel("Role");
property.setLabel("Role"); property.setHelpText("Role to grant to user if external role is present. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
property.setHelpText("Role to grant to user if external role is present. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.ROLE_TYPE); configProperties.add(property);
configProperties.add(property); }
}
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
public static final String PROVIDER_ID = "keycloak-oidc-role-to-role-idp-mapper";
@Override
@Override public List<ProviderConfigProperty> getConfigProperties() {
public List<ProviderConfigProperty> getConfigProperties() { return configProperties;
return configProperties; }
}
@Override
@Override public String getId() {
public String getId() { return PROVIDER_ID;
return PROVIDER_ID; }
}
@Override
@Override public String[] getCompatibleProviders() {
public String[] getCompatibleProviders() { return COMPATIBLE_PROVIDERS;
return COMPATIBLE_PROVIDERS; }
}
@Override
@Override public String getDisplayCategory() {
public String getDisplayCategory() { return "Role Importer";
return "Role Importer"; }
}
@Override
@Override public String getDisplayType() {
public String getDisplayType() { return "External Role to Role";
return "External Role to Role"; }
}
@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) { RoleModel role = hasRole(realm, mapperModel, context);
RoleModel role = hasRole(realm, mapperModel, context); if (role != null) {
if (role != null) { user.grantRole(role);
user.grantRole(role); }
} }
}
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
private RoleModel hasRole(RealmModel realm,IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { 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 = HardcodedRoleMapper.parseRole(mapperModel.getConfig().get(EXTERNAL_ROLE));
String[] parseRole = KeycloakModelUtils.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) { claimName = "realm_access.roles";
claimName = "realm_access.roles"; } else {
} else { claimName = "resource_access." + parseRole[0] + ".roles";
claimName = "resource_access." + parseRole[0] + ".roles"; }
} Object claim = getClaimValue(token, claimName);
Object claim = getClaimValue(token, claimName); if (valueEquals(externalRoleName, claim)) {
if (valueEquals(externalRoleName, claim)) { RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.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; }
} return null;
return null; }
}
@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) { RoleModel role = hasRole(realm, mapperModel, context);
RoleModel role = hasRole(realm, mapperModel, context); if (role == null) {
if (role == null) { user.deleteRoleMapping(role);
user.deleteRoleMapping(role); }
} }
}
@Override
@Override public String getHelpText() {
public String getHelpText() { return "Looks for an external role in a keycloak access token. If external role exists, grant the user the specified realm or application role.";
return "Looks for an external role in a keycloak access token. If external role exists, grant the user the specified realm or application role."; }
}
}
}
@@ -1,98 +1,100 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.UserModel; import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import java.util.ArrayList; import org.keycloak.models.UserModel;
import java.util.Collection; import org.keycloak.provider.ProviderConfigProperty;
import java.util.List;
import java.util.ArrayList;
/** import java.util.List;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ /**
*/ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
public class UserAttributeMapper extends AbstractClaimMapper { * @version $Revision: 1 $
*/
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID}; public class UserAttributeMapper extends AbstractClaimMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
public static final String USER_ATTRIBUTE = "user.attribute"; private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
static { public static final String USER_ATTRIBUTE = "user.attribute";
ProviderConfigProperty property;
ProviderConfigProperty property1; static {
property1 = new ProviderConfigProperty(); ProviderConfigProperty property;
property1.setName(CLAIM); ProviderConfigProperty property1;
property1.setLabel("Claim"); property1 = new ProviderConfigProperty();
property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'."); property1.setName(CLAIM);
property1.setType(ProviderConfigProperty.STRING_TYPE); property1.setLabel("Claim");
configProperties.add(property1); property1.setHelpText("Name of claim to search for in token. You can reference nested claims using a '.', i.e. 'address.locality'.");
property = new ProviderConfigProperty(); property1.setType(ProviderConfigProperty.STRING_TYPE);
property.setName(USER_ATTRIBUTE); configProperties.add(property1);
property.setLabel("User Attribute Name"); property = new ProviderConfigProperty();
property.setHelpText("User attribute name to store claim."); property.setName(USER_ATTRIBUTE);
property.setType(ProviderConfigProperty.STRING_TYPE); property.setLabel("User Attribute Name");
configProperties.add(property); property.setHelpText("User attribute name to store claim.");
} property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper"; }
@Override public static final String PROVIDER_ID = "oidc-user-attribute-idp-mapper";
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; @Override
} public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
@Override }
public String getId() {
return PROVIDER_ID; @Override
} public String getId() {
return PROVIDER_ID;
@Override }
public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS; @Override
} public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS;
@Override }
public String getDisplayCategory() {
return "Attribute Importer"; @Override
} public String getDisplayCategory() {
return "Attribute Importer";
@Override }
public String getDisplayType() {
return "Attribute Importer"; @Override
} public String getDisplayType() {
return "Attribute Importer";
@Override }
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE); @Override
Object value = getClaimValue(mapperModel, context); public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
if (value != null) { String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
context.setUserAttribute(attribute, value.toString()); Object value = getClaimValue(mapperModel, context);
} if (value != null) {
} user.setAttribute(attribute, value.toString());
}
@Override }
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE); @Override
Object value = getClaimValue(mapperModel, context); public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String current = user.getFirstAttribute(attribute); String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
if (value != null && !value.equals(current)) { Object value = getClaimValue(mapperModel, context);
user.setSingleAttribute(attribute, value.toString()); String current = user.getAttribute(attribute);
} else if (value == null) { if (value != null && !value.equals(current)) {
user.removeAttribute(attribute); user.setAttribute(attribute, value.toString());
} } else if (value == null) {
} user.removeAttribute(attribute);
}
@Override }
public String getHelpText() {
return "Import declared claim if it exists in ID or access token into the specified user attribute."; @Override
} public String getHelpText() {
return "Import declared claim if it exists in ID or access token into the specified user attribute.";
} }
}
@@ -1,105 +1,109 @@
package org.keycloak.broker.oidc.mappers; package org.keycloak.broker.oidc.mappers;
import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory; import org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory;
import org.keycloak.broker.oidc.OIDCIdentityProviderFactory; import org.keycloak.broker.oidc.OIDCIdentityProviderFactory;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
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.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UsernameTemplateMapper extends AbstractClaimMapper { public class UsernameTemplateMapper extends AbstractClaimMapper {
public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID}; public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
public static final String TEMPLATE = "template"; public static final String TEMPLATE = "template";
static { static {
ProviderConfigProperty property; ProviderConfigProperty property;
property = new ProviderConfigProperty(); property = new ProviderConfigProperty();
property.setName(TEMPLATE); property.setName(TEMPLATE);
property.setLabel("Template"); property.setLabel("Template");
property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM.<NAME> references an ID or Access token claim."); property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM.<NAME> references an ID or Access token claim.");
property.setType(ProviderConfigProperty.STRING_TYPE); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setDefaultValue("${ALIAS}.${CLAIM.preferred_username}"); property.setDefaultValue("${ALIAS}.${CLAIM.preferred_username}");
configProperties.add(property); configProperties.add(property);
} }
public static final String PROVIDER_ID = "oidc-username-idp-mapper"; public static final String PROVIDER_ID = "oidc-username-idp-mapper";
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; return configProperties;
} }
@Override @Override
public String getId() { public String getId() {
return PROVIDER_ID; return PROVIDER_ID;
} }
@Override @Override
public String[] getCompatibleProviders() { public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS; return COMPATIBLE_PROVIDERS;
} }
@Override @Override
public String getDisplayCategory() { public String getDisplayCategory() {
return "Preprocessor"; return "Preprocessor";
} }
@Override @Override
public String getDisplayType() { public String getDisplayType() {
return "Username Template Importer"; return "Username Template Importer";
} }
@Override @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
} }
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}"); @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
@Override }
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String template = mapperModel.getConfig().get(TEMPLATE); static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher m = substitution.matcher(template);
StringBuffer sb = new StringBuffer(); @Override
while (m.find()) { public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String variable = m.group(1); String template = mapperModel.getConfig().get(TEMPLATE);
if (variable.equals("ALIAS")) { Matcher m = substitution.matcher(template);
m.appendReplacement(sb, context.getIdpConfig().getAlias()); StringBuffer sb = new StringBuffer();
} else if (variable.equals("UUID")) { while (m.find()) {
m.appendReplacement(sb, KeycloakModelUtils.generateId()); String variable = m.group(1);
} else if (variable.startsWith("CLAIM.")) { if (variable.equals("ALIAS")) {
String name = variable.substring("CLAIM.".length()); m.appendReplacement(sb, context.getIdpConfig().getAlias());
Object value = AbstractClaimMapper.getClaimValue(context, name); } else if (variable.equals("UUID")) {
if (value == null) value = ""; m.appendReplacement(sb, KeycloakModelUtils.generateId());
m.appendReplacement(sb, value.toString()); } else if (variable.startsWith("CLAIM.")) {
} else { String name = variable.substring("CLAIM.".length());
m.appendReplacement(sb, m.group(1)); Object value = AbstractClaimMapper.getClaimValue(context, name);
} if (value == null) value = "";
m.appendReplacement(sb, value.toString());
} } else {
m.appendTail(sb); m.appendReplacement(sb, m.group(1));
String username = sb.toString(); }
context.setModelUsername(username);
}
} m.appendTail(sb);
String username = sb.toString();
@Override context.setModelUsername(username);
public String getHelpText() {
return "Format the username to import."; }
}
} @Override
public String getHelpText() {
return "Format the username to import.";
}
}
@@ -1,32 +1,32 @@
package org.keycloak.broker.oidc.util; package org.keycloak.broker.oidc.util;
import org.codehaus.jackson.JsonNode; import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.ObjectMapper;
import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.provider.util.SimpleHttp;
import java.io.IOException; import java.io.IOException;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class JsonSimpleHttp extends SimpleHttp { public class JsonSimpleHttp extends SimpleHttp {
public JsonSimpleHttp(String url, String method) { public JsonSimpleHttp(String url, String method) {
super(url, method); super(url, method);
} }
public static JsonSimpleHttp doGet(String url) { public static JsonSimpleHttp doGet(String url) {
return new JsonSimpleHttp(url, "GET"); return new JsonSimpleHttp(url, "GET");
} }
public static JsonSimpleHttp doPost(String url) { public static JsonSimpleHttp doPost(String url) {
return new JsonSimpleHttp(url, "POST"); return new JsonSimpleHttp(url, "POST");
} }
private static ObjectMapper mapper = new ObjectMapper(); private static ObjectMapper mapper = new ObjectMapper();
public static JsonNode asJson(SimpleHttp request) throws IOException { public static JsonNode asJson(SimpleHttp request) throws IOException {
return mapper.readTree(request.asString()); return mapper.readTree(request.asString());
} }
} }
@@ -1,2 +1,2 @@
org.keycloak.broker.oidc.OIDCIdentityProviderFactory org.keycloak.broker.oidc.OIDCIdentityProviderFactory
org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory org.keycloak.broker.oidc.KeycloakOIDCIdentityProviderFactory
@@ -1,4 +1,4 @@
org.keycloak.broker.oidc.mappers.ClaimToRoleMapper org.keycloak.broker.oidc.mappers.ClaimToRoleMapper
org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper
org.keycloak.broker.oidc.mappers.UserAttributeMapper org.keycloak.broker.oidc.mappers.UserAttributeMapper
org.keycloak.broker.oidc.mappers.UsernameTemplateMapper org.keycloak.broker.oidc.mappers.UsernameTemplateMapper
+23 -23
View File
@@ -1,23 +1,23 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-broker-parent</artifactId> <artifactId>keycloak-broker-parent</artifactId>
<name>Keycloak Broker Parent</name> <name>Keycloak Broker Parent</name>
<description/> <description/>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>
<module>core</module> <module>core</module>
<module>oidc</module> <module>oidc</module>
<module>saml</module> <module>saml</module>
</modules> </modules>
</project> </project>
+50 -55
View File
@@ -1,55 +1,50 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-broker-saml</artifactId> <artifactId>keycloak-broker-saml</artifactId>
<name>Keycloak Broker - SAML Identity Provider</name> <name>Keycloak Broker - SAML Identity Provider</name>
<description/> <description/>
<packaging>jar</packaging> <packaging>jar</packaging>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-broker-core</artifactId> <artifactId>keycloak-broker-core</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-protocol</artifactId> <artifactId>keycloak-saml-protocol</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-saml-core</artifactId> <artifactId>keycloak-saml-core</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-events-api</artifactId> <artifactId>keycloak-events-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> </dependencies>
<groupId>junit</groupId>
<artifactId>junit</artifactId> </project>
<scope>test</scope>
</dependency>
</dependencies>
</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,447 +1,491 @@
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; import org.keycloak.dom.saml.v2.assertion.AssertionType;
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;
import org.keycloak.dom.saml.v2.protocol.RequestAbstractType; import org.keycloak.dom.saml.v2.protocol.RequestAbstractType;
import org.keycloak.dom.saml.v2.protocol.ResponseType; import org.keycloak.dom.saml.v2.protocol.ResponseType;
import org.keycloak.dom.saml.v2.protocol.StatusResponseType; import org.keycloak.dom.saml.v2.protocol.StatusResponseType;
import org.keycloak.events.Details; import org.keycloak.events.Details;
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;
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.processing.core.saml.v2.common.SAMLDocumentHolder; import org.keycloak.saml.common.util.DocumentUtil;
import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants; import org.keycloak.saml.common.util.StaxParserUtil;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil; import org.keycloak.saml.processing.api.saml.v2.response.SAML2Response;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil; import org.keycloak.saml.processing.core.parsers.saml.SAMLParser;
import org.keycloak.saml.processing.web.util.PostBindingUtil; import org.keycloak.saml.processing.core.saml.v2.common.SAMLDocumentHolder;
import org.keycloak.services.ErrorPage; import org.keycloak.saml.processing.core.saml.v2.constants.X500SAMLProfileConstants;
import org.keycloak.services.managers.AuthenticationManager; import org.keycloak.saml.processing.core.util.JAXPValidationUtil;
import org.keycloak.services.messages.Messages; import org.keycloak.saml.processing.core.util.XMLEncryptionUtil;
import org.keycloak.saml.processing.core.util.XMLSignatureUtil;
import javax.ws.rs.Consumes; import org.keycloak.saml.processing.web.util.PostBindingUtil;
import javax.ws.rs.FormParam; import org.keycloak.services.ErrorPage;
import javax.ws.rs.GET; import org.keycloak.services.managers.AuthenticationManager;
import javax.ws.rs.POST; import org.keycloak.services.messages.Messages;
import javax.ws.rs.Path; import org.w3c.dom.Document;
import javax.ws.rs.QueryParam; import org.w3c.dom.Element;
import javax.ws.rs.core.Context; import org.w3c.dom.Node;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType; import javax.ws.rs.Consumes;
import javax.ws.rs.core.Response; import javax.ws.rs.FormParam;
import javax.ws.rs.core.UriBuilder; import javax.ws.rs.GET;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.POST;
import java.io.IOException; import javax.ws.rs.QueryParam;
import java.security.PublicKey; import javax.ws.rs.core.Context;
import java.security.cert.X509Certificate; import javax.ws.rs.core.HttpHeaders;
import java.util.List; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
/** import javax.ws.rs.core.UriBuilder;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> import javax.ws.rs.core.UriInfo;
* @version $Revision: 1 $ import javax.xml.namespace.QName;
*/ import java.io.IOException;
public class SAMLEndpoint { import java.security.PrivateKey;
protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class); import java.security.PublicKey;
public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX"; import java.security.cert.X509Certificate;
public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT"; import java.util.List;
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE"; /**
public static final String SAML_ASSERTION = "SAML_ASSERTION"; * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT"; * @version $Revision: 1 $
protected RealmModel realm; */
protected EventBuilder event; public class SAMLEndpoint {
protected SAMLIdentityProviderConfig config; protected static final Logger logger = Logger.getLogger(SAMLEndpoint.class);
protected IdentityProvider.AuthenticationCallback callback; public static final String SAML_FEDERATED_SESSION_INDEX = "SAML_FEDERATED_SESSION_INDEX";
protected SAMLIdentityProvider provider; public static final String SAML_FEDERATED_SUBJECT = "SAML_FEDERATED_SUBJECT";
public static final String SAML_FEDERATED_SUBJECT_NAMEFORMAT = "SAML_FEDERATED_SUBJECT_NAMEFORMAT";
@Context public static final String SAML_LOGIN_RESPONSE = "SAML_LOGIN_RESPONSE";
private UriInfo uriInfo; public static final String SAML_ASSERTION = "SAML_ASSERTION";
public static final String SAML_AUTHN_STATEMENT = "SAML_AUTHN_STATEMENT";
@Context protected RealmModel realm;
private KeycloakSession session; protected EventBuilder event;
protected SAMLIdentityProviderConfig config;
@Context protected IdentityProvider.AuthenticationCallback callback;
private ClientConnection clientConnection; protected SAMLIdentityProvider provider;
@Context @Context
private HttpHeaders headers; private UriInfo uriInfo;
@Context
public SAMLEndpoint(RealmModel realm, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) { private KeycloakSession session;
this.realm = realm;
this.config = config; @Context
this.callback = callback; private ClientConnection clientConnection;
this.provider = provider;
} @Context
private HttpHeaders headers;
@GET
@NoCache
@Path("descriptor") public SAMLEndpoint(RealmModel realm, SAMLIdentityProvider provider, SAMLIdentityProviderConfig config, IdentityProvider.AuthenticationCallback callback) {
public Response getSPDescriptor() { this.realm = realm;
return provider.export(uriInfo, realm, null); this.config = config;
} this.callback = callback;
this.provider = provider;
@GET }
public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
@QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @GET
@QueryParam(GeneralConstants.RELAY_STATE) String relayState) { public Response redirectBinding(@QueryParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
return new RedirectBinding().execute(samlRequest, samlResponse, relayState); @QueryParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
} @QueryParam(GeneralConstants.RELAY_STATE) String relayState) {
return new RedirectBinding().execute(samlRequest, samlResponse, relayState);
}
/**
*/
@POST /**
@Consumes(MediaType.APPLICATION_FORM_URLENCODED) */
public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest, @POST
@FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse, @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
@FormParam(GeneralConstants.RELAY_STATE) String relayState) { public Response postBinding(@FormParam(GeneralConstants.SAML_REQUEST_KEY) String samlRequest,
return new PostBinding().execute(samlRequest, samlResponse, relayState); @FormParam(GeneralConstants.SAML_RESPONSE_KEY) String samlResponse,
} @FormParam(GeneralConstants.RELAY_STATE) String relayState) {
return new PostBinding().execute(samlRequest, samlResponse, relayState);
protected abstract class Binding { }
private boolean checkSsl() {
if (uriInfo.getBaseUri().getScheme().equals("https")) { protected abstract class Binding {
return true; private boolean checkSsl() {
} else { if (uriInfo.getBaseUri().getScheme().equals("https")) {
return !realm.getSslRequired().isRequired(clientConnection); return true;
} } else {
} return !realm.getSslRequired().isRequired(clientConnection);
}
protected Response basicChecks(String samlRequest, String samlResponse) { }
if (!checkSsl()) {
event.event(EventType.LOGIN); protected Response basicChecks(String samlRequest, String samlResponse) {
event.error(Errors.SSL_REQUIRED); if (!checkSsl()) {
return ErrorPage.error(session, Messages.HTTPS_REQUIRED); event.event(EventType.LOGIN);
} event.error(Errors.SSL_REQUIRED);
if (!realm.isEnabled()) { return ErrorPage.error(session, Messages.HTTPS_REQUIRED);
event.event(EventType.LOGIN_ERROR); }
event.error(Errors.REALM_DISABLED); if (!realm.isEnabled()) {
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED); event.event(EventType.LOGIN_ERROR);
} event.error(Errors.REALM_DISABLED);
return ErrorPage.error(session, Messages.REALM_NOT_ENABLED);
if (samlRequest == null && samlResponse == null) { }
event.event(EventType.LOGIN);
event.error(Errors.INVALID_REQUEST); if (samlRequest == null && samlResponse == null) {
return ErrorPage.error(session, Messages.INVALID_REQUEST); event.event(EventType.LOGIN);
event.error(Errors.INVALID_REQUEST);
} return ErrorPage.error(session, Messages.INVALID_REQUEST);
return null;
} }
return null;
protected abstract String getBindingType(); }
protected abstract void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException;
protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest); protected abstract String getBindingType();
protected abstract SAMLDocumentHolder extractResponseDocument(String response); protected abstract void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException;
protected PublicKey getIDPKey() { protected abstract SAMLDocumentHolder extractRequestDocument(String samlRequest);
X509Certificate certificate = null; protected abstract SAMLDocumentHolder extractResponseDocument(String response);
try { protected PublicKey getIDPKey() {
certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", "")); X509Certificate certificate = null;
} catch (ProcessingException e) { try {
throw new RuntimeException(e); certificate = XMLSignatureUtil.getX509CertificateFromKeyInfoString(config.getSigningCertificate().replaceAll("\\s", ""));
} } catch (ProcessingException e) {
return certificate.getPublicKey(); throw new RuntimeException(e);
} }
return certificate.getPublicKey();
public Response execute(String samlRequest, String samlResponse, String relayState) { }
event = new EventBuilder(realm, session, clientConnection);
Response response = basicChecks(samlRequest, samlResponse); public Response execute(String samlRequest, String samlResponse, String relayState) {
if (response != null) return response; event = new EventBuilder(realm, session, clientConnection);
if (samlRequest != null) return handleSamlRequest(samlRequest, relayState); Response response = basicChecks(samlRequest, samlResponse);
else return handleSamlResponse(samlResponse, relayState); if (response != null) return response;
} if (samlRequest != null) return handleSamlRequest(samlRequest, relayState);
else return handleSamlResponse(samlResponse, relayState);
protected Response handleSamlRequest(String samlRequest, String relayState) { }
SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject(); protected Response handleSamlRequest(String samlRequest, String relayState) {
// validate destination SAMLDocumentHolder holder = extractRequestDocument(samlRequest);
if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) { RequestAbstractType requestAbstractType = (RequestAbstractType) holder.getSamlObject();
event.event(EventType.IDENTITY_PROVIDER_RESPONSE); // validate destination
event.detail(Details.REASON, "invalid_destination"); if (requestAbstractType.getDestination() != null && !uriInfo.getAbsolutePath().equals(requestAbstractType.getDestination())) {
event.error(Errors.INVALID_SAML_RESPONSE); event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
return ErrorPage.error(session, Messages.INVALID_REQUEST); event.detail(Details.REASON, "invalid_destination");
} event.error(Errors.INVALID_SAML_RESPONSE);
if (config.isValidateSignature()) { return ErrorPage.error(session, Messages.INVALID_REQUEST);
try { }
verifySignature(GeneralConstants.SAML_REQUEST_KEY, holder); if (config.isValidateSignature()) {
} catch (VerificationException e) { try {
logger.error("validation failed", e); verifySignature(holder);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE); } catch (VerificationException e) {
event.error(Errors.INVALID_SIGNATURE); logger.error("validation failed", e);
return ErrorPage.error(session, Messages.INVALID_REQUESTER); event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
} event.error(Errors.INVALID_SIGNATURE);
} return ErrorPage.error(session, Messages.INVALID_REQUESTER);
}
if (requestAbstractType instanceof LogoutRequestType) { }
logger.debug("** logout request");
event.event(EventType.LOGOUT); if (requestAbstractType instanceof LogoutRequestType) {
LogoutRequestType logout = (LogoutRequestType) requestAbstractType; logger.debug("** logout request");
return logoutRequest(logout, relayState); event.event(EventType.LOGOUT);
LogoutRequestType logout = (LogoutRequestType) requestAbstractType;
} else { return logoutRequest(logout, relayState);
event.event(EventType.LOGIN);
event.error(Errors.INVALID_TOKEN); } else {
return ErrorPage.error(session, Messages.INVALID_REQUEST); event.event(EventType.LOGIN);
} event.error(Errors.INVALID_TOKEN);
} return ErrorPage.error(session, Messages.INVALID_REQUEST);
}
protected Response logoutRequest(LogoutRequestType request, String relayState) { }
String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) { protected Response logoutRequest(LogoutRequestType request, String relayState) {
List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId); String brokerUserId = config.getAlias() + "." + request.getNameID().getValue();
for (UserSessionModel userSession : userSessions) { if (request.getSessionIndex() == null || request.getSessionIndex().isEmpty()) {
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { List<UserSessionModel> userSessions = session.sessions().getUserSessionByBrokerUserId(realm, brokerUserId);
continue; for (UserSessionModel userSession : userSessions) {
} if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
try { continue;
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false); }
} catch (Exception e) { try {
logger.warn("failed to do backchannel logout for userSession", e); AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
} } catch (Exception e) {
} logger.warn("failed to do backchannel logout for userSession", e);
}
} else { }
for (String sessionIndex : request.getSessionIndex()) {
String brokerSessionId = brokerUserId + "." + sessionIndex; } else {
UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId); for (String sessionIndex : request.getSessionIndex()) {
if (userSession != null) { String brokerSessionId = brokerUserId + "." + sessionIndex;
if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) { UserSessionModel userSession = session.sessions().getUserSessionByBrokerSessionId(realm, brokerSessionId);
continue; if (userSession != null) {
} if (userSession.getState() == UserSessionModel.State.LOGGING_OUT || userSession.getState() == UserSessionModel.State.LOGGED_OUT) {
try { continue;
AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false); }
} catch (Exception e) { try {
logger.warn("failed to do backchannel logout for userSession", e); AuthenticationManager.backchannelLogout(session, realm, userSession, uriInfo, clientConnection, headers, false);
} } catch (Exception e) {
} logger.warn("failed to do backchannel logout for userSession", e);
} }
} }
}
String issuerURL = getEntityId(uriInfo, realm); }
SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.logoutRequestID(request.getID()); String issuerURL = getEntityId(uriInfo, realm);
builder.destination(config.getSingleLogoutServiceUrl()); SAML2LogoutResponseBuilder builder = new SAML2LogoutResponseBuilder();
builder.issuer(issuerURL); builder.logoutRequestID(request.getID());
JaxrsSAML2BindingBuilder binding = new JaxrsSAML2BindingBuilder() builder.destination(config.getSingleLogoutServiceUrl());
.relayState(relayState); builder.issuer(issuerURL);
if (config.isWantAuthnRequestsSigned()) { builder.relayState(relayState);
binding.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate()) if (config.isWantAuthnRequestsSigned()) {
.signatureAlgorithm(provider.getSignatureAlgorithm()) builder.signWith(realm.getPrivateKey(), realm.getPublicKey(), realm.getCertificate())
.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);
} catch (ProcessingException e) { } catch (ProcessingException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private String getEntityId(UriInfo uriInfo, RealmModel realm) { private String getEntityId(UriInfo uriInfo, RealmModel realm) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString(); return UriBuilder.fromUri(uriInfo.getBaseUri()).path("realms").path(realm.getName()).build().toString();
} }
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();
//Map<String, String> notes = new HashMap<>(); //Map<String, String> notes = new HashMap<>();
BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue()); BrokeredIdentityContext identity = new BrokeredIdentityContext(subjectNameID.getValue());
identity.setCode(relayState); identity.setCode(relayState);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType); identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion); identity.getContextData().put(SAML_ASSERTION, assertion);
identity.setUsername(subjectNameID.getValue()); identity.setUsername(subjectNameID.getValue());
//SAML Spec 2.2.2 Format is optional if (subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) {
if (subjectNameID.getFormat() != null && subjectNameID.getFormat().toString().equals(JBossSAMLURIConstants.NAMEID_FORMAT_EMAIL.get())) { identity.setEmail(subjectNameID.getValue());
identity.setEmail(subjectNameID.getValue()); }
}
if (config.isStoreToken()) {
if (config.isStoreToken()) { identity.setToken(samlResponse);
identity.setToken(samlResponse); }
}
AuthnStatementType authn = null;
AuthnStatementType authn = null; for (Object statement : assertion.getStatements()) {
for (Object statement : assertion.getStatements()) { if (statement instanceof AuthnStatementType) {
if (statement instanceof AuthnStatementType) { authn = (AuthnStatementType)statement;
authn = (AuthnStatementType)statement; identity.getContextData().put(SAML_AUTHN_STATEMENT, authn);
identity.getContextData().put(SAML_AUTHN_STATEMENT, authn); break;
break; }
} }
} if (assertion.getAttributeStatements() != null ) {
if (assertion.getAttributeStatements() != null ) { for (AttributeStatementType attrStatement : assertion.getAttributeStatements()) {
for (AttributeStatementType attrStatement : assertion.getAttributeStatements()) { for (AttributeStatementType.ASTChoiceType choice : attrStatement.getAttributes()) {
for (AttributeStatementType.ASTChoiceType choice : attrStatement.getAttributes()) { AttributeType attribute = choice.getAttribute();
AttributeType attribute = choice.getAttribute(); if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attribute.getFriendlyName())
if (X500SAMLProfileConstants.EMAIL.getFriendlyName().equals(attribute.getFriendlyName()) || X500SAMLProfileConstants.EMAIL.get().equals(attribute.getName())) {
|| X500SAMLProfileConstants.EMAIL.get().equals(attribute.getName())) { if (!attribute.getAttributeValue().isEmpty()) identity.setEmail(attribute.getAttributeValue().get(0).toString());
if (!attribute.getAttributeValue().isEmpty()) identity.setEmail(attribute.getAttributeValue().get(0).toString()); }
} }
}
}
}
}
} String brokerUserId = config.getAlias() + "." + subjectNameID.getValue();
String brokerUserId = config.getAlias() + "." + subjectNameID.getValue(); identity.setBrokerUserId(brokerUserId);
identity.setBrokerUserId(brokerUserId); identity.setIdpConfig(config);
identity.setIdpConfig(config); identity.setIdp(provider);
identity.setIdp(provider); if (authn != null && authn.getSessionIndex() != null) {
if (authn != null && authn.getSessionIndex() != null) { identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex());
identity.setBrokerSessionId(identity.getBrokerUserId() + "." + authn.getSessionIndex()); }
}
return callback.authenticated(identity);
return callback.authenticated(identity);
} catch (Exception e) {
} catch (Exception e) { throw new IdentityBrokerException("Could not process response from SAML identity provider.", e);
throw new IdentityBrokerException("Could not process response from SAML identity provider.", e); }
} }
}
private AssertionType getAssertion(ResponseType responseType) throws ProcessingException {
List<ResponseType.RTChoiceType> assertions = responseType.getAssertions();
public Response handleSamlResponse(String samlResponse, String relayState) {
SAMLDocumentHolder holder = extractResponseDocument(samlResponse); if (assertions.isEmpty()) {
StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject(); throw new IdentityBrokerException("No assertion from response.");
// validate destination }
if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
event.event(EventType.IDENTITY_PROVIDER_RESPONSE); ResponseType.RTChoiceType rtChoiceType = assertions.get(0);
event.detail(Details.REASON, "invalid_destination"); EncryptedAssertionType encryptedAssertion = rtChoiceType.getEncryptedAssertion();
event.error(Errors.INVALID_SAML_RESPONSE);
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION); if (encryptedAssertion != null) {
} decryptAssertion(responseType, realm.getPrivateKey());
if (config.isValidateSignature()) {
try { }
verifySignature(GeneralConstants.SAML_RESPONSE_KEY, holder); return responseType.getAssertions().get(0).getAssertion();
} catch (VerificationException e) { }
logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE); public Response handleSamlResponse(String samlResponse, String relayState) {
event.error(Errors.INVALID_SIGNATURE); SAMLDocumentHolder holder = extractResponseDocument(samlResponse);
return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION); StatusResponseType statusResponse = (StatusResponseType)holder.getSamlObject();
} // validate destination
} if (statusResponse.getDestination() != null && !uriInfo.getAbsolutePath().toString().equals(statusResponse.getDestination())) {
if (statusResponse instanceof ResponseType) { event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState); event.detail(Details.REASON, "invalid_destination");
event.error(Errors.INVALID_SAML_RESPONSE);
} else { return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
// todo need to check that it is actually a LogoutResponse }
return handleLogoutResponse(holder, statusResponse, relayState); if (config.isValidateSignature()) {
} try {
//throw new RuntimeException("Unknown response type"); verifySignature(holder);
} catch (VerificationException e) {
} logger.error("validation failed", e);
event.event(EventType.IDENTITY_PROVIDER_RESPONSE);
protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) { event.error(Errors.INVALID_SIGNATURE);
if (relayState == null) { return ErrorPage.error(session, Messages.INVALID_FEDERATED_IDENTITY_ACTION);
logger.error("no valid user session"); }
event.event(EventType.LOGOUT); }
event.error(Errors.USER_SESSION_NOT_FOUND); if (statusResponse instanceof ResponseType) {
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR); return handleLoginResponse(samlResponse, holder, (ResponseType)statusResponse, relayState);
}
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState); } else {
if (userSession == null) { // todo need to check that it is actually a LogoutResponse
logger.error("no valid user session"); return handleLogoutResponse(holder, statusResponse, relayState);
event.event(EventType.LOGOUT); }
event.error(Errors.USER_SESSION_NOT_FOUND); //throw new RuntimeException("Unknown response type");
return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
} }
if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
logger.error("usersession in different state"); protected Response handleLogoutResponse(SAMLDocumentHolder holder, StatusResponseType responseType, String relayState) {
event.event(EventType.LOGOUT); if (relayState == null) {
event.error(Errors.USER_SESSION_NOT_FOUND); logger.error("no valid user session");
return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE); event.event(EventType.LOGOUT);
} event.error(Errors.USER_SESSION_NOT_FOUND);
return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers); return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
} }
UserSessionModel userSession = session.sessions().getUserSession(realm, relayState);
if (userSession == null) {
logger.error("no valid user session");
event.event(EventType.LOGOUT);
event.error(Errors.USER_SESSION_NOT_FOUND);
} return ErrorPage.error(session, Messages.IDENTITY_PROVIDER_UNEXPECTED_ERROR);
}
protected class PostBinding extends Binding { if (userSession.getState() != UserSessionModel.State.LOGGING_OUT) {
@Override logger.error("usersession in different state");
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException { event.event(EventType.LOGOUT);
SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey()); event.error(Errors.USER_SESSION_NOT_FOUND);
} return ErrorPage.error(session, Messages.SESSION_NOT_ACTIVE);
}
@Override return AuthenticationManager.finishBrowserLogout(session, realm, userSession, uriInfo, clientConnection, headers);
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) { }
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
}
@Override protected ResponseType decryptAssertion(ResponseType responseType, PrivateKey privateKey) throws ProcessingException {
protected SAMLDocumentHolder extractResponseDocument(String response) { SAML2Response saml2Response = new SAML2Response();
byte[] samlBytes = PostBindingUtil.base64Decode(response);
String xml = new String(samlBytes); try {
return SAMLRequestParser.parseResponseDocument(samlBytes); Document doc = saml2Response.convert(responseType);
} Element enc = DocumentUtil.getElement(doc, new QName(JBossSAMLConstants.ENCRYPTED_ASSERTION.get()));
@Override if (enc == null) {
protected String getBindingType() { throw new IdentityBrokerException("No encrypted assertion found.");
return SamlProtocol.SAML_POST_BINDING; }
}
} String oldID = enc.getAttribute(JBossSAMLConstants.ID.get());
Document newDoc = DocumentUtil.createDocument();
protected class RedirectBinding extends Binding { Node importedNode = newDoc.importNode(enc, true);
@Override newDoc.appendChild(importedNode);
protected void verifySignature(String key, SAMLDocumentHolder documentHolder) throws VerificationException {
PublicKey publicKey = getIDPKey(); Element decryptedDocumentElement = XMLEncryptionUtil.decryptElementInDocument(newDoc, privateKey);
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo, key); SAMLParser parser = new SAMLParser();
}
JAXPValidationUtil.checkSchemaValidation(decryptedDocumentElement);
AssertionType assertion = (AssertionType) parser.parse(StaxParserUtil.getXMLEventReader(DocumentUtil
.getNodeAsStream(decryptedDocumentElement)));
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) { responseType.replaceAssertion(oldID, new ResponseType.RTChoiceType(assertion));
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
} return responseType;
} catch (Exception e) {
@Override throw new IdentityBrokerException("Could not decrypt assertion.", e);
protected SAMLDocumentHolder extractResponseDocument(String response) { }
return SAMLRequestParser.parseRequestRedirectBinding(response); }
}
@Override }
protected String getBindingType() {
return SamlProtocol.SAML_REDIRECT_BINDING; protected class PostBinding extends Binding {
} @Override
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
} SamlProtocolUtils.verifyDocumentSignature(documentHolder.getSamlDocument(), getIDPKey());
}
}
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestPostBinding(samlRequest);
}
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
byte[] samlBytes = PostBindingUtil.base64Decode(response);
String xml = new String(samlBytes);
return SAMLRequestParser.parseResponseDocument(samlBytes);
}
@Override
protected String getBindingType() {
return SamlProtocol.SAML_POST_BINDING;
}
}
protected class RedirectBinding extends Binding {
@Override
protected void verifySignature(SAMLDocumentHolder documentHolder) throws VerificationException {
PublicKey publicKey = getIDPKey();
SamlProtocolUtils.verifyRedirectSignature(publicKey, uriInfo);
}
@Override
protected SAMLDocumentHolder extractRequestDocument(String samlRequest) {
return SAMLRequestParser.parseRequestRedirectBinding(samlRequest);
}
@Override
protected SAMLDocumentHolder extractResponseDocument(String response) {
return SAMLRequestParser.parseRequestRedirectBinding(response);
}
@Override
protected String getBindingType() {
return SamlProtocol.SAML_REDIRECT_BINDING;
}
}
}
@@ -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");
} }
@@ -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) {
@@ -1,141 +1,140 @@
package org.keycloak.broker.saml.mappers; package org.keycloak.broker.saml.mappers;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.provider.HardcodedRoleMapper; import org.keycloak.broker.provider.HardcodedRoleMapper;
import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.saml.SAMLEndpoint; import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory; import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.dom.saml.v2.assertion.AssertionType; 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.models.ClientModel; 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 org.keycloak.representations.JsonWebToken;
import org.keycloak.representations.JsonWebToken;
import java.util.ArrayList;
import java.util.ArrayList; import java.util.List;
import java.util.List; import java.util.Map;
import java.util.Map;
/**
/** * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @version $Revision: 1 $
* @version $Revision: 1 $ */
*/ public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public class AttributeToRoleMapper extends AbstractIdentityProviderMapper {
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
public static final String ATTRIBUTE_NAME = "attribute.name";
public static final String ATTRIBUTE_NAME = "attribute.name"; public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name"; public static final String ATTRIBUTE_VALUE = "attribute.value";
public static final String ATTRIBUTE_VALUE = "attribute.value";
static {
static { ProviderConfigProperty property;
ProviderConfigProperty property; property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(ATTRIBUTE_NAME);
property.setName(ATTRIBUTE_NAME); property.setLabel("Attribute Name");
property.setLabel("Attribute Name"); property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead."); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property);
configProperties.add(property); property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(ATTRIBUTE_FRIENDLY_NAME);
property.setName(ATTRIBUTE_FRIENDLY_NAME); property.setLabel("Friendly Name");
property.setLabel("Friendly Name"); property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead."); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property);
configProperties.add(property); property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(ATTRIBUTE_VALUE);
property.setName(ATTRIBUTE_VALUE); property.setLabel("Attribute Value");
property.setLabel("Attribute Value"); property.setHelpText("Value the attribute must have. If the attribute is a list, then the value must be contained in the list.");
property.setHelpText("Value the attribute must have. If the attribute is a list, then the value must be contained in the list."); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.STRING_TYPE); configProperties.add(property);
configProperties.add(property); property = new ProviderConfigProperty();
property = new ProviderConfigProperty(); property.setName(HardcodedRoleMapper.ROLE);
property.setName(HardcodedRoleMapper.ROLE); property.setLabel("Role");
property.setLabel("Role"); property.setHelpText("Role to grant to user. To reference an application role the syntax is appname.approle, i.e. myapp.myrole");
property.setHelpText("Role to grant to user. Click 'Select Role' button to browse roles, or just type it in the textbox. To reference an application role the syntax is appname.approle, i.e. myapp.myrole"); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setType(ProviderConfigProperty.ROLE_TYPE); configProperties.add(property);
configProperties.add(property); }
}
public static final String PROVIDER_ID = "saml-role-idp-mapper";
public static final String PROVIDER_ID = "saml-role-idp-mapper";
@Override
@Override public List<ProviderConfigProperty> getConfigProperties() {
public List<ProviderConfigProperty> getConfigProperties() { return configProperties;
return configProperties; }
}
@Override
@Override public String getId() {
public String getId() { return PROVIDER_ID;
return PROVIDER_ID; }
}
@Override
@Override public String[] getCompatibleProviders() {
public String[] getCompatibleProviders() { return COMPATIBLE_PROVIDERS;
return COMPATIBLE_PROVIDERS; }
}
@Override
@Override public String getDisplayCategory() {
public String getDisplayCategory() { return "Role Mapper";
return "Role Mapper"; }
}
@Override
@Override public String getDisplayType() {
public String getDisplayType() { return "SAML Attribute to Role";
return "SAML Attribute to Role"; }
}
@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(HardcodedRoleMapper.ROLE);
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE); if (isAttributePresent(mapperModel, context)) {
if (isAttributePresent(mapperModel, context)) { RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.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); }
} }
}
protected boolean isAttributePresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
protected boolean isAttributePresent(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME); if (name != null && name.trim().equals("")) name = null;
if (name != null && name.trim().equals("")) name = null; String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME); if (friendly != null && friendly.trim().equals("")) friendly = null;
if (friendly != null && friendly.trim().equals("")) friendly = null; String desiredValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE);
String desiredValue = mapperModel.getConfig().get(ATTRIBUTE_VALUE); AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); for (AttributeStatementType statement : assertion.getAttributeStatements()) {
for (AttributeStatementType statement : assertion.getAttributeStatements()) { for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { AttributeType attr = choice.getAttribute();
AttributeType attr = choice.getAttribute(); if (name != null && !name.equals(attr.getName())) continue;
if (name != null && !name.equals(attr.getName())) continue; if (friendly != null && !name.equals(attr.getFriendlyName())) continue;
if (friendly != null && !name.equals(attr.getFriendlyName())) continue; for (Object val : attr.getAttributeValue()) {
for (Object val : attr.getAttributeValue()) { if (val.equals(desiredValue)) return true;
if (val.equals(desiredValue)) return true; }
} }
} }
} return false;
return false; }
}
@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) { String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE);
String roleName = mapperModel.getConfig().get(HardcodedRoleMapper.ROLE); if (!isAttributePresent(mapperModel, context)) {
if (!isAttributePresent(mapperModel, context)) { RoleModel role = HardcodedRoleMapper.getRoleFromString(realm, roleName);
RoleModel role = KeycloakModelUtils.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); }
}
}
}
@Override
@Override public String getHelpText() {
public String getHelpText() { return "If a claim exists, grant the user the specified realm or application role.";
return "If a claim exists, grant the user the specified realm or application role."; }
}
}
}
@@ -1,129 +1,132 @@
package org.keycloak.broker.saml.mappers; package org.keycloak.broker.saml.mappers;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.saml.SAMLEndpoint; import org.keycloak.broker.provider.IdentityBrokerException;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory; import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.dom.saml.v2.assertion.AssertionType; import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.dom.saml.v2.assertion.AttributeStatementType; import org.keycloak.dom.saml.v2.assertion.AssertionType;
import org.keycloak.dom.saml.v2.assertion.AttributeType; import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.ClientModel;
import org.keycloak.models.RealmModel; import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.UserModel; import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import java.util.ArrayList; import org.keycloak.models.UserModel;
import java.util.List; import org.keycloak.provider.ProviderConfigProperty;
/** import java.util.ArrayList;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> import java.util.List;
* @version $Revision: 1 $
*/ /**
public class UserAttributeMapper extends AbstractIdentityProviderMapper { * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; */
public class UserAttributeMapper extends AbstractIdentityProviderMapper {
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
public static final String ATTRIBUTE_NAME = "attribute.name";
public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name"; private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
public static final String USER_ATTRIBUTE = "user.attribute";
public static final String ATTRIBUTE_NAME = "attribute.name";
static { public static final String ATTRIBUTE_FRIENDLY_NAME = "attribute.friendly.name";
ProviderConfigProperty property; public static final String USER_ATTRIBUTE = "user.attribute";
property = new ProviderConfigProperty();
property.setName(ATTRIBUTE_NAME); static {
property.setLabel("Attribute Name"); ProviderConfigProperty property;
property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead."); property = new ProviderConfigProperty();
property.setType(ProviderConfigProperty.STRING_TYPE); property.setName(ATTRIBUTE_NAME);
configProperties.add(property); property.setLabel("Attribute Name");
property = new ProviderConfigProperty(); property.setHelpText("Name of attribute to search for in assertion. You can leave this blank and specify a friendly name instead.");
property.setName(ATTRIBUTE_FRIENDLY_NAME); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setLabel("Friendly Name"); configProperties.add(property);
property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead."); property = new ProviderConfigProperty();
property.setType(ProviderConfigProperty.STRING_TYPE); property.setName(ATTRIBUTE_FRIENDLY_NAME);
configProperties.add(property); property.setLabel("Friendly Name");
property = new ProviderConfigProperty(); property.setHelpText("Friendly name of attribute to search for in assertion. You can leave this blank and specify a name instead.");
property.setName(USER_ATTRIBUTE); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setLabel("User Attribute Name"); configProperties.add(property);
property.setHelpText("User attribute name to store saml attribute."); property = new ProviderConfigProperty();
property.setType(ProviderConfigProperty.STRING_TYPE); property.setName(USER_ATTRIBUTE);
configProperties.add(property); property.setLabel("User Attribute Name");
} property.setHelpText("User attribute name to store saml attribute.");
property.setType(ProviderConfigProperty.STRING_TYPE);
public static final String PROVIDER_ID = "saml-user-attribute-idp-mapper"; configProperties.add(property);
}
@Override
public List<ProviderConfigProperty> getConfigProperties() { public static final String PROVIDER_ID = "saml-user-attribute-idp-mapper";
return configProperties;
} @Override
public List<ProviderConfigProperty> getConfigProperties() {
@Override return configProperties;
public String getId() { }
return PROVIDER_ID;
} @Override
public String getId() {
@Override return PROVIDER_ID;
public String[] getCompatibleProviders() { }
return COMPATIBLE_PROVIDERS;
} @Override
public String[] getCompatibleProviders() {
@Override return COMPATIBLE_PROVIDERS;
public String getDisplayCategory() { }
return "Attribute Importer";
} @Override
public String getDisplayCategory() {
@Override return "Attribute Importer";
public String getDisplayType() { }
return "Attribute Importer";
} @Override
public String getDisplayType() {
@Override return "Attribute Importer";
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { }
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
Object value = getAttribute(mapperModel, context); @Override
if (value != null) { public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
context.setUserAttribute(attribute, value.toString()); String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
} Object value = getAttribute(mapperModel, context);
} if (value != null) {
user.setAttribute(attribute, value.toString());
protected String getAttribute(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { }
String name = mapperModel.getConfig().get(ATTRIBUTE_NAME); }
if (name != null && name.trim().equals("")) name = null;
String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME); protected String getAttribute(IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
if (friendly != null && friendly.trim().equals("")) friendly = null; String name = mapperModel.getConfig().get(ATTRIBUTE_NAME);
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); if (name != null && name.trim().equals("")) name = null;
for (AttributeStatementType statement : assertion.getAttributeStatements()) { String friendly = mapperModel.getConfig().get(ATTRIBUTE_FRIENDLY_NAME);
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { if (friendly != null && friendly.trim().equals("")) friendly = null;
AttributeType attr = choice.getAttribute(); AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
if (name != null && !name.equals(attr.getName())) continue; for (AttributeStatementType statement : assertion.getAttributeStatements()) {
if (friendly != null && !friendly.equals(attr.getFriendlyName())) continue; for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
AttributeType attr = choice.getAttribute();
List<Object> attributeValue = attr.getAttributeValue(); if (name != null && !name.equals(attr.getName())) continue;
if (attributeValue == null || attributeValue.isEmpty()) return null; if (friendly != null && !name.equals(attr.getFriendlyName())) continue;
return attributeValue.get(0).toString();
} List<Object> attributeValue = attr.getAttributeValue();
} if (attributeValue == null || attributeValue.isEmpty()) return null;
return null; return attributeValue.get(0).toString();
} }
}
@Override return null;
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { }
String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
Object value = getAttribute(mapperModel, context); @Override
String current = user.getFirstAttribute(attribute); public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
if (value != null && !value.equals(current)) { String attribute = mapperModel.getConfig().get(USER_ATTRIBUTE);
user.setSingleAttribute(attribute, value.toString()); Object value = getAttribute(mapperModel, context);
} else if (value == null) { String current = user.getAttribute(attribute);
user.removeAttribute(attribute); if (value != null && !value.equals(current)) {
} user.setAttribute(attribute, value.toString());
} else if (value == null) {
} user.removeAttribute(attribute);
}
@Override
public String getHelpText() { }
return "Import declared saml attribute if it exists in assertion into the specified user attribute.";
} @Override
public String getHelpText() {
} return "Import declared saml attribute if it exists in assertion into the specified user attribute.";
}
}
@@ -1,128 +1,133 @@
package org.keycloak.broker.saml.mappers; package org.keycloak.broker.saml.mappers;
import org.keycloak.broker.provider.AbstractIdentityProviderMapper; import org.keycloak.broker.provider.AbstractIdentityProviderMapper;
import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.BrokeredIdentityContext;
import org.keycloak.broker.saml.SAMLEndpoint; import org.keycloak.broker.saml.SAMLEndpoint;
import org.keycloak.broker.saml.SAMLIdentityProviderFactory; import org.keycloak.broker.saml.SAMLIdentityProviderFactory;
import org.keycloak.dom.saml.v2.assertion.AssertionType; 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.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.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.UserModel; import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigProperty;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class UsernameTemplateMapper extends AbstractIdentityProviderMapper { public class UsernameTemplateMapper extends AbstractIdentityProviderMapper {
public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID};
private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>(); private static final List<ProviderConfigProperty> configProperties = new ArrayList<ProviderConfigProperty>();
public static final String TEMPLATE = "template"; public static final String TEMPLATE = "template";
static { static {
ProviderConfigProperty property; ProviderConfigProperty property;
property = new ProviderConfigProperty(); property = new ProviderConfigProperty();
property.setName(TEMPLATE); property.setName(TEMPLATE);
property.setLabel("Template"); property.setLabel("Template");
property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${NAMEID}'. ALIAS is the provider alias. NAMEID is that SAML name id assertion. ATTRIBUTE.<NAME> references a SAML attribute where name is the attribute name or friendly name."); property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${NAMEID}'. ALIAS is the provider alias. NAMEID is that SAML name id assertion. ATTRIBUTE.<NAME> references a SAML attribute where name is the attribute name or friendly name.");
property.setType(ProviderConfigProperty.STRING_TYPE); property.setType(ProviderConfigProperty.STRING_TYPE);
property.setDefaultValue("${ALIAS}.${NAMEID}"); property.setDefaultValue("${ALIAS}.${NAMEID}");
configProperties.add(property); configProperties.add(property);
} }
public static final String PROVIDER_ID = "saml-username-idp-mapper"; public static final String PROVIDER_ID = "saml-username-idp-mapper";
@Override @Override
public List<ProviderConfigProperty> getConfigProperties() { public List<ProviderConfigProperty> getConfigProperties() {
return configProperties; return configProperties;
} }
@Override @Override
public String getId() { public String getId() {
return PROVIDER_ID; return PROVIDER_ID;
} }
@Override @Override
public String[] getCompatibleProviders() { public String[] getCompatibleProviders() {
return COMPATIBLE_PROVIDERS; return COMPATIBLE_PROVIDERS;
} }
@Override @Override
public String getDisplayCategory() { public String getDisplayCategory() {
return "Preprocessor"; return "Preprocessor";
} }
@Override @Override
public String getDisplayType() { public String getDisplayType() {
return "Username Template Importer"; return "Username Template Importer";
} }
@Override @Override
public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
} }
static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
@Override
@Override public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); }
String template = mapperModel.getConfig().get(TEMPLATE); static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}");
Matcher m = substitution.matcher(template);
StringBuffer sb = new StringBuffer(); @Override
while (m.find()) { public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) {
String variable = m.group(1); AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION);
if (variable.equals("ALIAS")) { String template = mapperModel.getConfig().get(TEMPLATE);
m.appendReplacement(sb, context.getIdpConfig().getAlias()); Matcher m = substitution.matcher(template);
} else if (variable.equals("UUID")) { StringBuffer sb = new StringBuffer();
m.appendReplacement(sb, KeycloakModelUtils.generateId()); while (m.find()) {
} else if (variable.equals("NAMEID")) { String variable = m.group(1);
SubjectType subject = assertion.getSubject(); if (variable.equals("ALIAS")) {
SubjectType.STSubType subType = subject.getSubType(); m.appendReplacement(sb, context.getIdpConfig().getAlias());
NameIDType subjectNameID = (NameIDType) subType.getBaseID(); } else if (variable.equals("UUID")) {
m.appendReplacement(sb, subjectNameID.getValue()); m.appendReplacement(sb, KeycloakModelUtils.generateId());
} else if (variable.startsWith("ATTRIBUTE.")) { } else if (variable.equals("NAMEID")) {
String name = variable.substring("ATTRIBUTE.".length()); SubjectType subject = assertion.getSubject();
String value = ""; SubjectType.STSubType subType = subject.getSubType();
for (AttributeStatementType statement : assertion.getAttributeStatements()) { NameIDType subjectNameID = (NameIDType) subType.getBaseID();
for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { m.appendReplacement(sb, subjectNameID.getValue());
AttributeType attr = choice.getAttribute(); } else if (variable.startsWith("ATTRIBUTE.")) {
if (name.equals(attr.getName()) || name.equals(attr.getFriendlyName())) { String name = variable.substring("ATTRIBUTE.".length());
List<Object> attributeValue = attr.getAttributeValue(); String value = "";
if (attributeValue != null && !attributeValue.isEmpty()) { for (AttributeStatementType statement : assertion.getAttributeStatements()) {
value = attributeValue.get(0).toString(); for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) {
} AttributeType attr = choice.getAttribute();
break; if (name.equals(attr.getName()) || name.equals(attr.getFriendlyName())) {
} List<Object> attributeValue = attr.getAttributeValue();
} if (attributeValue != null && !attributeValue.isEmpty()) {
} value = attributeValue.get(0).toString();
m.appendReplacement(sb, value); }
} else { break;
m.appendReplacement(sb, m.group(1)); }
} }
}
} m.appendReplacement(sb, value);
m.appendTail(sb); } else {
context.setModelUsername(sb.toString()); m.appendReplacement(sb, m.group(1));
}
}
}
@Override m.appendTail(sb);
public String getHelpText() { context.setModelUsername(sb.toString());
return "Format the username to import.";
} }
} @Override
public String getHelpText() {
return "Format the username to import.";
}
}
@@ -1,3 +1,3 @@
org.keycloak.broker.saml.mappers.AttributeToRoleMapper org.keycloak.broker.saml.mappers.AttributeToRoleMapper
org.keycloak.broker.saml.mappers.UserAttributeMapper org.keycloak.broker.saml.mappers.UserAttributeMapper
org.keycloak.broker.saml.mappers.UsernameTemplateMapper org.keycloak.broker.saml.mappers.UsernameTemplateMapper
@@ -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);
}
}
-26
View File
@@ -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);
}
}
}
@@ -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));
}
}
}
@@ -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);
}
}
@@ -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;
}
@@ -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);
}
}
}
@@ -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;
}
-34
View File
@@ -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>
@@ -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()
}
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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();
}
}
-94
View File
@@ -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;
}
}
}
@@ -1,19 +0,0 @@
package org.keycloak.common.constants;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public interface ServiceAccountConstants {
String CLIENT_AUTH = "client_auth";
String SERVICE_ACCOUNT_USER_PREFIX = "service-account-";
String CLIENT_ID_PROTOCOL_MAPPER = "Client ID";
String CLIENT_HOST_PROTOCOL_MAPPER = "Client Host";
String CLIENT_ADDRESS_PROTOCOL_MAPPER = "Client IP Address";
String CLIENT_ID = "clientId";
String CLIENT_HOST = "clientHost";
String CLIENT_ADDRESS = "clientAddress";
}
File diff suppressed because it is too large. Load diff
@@ -1,56 +0,0 @@
package org.keycloak.common.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import org.keycloak.common.constants.GenericConstants;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeystoreUtil {
static {
BouncyIntegration.init();
}
public enum KeystoreFormat {
JKS,
PKCS12
}
public static KeyStore loadKeyStore(String filename, String password) throws Exception {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream trustStream = (filename.startsWith(GenericConstants.PROTOCOL_CLASSPATH))
?KeystoreUtil.class.getResourceAsStream(filename.replace(GenericConstants.PROTOCOL_CLASSPATH, ""))
:new FileInputStream(new File(filename));
trustStore.load(trustStream, password.toCharArray());
trustStream.close();
return trustStore;
}
public static PrivateKey loadPrivateKeyFromKeystore(String keystoreFile, String storePassword, String keyPassword, String keyAlias, KeystoreFormat format) {
InputStream stream = FindFile.findFile(keystoreFile);
try {
KeyStore keyStore = null;
if (format == KeystoreFormat.JKS) {
keyStore = KeyStore.getInstance(format.toString());
} else {
keyStore = KeyStore.getInstance(format.toString(), "BC");
}
keyStore.load(stream, storePassword.toCharArray());
PrivateKey key = (PrivateKey) keyStore.getKey(keyAlias, keyPassword.toCharArray());
if (key == null) {
throw new RuntimeException("Couldn't load key with alias '" + keyAlias + "' from keystore");
}
return key;
} catch (Exception e) {
throw new RuntimeException("Failed to load private key: " + e.getMessage(), e);
}
}
}
@@ -1,4 +0,0 @@
name=${product.name}
name-html=${product.name-html}
version=${product.version}
build-time=${product.build-time}
@@ -1,47 +1,43 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<parent> <parent>
<artifactId>keycloak-examples-providers-parent</artifactId> <artifactId>keycloak-parent</artifactId>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<version>1.8.0.CR3</version> <version>1.3.1.Final</version>
<relativePath>../../pom.xml</relativePath>
</parent> </parent>
<name>Authenticator Example</name>
<description/>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>authenticator-required-action-example</artifactId> <artifactId>keycloak-connections-file</artifactId>
<packaging>jar</packaging> <name>Keycloak Connections File</name>
<description/>
<dependencies> <dependencies>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-api</artifactId>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-export-import-single-file</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
<scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </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>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-login-api</artifactId>
<scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build>
<finalName>authenticator-required-action-example</finalName>
</build>
</project> </project>
@@ -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);
}
}
@@ -0,0 +1,221 @@
/*
* 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());
List<RealmRepresentation> realmReps = new ArrayList<RealmRepresentation>();
for (RealmRepresentation realmRep : model.getRealms()) {
if (Config.getAdminRealm().equals(realmRep.getRealm())) {
realmReps.add(0, realmRep);
} else {
realmReps.add(realmRep);
}
}
for (RealmRepresentation realmRep : realmReps) {
ImportUtils.importRealm(session, realmRep, Strategy.IGNORE_EXISTING);
}
} catch (IOException ioe) {
logger.error("Unable to read model file " + kcdata.getAbsolutePath(), ioe);
} finally {
//logger.info("Read model file for session=" + session.hashCode());
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
logger.error("Failed to close output stream.", e);
}
}
}
}
void writeModelFile(KeycloakSession session) {
synchronized(allProviders) {
FileOutputStream outStream = null;
try {
outStream = new FileOutputStream(kcdata);
exportModel(session, outStream);
} catch (IOException e) {
logger.error("Unable to write model file " + kcdata.getAbsolutePath(), e);
} finally {
//logger.info("Wrote model file for session=" + session.hashCode());
try {
if (outStream != null) {
outStream.close();
}
} catch (IOException e) {
logger.error("Failed to close output stream.", e);
}
}
}
}
private void exportModel(KeycloakSession session, FileOutputStream outStream) throws IOException {
List<RealmModel> realms = session.realms().getRealms();
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
for (RealmModel realm : realms) {
reps.add(ExportUtils.exportRealm(session, realm, true));
}
Model model = new Model();
model.setRealms(reps);
model.setModelVersion(session.realms().getMigrationModel().getStoredVersion());
JsonSerialization.prettyMapper.writeValue(outStream, model);
}
@Override
public FileConnectionProvider create(KeycloakSession session) {
synchronized (allProviders) {
FileConnectionProvider fcProvider = allProviders.get(session);
if (fcProvider == null) {
InMemoryModel model = new InMemoryModel();
fcProvider = new DefaultFileConnectionProvider(this, session, model);
allProviders.put(session, fcProvider);
session.getTransaction().enlist(fcProvider);
readModelFile(session);
//logger.info("Added session " + session.hashCode() + " total sessions=" + allModels.size());
}
return fcProvider;
}
}
// commitCount is used for debugging. This allows you to easily run a test
// to a particular point and then examine the JSON file.
//private static int commitCount = 0;
void commit(KeycloakSession session) {
//commitCount++;
synchronized (allProviders) {
// in case commit was somehow called twice on the same session
if (!allProviders.containsKey(session)) return;
try {
writeModelFile(session);
} finally {
allProviders.remove(session);
//logger.info("Removed session " + session.hashCode());
//logger.info("*** commitCount=" + commitCount);
//logger.info("commit(): Session count=" + allModels.size());
}
// if (commitCount == 16) {Thread.dumpStack();System.exit(0);}
}
}
void rollback(KeycloakSession session) {
synchronized (allProviders) {
allProviders.remove(session);
//logger.info("rollback(): Session count=" + allModels.size());
}
}
boolean isActive(KeycloakSession session) {
synchronized (allProviders) {
return allProviders.containsKey(session);
}
}
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
}
@@ -1,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);
} }
@@ -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
} }
@@ -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;
} }
} }
@@ -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;
}
}
@@ -0,0 +1 @@
org.keycloak.connections.file.DefaultFileConnectionProviderFactory
@@ -0,0 +1 @@
org.keycloak.connections.file.FileConnectionSpi
+36 -40
View File
@@ -1,40 +1,36 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-http-client</artifactId> <artifactId>keycloak-connections-http-client</artifactId>
<name>Keycloak Connections Apache HttpClient</name> <name>Keycloak Connections Apache HttpClient</name>
<description/> <description/>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.apache.httpcomponents</groupId>
<artifactId>keycloak-connections-truststore</artifactId> <artifactId>httpclient</artifactId>
</dependency> <scope>provided</scope>
<dependency> </dependency>
<groupId>org.apache.httpcomponents</groupId> <dependency>
<artifactId>httpclient</artifactId> <groupId>org.jboss.logging</groupId>
<scope>provided</scope> <artifactId>jboss-logging</artifactId>
</dependency> <scope>provided</scope>
<dependency> </dependency>
<groupId>org.jboss.logging</groupId> </dependencies>
<artifactId>jboss-logging</artifactId> </project>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
@@ -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,281 +1,281 @@
package org.keycloak.connections.httpclient; package org.keycloak.connections.httpclient;
import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier; import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.apache.http.conn.ssl.StrictHostnameVerifier;
import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.client.HttpClients;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocket;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.io.IOException; import java.io.IOException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.security.UnrecoverableKeyException; import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* Abstraction for creating HttpClients. Allows SSL configuration. * Abstraction for creating HttpClients. Allows SSL configuration.
* *
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class HttpClientBuilder { public class HttpClientBuilder {
public static enum HostnameVerificationPolicy { public static enum HostnameVerificationPolicy {
/** /**
* Hostname verification is not done on the server's certificate * Hostname verification is not done on the server's certificate
*/ */
ANY, ANY,
/** /**
* Allows wildcards in subdomain names i.e. *.foo.com * Allows wildcards in subdomain names i.e. *.foo.com
*/ */
WILDCARD, WILDCARD,
/** /**
* CN must match hostname connecting to * CN must match hostname connecting to
*/ */
STRICT STRICT
} }
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
private static class PassthroughTrustManager implements X509TrustManager { private static class PassthroughTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, public void checkClientTrusted(X509Certificate[] chain,
String authType) throws CertificateException { String authType) throws CertificateException {
} }
public void checkServerTrusted(X509Certificate[] chain, public void checkServerTrusted(X509Certificate[] chain,
String authType) throws CertificateException { String authType) throws CertificateException {
} }
public X509Certificate[] getAcceptedIssuers() { public X509Certificate[] getAcceptedIssuers() {
return null; return null;
} }
} }
protected KeyStore truststore; protected KeyStore truststore;
protected KeyStore clientKeyStore; protected KeyStore clientKeyStore;
protected String clientPrivateKeyPassword; protected String clientPrivateKeyPassword;
protected boolean disableTrustManager; protected boolean disableTrustManager;
protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD; protected HostnameVerificationPolicy policy = HostnameVerificationPolicy.WILDCARD;
protected SSLContext sslContext; protected SSLContext sslContext;
protected int connectionPoolSize = 100; protected int connectionPoolSize = 100;
protected int maxPooledPerRoute = 0; protected int maxPooledPerRoute = 0;
protected long connectionTTL = -1; protected long connectionTTL = -1;
protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS; protected TimeUnit connectionTTLUnit = TimeUnit.MILLISECONDS;
protected HostnameVerifier verifier = null; protected HostnameVerifier verifier = null;
protected long socketTimeout = -1; protected long socketTimeout = -1;
protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS; protected TimeUnit socketTimeoutUnits = TimeUnit.MILLISECONDS;
protected long establishConnectionTimeout = -1; protected long establishConnectionTimeout = -1;
protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS; protected TimeUnit establishConnectionTimeoutUnits = TimeUnit.MILLISECONDS;
protected boolean disableCookies = false; protected boolean disableCookies = false;
/** /**
* Socket inactivity timeout * Socket inactivity timeout
* *
* @param timeout * @param timeout
* @param unit * @param unit
* @return * @return
*/ */
public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit) public HttpClientBuilder socketTimeout(long timeout, TimeUnit unit)
{ {
this.socketTimeout = timeout; this.socketTimeout = timeout;
this.socketTimeoutUnits = unit; this.socketTimeoutUnits = unit;
return this; return this;
} }
/** /**
* When trying to make an initial socket connection, what is the timeout? * When trying to make an initial socket connection, what is the timeout?
* *
* @param timeout * @param timeout
* @param unit * @param unit
* @return * @return
*/ */
public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit) public HttpClientBuilder establishConnectionTimeout(long timeout, TimeUnit unit)
{ {
this.establishConnectionTimeout = timeout; this.establishConnectionTimeout = timeout;
this.establishConnectionTimeoutUnits = unit; this.establishConnectionTimeoutUnits = unit;
return this; return this;
} }
public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) { public HttpClientBuilder connectionTTL(long ttl, TimeUnit unit) {
this.connectionTTL = ttl; this.connectionTTL = ttl;
this.connectionTTLUnit = unit; this.connectionTTLUnit = unit;
return this; return this;
} }
public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) { public HttpClientBuilder maxPooledPerRoute(int maxPooledPerRoute) {
this.maxPooledPerRoute = maxPooledPerRoute; this.maxPooledPerRoute = maxPooledPerRoute;
return this; return this;
} }
public HttpClientBuilder connectionPoolSize(int connectionPoolSize) { public HttpClientBuilder connectionPoolSize(int connectionPoolSize) {
this.connectionPoolSize = connectionPoolSize; this.connectionPoolSize = connectionPoolSize;
return this; return this;
} }
/** /**
* Disable trust management and hostname verification. <i>NOTE</i> this is a security * Disable trust management and hostname verification. <i>NOTE</i> this is a security
* hole, so only set this option if you cannot or do not want to verify the identity of the * hole, so only set this option if you cannot or do not want to verify the identity of the
* host you are communicating with. * host you are communicating with.
*/ */
public HttpClientBuilder disableTrustManager() { public HttpClientBuilder disableTrustManager() {
this.disableTrustManager = true; this.disableTrustManager = true;
return this; return this;
} }
/** /**
* 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;
} }
/** /**
* SSL policy used to verify hostnames * SSL policy used to verify hostnames
* *
* @param policy * @param policy
* @return * @return
*/ */
public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) { public HttpClientBuilder hostnameVerification(HostnameVerificationPolicy policy) {
this.policy = policy; this.policy = policy;
return this; return this;
} }
public HttpClientBuilder sslContext(SSLContext sslContext) { public HttpClientBuilder sslContext(SSLContext sslContext) {
this.sslContext = sslContext; this.sslContext = sslContext;
return this; return this;
} }
public HttpClientBuilder trustStore(KeyStore truststore) { public HttpClientBuilder trustStore(KeyStore truststore) {
this.truststore = truststore; this.truststore = truststore;
return this; return this;
} }
public HttpClientBuilder keyStore(KeyStore keyStore, String password) { public HttpClientBuilder keyStore(KeyStore keyStore, String password) {
this.clientKeyStore = keyStore; this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = password; this.clientPrivateKeyPassword = password;
return this; return this;
} }
public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) { public HttpClientBuilder keyStore(KeyStore keyStore, char[] password) {
this.clientKeyStore = keyStore; this.clientKeyStore = keyStore;
this.clientPrivateKeyPassword = new String(password); this.clientPrivateKeyPassword = new String(password);
return this; return this;
} }
static class VerifierWrapper implements X509HostnameVerifier { static class VerifierWrapper implements X509HostnameVerifier {
protected HostnameVerifier verifier; protected HostnameVerifier verifier;
VerifierWrapper(HostnameVerifier verifier) { VerifierWrapper(HostnameVerifier verifier) {
this.verifier = verifier; this.verifier = verifier;
} }
@Override @Override
public void verify(String host, SSLSocket ssl) throws IOException { public void verify(String host, SSLSocket ssl) throws IOException {
if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure"); if (!verifier.verify(host, ssl.getSession())) throw new SSLException("Hostname verification failure");
} }
@Override @Override
public void verify(String host, X509Certificate cert) throws SSLException { public void verify(String host, X509Certificate cert) throws SSLException {
throw new SSLException("This verification path not implemented"); throw new SSLException("This verification path not implemented");
} }
@Override @Override
public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException { public void verify(String host, String[] cns, String[] subjectAlts) throws SSLException {
throw new SSLException("This verification path not implemented"); throw new SSLException("This verification path not implemented");
} }
@Override @Override
public boolean verify(String s, SSLSession sslSession) { public boolean verify(String s, SSLSession sslSession) {
return verifier.verify(s, sslSession); return verifier.verify(s, sslSession);
} }
} }
public CloseableHttpClient build() { public CloseableHttpClient build() {
X509HostnameVerifier verifier = null; X509HostnameVerifier verifier = null;
if (this.verifier != null) verifier = new VerifierWrapper(this.verifier); if (this.verifier != null) verifier = new VerifierWrapper(this.verifier);
else { else {
switch (policy) { switch (policy) {
case ANY: case ANY:
verifier = new AllowAllHostnameVerifier(); verifier = new AllowAllHostnameVerifier();
break; break;
case WILDCARD: case WILDCARD:
verifier = new BrowserCompatHostnameVerifier(); verifier = new BrowserCompatHostnameVerifier();
break; break;
case STRICT: case STRICT:
verifier = new StrictHostnameVerifier(); verifier = new StrictHostnameVerifier();
break; break;
} }
} }
try { try {
SSLConnectionSocketFactory sslsf = null; SSLConnectionSocketFactory sslsf = null;
SSLContext theContext = sslContext; SSLContext theContext = sslContext;
if (disableTrustManager) { if (disableTrustManager) {
theContext = SSLContext.getInstance("TLS"); theContext = SSLContext.getInstance("TLS");
theContext.init(null, new TrustManager[]{new PassthroughTrustManager()}, theContext.init(null, new TrustManager[]{new PassthroughTrustManager()},
new SecureRandom()); new SecureRandom());
verifier = new AllowAllHostnameVerifier(); verifier = new AllowAllHostnameVerifier();
sslsf = new SSLConnectionSocketFactory(theContext, verifier); sslsf = new SSLConnectionSocketFactory(theContext, verifier);
} else if (theContext != null) { } else if (theContext != null) {
sslsf = new SSLConnectionSocketFactory(theContext, verifier); sslsf = new SSLConnectionSocketFactory(theContext, verifier);
} else if (clientKeyStore != null || truststore != null) { } else if (clientKeyStore != null || truststore != null) {
theContext = createSslContext("TLS", clientKeyStore, clientPrivateKeyPassword, truststore, null); theContext = createSslContext("TLS", clientKeyStore, clientPrivateKeyPassword, truststore, null);
sslsf = new SSLConnectionSocketFactory(theContext, verifier); sslsf = new SSLConnectionSocketFactory(theContext, verifier);
} else { } else {
final SSLContext tlsContext = SSLContext.getInstance("TLS"); final SSLContext tlsContext = SSLContext.getInstance("TLS");
tlsContext.init(null, null, null); tlsContext.init(null, null, null);
sslsf = new SSLConnectionSocketFactory(tlsContext, verifier); sslsf = new SSLConnectionSocketFactory(tlsContext, verifier);
} }
RequestConfig requestConfig = RequestConfig.custom() RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout((int) establishConnectionTimeout) .setConnectTimeout((int) establishConnectionTimeout)
.setSocketTimeout((int) socketTimeout).build(); .setSocketTimeout((int) socketTimeout).build();
org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom() org.apache.http.impl.client.HttpClientBuilder builder = HttpClients.custom()
.setDefaultRequestConfig(requestConfig) .setDefaultRequestConfig(requestConfig)
.setSSLSocketFactory(sslsf) .setSSLSocketFactory(sslsf)
.setMaxConnTotal(connectionPoolSize) .setMaxConnTotal(connectionPoolSize)
.setMaxConnPerRoute(maxPooledPerRoute); .setMaxConnPerRoute(maxPooledPerRoute);
if (disableCookies) builder.disableCookieManagement(); if (disableCookies) builder.disableCookieManagement();
return builder.build(); return builder.build();
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
private SSLContext createSslContext( private SSLContext createSslContext(
final String algorithm, final String algorithm,
final KeyStore keystore, final KeyStore keystore,
final String keyPassword, final String keyPassword,
final KeyStore truststore, final KeyStore truststore,
final SecureRandom random) final SecureRandom random)
throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
return SSLContexts.custom() return SSLContexts.custom()
.useProtocol(algorithm) .useProtocol(algorithm)
.setSecureRandom(random) .setSecureRandom(random)
.loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null) .loadKeyMaterial(keystore, keyPassword != null ? keyPassword.toCharArray() : null)
.loadTrustMaterial(truststore) .loadTrustMaterial(truststore)
.build(); .build();
} }
} }
+31 -32
View File
@@ -1,32 +1,31 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-infinispan</artifactId> <artifactId>keycloak-connections-infinispan</artifactId>
<name>Keycloak Connections Infinispan</name> <name>Keycloak Connections Infinispan</name>
<description/> <description/>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.infinispan</groupId> <groupId>org.infinispan</groupId>
<artifactId>infinispan-core</artifactId> <artifactId>infinispan-core</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies>
</dependencies> </project>
</project>
@@ -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);
} }
@@ -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);
+76 -76
View File
@@ -1,76 +1,76 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-jpa-liquibase</artifactId> <artifactId>keycloak-connections-jpa-liquibase</artifactId>
<name>Keycloak Connections JPA Liquibase Updater</name> <name>Keycloak Connections JPA Liquibase Updater</name>
<description/> <description/>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-connections-jpa</artifactId> <artifactId>keycloak-connections-jpa</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.liquibase</groupId> <groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId> <artifactId>liquibase-core</artifactId>
<exclusions> <exclusions>
<exclusion> <exclusion>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
</exclusion> </exclusion>
</exclusions> </exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.jboss.logging</groupId> <groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId> <artifactId>jboss-logging</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-services</artifactId> <artifactId>keycloak-services</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.h2database</groupId> <groupId>com.h2database</groupId>
<artifactId>h2</artifactId> <artifactId>h2</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.liquibase</groupId> <groupId>org.liquibase</groupId>
<artifactId>liquibase-maven-plugin</artifactId> <artifactId>liquibase-maven-plugin</artifactId>
<configuration> <configuration>
<changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile> <changeLogFile>META-INF/jpa-changelog-master.xml</changeLogFile>
<url>${project.url}</url> <url>${project.url}</url>
<driver>${driver}</driver> <driver>${driver}</driver>
<username>${username}</username> <username>${username}</username>
<password>${password}</password> <password>${password}</password>
<referenceUrl>${referenceUrl}</referenceUrl> <referenceUrl>${referenceUrl}</referenceUrl>
<referenceDriver>${driver}</referenceDriver> <referenceDriver>${driver}</referenceDriver>
<referenceUsername>${username}</referenceUsername> <referenceUsername>${username}</referenceUsername>
<referencePassword>${password}</referencePassword> <referencePassword>${password}</referencePassword>
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<properties> <properties>
<username>sa</username> <username>sa</username>
<password></password> <password></password>
<driver>org.h2.Driver</driver> <driver>org.h2.Driver</driver>
</properties> </properties>
</project> </project>
@@ -46,26 +46,26 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml"; private static final String CHANGELOG = "META-INF/jpa-changelog-master.xml";
@Override @Override
public String getCurrentVersionSql(String defaultSchema) { public String getCurrentVersionSql() {
return "SELECT ID from " + getTable("DATABASECHANGELOG", defaultSchema) + " ORDER BY DATEEXECUTED DESC LIMIT 1"; return "SELECT ID from DATABASECHANGELOG ORDER BY DATEEXECUTED DESC LIMIT 1";
} }
@Override @Override
public void update(KeycloakSession session, Connection connection, String defaultSchema) { public void update(KeycloakSession session, Connection connection) {
logger.debug("Starting database update"); logger.debug("Starting database update");
// Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks // Need ThreadLocal as liquibase doesn't seem to have API to inject custom objects into tasks
ThreadLocalSessionContext.setCurrentSession(session); ThreadLocalSessionContext.setCurrentSession(session);
try { try {
Liquibase liquibase = getLiquibase(connection, defaultSchema); Liquibase liquibase = getLiquibase(connection);
List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null); List<ChangeSet> changeSets = liquibase.listUnrunChangeSets((Contexts) null);
if (!changeSets.isEmpty()) { if (!changeSets.isEmpty()) {
if (changeSets.get(0).getId().equals(FIRST_VERSION)) { if (changeSets.get(0).getId().equals(FIRST_VERSION)) {
Statement statement = connection.createStatement(); Statement statement = connection.createStatement();
try { try {
statement.executeQuery("SELECT id FROM " + getTable("REALM", defaultSchema)); statement.executeQuery("SELECT id FROM REALM");
logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId()); logger.infov("Updating database from {0} to {1}", FIRST_VERSION, changeSets.get(changeSets.size() - 1).getId());
liquibase.markNextChangeSetRan(null); liquibase.markNextChangeSetRan(null);
@@ -93,9 +93,9 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
} }
@Override @Override
public void validate(Connection connection, String defaultSchema) { public void validate(Connection connection) {
try { try {
Liquibase liquibase = getLiquibase(connection, defaultSchema); Liquibase liquibase = getLiquibase(connection);
liquibase.validate(); liquibase.validate();
} catch (Exception e) { } catch (Exception e) {
@@ -103,7 +103,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
} }
} }
private Liquibase getLiquibase(Connection connection, String defaultSchema) throws Exception { private Liquibase getLiquibase(Connection connection) throws Exception {
ServiceLocator sl = ServiceLocator.getInstance(); ServiceLocator sl = ServiceLocator.getInstance();
if (!System.getProperties().containsKey("liquibase.scan.packages")) { if (!System.getProperties().containsKey("liquibase.scan.packages")) {
@@ -125,9 +125,6 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
LogFactory.setInstance(new LogWrapper()); LogFactory.setInstance(new LogWrapper());
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection)); Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
if (defaultSchema != null) {
database.setDefaultSchemaName(defaultSchema);
}
return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database); return new Liquibase(CHANGELOG, new ClassLoaderResourceAccessor(getClass().getClassLoader()), database);
} }
@@ -231,8 +228,4 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
} }
public static String getTable(String table, String defaultSchema) {
return defaultSchema != null ? defaultSchema + "." + table : table;
}
} }
@@ -12,7 +12,6 @@ import liquibase.statement.SqlStatement;
import liquibase.statement.core.UpdateStatement; import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Schema; import liquibase.structure.core.Schema;
import liquibase.structure.core.Table; import liquibase.structure.core.Table;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.models.utils.KeycloakModelUtils;
import java.sql.Connection; import java.sql.Connection;
@@ -37,7 +36,7 @@ public class AddRealmCodeSecret implements CustomSqlChange {
String correctedTableName = database.correctObjectName("REALM", Table.class); String correctedTableName = database.correctObjectName("REALM", Table.class);
if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) { if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) {
ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM " + LiquibaseJpaUpdaterProvider.getTable(correctedTableName, database.getDefaultSchemaName()) + " WHERE CODE_SECRET IS NULL"); ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM WHERE CODE_SECRET IS NULL");
while (resultSet.next()) { while (resultSet.next()) {
String id = resultSet.getString(1); String id = resultSet.getString(1);
@@ -16,7 +16,6 @@ import liquibase.snapshot.SnapshotGeneratorFactory;
import liquibase.statement.SqlStatement; import liquibase.statement.SqlStatement;
import liquibase.structure.core.Table; import liquibase.structure.core.Table;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.ThreadLocalSessionContext; import org.keycloak.connections.jpa.updater.liquibase.ThreadLocalSessionContext;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.services.DefaultKeycloakSessionFactory; import org.keycloak.services.DefaultKeycloakSessionFactory;
@@ -89,7 +88,7 @@ public abstract class CustomKeycloakTask implements CustomSqlChange {
try { try {
String correctedTableName = database.correctObjectName("REALM", Table.class); String correctedTableName = database.correctObjectName("REALM", Table.class);
if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) { if (SnapshotGeneratorFactory.getInstance().has(new Table().setName(correctedTableName), database)) {
ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM " + getTableName(correctedTableName)); ResultSet resultSet = connection.createStatement().executeQuery("SELECT ID FROM REALM");
try { try {
return (resultSet.next()); return (resultSet.next());
} finally { } finally {
@@ -109,9 +108,4 @@ public abstract class CustomKeycloakTask implements CustomSqlChange {
protected abstract void generateStatementsImpl() throws CustomChangeException; protected abstract void generateStatementsImpl() throws CustomChangeException;
protected abstract String getTaskId(); protected abstract String getTaskId();
// get Table name for sql selects
protected String getTableName(String tableName) {
return LiquibaseJpaUpdaterProvider.getTable(tableName, database.getDefaultSchemaName());
}
} }
@@ -11,7 +11,6 @@ import liquibase.statement.core.InsertStatement;
import liquibase.statement.core.UpdateStatement; import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Table; import liquibase.structure.core.Table;
import org.keycloak.Config; import org.keycloak.Config;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.migration.MigrationProvider; import org.keycloak.migration.MigrationProvider;
import org.keycloak.models.AdminRoles; import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClaimMask; import org.keycloak.models.ClaimMask;
@@ -50,10 +49,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
String identityProviderTableName = database.correctObjectName("IDENTITY_PROVIDER", Table.class); String identityProviderTableName = database.correctObjectName("IDENTITY_PROVIDER", Table.class);
String idpConfigTableName = database.correctObjectName("IDENTITY_PROVIDER_CONFIG", Table.class); String idpConfigTableName = database.correctObjectName("IDENTITY_PROVIDER_CONFIG", Table.class);
String realmSocialConfigTable = getTableName("REALM_SOCIAL_CONFIG"); PreparedStatement statement = jdbcConnection.prepareStatement("select RSC.NAME, VALUE, REALM_ID, UPDATE_PROFILE_ON_SOC_LOGIN from REALM_SOCIAL_CONFIG RSC,REALM where RSC.REALM_ID = REALM.ID ORDER BY RSC.REALM_ID, RSC.NAME");
String realmTableName = getTableName("REALM");
PreparedStatement statement = jdbcConnection.prepareStatement("select RSC.NAME, VALUE, REALM_ID, UPDATE_PROFILE_ON_SOC_LOGIN from " + realmSocialConfigTable + " RSC," + realmTableName +
" REALM where RSC.REALM_ID = REALM.ID ORDER BY RSC.REALM_ID, RSC.NAME");
try { try {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
try { try {
@@ -128,7 +124,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
protected void convertSocialToIdFedUsers() throws SQLException, DatabaseException { protected void convertSocialToIdFedUsers() throws SQLException, DatabaseException {
String federatedIdentityTableName = database.correctObjectName("FEDERATED_IDENTITY", Table.class); String federatedIdentityTableName = database.correctObjectName("FEDERATED_IDENTITY", Table.class);
PreparedStatement statement = jdbcConnection.prepareStatement("select REALM_ID, USER_ID, SOCIAL_PROVIDER, SOCIAL_USER_ID, SOCIAL_USERNAME from " + getTableName("USER_SOCIAL_LINK")); PreparedStatement statement = jdbcConnection.prepareStatement("select REALM_ID, USER_ID, SOCIAL_PROVIDER, SOCIAL_USER_ID, SOCIAL_USERNAME from USER_SOCIAL_LINK");
try { try {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
try { try {
@@ -174,7 +170,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
String adminRoleId = getAdminRoleId(); String adminRoleId = getAdminRoleId();
String masterRealmId = Config.getAdminRealm(); String masterRealmId = Config.getAdminRealm();
PreparedStatement statement = jdbcConnection.prepareStatement("select NAME from " + getTableName("REALM")); PreparedStatement statement = jdbcConnection.prepareStatement("select NAME from REALM");
try { try {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
try { try {
@@ -182,7 +178,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
String realmName = resultSet.getString("NAME"); String realmName = resultSet.getString("NAME");
String masterAdminAppName = realmName + "-realm"; String masterAdminAppName = realmName + "-realm";
PreparedStatement statement2 = jdbcConnection.prepareStatement("select ID from " + getTableName("CLIENT") + " where REALM_ID = ? AND NAME = ?"); PreparedStatement statement2 = jdbcConnection.prepareStatement("select ID from CLIENT where REALM_ID = ? AND NAME = ?");
statement2.setString(1, masterRealmId); statement2.setString(1, masterRealmId);
statement2.setString(2, masterAdminAppName); statement2.setString(2, masterAdminAppName);
@@ -213,7 +209,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
} }
private String getAdminRoleId() throws SQLException, DatabaseException { private String getAdminRoleId() throws SQLException, DatabaseException {
PreparedStatement statement = jdbcConnection.prepareStatement("select ID from " + getTableName("KEYCLOAK_ROLE") + " where NAME = ? AND REALM = ?"); PreparedStatement statement = jdbcConnection.prepareStatement("select ID from KEYCLOAK_ROLE where NAME = ? AND REALM = ?");
statement.setString(1, AdminRoles.ADMIN); statement.setString(1, AdminRoles.ADMIN);
statement.setString(2, Config.getAdminRealm()); statement.setString(2, Config.getAdminRealm());
@@ -235,8 +231,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
protected void addNewRealmAdminRoles() throws SQLException, DatabaseException { protected void addNewRealmAdminRoles() throws SQLException, DatabaseException {
PreparedStatement statement = jdbcConnection.prepareStatement("select CLIENT.ID REALM_ADMIN_APP_ID, CLIENT.REALM_ID REALM_ID, KEYCLOAK_ROLE.ID ADMIN_ROLE_ID from " + PreparedStatement statement = jdbcConnection.prepareStatement("select CLIENT.ID REALM_ADMIN_APP_ID, CLIENT.REALM_ID REALM_ID, KEYCLOAK_ROLE.ID ADMIN_ROLE_ID from CLIENT,KEYCLOAK_ROLE where KEYCLOAK_ROLE.APPLICATION = CLIENT.ID AND CLIENT.NAME = 'realm-management' AND KEYCLOAK_ROLE.NAME = ?");
getTableName("CLIENT") + " CLIENT," + getTableName("KEYCLOAK_ROLE") + " KEYCLOAK_ROLE where KEYCLOAK_ROLE.APPLICATION = CLIENT.ID AND CLIENT.NAME = 'realm-management' AND KEYCLOAK_ROLE.NAME = ?");
statement.setString(1, AdminRoles.REALM_ADMIN); statement.setString(1, AdminRoles.REALM_ADMIN);
try { try {
@@ -285,7 +280,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
String protocolMapperTableName = database.correctObjectName("PROTOCOL_MAPPER", Table.class); String protocolMapperTableName = database.correctObjectName("PROTOCOL_MAPPER", Table.class);
String protocolMapperCfgTableName = database.correctObjectName("PROTOCOL_MAPPER_CONFIG", Table.class); String protocolMapperCfgTableName = database.correctObjectName("PROTOCOL_MAPPER_CONFIG", Table.class);
PreparedStatement statement = jdbcConnection.prepareStatement("select ID, NAME, ALLOWED_CLAIMS_MASK from " + getTableName("CLIENT")); PreparedStatement statement = jdbcConnection.prepareStatement("select ID, NAME, ALLOWED_CLAIMS_MASK from CLIENT");
try { try {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
@@ -298,7 +293,7 @@ public class JpaUpdate1_2_0_Beta1 extends CustomKeycloakTask {
} }
Object acmObj = resultSet.getObject("ALLOWED_CLAIMS_MASK"); Object acmObj = resultSet.getObject("ALLOWED_CLAIMS_MASK");
long mask = (acmObj != null) ? ((Number) acmObj).longValue() : ClaimMask.ALL; long mask = (acmObj != null) ? (Long) acmObj : ClaimMask.ALL;
MigrationProvider migrationProvider = this.kcSession.getProvider(MigrationProvider.class); MigrationProvider migrationProvider = this.kcSession.getProvider(MigrationProvider.class);
List<ProtocolMapperRepresentation> protocolMappers = migrationProvider.getMappersForClaimMask(mask); List<ProtocolMapperRepresentation> protocolMappers = migrationProvider.getMappersForClaimMask(mask);
@@ -19,7 +19,7 @@ public class JpaUpdate1_2_0_CR1 extends CustomKeycloakTask {
try { try {
String trueValue = DataTypeFactory.getInstance().getTrueBooleanValue(database); String trueValue = DataTypeFactory.getInstance().getTrueBooleanValue(database);
PreparedStatement statement = jdbcConnection.prepareStatement("select CLIENT.REALM_ID, CLIENT.ID CLIENT_ID from " + getTableName("CLIENT") + " CLIENT where CLIENT.CONSENT_REQUIRED = " + trueValue); PreparedStatement statement = jdbcConnection.prepareStatement("select CLIENT.REALM_ID, CLIENT.ID CLIENT_ID from CLIENT where CLIENT.CONSENT_REQUIRED = " + trueValue);
try { try {
ResultSet resultSet = statement.executeQuery(); ResultSet resultSet = statement.executeQuery();
@@ -1,54 +0,0 @@
package org.keycloak.connections.jpa.updater.liquibase.custom;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import liquibase.datatype.DataTypeFactory;
import liquibase.exception.CustomChangeException;
import liquibase.statement.core.InsertStatement;
import liquibase.statement.core.UpdateStatement;
import liquibase.structure.core.Table;
import org.keycloak.models.utils.KeycloakModelUtils;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JpaUpdate1_4_0_Final extends CustomKeycloakTask {
@Override
protected void generateStatementsImpl() throws CustomChangeException {
String userAttributeTableName = database.correctObjectName("USER_ATTRIBUTE", Table.class);
try {
PreparedStatement statement = jdbcConnection.prepareStatement("select NAME, USER_ID from " + getTableName("USER_ATTRIBUTE"));
try {
ResultSet resultSet = statement.executeQuery();
try {
while (resultSet.next()) {
String name = resultSet.getString(1);
String userId = resultSet.getString(2);
UpdateStatement updateStatement = new UpdateStatement(null, null, userAttributeTableName)
.addNewColumnValue("ID", KeycloakModelUtils.generateId())
.setWhereClause("NAME='" + name + "' AND USER_ID='" + userId + "'");
statements.add(updateStatement);
}
} finally {
resultSet.close();
}
} finally {
statement.close();
}
confirmationMessage.append("Updated " + statements.size() + " attributes in USER_ATTRIBUTE table");
} catch (Exception e) {
throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e);
}
}
@Override
protected String getTaskId() {
return "Update 1.4.0.Final";
}
}
@@ -1,156 +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.4.0">
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
<delete tableName="CLIENT_SESSION_ROLE"/>
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
<delete tableName="CLIENT_SESSION_NOTE"/>
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION_NOTE"/>
<delete tableName="USER_SESSION"/>
<addColumn tableName="CLIENT">
<column name="SERVICE_ACCOUNTS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="AUTHENTICATION_FLOW">
<column name="PROVIDER_ID" type="VARCHAR(36)" defaultValue="basic-flow">
<constraints nullable="false"/>
</column>
<column name="TOP_LEVEL" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="BUILT_IN" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="AUTHENTICATION_EXECUTION">
<column name="AUTH_FLOW_ID" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
<column name="AUTH_CONFIG" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="USER_ATTRIBUTE">
<column name="ID" type="VARCHAR(36)" defaultValue="sybase-needs-something-here">
<constraints nullable="false"/>
</column>
</addColumn>
<dropColumn tableName="AUTHENTICATOR" columnName="PROVIDER_ID"/>
<renameTable oldTableName="AUTHENTICATOR_CONFIG" newTableName="AUTHENTICATOR_CONFIG_ENTRY"/>
<renameTable oldTableName="AUTHENTICATOR" newTableName="AUTHENTICATOR_CONFIG"/>
<!-- OAUTH_GRANT,
CODE_TO_TOKEN,
VERIFY_EMAIL,
UPDATE_PROFILE,
CONFIGURE_TOTP,
UPDATE_PASSWORD,
RECOVER_PASSWORD,
AUTHENTICATE,
SOCIAL_CALLBACK,
LOGGED_OUT -->
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="OAUTH_GRANT"/>
<where>ACTION = 0</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="CODE_TO_TOKEN"/>
<where>ACTION = 1</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="VERIFY_EMAIL"/>
<where>ACTION = 2</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="UPDATE_PROFILE"/>
<where>ACTION = 3</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="CONFIGURE_TOTP"/>
<where>ACTION = 4</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="UPDATE_PASSWORD"/>
<where>ACTION = 5</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="RECOVER_PASSWORD"/>
<where>ACTION = 6</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="AUTHENTICATE"/>
<where>ACTION = 7</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="SOCIAL_CALLBACK"/>
<where>ACTION = 8</where>
</update>
<update tableName="CLIENT_SESSION">
<column name="CURRENT_ACTION" value="LOGGED_OUT"/>
<where>ACTION = 9</where>
</update>
<createTable tableName="CLIENT_USER_SESSION_NOTE">
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="VARCHAR(2048)"/>
<column name="CLIENT_SESSION" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<createTable tableName="REQUIRED_ACTION_PROVIDER">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="ALIAS" type="VARCHAR(255)"/>
<column name="NAME" type="VARCHAR(255)"/>
<column name="REALM_ID" type="VARCHAR(36)"/>
<column name="ENABLED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="DEFAULT_ACTION" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
<column name="PROVIDER_ID" type="VARCHAR(255)"/>
</createTable>
<createTable tableName="REQUIRED_ACTION_CONFIG">
<column name="REQUIRED_ACTION_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="VALUE" type="CLOB"/>
<column name="NAME" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
</createTable>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate1_4_0_Final"/>
<dropPrimaryKey constraintName="CONSTRAINT_6" tableName="USER_ATTRIBUTE"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_USER_ATTRIBUTE_PK" tableName="USER_ATTRIBUTE"/>
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_REQ_ACT_PRV_PK" tableName="REQUIRED_ACTION_PROVIDER"/>
<addPrimaryKey columnNames="REQUIRED_ACTION_ID, NAME" constraintName="CONSTRAINT_REQ_ACT_CFG_PK" tableName="REQUIRED_ACTION_CONFIG"/>
<addPrimaryKey columnNames="CLIENT_SESSION, NAME" constraintName="CONSTR_CL_USR_SES_NOTE" tableName="CLIENT_USER_SESSION_NOTE"/>
<addForeignKeyConstraint baseColumnNames="REALM_ID" baseTableName="REQUIRED_ACTION_PROVIDER" constraintName="FK_REQ_ACT_REALM" referencedColumnNames="ID" referencedTableName="REALM"/>
<addForeignKeyConstraint baseColumnNames="CLIENT_SESSION" baseTableName="CLIENT_USER_SESSION_NOTE" constraintName="FK_CL_USR_SES_NOTE" referencedColumnNames="ID" referencedTableName="CLIENT_SESSION"/>
<dropColumn tableName="CLIENT_SESSION" columnName="ACTION"/>
<addColumn tableName="USER_ENTITY">
<column name="CREATED_TIMESTAMP" type="BIGINT"/>
<column name="SERVICE_ACCOUNT_CLIENT_LINK" type="VARCHAR(36)"/>
</addColumn>
<!-- Sybase specific hacks -->
<modifySql dbms="sybase">
<replace replace="[USER_ATTRIBUTE] DROP PRIMARY KEY" with="[USER_ATTRIBUTE] DROP CONSTRAINT CONSTRAINT_6" />
</modifySql>
</changeSet>
</databaseChangeLog>
@@ -1,76 +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.5.0">
<delete tableName="CLIENT_SESSION_AUTH_STATUS"/>
<delete tableName="CLIENT_SESSION_ROLE"/>
<delete tableName="CLIENT_SESSION_PROT_MAPPER"/>
<delete tableName="CLIENT_SESSION_NOTE"/>
<delete tableName="CLIENT_SESSION"/>
<delete tableName="USER_SESSION_NOTE"/>
<delete tableName="USER_SESSION"/>
<dropDefaultValue tableName="AUTHENTICATION_EXECUTION" columnName="USER_SETUP_ALLOWED" />
<dropColumn tableName="AUTHENTICATION_EXECUTION" columnName="USER_SETUP_ALLOWED"/>
<addColumn tableName="CREDENTIAL">
<column name="COUNTER" type="INT" defaultValueNumeric="0">
<constraints nullable="true"/>
</column>
<column name="DIGITS" type="INT" defaultValueNumeric="6">
<constraints nullable="true"/>
</column>
<column name="PERIOD" type="INT" defaultValueNumeric="30">
<constraints nullable="true"/>
</column>
<column name="ALGORITHM" type="VARCHAR(36)" defaultValue="HmacSHA1">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="REALM">
<column name="OTP_POLICY_COUNTER" type="INT" defaultValueNumeric="0">
<constraints nullable="true"/>
</column>
<column name="OTP_POLICY_WINDOW" type="INT" defaultValueNumeric="1">
<constraints nullable="true"/>
</column>
<column name="OTP_POLICY_PERIOD" type="INT" defaultValueNumeric="30">
<constraints nullable="true"/>
</column>
<column name="OTP_POLICY_DIGITS" type="INT" defaultValueNumeric="6">
<constraints nullable="true"/>
</column>
<column name="OTP_POLICY_ALG" type="VARCHAR(36)" defaultValue="HmacSHA1">
<constraints nullable="true"/>
</column>
<column name="OTP_POLICY_TYPE" type="VARCHAR(36)" defaultValue="totp">
<constraints nullable="true"/>
</column>
<column name="BROWSER_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
<column name="REGISTRATION_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
<column name="DIRECT_GRANT_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
<column name="RESET_CREDENTIALS_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
<column name="CLIENT_AUTH_FLOW" type="VARCHAR(36)">
<constraints nullable="true"/>
</column>
</addColumn>
<addColumn tableName="CLIENT">
<column name="CLIENT_AUTHENTICATOR_TYPE" type="VARCHAR(255)">
<constraints nullable="true"/>
</column>
</addColumn>
<!-- Sybase specific hacks -->
<modifySql dbms="sybase">
<regExpReplace replace=".*(SET DEFAULT NULL)" with="SELECT 1" />
</modifySql>
</changeSet>
</databaseChangeLog>
@@ -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>
@@ -7,9 +7,4 @@
<include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/> <include file="META-INF/jpa-changelog-1.2.0.CR1.xml"/>
<include file="META-INF/jpa-changelog-1.2.0.Final.xml"/> <include file="META-INF/jpa-changelog-1.2.0.Final.xml"/>
<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.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>
+36 -36
View File
@@ -1,36 +1,36 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<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.3.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-jpa</artifactId> <artifactId>keycloak-connections-jpa</artifactId>
<name>Keycloak Connections JPA</name> <name>Keycloak Connections JPA</name>
<description/> <description/>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-core</artifactId> <artifactId>keycloak-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.keycloak</groupId> <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-api</artifactId> <artifactId>keycloak-model-api</artifactId>
</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.0-api</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId> <artifactId>hibernate-entitymanager</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>
@@ -1,41 +1,34 @@
package org.keycloak.connections.jpa; package org.keycloak.connections.jpa;
import java.sql.Connection; import org.hibernate.ejb.AvailableSettings;
import java.sql.DatabaseMetaData; import org.jboss.logging.Logger;
import java.sql.DriverManager; import org.keycloak.Config;
import java.sql.ResultSet; import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import java.sql.SQLException; import org.keycloak.models.KeycloakSession;
import java.util.HashMap; import org.keycloak.models.KeycloakSessionFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.naming.InitialContext; import javax.naming.InitialContext;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence; import javax.persistence.Persistence;
import javax.sql.DataSource; import javax.sql.DataSource;
import java.sql.Connection;
import org.hibernate.ejb.AvailableSettings; import java.sql.DriverManager;
import org.jboss.logging.Logger; import java.sql.ResultSet;
import org.keycloak.Config; import java.sql.SQLException;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider; import java.util.HashMap;
import org.keycloak.connections.jpa.util.JpaUtils; import java.util.Map;
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);
private volatile EntityManagerFactory emf; private volatile EntityManagerFactory emf;
private Config.Scope config; private Config.Scope config;
private Map<String,String> operationalInfo;
@Override @Override
public JpaConnectionProvider create(KeycloakSession session) { public JpaConnectionProvider create(KeycloakSession session) {
@@ -77,123 +70,105 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
Connection connection = null; Connection connection = null;
String unitName = config.get("unitName");
String databaseSchema = config.get("databaseSchema"); String databaseSchema = config.get("databaseSchema");
Map<String, Object> properties = new HashMap<String, Object>(); Map<String, Object> properties = new HashMap<String, Object>();
String unitName = "keycloak-default"; // Only load config from keycloak-server.json if unitName is not specified
if (unitName == null) {
unitName = "keycloak-default";
String dataSource = config.get("dataSource"); String dataSource = config.get("dataSource");
if (dataSource != null) { if (dataSource != null) {
if (config.getBoolean("jta", false)) { if (config.getBoolean("jta", false)) {
properties.put(AvailableSettings.JTA_DATASOURCE, dataSource); properties.put(AvailableSettings.JTA_DATASOURCE, dataSource);
} else {
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource);
}
} else { } else {
properties.put(AvailableSettings.NON_JTA_DATASOURCE, dataSource); properties.put(AvailableSettings.JDBC_URL, config.get("url"));
} properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
} else {
properties.put(AvailableSettings.JDBC_URL, config.get("url"));
properties.put(AvailableSettings.JDBC_DRIVER, config.get("driver"));
String user = config.get("user"); String user = config.get("user");
if (user != null) { if (user != null) {
properties.put(AvailableSettings.JDBC_USER, user); properties.put(AvailableSettings.JDBC_USER, user);
}
String password = config.get("password");
if (password != null) {
properties.put(AvailableSettings.JDBC_PASSWORD, password);
}
} }
String password = config.get("password");
if (password != null) { String driverDialect = config.get("driverDialect");
properties.put(AvailableSettings.JDBC_PASSWORD, password); if (driverDialect != null && driverDialect.length() > 0) {
properties.put("hibernate.dialect", driverDialect);
} }
}
String driverDialect = config.get("driverDialect"); if (databaseSchema != null) {
if (driverDialect != null && driverDialect.length() > 0) { if (databaseSchema.equals("development-update")) {
properties.put("hibernate.dialect", driverDialect); properties.put("hibernate.hbm2ddl.auto", "update");
} databaseSchema = null;
} else if (databaseSchema.equals("development-validate")) {
properties.put("hibernate.hbm2ddl.auto", "validate");
databaseSchema = null;
}
}
String schema = config.get("schema"); properties.put("hibernate.show_sql", config.getBoolean("showSql", false));
if (schema != null) { properties.put("hibernate.format_sql", config.getBoolean("formatSql", true));
properties.put(JpaUtils.HIBERNATE_DEFAULT_SCHEMA, schema);
} }
if (databaseSchema != null) { if (databaseSchema != null) {
if (databaseSchema.equals("development-update")) { logger.trace("Updating database");
properties.put("hibernate.hbm2ddl.auto", "update");
databaseSchema = null; JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
} else if (databaseSchema.equals("development-validate")) { if (updater == null) {
properties.put("hibernate.hbm2ddl.auto", "validate"); throw new RuntimeException("Can't update database: JPA updater provider not found");
databaseSchema = null;
} }
connection = getConnection();
if (databaseSchema.equals("update")) {
String currentVersion = null;
try {
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql());
if (resultSet.next()) {
currentVersion = resultSet.getString(1);
}
} catch (SQLException e) {
}
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
updater.update(session, connection);
} else {
logger.debug("Database is up to date");
}
} else if (databaseSchema.equals("validate")) {
updater.validate(connection);
} else {
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
}
logger.trace("Database update completed");
} }
properties.put("hibernate.show_sql", config.getBoolean("showSql", false)); logger.trace("Creating EntityManagerFactory");
properties.put("hibernate.format_sql", config.getBoolean("formatSql", true)); emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");
connection = getConnection(); // Close after creating EntityManagerFactory to prevent in-mem databases from closing
try{ if (connection != null) {
prepareOperationalInfo(connection); try {
connection.close();
if (databaseSchema != null) { } catch (SQLException e) {
logger.trace("Updating database"); logger.warn(e);
}
JpaUpdaterProvider updater = session.getProvider(JpaUpdaterProvider.class);
if (updater == null) {
throw new RuntimeException("Can't update database: JPA updater provider not found");
}
if (databaseSchema.equals("update")) {
String currentVersion = null;
try {
ResultSet resultSet = connection.createStatement().executeQuery(updater.getCurrentVersionSql(schema));
if (resultSet.next()) {
currentVersion = resultSet.getString(1);
}
} catch (SQLException e) {
}
if (currentVersion == null || !JpaUpdaterProvider.LAST_VERSION.equals(currentVersion)) {
updater.update(session, connection, schema);
} else {
logger.debug("Database is up to date");
}
} else if (databaseSchema.equals("validate")) {
updater.validate(connection, schema);
} else {
throw new RuntimeException("Invalid value for databaseSchema: " + databaseSchema);
}
logger.trace("Database update completed");
}
logger.trace("Creating EntityManagerFactory");
emf = Persistence.createEntityManagerFactory(unitName, properties);
logger.trace("EntityManagerFactory created");
} finally {
// Close after creating EntityManagerFactory to prevent in-mem databases from closing
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
logger.warn(e);
}
}
} }
} }
} }
} }
} }
protected void prepareOperationalInfo(Connection connection) {
try {
operationalInfo = new LinkedHashMap<>();
DatabaseMetaData md = connection.getMetaData();
operationalInfo.put("databaseUrl",md.getURL());
operationalInfo.put("databaseUser", md.getUserName());
operationalInfo.put("databaseProduct", md.getDatabaseProductName() + " " + md.getDatabaseProductVersion());
operationalInfo.put("databaseDriver", md.getDriverName() + " " + md.getDriverVersion());
} catch (SQLException e) {
logger.warn("Unable to prepare operational info due database exception: " + e.getMessage());
}
}
private Connection getConnection() { private Connection getConnection() {
try { try {
@@ -209,10 +184,5 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
throw new RuntimeException("Failed to connect to database", e); throw new RuntimeException("Failed to connect to database", e);
} }
} }
@Override
public Map<String,String> getOperationalInfo() {
return operationalInfo;
}
} }
@@ -6,5 +6,4 @@ import org.keycloak.provider.ProviderFactory;
* @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 ProviderFactory<JpaConnectionProvider> {
} }
@@ -1,53 +1,53 @@
package org.keycloak.connections.jpa; package org.keycloak.connections.jpa;
import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.KeycloakTransaction;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
/** /**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class JpaKeycloakTransaction implements KeycloakTransaction { public class JpaKeycloakTransaction implements KeycloakTransaction {
protected EntityManager em; protected EntityManager em;
public JpaKeycloakTransaction(EntityManager em) { public JpaKeycloakTransaction(EntityManager em) {
this.em = em; this.em = em;
} }
@Override @Override
public void begin() { public void begin() {
em.getTransaction().begin(); em.getTransaction().begin();
} }
@Override @Override
public void commit() { public void commit() {
try { try {
em.getTransaction().commit(); em.getTransaction().commit();
} catch (PersistenceException e) { } catch (PersistenceException e) {
throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e); throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
} }
} }
@Override @Override
public void rollback() { public void rollback() {
em.getTransaction().rollback(); em.getTransaction().rollback();
} }
@Override @Override
public void setRollbackOnly() { public void setRollbackOnly() {
em.getTransaction().setRollbackOnly(); em.getTransaction().setRollbackOnly();
} }
@Override @Override
public boolean getRollbackOnly() { public boolean getRollbackOnly() {
return em.getTransaction().getRollbackOnly(); return em.getTransaction().getRollbackOnly();
} }
@Override @Override
public boolean isActive() { public boolean isActive() {
return em.getTransaction().isActive(); return em.getTransaction().isActive();
} }
} }
@@ -12,12 +12,12 @@ 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.3.0.Beta1";
public String getCurrentVersionSql(String defaultSchema); public String getCurrentVersionSql();
public void update(KeycloakSession session, Connection connection, String defaultSchema); public void update(KeycloakSession session, Connection connection);
public void validate(Connection connection, String defaultSchema); public void validate(Connection connection);
} }
@@ -1,16 +0,0 @@
package org.keycloak.connections.jpa.util;
import javax.persistence.EntityManager;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class JpaUtils {
public static final String HIBERNATE_DEFAULT_SCHEMA = "hibernate.default_schema";
public static String getTableNameForNativeQuery(String tableName, EntityManager em) {
String schema = (String) em.getEntityManagerFactory().getProperties().get(HIBERNATE_DEFAULT_SCHEMA);
return (schema==null) ? tableName : schema + "." + tableName;
}
}
@@ -1,51 +1,52 @@
<persistence xmlns="http://java.sun.com/xml/ns/persistence" <persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0"> version="1.0">
<persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL"> <persistence-unit name="keycloak-default" transaction-type="RESOURCE_LOCAL">
<class>org.keycloak.models.jpa.entities.ClientEntity</class> <class>org.keycloak.models.jpa.entities.ClientEntity</class>
<class>org.keycloak.models.jpa.entities.CredentialEntity</class> <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
<class>org.keycloak.models.jpa.entities.RealmEntity</class> <class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class> <class>org.keycloak.models.jpa.entities.RealmAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class> <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
<class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class> <class>org.keycloak.models.jpa.entities.UserFederationProviderEntity</class>
<class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class> <class>org.keycloak.models.jpa.entities.UserFederationMapperEntity</class>
<class>org.keycloak.models.jpa.entities.RoleEntity</class> <class>org.keycloak.models.jpa.entities.RoleEntity</class>
<class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</class> <class>org.keycloak.models.jpa.entities.FederatedIdentityEntity</class>
<class>org.keycloak.models.jpa.entities.MigrationModelEntity</class> <class>org.keycloak.models.jpa.entities.MigrationModelEntity</class>
<class>org.keycloak.models.jpa.entities.UserEntity</class> <class>org.keycloak.models.jpa.entities.UserEntity</class>
<class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class> <class>org.keycloak.models.jpa.entities.UserRequiredActionEntity</class>
<class>org.keycloak.models.jpa.entities.UserAttributeEntity</class> <class>org.keycloak.models.jpa.entities.UserAttributeEntity</class>
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class> <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class> <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class> <class>org.keycloak.models.jpa.entities.IdentityProviderEntity</class>
<class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class> <class>org.keycloak.models.jpa.entities.IdentityProviderMapperEntity</class>
<class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class> <class>org.keycloak.models.jpa.entities.ClientIdentityProviderMappingEntity</class>
<class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class> <class>org.keycloak.models.jpa.entities.ProtocolMapperEntity</class>
<class>org.keycloak.models.jpa.entities.UserConsentEntity</class> <class>org.keycloak.models.jpa.entities.UserConsentEntity</class>
<class>org.keycloak.models.jpa.entities.UserConsentRoleEntity</class> <class>org.keycloak.models.jpa.entities.UserConsentRoleEntity</class>
<class>org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity</class> <class>org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationFlowEntity</class> <class>org.keycloak.models.jpa.entities.AuthenticationFlowEntity</class>
<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.AuthenticatorEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredActionProviderEntity</class>
<class>org.keycloak.models.jpa.session.PersistentUserSessionEntity</class> <!-- JpaUserSessionProvider -->
<class>org.keycloak.models.jpa.session.PersistentClientSessionEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionEntity</class>
<class>org.keycloak.models.jpa.entities.GroupEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionRoleEntity</class>
<class>org.keycloak.models.jpa.entities.GroupAttributeEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionAuthStatusEntity</class>
<class>org.keycloak.models.jpa.entities.GroupRoleMappingEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionProtocolMapperEntity</class>
<class>org.keycloak.models.jpa.entities.UserGroupMembershipEntity</class> <class>org.keycloak.models.sessions.jpa.entities.ClientSessionNoteEntity</class>
<class>org.keycloak.models.jpa.entities.ClientTemplateEntity</class> <class>org.keycloak.models.sessions.jpa.entities.UserSessionNoteEntity</class>
<class>org.keycloak.models.jpa.entities.TemplateScopeMappingEntity</class> <class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
<!-- JpaAuditProviders -->
<class>org.keycloak.events.jpa.EventEntity</class> <!-- JpaAuditProviders -->
<class>org.keycloak.events.jpa.AdminEventEntity</class> <class>org.keycloak.events.jpa.EventEntity</class>
<class>org.keycloak.events.jpa.AdminEventEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="jboss.as.jpa.managed" value="false"/> <properties>
</properties> <property name="jboss.as.jpa.managed" value="false"/>
</persistence-unit> </properties>
</persistence> </persistence-unit>
</persistence>
+1 -1
View File
@@ -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.3.1.Final</version>
<relativePath>../../pom.xml</relativePath> <relativePath>../../pom.xml</relativePath>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
Loaded 100 of 3741 files, more files were not shown because too many files have changed in this diff. Show more