Merge pull request #3117 from patriot1burke/master

deployer, jta lookup, merge user fed/storage
This commit is contained in:
Bill Burke 2016-08-08 17:46:35 -04:00 committed by GitHub
commit d94515cdae
45 changed files with 1136 additions and 295 deletions

View file

@ -20,6 +20,7 @@
<config>
<subsystems>
<subsystem>logging.xml</subsystem>
<subsystem>deployment-scanner.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="default">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>

View file

@ -20,6 +20,7 @@
<config>
<subsystems>
<subsystem>logging.xml</subsystem>
<subsystem>deployment-scanner.xml</subsystem>
<subsystem>bean-validation.xml</subsystem>
<subsystem supplement="default">keycloak-datasources.xml</subsystem>
<subsystem>ee.xml</subsystem>

View file

@ -75,5 +75,13 @@
"default": {
"cacheContainer" : "java:comp/env/infinispan/Keycloak"
}
},
"jta-lookup": {
"provider": "${keycloak.jta.lookup.provider:jboss}",
"jboss" : {
"enabled": true
}
}
}

View file

@ -68,5 +68,6 @@
<module name="javax.activation.api"/>
<module name="org.apache.httpcomponents"/>
<module name="org.twitter4j"/>
<module name="javax.transaction.api"/>
</dependencies>
</module>

View file

@ -40,6 +40,7 @@
<module name="org.jboss.as.web-common" optional="true"/>
<module name="org.jboss.as.web" optional="true"/>
<module name="org.jboss.as.version" optional="true"/>
<module name="org.keycloak.keycloak-services"/>
<module name="org.keycloak.keycloak-wildfly-adapter" optional="true"/>
<module name="org.jboss.metadata"/>
</dependencies>

View file

