keycloak-uncached
Changes
examples/providers/user-storage-jpa/pom.xml 24(+24 -0)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java 116(+113 -3)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java 34(+32 -2)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java 36(+36 -0)
examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java 4(+4 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 6(+3 -3)
services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java 2(+1 -1)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProvider.java 46(+46 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java 54(+54 -0)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java 41(+26 -15)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java 4(+2 -2)
testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java 5(+3 -2)
Details
diff --git a/examples/providers/user-storage-jpa/assembly.xml b/examples/providers/user-storage-jpa/assembly.xml
new file mode 100755
index 0000000..4a3e1f9
--- /dev/null
+++ b/examples/providers/user-storage-jpa/assembly.xml
@@ -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>
diff --git a/examples/providers/user-storage-jpa/module.xml b/examples/providers/user-storage-jpa/module.xml
new file mode 100644
index 0000000..ffe4f83
--- /dev/null
+++ b/examples/providers/user-storage-jpa/module.xml
@@ -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>
examples/providers/user-storage-jpa/pom.xml 24(+24 -0)
diff --git a/examples/providers/user-storage-jpa/pom.xml b/examples/providers/user-storage-jpa/pom.xml
index b025900..c1c38d2 100755
--- a/examples/providers/user-storage-jpa/pom.xml
+++ b/examples/providers/user-storage-jpa/pom.xml
@@ -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>
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java
index 0705333..f94ded8 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java
@@ -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;
+ }
}
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java
index 963a558..f282df2 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java
@@ -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();
}
}
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
index f851904..f4ff5a7 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java
@@ -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;
}
diff --git a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java
index c9227d2..299e900 100644
--- a/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java
+++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java
@@ -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 {
diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
index 5af362d..d082c23 100644
--- a/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
+++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml
@@ -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>
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
index 3016ae8..8a62c33 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java
@@ -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
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index a928e58..0d938d6 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -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;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 96d1f91..c0537ba 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -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());
diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
index c48ae4d..cb6867a 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java
@@ -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);
}
diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java
index ceb4fe0..877b09f 100644
--- a/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java
+++ b/server-spi/src/main/java/org/keycloak/storage/user/UserQueryProvider.java
@@ -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);
- 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 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);
+ /**
+ * 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);
diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
index 72906b0..1a1fca5 100755
--- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
+++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java
@@ -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;
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
index 1a1d2bf..e1c2363 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/info/ServerInfoAdminResource.java
@@ -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);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index aa6aa68..367217c 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -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);
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProvider.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProvider.java
new file mode 100644
index 0000000..f7aba5c
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProvider.java
@@ -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() {
+
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java
new file mode 100644
index 0000000..cdb324c
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/PlainTextPasswordProviderFactory.java
@@ -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";
+ }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
index a53479c..7f72e8e 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorage.java
@@ -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);
+ }
+ }
};
}
@@ -131,19 +155,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();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
index 489dccd..c297aca 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorage.java
@@ -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;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java
index 3e75d64..3c3a6a0 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java
@@ -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);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
index 01f99fb..4b3f78e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserModelTest.java
@@ -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");
}
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory
new file mode 100644
index 0000000..9b9b525
--- /dev/null
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.hash.PasswordHashProviderFactory
@@ -0,0 +1 @@
+org.keycloak.testsuite.federation.storage.PlainTextPasswordProviderFactory
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
index 6165420..a867747 100644
--- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
+++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties
@@ -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
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js
index 2609e81..c2c44c0 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/app.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js
@@ -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"
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
index 8f07ffd..82072ea 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/users.js
@@ -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;
diff --git a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
index e949a9d..33fb7ac 100755
--- a/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
+++ b/themes/src/main/resources/theme/base/admin/resources/js/loaders.js
@@ -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 {
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage.html
index fb6b87a..156a1ff 100755
--- a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage.html
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage.html
@@ -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">
diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html
new file mode 100755
index 0000000..0a89ac8
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-storage-generic.html
@@ -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>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
new file mode 100755
index 0000000..97d8876
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-component-config.html
@@ -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>
\ No newline at end of file
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
index b27e7f3..926b43c 100755
--- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-menu.html
@@ -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>
diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user-storage.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user-storage.html
new file mode 100644
index 0000000..03e1ddc
--- /dev/null
+++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-tabs-user-storage.html
@@ -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>