user storage provider jpa example
This commit is contained in:
parent
3b9b673e5e
commit
70722d0d3d
32 changed files with 876 additions and 64 deletions
45
examples/providers/user-storage-jpa/assembly.xml
Executable file
45
examples/providers/user-storage-jpa/assembly.xml
Executable file
|
@ -0,0 +1,45 @@
|
|||
<!--
|
||||
~ 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>
|
40
examples/providers/user-storage-jpa/module.xml
Normal file
40
examples/providers/user-storage-jpa/module.xml
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?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>
|
|
@ -74,6 +74,30 @@
|
|||
<target>1.8</target>
|
||||
</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>
|
||||
<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>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
|
|
|
@ -16,27 +16,40 @@
|
|||
*/
|
||||
package org.keycloak.examples.storage.user;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.component.ComponentModel;
|
||||
import org.keycloak.models.GroupModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
||||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserQueryProvider;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.TypedQuery;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class ExampleUserStorageProvider implements UserStorageProvider, UserLookupProvider, UserRegistrationProvider {
|
||||
public class ExampleUserStorageProvider implements UserStorageProvider,
|
||||
UserLookupProvider,
|
||||
UserRegistrationProvider,
|
||||
UserCredentialValidatorProvider,
|
||||
UserQueryProvider {
|
||||
private static final Logger logger = Logger.getLogger(ExampleUserStorageProvider.class);
|
||||
protected EntityManager em;
|
||||
protected ComponentModel model;
|
||||
protected KeycloakSession session;
|
||||
|
@ -69,18 +82,27 @@ public class ExampleUserStorageProvider implements UserStorageProvider, UserLook
|
|||
|
||||
@Override
|
||||
public UserModel getUserById(String id, RealmModel realm) {
|
||||
logger.info("getUserById: " + id);
|
||||
String persistenceId = StorageId.externalId(id);
|
||||
UserEntity entity = em.find(UserEntity.class, persistenceId);
|
||||
if (entity == null) return null;
|
||||
if (entity == null) {
|
||||
logger.info("could not find user by id: " + id);
|
||||
return null;
|
||||
}
|
||||
return new UserAdapter(session, realm, model, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserModel getUserByUsername(String username, RealmModel realm) {
|
||||
logger.info("getUserByUsername: " + username);
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("getUserByUsername", UserEntity.class);
|
||||
query.setParameter("username", username);
|
||||
List<UserEntity> result = query.getResultList();
|
||||
if (result.isEmpty()) return null;
|
||||
if (result.isEmpty()) {
|
||||
logger.info("could not find username: " + username);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new UserAdapter(session, realm, model, result.get(0));
|
||||
}
|
||||
|
||||
|
@ -99,6 +121,7 @@ public class ExampleUserStorageProvider implements UserStorageProvider, UserLook
|
|||
entity.setId(KeycloakModelUtils.generateId());
|
||||
entity.setUsername(username);
|
||||
em.persist(entity);
|
||||
logger.info("added user: " + username);
|
||||
return new UserAdapter(session, realm, model, entity);
|
||||
}
|
||||
|
||||
|
@ -115,4 +138,91 @@ public class ExampleUserStorageProvider implements UserStorageProvider, UserLook
|
|||
public void grantToAllUsers(RealmModel realm, RoleModel role) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
for (UserCredentialModel cred : input) {
|
||||
if (!UserCredentialModel.PASSWORD.equals(cred.getType())) return false;
|
||||
if (!cred.getValue().equals(user.getFirstAttribute("password"))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getUsersCount(RealmModel realm) {
|
||||
Object count = em.createNamedQuery("getUserCount")
|
||||
.getSingleResult();
|
||||
return ((Number)count).intValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm) {
|
||||
return getUsers(realm, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
|
||||
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("getAllUsers", UserEntity.class);
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, model, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm) {
|
||||
return searchForUser(search, realm, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
|
||||
TypedQuery<UserEntity> query = em.createNamedQuery("searchForUser", UserEntity.class);
|
||||
query.setParameter("search", "%" + search.toLowerCase() + "%");
|
||||
if (firstResult != -1) {
|
||||
query.setFirstResult(firstResult);
|
||||
}
|
||||
if (maxResults != -1) {
|
||||
query.setMaxResults(maxResults);
|
||||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, model, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> getGroupMembers(RealmModel realm, GroupModel group) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,25 +16,39 @@
|
|||
*/
|
||||
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> {
|
||||
protected String jndiName = "java:jboss/ExampleUserEntityManagerFactory";
|
||||
|
||||
EntityManagerFactory emf = null;
|
||||
|
||||
@Override
|
||||
public ExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) {
|
||||
EntityManager em = JndiEntityManagerLookup.getSessionEntityManager(session, jndiName);
|
||||
EntityManager em = emf.createEntityManager();
|
||||
session.getTransactionManager().enlist(new JpaKeycloakTransaction(em));
|
||||
return new ExampleUserStorageProvider(em, model, session);
|
||||
}
|
||||
|
||||
|
@ -50,11 +64,27 @@ public class ExampleUserStorageProviderFactory implements UserStorageProviderFac
|
|||
|
||||
@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();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,11 +16,13 @@
|
|||
*/
|
||||
package org.keycloak.examples.storage.user;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.common.util.MultivaluedHashMap;
|
||||
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;
|
||||
|
||||
|
@ -33,6 +35,7 @@ import java.util.Map;
|
|||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
||||
private static final Logger logger = Logger.getLogger(ExampleUserStorageProvider.class);
|
||||
protected UserEntity entity;
|
||||
protected String keycloakId;
|
||||
|
||||
|
@ -81,6 +84,13 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
|||
public void setSingleAttribute(String name, String value) {
|
||||
if (name.equals("phone")) {
|
||||
entity.setPhone(value);
|
||||
} else if (name.equals("password")) {
|
||||
// ignore
|
||||
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
} else {
|
||||
super.setSingleAttribute(name, value);
|
||||
}
|
||||
|
@ -90,6 +100,13 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
|||
public void removeAttribute(String name) {
|
||||
if (name.equals("phone")) {
|
||||
entity.setPhone(null);
|
||||
} else if (name.equals("password")) {
|
||||
// ignore
|
||||
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
} else {
|
||||
super.removeAttribute(name);
|
||||
}
|
||||
|
@ -99,6 +116,13 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
|||
public void setAttribute(String name, List<String> values) {
|
||||
if (name.equals("phone")) {
|
||||
entity.setPhone(values.get(0));
|
||||
} else if (name.equals("password")) {
|
||||
// ignore
|
||||
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
} else {
|
||||
super.setAttribute(name, values);
|
||||
}
|
||||
|
@ -108,6 +132,12 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
|||
public String getFirstAttribute(String name) {
|
||||
if (name.equals("phone")) {
|
||||
return entity.getPhone();
|
||||
} else if (name.equals("password")) {
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
return entity.getPassword();
|
||||
} else {
|
||||
return super.getFirstAttribute(name);
|
||||
}
|
||||
|
@ -119,6 +149,12 @@ public class UserAdapter extends AbstractUserAdapterFederatedStorage {
|
|||
MultivaluedHashMap<String, String> all = new MultivaluedHashMap<>();
|
||||
all.putAll(attrs);
|
||||
all.add("phone", entity.getPhone());
|
||||
|
||||
// having a "password" attribute is a workaround so that passwords can be cached. All done for performance reasons...
|
||||
// If we override getCredentialsDirectly/updateCredentialsDirectly
|
||||
// then the realm passsword policy will/may try and overwrite the plain text password with a hash.
|
||||
// If you don't like this workaround, you can query the database every time to validate the password
|
||||
all.add("password", entity.getPassword());
|
||||
return all;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,10 @@ import javax.persistence.NamedQuery;
|
|||
@NamedQueries({
|
||||
@NamedQuery(name="getUserByUsername", query="select u from UserEntity u where u.username = :username"),
|
||||
@NamedQuery(name="getUserByEmail", query="select u from UserEntity u where u.email = :email"),
|
||||
@NamedQuery(name="getUserCount", query="select count(u) from UserEntity u"),
|
||||
@NamedQuery(name="getAllUsers", query="select u from UserEntity u"),
|
||||
@NamedQuery(name="searchForUser", query="select u from UserEntity u where " +
|
||||
"( lower(u.username) like :search or u.email like :search ) order by u.username"),
|
||||
})
|
||||
@Entity
|
||||
public class UserEntity {
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
xsi:schemaLocation="
|
||||
http://java.sun.com/xml/ns/persistence
|
||||
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
|
||||
<persistence-unit name="primary">
|
||||
<non-jta-data-source>java:jboss/datasources/ExampleUserDS</non-jta-data-source>
|
||||
<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>
|
||||
|
||||
<class>org.keycloak.examples.storage.user.UserEntity</class>
|
||||
|
||||
<properties>
|
||||
<property name="jboss.entity.manager.factory.jndi.name" value="java:jboss/ExampleUserEntityManagerFactory" />
|
||||
<property name="hibernate.hbm2ddl.auto" value="update" />
|
||||
<property name="hibernate.show_sql" value="false" />
|
||||
</properties>
|
||||
|
|
|
@ -393,13 +393,13 @@ public class UserCacheSession implements CacheUserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return getDelegate().searchForUserByAttributes(attributes, realm);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return getDelegate().searchForUser(attributes, realm);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
return getDelegate().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
return getDelegate().searchForUser(attributes, realm, firstResult, maxResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -49,6 +49,7 @@ import javax.persistence.TypedQuery;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -547,7 +548,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
query.setMaxResults(maxResults);
|
||||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
|
||||
return users;
|
||||
}
|
||||
|
@ -564,7 +565,7 @@ public class JpaUserProvider implements UserProvider {
|
|||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
for (UserEntity user : results) {
|
||||
users.add(new UserAdapter(session, realm, em, user));
|
||||
}
|
||||
|
@ -588,18 +589,18 @@ public class JpaUserProvider implements UserProvider {
|
|||
query.setMaxResults(maxResults);
|
||||
}
|
||||
List<UserEntity> results = query.getResultList();
|
||||
List<UserModel> users = new ArrayList<UserModel>();
|
||||
List<UserModel> users = new LinkedList<>();
|
||||
for (UserEntity entity : results) users.add(new UserAdapter(session, realm, em, entity));
|
||||
return users;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUserByAttributes(attributes, realm, -1, -1);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUser(attributes, realm, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
StringBuilder builder = new StringBuilder("select u from UserEntity u where u.realmId = :realmId");
|
||||
for (Map.Entry<String, String> entry : attributes.entrySet()) {
|
||||
String attribute = null;
|
||||
|
|
|
@ -253,12 +253,12 @@ public class MongoUserProvider implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUserByAttributes(attributes, realm, -1, -1);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUser(attributes, realm, -1, -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
QueryBuilder queryBuilder = new QueryBuilder()
|
||||
.and("realmId").is(realm.getId());
|
||||
|
||||
|
|
|
@ -404,17 +404,17 @@ public class UserFederationManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUser(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(final Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> searchForUser(final Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
federationLoad(realm, attributes);
|
||||
return query(new PaginatedQuery() {
|
||||
@Override
|
||||
public List<UserModel> query(RealmModel realm, int first, int max) {
|
||||
return session.userStorage().searchForUserByAttributes(attributes, realm, first, max);
|
||||
return session.userStorage().searchForUser(attributes, realm, first, max);
|
||||
}
|
||||
}, realm, firstResult, maxResults);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ import org.keycloak.storage.federated.UserFederatedStorageProvider;
|
|||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
@ -387,15 +386,15 @@ public class UserStorageManager implements UserProvider {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return searchForUser(attributes, realm, 0, Integer.MAX_VALUE - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
return query((provider, first, max) -> {
|
||||
if (provider instanceof UserQueryProvider) {
|
||||
return ((UserQueryProvider)provider).searchForUserByAttributes(attributes, realm, first, max);
|
||||
return ((UserQueryProvider)provider).searchForUser(attributes, realm, first, max);
|
||||
|
||||
}
|
||||
return Collections.EMPTY_LIST;
|
||||
|
|
|
@ -29,24 +29,79 @@ import java.util.Map;
|
|||
*/
|
||||
public interface UserQueryProvider {
|
||||
|
||||
// Service account is included for counts
|
||||
int getUsersCount(RealmModel realm);
|
||||
|
||||
List<UserModel> getUsers(RealmModel realm);
|
||||
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm);
|
||||
|
||||
List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults);
|
||||
|
||||
/**
|
||||
* Search for users with username, email or first + last name that is like search string.
|
||||
*
|
||||
* If possible, implementations should treat the parameter values as partial match patterns i.e. in RDMBS terms use LIKE.
|
||||
*
|
||||
* This method is used by the admin console search box
|
||||
*
|
||||
* @param search
|
||||
* @param realm
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> searchForUser(String search, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Search for users with username, email or first + last name that is like search string.
|
||||
*
|
||||
* If possible, implementations should treat the parameter values as partial match patterns i.e. in RDMBS terms use LIKE.
|
||||
*
|
||||
* This method is used by the admin console search box
|
||||
*
|
||||
* @param search
|
||||
* @param realm
|
||||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults);
|
||||
List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Search for user by parameter. Valid parameters are:
|
||||
* "first" - first name
|
||||
* "last" - last name
|
||||
* "email" - email
|
||||
* "username" - username
|
||||
*
|
||||
* If possible, implementations should treat the parameter values as partial match patterns i.e. in RDMBS terms use LIKE.
|
||||
*
|
||||
* This method is used by the REST API when querying users.
|
||||
*
|
||||
*
|
||||
* @param params
|
||||
* @param realm
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> searchForUser(Map<String, String> params, RealmModel realm);
|
||||
|
||||
/**
|
||||
* Search for user by parameter. Valid parameters are:
|
||||
* "first" - first name
|
||||
* "last" - last name
|
||||
* "email" - email
|
||||
* "username" - username
|
||||
*
|
||||
* If possible, implementations should treat the parameter values as patterns i.e. in RDMBS terms use LIKE.
|
||||
* This method is used by the REST API when querying users.
|
||||
*
|
||||
*
|
||||
* @param params
|
||||
* @param realm
|
||||
* @param firstResult
|
||||
* @param maxResults
|
||||
* @return
|
||||
*/
|
||||
List<UserModel> searchForUser(Map<String, String> params, RealmModel realm, int firstResult, int maxResults);
|
||||
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults);
|
||||
List<UserModel> getGroupMembers(RealmModel realm, GroupModel group);
|
||||
|
||||
List<UserModel> searchForUser(String search, RealmModel realm);
|
||||
|
||||
// Searching by UserModel.attribute (not property)
|
||||
List<UserModel> searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm);
|
||||
|
|
|
@ -46,7 +46,6 @@ import org.keycloak.models.UserSessionModel;
|
|||
import org.keycloak.models.utils.ModelToRepresentation;
|
||||
import org.keycloak.models.utils.RepresentationToModel;
|
||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||
import org.keycloak.protocol.oidc.TokenManager;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||
|
@ -682,7 +681,7 @@ public class UsersResource {
|
|||
if (username != null) {
|
||||
attributes.put(UserModel.USERNAME, username);
|
||||
}
|
||||
userModels = session.users().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
|
||||
userModels = session.users().searchForUser(attributes, realm, firstResult, maxResults);
|
||||
} else {
|
||||
userModels = session.users().getUsers(realm, firstResult, maxResults, false);
|
||||
}
|
||||
|
|
|
@ -95,6 +95,7 @@ public class ServerInfoAdminResource {
|
|||
}
|
||||
|
||||
private void setProviders(ServerInfoRepresentation info) {
|
||||
info.setComponentTypes(new HashMap<>());
|
||||
LinkedHashMap<String, SpiInfoRepresentation> spiReps = new LinkedHashMap<>();
|
||||
|
||||
List<Spi> spis = new LinkedList<>(session.getKeycloakSessionFactory().getSpis());
|
||||
|
@ -115,7 +116,6 @@ public class ServerInfoAdminResource {
|
|||
Map<String, ProviderRepresentation> providers = new HashMap<>();
|
||||
|
||||
if (providerIds != null) {
|
||||
info.setComponentTypes(new HashMap<>());
|
||||
for (String name : providerIds) {
|
||||
ProviderRepresentation provider = new ProviderRepresentation();
|
||||
ProviderFactory<?> pi = session.getKeycloakSessionFactory().getProviderFactory(spi.getProviderClass(), name);
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.testsuite.federation.storage;
|
||||
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PlainTextPasswordProvider implements PasswordHashProvider {
|
||||
@Override
|
||||
public UserCredentialValueModel encode(String rawPassword, int iterations) {
|
||||
UserCredentialValueModel model = new UserCredentialValueModel();
|
||||
model.setType(UserCredentialModel.PASSWORD);
|
||||
model.setValue(rawPassword);
|
||||
model.setAlgorithm("text");
|
||||
return model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String rawPassword, UserCredentialValueModel credential) {
|
||||
return rawPassword.equals(credential.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.testsuite.federation.storage;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.hash.PasswordHashProvider;
|
||||
import org.keycloak.hash.PasswordHashProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class PlainTextPasswordProviderFactory implements PasswordHashProviderFactory {
|
||||
@Override
|
||||
public PasswordHashProvider create(KeycloakSession session) {
|
||||
return new PlainTextPasswordProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postInit(KeycloakSessionFactory factory) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "text";
|
||||
}
|
||||
}
|
|
@ -22,6 +22,7 @@ import org.keycloak.models.KeycloakSession;
|
|||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.RoleModel;
|
||||
import org.keycloak.models.UserCredentialModel;
|
||||
import org.keycloak.models.UserCredentialValueModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.storage.StorageId;
|
||||
import org.keycloak.storage.UserStorageProvider;
|
||||
|
@ -30,6 +31,7 @@ import org.keycloak.storage.user.UserCredentialValidatorProvider;
|
|||
import org.keycloak.storage.user.UserLookupProvider;
|
||||
import org.keycloak.storage.user.UserRegistrationProvider;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -38,7 +40,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||
* @version $Revision: 1 $
|
||||
*/
|
||||
public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider {
|
||||
public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider {
|
||||
|
||||
protected Map<String, String> userPasswords;
|
||||
protected ComponentModel model;
|
||||
|
@ -79,11 +81,33 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
|
|||
@Override
|
||||
public void updateCredential(UserCredentialModel cred) {
|
||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
userPasswords.put(username, cred.getValue());
|
||||
userPasswords.put(getUsername(), cred.getValue());
|
||||
} else {
|
||||
super.updateCredential(cred);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<UserCredentialValueModel> getCredentialsDirectly() {
|
||||
UserCredentialValueModel pw = new UserCredentialValueModel();
|
||||
pw.setId(getId());
|
||||
pw.setType(UserCredentialModel.PASSWORD);
|
||||
pw.setAlgorithm("text");
|
||||
pw.setValue(userPasswords.get(getUsername()));
|
||||
List<UserCredentialValueModel> creds = new LinkedList<>();
|
||||
creds.addAll(super.getCredentialsDirectly());
|
||||
creds.add(pw);
|
||||
return creds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCredentialDirectly(UserCredentialValueModel cred) {
|
||||
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
|
||||
//userPasswords.put(getUsername(), cred.getValue());
|
||||
} else {
|
||||
super.updateCredentialDirectly(cred);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -130,19 +154,6 @@ public class UserMapStorage implements UserLookupProvider, UserStorageProvider,
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List<UserCredentialModel> input) {
|
||||
for (UserCredentialModel cred : input) {
|
||||
if (!cred.getType().equals(UserCredentialModel.PASSWORD)) return false;
|
||||
String password = (String)userPasswords.get(user.getUsername());
|
||||
if (password == null) return false;
|
||||
if (!password.equals(cred.getValue())) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
closings.incrementAndGet();
|
||||
|
|
|
@ -141,7 +141,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
|
||||
|
@ -178,7 +178,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, UserStorageP
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
public List<UserModel> searchForUser(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
|
||||
if (attributes.size() != 1) return Collections.EMPTY_LIST;
|
||||
String username = attributes.get(UserModel.USERNAME);
|
||||
if (username == null) return Collections.EMPTY_LIST;
|
||||
|
|
|
@ -108,7 +108,6 @@ public class UserStorageTest {
|
|||
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testLoginSuccess() {
|
||||
loginSuccessAndLogout("tbrady", "goat");
|
||||
|
@ -258,12 +257,14 @@ public class UserStorageTest {
|
|||
user.updateCredential(UserCredentialModel.password("password"));
|
||||
keycloakRule.stopSession(session, true);
|
||||
loginSuccessAndLogout("memuser", "password");
|
||||
loginSuccessAndLogout("memuser", "password");
|
||||
loginSuccessAndLogout("memuser", "password");
|
||||
|
||||
session = keycloakRule.startSession();
|
||||
realm = session.realms().getRealmByName("test");
|
||||
user = session.users().getUserByUsername("memuser", realm);
|
||||
Assert.assertEquals(memoryProvider.getId(), StorageId.resolveProviderId(user));
|
||||
Assert.assertEquals(0, user.getCredentialsDirectly().size());
|
||||
Assert.assertEquals(1, user.getCredentialsDirectly().size());
|
||||
session.users().removeUser(realm, user);
|
||||
Assert.assertNull(session.users().getUserByUsername("memuser", realm));
|
||||
keycloakRule.stopSession(session, true);
|
||||
|
|
|
@ -66,20 +66,20 @@ public class UserModelTest extends AbstractModelTest {
|
|||
|
||||
Map<String, String> attributes = new HashMap<String, String>();
|
||||
attributes.put(UserModel.LAST_NAME, "last-name");
|
||||
List<UserModel> search = session.users().searchForUserByAttributes(attributes, realm);
|
||||
List<UserModel> search = session.users().searchForUser(attributes, realm);
|
||||
Assert.assertEquals(search.size(), 1);
|
||||
Assert.assertEquals(search.get(0).getUsername(), "user");
|
||||
|
||||
attributes.clear();
|
||||
attributes.put(UserModel.EMAIL, "email");
|
||||
search = session.users().searchForUserByAttributes(attributes, realm);
|
||||
search = session.users().searchForUser(attributes, realm);
|
||||
Assert.assertEquals(search.size(), 1);
|
||||
Assert.assertEquals(search.get(0).getUsername(), "user");
|
||||
|
||||
attributes.clear();
|
||||
attributes.put(UserModel.LAST_NAME, "last-name");
|
||||
attributes.put(UserModel.EMAIL, "email");
|
||||
search = session.users().searchForUserByAttributes(attributes, realm);
|
||||
search = session.users().searchForUser(attributes, realm);
|
||||
Assert.assertEquals(search.size(), 1);
|
||||
Assert.assertEquals(search.get(0).getUsername(), "user");
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
org.keycloak.testsuite.federation.storage.PlainTextPasswordProviderFactory
|
|
@ -597,6 +597,7 @@ client-template.description.tooltip=Description of the client template
|
|||
client-template.protocol.tooltip=Which SSO protocol configuration is being supplied by this client template
|
||||
|
||||
add-user-federation-provider=Add user federation provider
|
||||
add-user-storage-provider=Add user storage provider
|
||||
required-settings=Required Settings
|
||||
provider-id=Provider ID
|
||||
console-display-name=Console Display Name
|
||||
|
|
|
@ -1350,6 +1350,44 @@ module.config([ '$routeProvider', function($routeProvider) {
|
|||
},
|
||||
controller : 'UserStorageCtrl'
|
||||
})
|
||||
.when('/create/user-storage/:realm/providers/:provider', {
|
||||
templateUrl : resourceUrl + '/partials/user-storage-generic.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
instance : function() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
providerId : function($route) {
|
||||
return $route.current.params.provider;
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GenericUserStorageCtrl'
|
||||
})
|
||||
.when('/realms/:realm/user-storage/providers/:provider/:componentId', {
|
||||
templateUrl : resourceUrl + '/partials/user-storage-generic.html',
|
||||
resolve : {
|
||||
realm : function(RealmLoader) {
|
||||
return RealmLoader();
|
||||
},
|
||||
instance : function(ComponentLoader) {
|
||||
return ComponentLoader();
|
||||
},
|
||||
providerId : function($route) {
|
||||
return $route.current.params.provider;
|
||||
},
|
||||
serverInfo : function(ServerInfoLoader) {
|
||||
return ServerInfoLoader();
|
||||
}
|
||||
},
|
||||
controller : 'GenericUserStorageCtrl'
|
||||
})
|
||||
.when('/realms/:realm/user-federation', {
|
||||
templateUrl : resourceUrl + '/partials/user-federation.html',
|
||||
resolve : {
|
||||
|
@ -2344,6 +2382,89 @@ module.directive('kcProviderConfig', function ($modal) {
|
|||
}
|
||||
});
|
||||
|
||||
module.controller('ComponentRoleSelectorModalCtrl', function($scope, realm, config, configName, RealmRoles, Client, ClientRole, $modalInstance) {
|
||||
$scope.selectedRealmRole = {
|
||||
role: undefined
|
||||
};
|
||||
$scope.selectedClientRole = {
|
||||
role: undefined
|
||||
};
|
||||
$scope.client = {
|
||||
selected: undefined
|
||||
};
|
||||
|
||||
$scope.selectRealmRole = function() {
|
||||
config[configName][0] = $scope.selectedRealmRole.role.name;
|
||||
$modalInstance.close();
|
||||
}
|
||||
|
||||
$scope.selectClientRole = function() {
|
||||
config[configName][0] = $scope.client.selected.clientId + "." + $scope.selectedClientRole.role.name;
|
||||
$modalInstance.close();
|
||||
}
|
||||
|
||||
$scope.cancel = function() {
|
||||
$modalInstance.dismiss();
|
||||
}
|
||||
|
||||
$scope.changeClient = function() {
|
||||
if ($scope.client.selected) {
|
||||
ClientRole.query({realm: realm.realm, client: $scope.client.selected.id}, function (data) {
|
||||
$scope.clientRoles = data;
|
||||
});
|
||||
} else {
|
||||
console.log('selected client was null');
|
||||
$scope.clientRoles = null;
|
||||
}
|
||||
|
||||
}
|
||||
RealmRoles.query({realm: realm.realm}, function(data) {
|
||||
$scope.realmRoles = data;
|
||||
})
|
||||
Client.query({realm: realm.realm}, function(data) {
|
||||
$scope.clients = data;
|
||||
if (data.length > 0) {
|
||||
$scope.client.selected = data[0];
|
||||
$scope.changeClient();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
module.controller('ComponentConfigCtrl', function ($modal, $scope) {
|
||||
$scope.openRoleSelector = function (configName, config) {
|
||||
$modal.open({
|
||||
templateUrl: resourceUrl + '/partials/modal/component-role-selector.html',
|
||||
controller: 'ComponentRoleSelectorModalCtrl',
|
||||
resolve: {
|
||||
realm: function () {
|
||||
return $scope.realm;
|
||||
},
|
||||
config: function () {
|
||||
return config;
|
||||
},
|
||||
configName: function () {
|
||||
return configName;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
module.directive('kcComponentConfig', function ($modal) {
|
||||
return {
|
||||
scope: {
|
||||
config: '=',
|
||||
properties: '=',
|
||||
realm: '=',
|
||||
clients: '=',
|
||||
configName: '='
|
||||
},
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
controller: 'ComponentConfigCtrl',
|
||||
templateUrl: resourceUrl + '/templates/kc-component-config.html'
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Used to select the element (invoke $(elem).select()) on specified action list.
|
||||
* Usages kc-select-action="click mouseover"
|
||||
|
|
|
@ -620,6 +620,123 @@ module.controller('UserStorageCtrl', function($scope, $location, $route, realm,
|
|||
};
|
||||
});
|
||||
|
||||
module.controller('GenericUserStorageCtrl', function($scope, $location, Notifications, $route, Dialog, realm, serverInfo, instance, providerId, Components) {
|
||||
console.log('GenericUserFederationCtrl');
|
||||
console.log('providerId: ' + providerId);
|
||||
$scope.create = !instance.providerId;
|
||||
console.log('create: ' + $scope.create);
|
||||
var providers = serverInfo.componentTypes['org.keycloak.storage.UserStorageProvider'];
|
||||
console.log('providers length ' + providers.length);
|
||||
var providerFactory = null;
|
||||
for (var i = 0; i < providers.length; i++) {
|
||||
var p = providers[i];
|
||||
console.log('provider: ' + p.id);
|
||||
if (p.id == providerId) {
|
||||
$scope.providerFactory = p;
|
||||
providerFactory = p;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
$scope.provider = instance;
|
||||
|
||||
console.log("providerFactory: " + providerFactory.id);
|
||||
|
||||
function initUserStorageSettings() {
|
||||
if ($scope.create) {
|
||||
instance.name = providerFactory.id;
|
||||
instance.providerId = providerFactory.id;
|
||||
instance.providerType = 'org.keycloak.storage.UserStorageProvider';
|
||||
instance.parentId = realm.id;
|
||||
instance.config = {
|
||||
|
||||
};
|
||||
instance.config['priority'] = ["0"];
|
||||
|
||||
if (providerFactory.properties) {
|
||||
|
||||
for (var i = 0; i < providerFactory.properties.length; i++) {
|
||||
var configProperty = providerFactory.properties[i];
|
||||
if (configProperty.defaultValue) {
|
||||
instance.config[configProperty.name] = [configProperty.defaultValue];
|
||||
} else {
|
||||
instance.config[configProperty.name] = [''];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
/*
|
||||
console.log('Manage instance');
|
||||
console.log(instance.name);
|
||||
console.log(instance.providerId);
|
||||
console.log(instance.providerType);
|
||||
console.log(instance.parentId);
|
||||
for (var k in instance.config) {
|
||||
console.log('config[' + k + "] =");
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
$scope.changed = false;
|
||||
}
|
||||
|
||||
initUserStorageSettings();
|
||||
$scope.instance = angular.copy(instance);
|
||||
$scope.realm = realm;
|
||||
|
||||
$scope.$watch('instance', function() {
|
||||
if (!angular.equals($scope.instance, instance)) {
|
||||
$scope.changed = true;
|
||||
}
|
||||
|
||||
}, true);
|
||||
|
||||
$scope.save = function() {
|
||||
$scope.changed = false;
|
||||
if ($scope.create) {
|
||||
Components.save({realm: realm.realm}, $scope.instance, function (data, headers) {
|
||||
var l = headers().location;
|
||||
var id = l.substring(l.lastIndexOf("/") + 1);
|
||||
|
||||
$location.url("/realms/" + realm.realm + "/user-storage/providers/" + $scope.instance.providerId + "/" + id);
|
||||
Notifications.success("The provider has been created.");
|
||||
}, function (errorResponse) {
|
||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||
Notifications.error(errorResponse.data['error_description']);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Components.update({realm: realm.realm,
|
||||
componentId: instance.id
|
||||
},
|
||||
$scope.instance, function () {
|
||||
$route.reload();
|
||||
Notifications.success("The provider has been updated.");
|
||||
}, function (errorResponse) {
|
||||
if (errorResponse.data && errorResponse.data['error_description']) {
|
||||
Notifications.error(errorResponse.data['error_description']);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
$scope.reset = function() {
|
||||
initUserStorageSettings();
|
||||
$scope.instance = angular.copy(instance);
|
||||
};
|
||||
|
||||
$scope.cancel = function() {
|
||||
if ($scope.create) {
|
||||
$location.url("/realms/" + realm.realm + "/user-storage");
|
||||
} else {
|
||||
$route.reload();
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
module.controller('UserFederationCtrl', function($scope, $location, $route, realm, UserFederationProviders, UserFederationInstances, Notifications, Dialog) {
|
||||
console.log('UserFederationCtrl ++++****');
|
||||
$scope.realm = realm;
|
||||
|
|
|
@ -126,6 +126,15 @@ module.factory('UserLoader', function(Loader, User, $route, $q) {
|
|||
});
|
||||
});
|
||||
|
||||
module.factory('ComponentLoader', function(Loader, Components, $route, $q) {
|
||||
return Loader.get(Components, function() {
|
||||
return {
|
||||
realm : $route.current.params.realm,
|
||||
componentId: $route.current.params.componentId
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.factory('UserFederationInstanceLoader', function(Loader, UserFederationInstances, $route, $q) {
|
||||
return Loader.get(UserFederationInstances, function() {
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
<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 data-ng-hide="create">{{instance.name|capitalize}}</li>
|
||||
<li data-ng-show="create">{{:: 'add-user-storage-provider' | translate}}</li>
|
||||
</ol>
|
||||
|
||||
<kc-tabs-user-storage></kc-tabs-user-storage>
|
||||
|
||||
<form class="form-horizontal" name="realmForm" novalidate kc-read-only="!access.manageRealm">
|
||||
<fieldset>
|
||||
<legend><span class="text">{{:: 'required-settings' | translate}}</span></legend>
|
||||
<div class="form-group clearfix" data-ng-show="!create">
|
||||
<label class="col-md-2 control-label" for="providerId">{{:: 'provider-id' | translate}} </label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" id="providerId" type="text" ng-model="instance.id" readonly>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-md-2 control-label" for="consoleDisplayName">{{:: 'console-display-name' | translate}} </label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" id="consoleDisplayName" type="text" ng-model="instance.name" placeholder="{{:: 'defaults-to-id' | translate}}">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'console-display-name.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
<div class="form-group clearfix">
|
||||
<label class="col-md-2 control-label" for="priority">{{:: 'priority' | translate}} </label>
|
||||
<div class="col-md-6">
|
||||
<input class="form-control" id="priority" type="text" ng-model="instance.config['priority'][0]">
|
||||
</div>
|
||||
<kc-tooltip>{{:: 'priority.tooltip' | translate}}</kc-tooltip>
|
||||
</div>
|
||||
|
||||
<kc-component-config realm="realm" config="instance.config" properties="providerFactory.properties"></kc-component-config>
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="create && access.manageUsers">
|
||||
<button kc-save>{{:: 'save' | translate}}</button>
|
||||
<button kc-cancel data-ng-click="cancel()">{{:: 'cancel' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-md-10 col-md-offset-2" data-ng-show="!create && access.manageUsers">
|
||||
<button kc-save data-ng-disabled="!changed">{{:: 'save' | translate}}</button>
|
||||
<button kc-reset data-ng-disabled="!changed">{{:: 'cancel' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<kc-menu></kc-menu>
|
|
@ -1,6 +1,6 @@
|
|||
<div class="col-sm-9 col-md-10 col-sm-push-3 col-md-push-2">
|
||||
<h1>
|
||||
<span>{{:: 'user-federation' | translate}}</span>
|
||||
<span>{{:: 'user-storage' | translate}}</span>
|
||||
</h1>
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
<div>
|
||||
<div data-ng-repeat="option in properties" class="form-group" data-ng-controller="ProviderConfigCtrl">
|
||||
<label class="col-md-2 control-label">{{:: option.label | translate}}</label>
|
||||
|
||||
<div class="col-md-6" data-ng-hide="option.type == 'boolean' || option.type == 'List' || option.type == 'Role' || option.type == 'ClientList' || option.type == 'Password' || option.type=='Script'">
|
||||
<input class="form-control" type="text" data-ng-model="config[ option.name ][0]" >
|
||||
</div>
|
||||
<div class="col-md-6" data-ng-show="option.type == 'Password'">
|
||||
<input class="form-control" type="password" data-ng-model="config[ option.name ][0]" >
|
||||
</div>
|
||||
<div class="col-md-6" data-ng-show="option.type == 'boolean'">
|
||||
<input ng-model="config[ option.name ][0]" value="'true'" name="option.name" id="option.name" onoffswitchstring on-text="{{:: 'onText' | translate}}" off-text="{{:: 'offText' | translate}}"/>
|
||||
</div>
|
||||
<div class="col-md-6" data-ng-show="option.type == 'List'">
|
||||
<select ng-model="config[ option.name ][0]" ng-options="data for data in option.defaultValue">
|
||||
<option value="" selected> {{:: 'selectOne' | translate}} </option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6" data-ng-show="option.type == 'Role'">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<input class="form-control" type="text" data-ng-model="config[ option.name ][0]" >
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<button type="submit" data-ng-click="openRoleSelector(option.name, config)" class="btn btn-default" tooltip-placement="top" tooltip-trigger="mouseover mouseout" tooltip="{{:: 'selectRole.tooltip' | translate}}">{{:: 'selectRole.label' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4" data-ng-show="option.type == 'ClientList'">
|
||||
<select ng-model="config[ option.name ][0]" ng-options="client.clientId as client.clientId for client in clients">
|
||||
<option value="" selected> {{:: 'selectOne' | translate}} </option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6" data-ng-show="option.type == 'Script'">
|
||||
<div ng-model="config[option.name][0]" placeholder="Enter your script..." ui-ace="{ useWrapMode: true, showGutter: true, theme:'github', mode: 'javascript'}">
|
||||
{{config[option.name]}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<kc-tooltip>{{:: option.helpText | translate}}</kc-tooltip>
|
||||
</div>
|
||||
</div>
|
|
@ -34,7 +34,7 @@
|
|||
<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.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>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div data-ng-controller="UserStorageTabCtrl">
|
||||
<h1 data-ng-hide="create">
|
||||
{{instance.name|capitalize}}
|
||||
<i class="pficon pficon-delete clickable" data-ng-show="!create && access.manageUsers" data-ng-click="removeUserStorage()"></i>
|
||||
</h1>
|
||||
<h1 data-ng-show="create">{{:: 'add-user-storage-provider' | translate}}</h1>
|
||||
|
||||
<ul class="nav nav-tabs" data-ng-hide="create">
|
||||
<li ng-class="{active: !path[6]}"><a href="#/realms/{{realm.realm}}/user-storage/providers/{{instance.providerName}}/{{instance.id}}">{{:: 'settings' | translate}}</a></li>
|
||||
</ul>
|
||||
</div>
|
Loading…
Reference in a new issue