@ -44,8 +44,6 @@
<exclude>welcome-content/**</exclude>
<exclude>appclient</exclude>
<exclude>appclient/**</exclude>
<exclude>standalone/deployments</exclude>
<exclude>standalone/deployments/*</exclude>
<exclude>copyright.txt</exclude>
<exclude>README.txt</exclude>
</excludes>

View file

@ -0,0 +1,13 @@
Example User Storage Provider with EJB and JPA
===================================================
This is an example of the User Storage SPI implemented using EJB and JPA. To deploy this provider you must have Keycloak
running in standalone or standalone-ha mode. Then type the follow maven command:
mvn clean install wildfly:deploy
Login and go to the User Federation tab and you should now see your deployed provider in the add-provider list box.
Add the provider, save it, then any new user you create will be stored and in the custom store you implemented. You
can modify the example and hot deploy it using the above maven command again.
This example uses the built in in-memory datasource that comes with keycloak: ExampleDS.

View file

@ -1,45 +0,0 @@
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<assembly>
<id>example-module</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<fileSets>
<fileSet>
<directory>target</directory>
<outputDirectory>system/layers/keycloak/org/keycloak/examples/jpa-example/main</outputDirectory>
<filtered>true</filtered>
<includes>
<include>user-storage-jpa-example.jar</include>
</includes>
</fileSet>
<fileSet>
<directory>.</directory>
<outputDirectory>system/layers/keycloak/org/keycloak/examples/jpa-example/main</outputDirectory>
<filtered>true</filtered>
<includes>
<include>module.xml</include>
</includes>
</fileSet>
</fileSets>
</assembly>

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2016 Red Hat, Inc. and/or its affiliates
~ and other contributors as indicated by the @author tags.
~
~ 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.
-->
<module xmlns="urn:jboss:module:1.3" name="org.keycloak.examples.jpa-example">
<properties>
<property name="jboss.api" value="private"/>
</properties>
<resources>
<resource-root path="user-storage-jpa-example.jar"/>
</resources>
<dependencies>
<module name="org.keycloak.keycloak-common"/>
<module name="org.keycloak.keycloak-core"/>
<module name="org.keycloak.keycloak-server-spi"/>
<module name="org.keycloak.keycloak-model-jpa"/>
<module name="javax.persistence.api"/>
<module name="org.jboss.logging"/>
<module name="org.javassist"/>
<module name="org.hibernate" services="import"/>
<module name="org.bouncycastle" />
<module name="javax.api"/>
</dependencies>
</module>

View file

@ -23,7 +23,7 @@
<version>2.1.0-SNAPSHOT</version>
</parent>
<name>Properties Authentication Provider Example</name>
<name>User Storage JPA Provider Exapmle</name>
<description/>
<modelVersion>4.0.0</modelVersion>
@ -41,11 +41,6 @@
<artifactId>keycloak-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
<artifactId>keycloak-model-jpa</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
@ -61,6 +56,12 @@
<artifactId>hibernate-entitymanager</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ejb</groupId>
<artifactId>jboss-ejb-api_3.2_spec</artifactId>
<version>1.0.0.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
@ -75,28 +76,11 @@
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>assemble</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<groupId>org.wildfly.plugins</groupId>
<artifactId>wildfly-maven-plugin</artifactId>
<configuration>
<descriptors>
<descriptor>assembly.xml</descriptor>
</descriptors>
<recompressZippedFiles>true</recompressZippedFiles>
<finalName>modules</finalName>
<appendAssemblyId>false</appendAssemblyId>
<outputDirectory>${project.build.directory}</outputDirectory>
<workDirectory>${project.build.directory}/assembly/work</workDirectory>
<tarLongFileMode>gnu</tarLongFileMode>
<skip>false</skip>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

View file

@ -32,9 +32,12 @@ import org.keycloak.storage.user.UserLookupProvider;
import org.keycloak.storage.user.UserQueryProvider;
import org.keycloak.storage.user.UserRegistrationProvider;
import javax.ejb.Local;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@ -44,19 +47,26 @@ import java.util.Map;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ExampleUserStorageProvider implements UserStorageProvider,
@Stateful
@Local(EjbExampleUserStorageProvider.class)
public class EjbExampleUserStorageProvider implements UserStorageProvider,
UserLookupProvider,
UserRegistrationProvider,
UserCredentialValidatorProvider,
UserQueryProvider {
private static final Logger logger = Logger.getLogger(ExampleUserStorageProvider.class);
private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
@PersistenceContext
protected EntityManager em;
protected ComponentModel model;
protected KeycloakSession session;
public ExampleUserStorageProvider(EntityManager em, ComponentModel model, KeycloakSession session) {
this.em = em;
public void setModel(ComponentModel model) {
this.model = model;
}
public void setSession(KeycloakSession session) {
this.session = session;
}
@ -75,9 +85,9 @@ public class ExampleUserStorageProvider implements UserStorageProvider,
}
@Remove
@Override
public void close() {
em.close();
}
@Override

View file

@ -0,0 +1,59 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.examples.storage.user;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.storage.UserStorageProviderFactory;
import javax.naming.InitialContext;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class EjbExampleUserStorageProviderFactory implements UserStorageProviderFactory<EjbExampleUserStorageProvider> {
@Override
public EjbExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) {
try {
InitialContext ctx = new InitialContext();
EjbExampleUserStorageProvider provider = (EjbExampleUserStorageProvider)ctx.lookup("java:global/user-storage-jpa-example/" + EjbExampleUserStorageProvider.class.getSimpleName());
provider.setModel(model);
provider.setSession(session);
return provider;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public String getId() {
return "example-user-storage-jpa";
}
@Override
public String getHelpText() {
return "JPA Example User Storage Provider";
}
}

View file

@ -1,90 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.examples.storage.user;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.jpa.boot.internal.ParsedPersistenceXmlDescriptor;
import org.hibernate.jpa.boot.internal.PersistenceXmlParser;
import org.hibernate.jpa.boot.spi.Bootstrap;
import org.keycloak.Config;
import org.keycloak.component.ComponentModel;
import org.keycloak.connections.jpa.JndiEntityManagerLookup;
import org.keycloak.connections.jpa.JpaKeycloakTransaction;
import org.keycloak.connections.jpa.entityprovider.ProxyClassLoader;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.storage.UserStorageProviderFactory;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.spi.PersistenceUnitTransactionType;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ExampleUserStorageProviderFactory implements UserStorageProviderFactory<ExampleUserStorageProvider> {
EntityManagerFactory emf = null;
@Override
public ExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) {
EntityManager em = emf.createEntityManager();
session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
return new ExampleUserStorageProvider(em, model, session);
}
@Override
public String getId() {
return "example-user-storage";
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
//emf = Persistence.createEntityManagerFactory("user-storage-jpa-example");
emf = createEntityManagerFactory("user-storage-jpa-example");
//emf = Bootstrap.getEntityManagerFactoryBuilder()
}
public static EntityManagerFactory createEntityManagerFactory(String unitName) {
PersistenceXmlParser parser = new PersistenceXmlParser(new ClassLoaderServiceImpl(ExampleUserStorageProviderFactory.class.getClassLoader()), PersistenceUnitTransactionType.RESOURCE_LOCAL);
List<ParsedPersistenceXmlDescriptor> persistenceUnits = parser.doResolve(new HashMap());
for (ParsedPersistenceXmlDescriptor persistenceUnit : persistenceUnits) {
if (persistenceUnit.getName().equals(unitName)) {
return Bootstrap.getEntityManagerFactoryBuilder(persistenceUnit, new HashMap(), ExampleUserStorageProviderFactory.class.getClassLoader()).build();
}
}
throw new RuntimeException("Persistence unit '" + unitName + "' not found");
}
@Override
public void close() {
emf.close();
}
}

View file

@ -22,7 +22,6 @@ import org.keycloak.component.ComponentModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage;
@ -35,7 +34,7 @@ import java.util.Map;
* @version $Revision: 1 $
*/
public class UserAdapter extends AbstractUserAdapterFederatedStorage {
private static final Logger logger = Logger.getLogger(ExampleUserStorageProvider.class);
private static final Logger logger = Logger.getLogger(EjbExampleUserStorageProvider.class);
protected UserEntity entity;
protected String keycloakId;

View file

@ -4,9 +4,8 @@
xsi:schemaLocation="
http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
<persistence-unit name="user-storage-jpa-example" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<non-jta-data-source>java:jboss/datasources/ExampleDS</non-jta-data-source>
<persistence-unit name="user-storage-jpa-example">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.keycloak.examples.storage.user.UserEntity</class>

View file

@ -1 +1 @@
org.keycloak.examples.storage.user.ExampleUserStorageProviderFactory
org.keycloak.examples.storage.user.EjbExampleUserStorageProviderFactory

View file

@ -63,6 +63,7 @@
<jboss.logging.tools.version>2.0.1.Final</jboss.logging.tools.version>
<jboss.logging.tools.wf8.version>1.2.0.Final</jboss.logging.tools.wf8.version>
<jboss-jaxrs-api_2.0_spec>1.0.0.Final</jboss-jaxrs-api_2.0_spec>
<jboss-transaction-api_1.2_spec>1.0.0.Final</jboss-transaction-api_1.2_spec>
<jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>1.0.4.Final</jboss.spec.javax.xml.bind.jboss-jaxb-api_2.2_spec.version>
<log4j.version>1.2.16</log4j.version>
<resteasy.version>3.0.14.Final</resteasy.version>
@ -227,6 +228,11 @@
<artifactId>javax.mail-api</artifactId>
<version>${javax.mail.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
<version>${jboss-transaction-api_1.2_spec}</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>

View file

@ -36,6 +36,11 @@
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-jaxrs</artifactId>

View file

@ -28,7 +28,7 @@ public class UserStorageProviderSpi implements Spi {
@Override
public boolean isInternal() {
return true;
return false;
}
@Override

View file

@ -109,6 +109,10 @@
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>

View file

@ -16,8 +16,10 @@
*/
package org.keycloak.provider;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.services.ServicesLogger;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.LinkedList;
@ -33,7 +35,8 @@ public class ProviderManager {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private List<ProviderLoader> loaders = new LinkedList<ProviderLoader>();
private Map<String, List<ProviderFactory>> cache = new HashMap<String, List<ProviderFactory>>();
private MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> cache = new MultivaluedHashMap<>();
public ProviderManager(ClassLoader baseClassLoader, String... resources) {
List<ProviderLoaderFactory> factories = new LinkedList<ProviderLoaderFactory>();
@ -65,6 +68,10 @@ public class ProviderManager {
}
}
public ProviderManager(ClassLoader baseClassLoader) {
loaders.add(new DefaultProviderLoader(baseClassLoader));
}
public synchronized List<Spi> loadSpis() {
// Use a map to prevent duplicates, since the loaders may have overlapping classpaths.
Map<String, Spi> spiMap = new HashMap<>();
@ -80,9 +87,7 @@ public class ProviderManager {
}
public synchronized List<ProviderFactory> load(Spi spi) {
List<ProviderFactory> factories = cache.get(spi.getName());
if (factories == null) {
factories = new LinkedList<ProviderFactory>();
if (!cache.containsKey(spi.getProviderClass())) {
IdentityHashMap factoryClasses = new IdentityHashMap();
for (ProviderLoader loader : loaders) {
List<ProviderFactory> f = loader.load(spi);
@ -90,14 +95,26 @@ public class ProviderManager {
for (ProviderFactory pf: f) {
// make sure there are no duplicates
if (!factoryClasses.containsKey(pf.getClass())) {
factories.add(pf);
cache.add(spi.getProviderClass(), pf);
factoryClasses.put(pf.getClass(), pf);
}
}
}
}
}
return factories;
List<ProviderFactory> rtn = cache.get(spi.getProviderClass());
return rtn == null ? Collections.EMPTY_LIST : rtn;
}
/**
* returns a copy of internal factories.
*
* @return
*/
public synchronized MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> getLoadedFactories() {
MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> copy = new MultivaluedHashMap<>();
copy.addAll(cache);
return copy;
}
public synchronized ProviderFactory load(Spi spi, String providerId) {

View file

@ -0,0 +1,26 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.provider;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface ProviderManagerDeployer {
void deploy(ProviderManager pm);
void undeploy(ProviderManager pm);
}

View file

@ -0,0 +1,62 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.provider;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ProviderManagerRegistry {
public static final ProviderManagerRegistry SINGLETON = new ProviderManagerRegistry();
protected List<ProviderManager> preBoot = Collections.synchronizedList(new LinkedList<>());
protected AtomicReference<ProviderManagerDeployer> deployerRef = new AtomicReference<>();
public void setDeployer(ProviderManagerDeployer deployer) {
this.deployerRef.set(deployer);
}
public void deploy(ProviderManager pm) {
ProviderManagerDeployer deployer = getDeployer();
if (deployer == null) {
preBoot.add(pm);
} else {
deployer.deploy(pm);
}
}
public void undeploy(ProviderManager pm) {
preBoot.remove(pm);
ProviderManagerDeployer deployer = getDeployer();
if (deployer != null) {
deployer.undeploy(pm);
}
}
public ProviderManagerDeployer getDeployer() {
return deployerRef.get();
}
public List<ProviderManager> getPreBoot() {
return preBoot;
}
}

View file

@ -17,6 +17,7 @@
package org.keycloak.services;
import org.keycloak.Config;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.provider.Provider;
@ -24,26 +25,31 @@ import org.keycloak.provider.ProviderEvent;
import org.keycloak.provider.ProviderEventListener;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.ProviderManagerDeployer;
import org.keycloak.provider.ProviderManagerRegistry;
import org.keycloak.provider.Spi;
import org.keycloak.services.ServicesLogger;
import org.keycloak.transaction.JtaRegistration;
import org.keycloak.transaction.JtaTransactionManagerLookup;
import org.keycloak.transaction.JtaTransactionWrapper;
import javax.transaction.TransactionManager;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, ProviderManagerDeployer {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private Set<Spi> spis = new HashSet<>();
private Map<Class<? extends Provider>, String> provider = new HashMap<Class<? extends Provider>, String>();
private Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<Class<? extends Provider>, Map<String, ProviderFactory>>();
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<ProviderEventListener>();
private Map<Class<? extends Provider>, String> provider = new HashMap<>();
private volatile Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoriesMap = new HashMap<>();
protected CopyOnWriteArrayList<ProviderEventListener> listeners = new CopyOnWriteArrayList<>();
private TransactionManager tm;
// TODO: Likely should be changed to int and use Time.currentTime() to be compatible with all our "time" reps
protected long serverStartupTimestamp;
@ -69,15 +75,155 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
serverStartupTimestamp = System.currentTimeMillis();
ProviderManager pm = new ProviderManager(getClass().getClassLoader(), Config.scope().getArray("providers"));
// Load the SPI classes through the provider manager, so both Keycloak internal SPI's and
// the ones defined in deployed modules will be found.
loadSPIs(pm, pm.loadSpis());
spis.addAll(pm.loadSpis());
factoriesMap = loadFactories(pm);
for (ProviderManager manager : ProviderManagerRegistry.SINGLETON.getPreBoot()) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoryMap = loadFactories(manager);
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> entry : factoryMap.entrySet()) {
Map<String, ProviderFactory> factories = factoriesMap.get(entry.getKey());
if (factories == null) {
factoriesMap.put(entry.getKey(), entry.getValue());
} else {
factories.putAll(entry.getValue());
}
}
}
checkProvider();
for ( Map<String, ProviderFactory> factories : factoriesMap.values()) {
for (ProviderFactory factory : factories.values()) {
factory.postInit(this);
}
}
// make the session factory ready for hot deployment
ProviderManagerRegistry.SINGLETON.setDeployer(this);
JtaTransactionManagerLookup lookup = (JtaTransactionManagerLookup)getProviderFactory(JtaTransactionManagerLookup.class);
if (lookup != null) tm = lookup.getTransactionManager();
}
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> getFactoriesCopy() {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = new HashMap<>();
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> entry : factoriesMap.entrySet()) {
Map<String, ProviderFactory> valCopy = new HashMap<>();
valCopy.putAll(entry.getValue());
copy.put(entry.getKey(), valCopy);
}
return copy;
}
@Override
public void deploy(ProviderManager pm) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
Map<Class<? extends Provider>, Map<String, ProviderFactory>> newFactories = loadFactories(pm);
List<ProviderFactory> undeployed = new LinkedList<>();
for (Map.Entry<Class<? extends Provider>, Map<String, ProviderFactory>> entry : newFactories.entrySet()) {
Map<String, ProviderFactory> current = copy.get(entry.getKey());
if (current == null) {
copy.put(entry.getKey(), entry.getValue());
} else {
for (ProviderFactory f : entry.getValue().values()) {
ProviderFactory old = current.remove(f.getId());
if (old != null) undeployed.add(old);
}
current.putAll(entry.getValue());
}
}
factoriesMap = copy;
for (ProviderFactory factory : undeployed) {
factory.close();
}
}
@Override
public void undeploy(ProviderManager pm) {
// we make a copy to avoid concurrent access exceptions
Map<Class<? extends Provider>, Map<String, ProviderFactory>> copy = getFactoriesCopy();
MultivaluedHashMap<Class<? extends Provider>, ProviderFactory> factories = pm.getLoadedFactories();
List<ProviderFactory> undeployed = new LinkedList<>();
for (Map.Entry<Class<? extends Provider>, List<ProviderFactory>> entry : factories.entrySet()) {
Map<String, ProviderFactory> registered = copy.get(entry.getKey());
for (ProviderFactory factory : entry.getValue()) {
undeployed.add(factory);
if (registered != null) {
registered.remove(factory.getId());
}
}
}
factoriesMap = copy;
for (ProviderFactory factory : undeployed) {
factory.close();
}
}
protected void checkProvider() {
for (Spi spi : spis) {
String provider = Config.getProvider(spi.getName());
if (provider != null) {
this.provider.put(spi.getProviderClass(), provider);
if (getProviderFactory(spi.getProviderClass(), provider) == null) {
throw new RuntimeException("Failed to find provider " + provider + " for " + spi.getName());
}
} else {
Map<String, ProviderFactory> factories = factoriesMap.get(spi.getProviderClass());
if (factories != null && factories.size() == 1) {
provider = factories.values().iterator().next().getId();
this.provider.put(spi.getProviderClass(), provider);
}
}
}
}
protected Map<Class<? extends Provider>, Map<String, ProviderFactory>> loadFactories(ProviderManager pm) {
Map<Class<? extends Provider>, Map<String, ProviderFactory>> factoryMap = new HashMap<>();
Set<Spi> spiList = spis;
for (Spi spi : spiList) {
Map<String, ProviderFactory> factories = new HashMap<String, ProviderFactory>();
factoryMap.put(spi.getProviderClass(), factories);
String provider = Config.getProvider(spi.getName());
if (provider != null) {
ProviderFactory factory = pm.load(spi, provider);
if (factory == null) {
continue;
}
Config.Scope scope = Config.scope(spi.getName(), provider);
if (scope.getBoolean("enabled", true)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
logger.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName());
}
factories.put(factory.getId(), factory);
logger.debugv("Loaded SPI {0} (provider = {1})", spi.getName(), provider);
}
} else {
for (ProviderFactory factory : pm.load(spi)) {
Config.Scope scope = Config.scope(spi.getName(), factory.getId());
if (scope.getBoolean("enabled", true)) {
factory.init(scope);
if (spi.isInternal() && !isInternal(factory)) {
logger.spiMayChange(factory.getId(), factory.getClass().getName(), spi.getName());
}
factories.put(factory.getId(), factory);
} else {
logger.debugv("SPI {0} provider {1} disabled", spi.getName(), factory.getId());
}
}
}
}
return factoryMap;
}
protected void loadSPIs(ProviderManager pm, List<Spi> spiList) {
@ -135,7 +281,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
public KeycloakSession create() {
return new DefaultKeycloakSession(this);
KeycloakSession session = new DefaultKeycloakSession(this);
if (tm != null) {
session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
}
return session;
}
<T extends Provider> String getDefaultProvider(Class<T> clazz) {
@ -180,6 +330,7 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
}
public void close() {
ProviderManagerRegistry.SINGLETON.setDeployer(null);
for (Map<String, ProviderFactory> factories : factoriesMap.values()) {
for (ProviderFactory factory : factories.values()) {
factory.close();

View file

@ -0,0 +1,63 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.services.ServicesLogger;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JBossJtaTransactionManagerLookup implements JtaTransactionManagerLookup {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private TransactionManager tm;
@Override
public TransactionManager getTransactionManager() {
return tm;
}
@Override
public void init(Config.Scope config) {
try {
InitialContext ctx = new InitialContext();
tm = (TransactionManager)ctx.lookup("java:jboss/TransactionManager");
if (tm == null) {
logger.debug("Could not locate TransactionManager");
}
} catch (NamingException e) {
logger.debug("Could not load TransactionManager", e);
}
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public String getId() {
return "jboss";
}
}

View file

@ -0,0 +1,40 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.services.ServicesLogger;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.transaction.TransactionManager;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JtaRegistration {
public void begin(KeycloakSession session) {
TransactionManager tm = session.getProvider(JtaTransactionManagerLookup.class).getTransactionManager();
if (tm == null) return;
session.getTransactionManager().enlist(new JtaTransactionWrapper(tm));
}
}

View file

@ -0,0 +1,43 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import javax.transaction.TransactionManager;
/**
* JTA TransactionManager lookup
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface JtaTransactionManagerLookup extends Provider, ProviderFactory<JtaTransactionManagerLookup> {
@Override
default void close() {
}
@Override
default JtaTransactionManagerLookup create(KeycloakSession session) {
return this;
}
TransactionManager getTransactionManager();
}

View file

@ -0,0 +1,111 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.models.KeycloakTransaction;
import javax.transaction.InvalidTransactionException;
import javax.transaction.NotSupportedException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class JtaTransactionWrapper implements KeycloakTransaction {
protected TransactionManager tm;
protected Transaction ut;
protected Transaction suspended;
public JtaTransactionWrapper(TransactionManager tm) {
this.tm = tm;
try {
suspended = tm.suspend();
tm.begin();
ut = tm.getTransaction();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void begin() {
}
@Override
public void commit() {
try {
ut.commit();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void rollback() {
try {
ut.rollback();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
end();
}
}
@Override
public void setRollbackOnly() {
try {
ut.setRollbackOnly();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean getRollbackOnly() {
try {
return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isActive() {
try {
return ut.getStatus() == Status.STATUS_ACTIVE;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected void end() {
if (suspended != null) {
try {
tm.resume(suspended);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class TransactionManagerLookupSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "jta-lookup";
}
@Override
public Class<? extends Provider> getProviderClass() {
return JtaTransactionManagerLookup.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return JtaTransactionManagerLookup.class;
}
}

View file

@ -0,0 +1,94 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.transaction;
import org.keycloak.models.KeycloakTransaction;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UserTransactionWrapper implements KeycloakTransaction {
protected UserTransaction ut;
public UserTransactionWrapper(UserTransaction ut) {
this.ut = ut;
}
@Override
public void begin() {
try {
ut.begin();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void commit() {
try {
ut.commit();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void rollback() {
try {
ut.rollback();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void setRollbackOnly() {
try {
ut.setRollbackOnly();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean getRollbackOnly() {
try {
return ut.getStatus() == Status.STATUS_MARKED_ROLLBACK;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public boolean isActive() {
try {
return ut.getStatus() == Status.STATUS_ACTIVE;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View file

@ -18,4 +18,5 @@
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi
org.keycloak.transaction.TransactionManagerLookupSpi

View file

@ -0,0 +1 @@
org.keycloak.transaction.JBossJtaTransactionManagerLookup

View file

@ -130,5 +130,13 @@
"hostname-verification-policy": "${keycloak.truststore.policy:WILDCARD}",
"disabled": "${keycloak.truststore.disabled:false}"
}
},
"jta-lookup": {
"provider": "${keycloak.jta.lookup.provider:jboss}",
"jboss" : {
"enabled": true
}
}
}

View file

@ -68,6 +68,10 @@
<groupId>org.jboss.spec.javax.ws.rs</groupId>
<artifactId>jboss-jaxrs-api_2.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.transaction</groupId>
<artifactId>jboss-transaction-api_1.2_spec</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>async-http-servlet-3.0</artifactId>

View file

@ -97,5 +97,13 @@
},
"scripting": {
},
"jta-lookup": {
"provider": "${keycloak.jta.lookup.provider:jboss}",
"jboss" : {
"enabled": true
}
}
}

View file

@ -1338,18 +1338,6 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'RealmSessionStatsCtrl'
})
.when('/realms/:realm/user-storage', {
templateUrl : resourceUrl + '/partials/user-storage.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller : 'UserStorageCtrl'
})
.when('/create/user-storage/:realm/providers/:provider', {
templateUrl : resourceUrl + '/partials/user-storage-generic.html',
resolve : {
@ -1393,6 +1381,9 @@ module.config([ '$routeProvider', function($routeProvider) {
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
serverInfo : function(ServerInfoLoader) {
return ServerInfoLoader();
}
},
controller : 'UserFederationCtrl'

View file

@ -592,22 +592,91 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, RequiredA
};
});
module.controller('UserStorageCtrl', function($scope, $location, $route, realm, serverInfo, Components, Notifications, Dialog) {
console.log('UserStorageCtrl ++++****');
module.controller('UserFederationCtrl', function($scope, $location, $route, realm, serverInfo, Components, UserFederationProviders, UserFederationInstances, Notifications, Dialog) {
console.log('UserFederationCtrl ++++****');
$scope.realm = realm;
$scope.providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
for (var i = 0; i < $scope.providers.length; i++) {
$scope.providers[i].isUserFederationProvider = false;
}
UserFederationProviders.query({realm: realm.realm}, function(data) {
for (var i = 0; i < data.length; i++) {
data[i].isUserFederationProvider = true;
$scope.providers.push(data[i]);
}
});
$scope.addProvider = function(provider) {
console.log('Add provider: ' + provider.id);
if (provider.isUserFederationProvider) {
$location.url("/create/user-federation/" + realm.realm + "/providers/" + provider.id);
} else {
$location.url("/create/user-storage/" + realm.realm + "/providers/" + provider.id);
}
};
$scope.instances = Components.query({realm: realm.realm,
$scope.getInstanceLink = function(instance) {
if (instance.isUserFederationProvider) {
return "/realms/" + realm.realm + "/user-federation/providers/" + instance.providerName + "/" + instance.id;
} else {
return "/realms/" + realm.realm + "/user-storage/providers/" + instance.providerId + "/" + instance.id;
}
}
$scope.getInstanceName = function(instance) {
if (instance.isUserFederationProvider) {
return instance.displayName;
} else {
return instance.name;
}
}
$scope.getInstanceProvider = function(instance) {
if (instance.isUserFederationProvider) {
return instance.providerName;
} else {
return instance.providerId;
}
}
$scope.getInstancePriority = function(instance) {
if (instance.isUserFederationProvider) {
return instance.priority;
} else {
return instance.config['priority'][0];
}
}
Components.query({realm: realm.realm,
parent: realm.id,
type: 'org.keycloak.storage.UserStorageProvider'
}, function(data) {
$scope.instances = data;
for (var i = 0; i < data.length; i++) {
data[i].isUserFederationProvider = false;
}
UserFederationInstances.query({realm: realm.realm}, function(data) {
for (var i = 0; i < data.length; i++) {
data[i].isUserFederationProvider = true;
$scope.instances.push(data[i]);
}
});
});
$scope.removeUserStorage = function(instance) {
$scope.removeInstance = function(instance) {
if (instance.isUserFederationProvider) {
Dialog.confirmDelete(instance.displayName, 'user federation provider', function() {
UserFederationInstances.remove({
realm : realm.realm,
instance : instance.id
}, function() {
$route.reload();
Notifications.success("The provider has been deleted.");
});
});
} else {
Dialog.confirmDelete(instance.name, 'user storage provider', function() {
Components.remove({
realm : realm.realm,
@ -617,11 +686,12 @@ module.controller('UserStorageCtrl', function($scope, $location, $route, realm,
Notifications.success("The provider has been deleted.");
});
});
}
};
});
module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) {
console.log('GenericUserFederationCtrl');
console.log('GenericUserStorageCtrl');
console.log('providerId: ' + providerId);
$scope.create = !instance.providerId;
console.log('create: ' + $scope.create);
@ -737,31 +807,6 @@ module.controller('GenericUserStorageCtrl', function($scope, $location, Notifica
});
module.controller('UserFederationCtrl', function($scope, $location, $route, realm, UserFederationProviders, UserFederationInstances, Notifications, Dialog) {
console.log('UserFederationCtrl ++++****');
$scope.realm = realm;
$scope.providers = UserFederationProviders.query({realm: realm.realm});
$scope.addProvider = function(provider) {
console.log('Add provider: ' + provider.id);
$location.url("/create/user-federation/" + realm.realm + "/providers/" + provider.id);
};
$scope.instances = UserFederationInstances.query({realm: realm.realm});
$scope.removeUserFederation = function(instance) {
Dialog.confirmDelete(instance.displayName, 'user federation provider', function() {
UserFederationInstances.remove({
realm : realm.realm,
instance : instance.id
}, function() {
$route.reload();
Notifications.success("The provider has been deleted.");
});
});
};
});
module.controller('UserFederationTabCtrl', function(Dialog, $scope, Current, Notifications, $location) {
$scope.removeUserFederation = function() {
Dialog.confirmDelete($scope.instance.displayName, 'user federation provider', function() {

View file

@ -27,11 +27,11 @@
</thead>
<tbody>
<tr ng-repeat="instance in instances">
<td><a href="#/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{instance.displayName}}</a></td>
<td>{{instance.providerName|capitalize}}</td>
<td>{{instance.priority}}</td>
<td class="kc-action-cell" kc-open="/realms/{{realm.realm}}/user-federation/providers/{{instance.providerName}}/{{instance.id}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-click="removeUserFederation(instance)">{{:: 'delete' | translate}}</td>
<td><a href="#{{getInstanceLink(instance)}}">{{getInstanceName(instance)}}</a></td>
<td>{{getInstanceProvider(instance)|capitalize}}</td>
<td>{{getInstancePriority(instance)}}</td>
<td class="kc-action-cell" kc-open="{{getInstanceLink(instance)}}">{{:: 'edit' | translate}}</td>
<td class="kc-action-cell" data-ng-click="removeInstance(instance)">{{:: 'delete' | translate}}</td>
</tr>
<tr data-ng-show="!instances || instances.length == 0">
<td class="text-muted">{{:: 'no-user-federation-providers-configured' | translate}}</td>

View file

@ -1,6 +1,6 @@
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/user-storage">{{:: 'user-storage' | translate}}</a></li>
<li><a href="#/realms/{{realm.realm}}/user-federation">{{:: 'user-federation' | translate}}</a></li>
<li data-ng-hide="create">{{instance.name|capitalize}}</li>
<li data-ng-show="create">{{:: 'add-user-storage-provider' | translate}}</li>
</ol>

View file

@ -34,7 +34,6 @@
<li data-ng-show="access.viewRealm" data-ng-class="(path[2] == 'roles' || path[2] == 'default-roles' || (path[1] == 'role' && path[3] != 'clients')) && 'active'"><a href="#/realms/{{realm.realm}}/roles"><i class="fa fa-tasks"></i> {{:: 'roles' | translate}}</a></li>
<li data-ng-show="access.viewIdentityProviders" data-ng-class="(path[2] == 'identity-provider-settings' || path[2] == 'identity-provider-mappers') && 'active'"><a href="#/realms/{{realm.realm}}/identity-provider-settings"><i class="fa fa-exchange"></i> {{:: 'identity-providers' | translate}}</a></li>
<li data-ng-show="access.viewUsers" data-ng-class="(path[1] == 'user-federation' || path[2] == 'user-federation') && 'active'"><a href="#/realms/{{realm.realm}}/user-federation"><i class="fa fa-database"></i> {{:: 'user-federation' | translate}}</a></li>
<li data-ng-show="access.viewUsers" data-ng-class="(path[1] == 'user-storage' || path[2] == 'user-storage') && 'active'"><a href="#/realms/{{realm.realm}}/user-storage"><i class="fa fa-database"></i> {{:: 'user-storage' | translate}}</a></li>
<li data-ng-show="access.viewRealm" data-ng-class="(path[1] == 'authentication' || path[2] == 'authentication') && 'active'"><a href="#/realms/{{realm.realm}}/authentication/flows"><i class="fa fa-lock"></i> {{:: 'authentication' | translate}}</a></li>
</ul>
</div>

View file

@ -78,6 +78,16 @@
<artifactId>jboss-logging-processor</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-server-spi</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.core</groupId>

View file

@ -0,0 +1,110 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.subsystem.server.extension;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.logging.Logger;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
import org.jboss.vfs.VirtualFile;
import org.jboss.vfs.util.AbstractVirtualFileFilterWithAttributes;
import java.io.IOException;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakProviderDependencyProcessor implements DeploymentUnitProcessor {
private static final ModuleIdentifier KEYCLOAK_COMMON = ModuleIdentifier.create("org.keycloak.keycloak-common");
private static final ModuleIdentifier KEYCLOAK_CORE = ModuleIdentifier.create("org.keycloak.keycloak-core");
private static final ModuleIdentifier KEYCLOAK_SERVER_SPI = ModuleIdentifier.create("org.keycloak.keycloak-server-spi");
private static final ModuleIdentifier KEYCLOAK_JPA = ModuleIdentifier.create("org.keycloak.keycloak-model-jpa");
private static final ModuleIdentifier JAXRS = ModuleIdentifier.create("javax.ws.rs.api");
private static final ModuleIdentifier RESTEASY = ModuleIdentifier.create("org.jboss.resteasy.resteasy-jaxrs");
private static final ModuleIdentifier APACHE = ModuleIdentifier.create("org.apache.httpcomponents");
private static final Logger logger = Logger.getLogger(KeycloakProviderDependencyProcessor.class);
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
KeycloakAdapterConfigService config = KeycloakAdapterConfigService.INSTANCE;
String deploymentName = deploymentUnit.getName();
if (config.isKeycloakServerDeployment(deploymentName)) {
return;
}
if (!isKeycloakProviderDeployment(deploymentUnit)) return;
final ModuleSpecification moduleSpecification = deploymentUnit.getAttachment(Attachments.MODULE_SPECIFICATION);
final ModuleLoader moduleLoader = Module.getBootModuleLoader();
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_COMMON, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_CORE, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_SERVER_SPI, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, JAXRS, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, RESTEASY, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, APACHE, false, false, false, false));
moduleSpecification.addSystemDependency(new ModuleDependency(moduleLoader, KEYCLOAK_JPA, false, false, false, false));
}
public KeycloakProviderDependencyProcessor() {
super();
}
public static boolean isKeycloakProviderDeployment(DeploymentUnit du) {
final ResourceRoot resourceRoot = du.getAttachment(Attachments.DEPLOYMENT_ROOT);
if (resourceRoot == null) {
return false;
}
final VirtualFile deploymentRoot = resourceRoot.getRoot();
if (deploymentRoot == null || !deploymentRoot.exists()) {
return false;
}
VirtualFile services = deploymentRoot.getChild("META-INF/services");
if (!services.exists()) return false;
try {
List<VirtualFile> archives = services.getChildren(new AbstractVirtualFileFilterWithAttributes(){
@Override
public boolean accepts(VirtualFile file) {
return file.getName().startsWith("org.keycloak");
}
});
return !archives.isEmpty();
} catch (IOException e) {
}
return false;
}
@Override
public void undeploy(DeploymentUnit context) {
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.subsystem.server.extension;
import org.jboss.as.server.deployment.AttachmentKey;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ModuleDependency;
import org.jboss.as.server.deployment.module.ModuleSpecification;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.logging.Logger;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.modules.ModuleLoader;
import org.jboss.vfs.VirtualFile;
import org.jboss.vfs.util.AbstractVirtualFileFilterWithAttributes;
import org.keycloak.provider.ProviderManager;
import org.keycloak.provider.ProviderManagerRegistry;
import java.io.IOException;
import java.util.List;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class KeycloakProviderDeploymentProcessor implements DeploymentUnitProcessor {
AttachmentKey<ProviderManager> ATTACHMENT_KEY = AttachmentKey.create(ProviderManager.class);
private static final Logger logger = Logger.getLogger(KeycloakProviderDeploymentProcessor.class);
@Override
public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
KeycloakAdapterConfigService config = KeycloakAdapterConfigService.INSTANCE;
String deploymentName = deploymentUnit.getName();
if (config.isKeycloakServerDeployment(deploymentName)) {
return;
}
if (!KeycloakProviderDependencyProcessor.isKeycloakProviderDeployment(deploymentUnit)) return;
logger.infof("Deploying Keycloak provider: {0}", deploymentUnit.getName());
final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
ProviderManager pm = new ProviderManager(module.getClassLoader());
ProviderManagerRegistry.SINGLETON.deploy(pm);
deploymentUnit.putAttachment(ATTACHMENT_KEY, pm);
}
public KeycloakProviderDeploymentProcessor() {
super();
}
@Override
public void undeploy(DeploymentUnit context) {
ProviderManager pm = context.getAttachment(ATTACHMENT_KEY);
if (pm != null) {
logger.infof("Undeploying Keycloak provider: {0}", context.getName());
ProviderManagerRegistry.SINGLETON.undeploy(pm);
context.removeAttachment(ATTACHMENT_KEY);
}
}
}

View file

@ -44,12 +44,22 @@ class KeycloakSubsystemAdd extends AbstractBoottimeAddStepHandler {
context.addStep(new AbstractDeploymentChainStep() {
@Override
protected void execute(DeploymentProcessorTarget processorTarget) {
processorTarget.addDeploymentProcessor(SUBSYSTEM_NAME, Phase.DEPENDENCIES, 0, new KeycloakProviderDependencyProcessor());
processorTarget.addDeploymentProcessor(SUBSYSTEM_NAME,
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 2, // PRIORITY
new KeycloakProviderDeploymentProcessor());
processorTarget.addDeploymentProcessor(SUBSYSTEM_NAME,
Phase.POST_MODULE, // PHASE
Phase.POST_MODULE_VALIDATOR_FACTORY - 1, // PRIORITY
new KeycloakServerDeploymentProcessor());
}
}, OperationContext.Stage.RUNTIME);
context.addStep(new AbstractDeploymentChainStep() {
@Override
protected void execute(DeploymentProcessorTarget processorTarget) {
}
}, OperationContext.Stage.RUNTIME);
}
protected void populateModel(final OperationContext context, final ModelNode operation, final Resource resource) throws OperationFailedException {

View file

@ -29,7 +29,7 @@
<password>sa</password>
</security>
</datasource>
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
<datasource jndi-name="java:jboss/datasources/KeycloakDS" jta="false" pool-name="KeycloakDS" enabled="true" use-java-context="true">
<?KEYCLOAK_DS_CONNECTION_URL?>
<driver>h2</driver>
<security>