From a5677e87dbb717b9a7375971c7787fff65af28c1 Mon Sep 17 00:00:00 2001 From: Tomas Kyjovsky Date: Thu, 26 Jan 2017 15:46:45 +0100 Subject: [PATCH] UserStorageTest migrated to Arquillian testsuite --- .../testsuite/federation/UserMapStorage.java | 188 ++++++ .../federation/UserMapStorageFactory.java | 63 ++ .../federation/UserPropertyFileStorage.java | 224 +++++++ .../UserPropertyFileStorageFactory.java | 136 ++++ ...eycloak.storage.UserStorageProviderFactory | 4 +- .../undertow/UndertowDeployerHelper.java | 30 +- .../integration-arquillian/tests/base/pom.xml | 30 +- .../keycloak/testsuite/AbstractAuthTest.java | 12 +- .../org/keycloak/testsuite/admin/ApiUtil.java | 27 +- .../admin/client/AbstractClientTest.java | 11 +- .../admin/event/AbstractEventTest.java | 10 +- .../partialimport/PartialImportTest.java | 4 +- .../storage/ComponentExportImportTest.java | 139 +++++ .../federation/storage/UserStorageTest.java | 585 ++++++++++++++++++ .../read-only-user-password.properties | 4 + .../storage-test/user-password.properties | 4 + 16 files changed, 1426 insertions(+), 45 deletions(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorageFactory.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java create mode 100644 testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorageFactory.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ComponentExportImportTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/read-only-user-password.properties create mode 100644 testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/user-password.properties diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java new file mode 100644 index 0000000000..b2f80bbeea --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorage.java @@ -0,0 +1,188 @@ +/* + * 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; + +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputUpdater; +import org.keycloak.credential.CredentialInputValidator; +import org.keycloak.credential.CredentialModel; +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.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserRegistrationProvider; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import org.jboss.logging.Logger; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserRegistrationProvider, CredentialInputUpdater, CredentialInputValidator { + + private static final Logger log = Logger.getLogger(UserMapStorage.class); + + protected Map userPasswords; + protected ComponentModel model; + protected KeycloakSession session; + + public static final AtomicInteger allocations = new AtomicInteger(0); + public static final AtomicInteger closings = new AtomicInteger(0); + public static final AtomicInteger realmRemovals = new AtomicInteger(0); + public static final AtomicInteger groupRemovals = new AtomicInteger(0); + public static final AtomicInteger roleRemovals = new AtomicInteger(0); + + public UserMapStorage(KeycloakSession session, ComponentModel model, Map userPasswords) { + this.session = session; + this.model = model; + this.userPasswords = userPasswords; + allocations.incrementAndGet(); + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + StorageId storageId = new StorageId(id); + final String username = storageId.getExternalId(); + if (!userPasswords.containsKey(username)) { + return null; + } + + return createUser(realm, username); + } + + private UserModel createUser(RealmModel realm, String username) { + return new AbstractUserAdapterFederatedStorage(session, realm, model) { + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + throw new RuntimeException("Unsupported"); + } + + }; + } + + @Override + public boolean supportsCredentialType(String credentialType) { + return CredentialModel.PASSWORD.equals(credentialType); + } + + @Override + public boolean updateCredential(RealmModel realm, UserModel user, CredentialInput input) { + if (!(input instanceof UserCredentialModel)) { + return false; + } + if (input.getType().equals(UserCredentialModel.PASSWORD)) { + userPasswords.put(user.getUsername(), ((UserCredentialModel) input).getValue()); + return true; + + } else { + return false; + } + } + + @Override + public void disableCredentialType(RealmModel realm, UserModel user, String credentialType) { + + } + + @Override + public Set getDisableableCredentialTypes(RealmModel realm, UserModel user) { + return Collections.EMPTY_SET; + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + return CredentialModel.PASSWORD.equals(credentialType); + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { + if (!(input instanceof UserCredentialModel)) { + return false; + } + if (input.getType().equals(UserCredentialModel.PASSWORD)) { + String pw = userPasswords.get(user.getUsername()); + return pw != null && pw.equals(((UserCredentialModel) input).getValue()); + } else { + return false; + } + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + if (!userPasswords.containsKey(username)) { + return null; + } + + return createUser(realm, username); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + return null; + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + userPasswords.put(username, ""); + return createUser(realm, username); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + return userPasswords.remove(user.getUsername()) != null; + } + + @Override + public void preRemove(RealmModel realm) { + log.infof("preRemove: realm=%s", realm.getName()); + realmRemovals.incrementAndGet(); + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + log.infof("preRemove: realm=%s, group=%s", realm.getName(), group.getName()); + groupRemovals.incrementAndGet(); + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + log.infof("preRemove: realm=%s, role=%s", realm.getName(), role.getName()); + roleRemovals.incrementAndGet(); + } + + @Override + public void close() { + closings.incrementAndGet(); + } + +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorageFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorageFactory.java new file mode 100644 index 0000000000..885c3164ce --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserMapStorageFactory.java @@ -0,0 +1,63 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.testsuite.federation; + +import java.util.HashMap; +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.UserStorageProviderFactory; + +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserMapStorageFactory implements UserStorageProviderFactory { + + + public static final String PROVIDER_ID = "user-password-map-arq"; + + protected Map userPasswords = new HashMap<>(); + + @Override + public UserMapStorage create(KeycloakSession session, ComponentModel model) { + return new UserMapStorage(session, model, userPasswords); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java new file mode 100644 index 0000000000..3716afffff --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorage.java @@ -0,0 +1,224 @@ +/* + * 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; + +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialInput; +import org.keycloak.credential.CredentialInputValidator; +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.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.adapter.AbstractUserAdapter; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserQueryProvider; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserPropertyFileStorage implements UserLookupProvider, UserStorageProvider, UserQueryProvider, CredentialInputValidator { + + protected Properties userPasswords; + protected ComponentModel model; + protected KeycloakSession session; + protected boolean federatedStorageEnabled; + + public UserPropertyFileStorage(KeycloakSession session, ComponentModel model, Properties userPasswords) { + this.session = session; + this.model = model; + this.userPasswords = userPasswords; + this.federatedStorageEnabled = model.getConfig().containsKey("federatedStorage") && Boolean.valueOf(model.getConfig().getFirst("federatedStorage")).booleanValue(); + } + + + @Override + public UserModel getUserById(String id, RealmModel realm) { + StorageId storageId = new StorageId(id); + final String username = storageId.getExternalId(); + if (!userPasswords.containsKey(username)) return null; + + return createUser(realm, username); + } + + private UserModel createUser(RealmModel realm, String username) { + if (federatedStorageEnabled) { + return new AbstractUserAdapterFederatedStorage(session, realm, model) { + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + throw new RuntimeException("Unsupported"); + } + }; + } else { + return new AbstractUserAdapter(session, realm, model) { + @Override + public String getUsername() { + return username; + } + }; + } + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + if (!userPasswords.containsKey(username)) return null; + + return createUser(realm, username); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + return null; + } + + @Override + public void preRemove(RealmModel realm) { + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + + } + + @Override + public boolean supportsCredentialType(String credentialType) { + return credentialType.equals(UserCredentialModel.PASSWORD); + } + + @Override + public boolean isConfiguredFor(RealmModel realm, UserModel user, String credentialType) { + return credentialType.equals(UserCredentialModel.PASSWORD) && userPasswords.get(user.getUsername()) != null; + } + + @Override + public boolean isValid(RealmModel realm, UserModel user, CredentialInput input) { + if (!(input instanceof UserCredentialModel)) return false; + if (input.getType().equals(UserCredentialModel.PASSWORD)) { + String pw = (String)userPasswords.get(user.getUsername()); + return pw != null && pw.equals( ((UserCredentialModel)input).getValue()); + } else { + return false; + } + } + + + @Override + public int getUsersCount(RealmModel realm) { + return userPasswords.size(); + } + + @Override + public List getUsers(RealmModel realm) { + List users = new LinkedList<>(); + for (Object username : userPasswords.keySet()) { + users.add(createUser(realm, (String)username)); + } + return users; + } + + @Override + public List searchForUser(Map attributes, RealmModel realm) { + return Collections.EMPTY_LIST; + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + if (maxResults == 0) return Collections.EMPTY_LIST; + List users = new LinkedList<>(); + int count = 0; + for (Object un : userPasswords.keySet()) { + if (count++ < firstResult) continue; + String username = (String)un; + users.add(createUser(realm, username)); + if (users.size() + 1 > maxResults) break; + } + return users; + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + if (maxResults == 0) return Collections.EMPTY_LIST; + List users = new LinkedList<>(); + int count = 0; + for (Object un : userPasswords.keySet()) { + String username = (String)un; + if (username.contains(search)) { + if (count++ < firstResult) { + continue; + } + users.add(createUser(realm, username)); + if (users.size() + 1 > maxResults) break; + } + } + return users; + } + + @Override + public List searchForUser(Map 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; + return searchForUser(username, realm, firstResult, maxResults); + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return Collections.EMPTY_LIST; + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + return Collections.EMPTY_LIST; + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return getUsers(realm, 0, Integer.MAX_VALUE - 1); + } + + @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return Collections.EMPTY_LIST; + } + + @Override + public void close() { + + } +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorageFactory.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorageFactory.java new file mode 100644 index 0000000000..7b3f375359 --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/federation/UserPropertyFileStorageFactory.java @@ -0,0 +1,136 @@ +/* + * 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; + +import java.io.File; +import java.io.FileInputStream; +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.storage.UserStorageProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.user.ImportSynchronization; +import org.keycloak.storage.user.SynchronizationResult; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; +import java.util.List; +import java.util.Properties; +import org.keycloak.common.util.EnvUtil; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.RealmModel; +import org.keycloak.provider.ProviderConfigurationBuilder; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserPropertyFileStorageFactory implements UserStorageProviderFactory, ImportSynchronization { + + public static final String PROVIDER_ID = "user-password-props-arq"; + public static final String PROPERTY_FILE = "propertyFile"; + + public static final String VALIDATION_PROP_FILE_NOT_CONFIGURED = "user property file is not configured"; + public static final String VALIDATION_PROP_FILE_DOESNT_EXIST = "user property file does not exist"; + + protected static final List CONFIG_PROPERTIES; + + static { + CONFIG_PROPERTIES = ProviderConfigurationBuilder.create() + .property().name(PROPERTY_FILE) + .type(ProviderConfigProperty.STRING_TYPE) + .label("Property File") + .helpText("File that contains name value pairs") + .defaultValue(null) + .add() + .property().name("federatedStorage") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .label("User Federated Storage") + .helpText("User Federated Storage") + .defaultValue(null) + .add() + .build(); + } + + @Override + public void validateConfiguration(KeycloakSession session, RealmModel realm, ComponentModel config) throws ComponentValidationException { + String fp = config.getConfig().getFirst(PROPERTY_FILE); + if (fp == null) { + throw new ComponentValidationException(VALIDATION_PROP_FILE_NOT_CONFIGURED); + } + fp = EnvUtil.replace(fp); + File file = new File(fp); + if (!file.exists()) { + throw new ComponentValidationException(VALIDATION_PROP_FILE_DOESNT_EXIST); + } + } + + @Override + public UserPropertyFileStorage create(KeycloakSession session, ComponentModel model) { + String path = model.getConfig().getFirst(PROPERTY_FILE); + path = EnvUtil.replace(path); + + Properties props = new Properties(); + try (InputStream is = new FileInputStream(path)) { + props.load(is); + is.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + return new UserPropertyFileStorage(session, model, props); + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public List getConfigProperties() { + return CONFIG_PROPERTIES; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public SynchronizationResult sync(KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + return SynchronizationResult.ignored(); + } + + @Override + public SynchronizationResult syncSince(Date lastSync, KeycloakSessionFactory sessionFactory, String realmId, UserStorageProviderModel model) { + return SynchronizationResult.ignored(); + } + +} diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory index a9ae823e92..7b57770ca3 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -1,2 +1,4 @@ org.keycloak.testsuite.federation.DummyUserFederationProviderFactory -org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProviderFactory \ No newline at end of file +org.keycloak.testsuite.federation.UserMapStorageFactory +org.keycloak.testsuite.federation.UserPropertyFileStorageFactory +org.keycloak.testsuite.federation.PassThroughFederatedUserStorageProviderFactory diff --git a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java index bd632c35e5..bcb726f3b6 100644 --- a/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java +++ b/testsuite/integration-arquillian/servers/auth-server/undertow/src/main/java/org/keycloak/testsuite/arquillian/undertow/UndertowDeployerHelper.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.keycloak.testsuite.arquillian.undertow; import java.io.IOException; @@ -83,7 +82,6 @@ class UndertowDeployerHelper { } } - private ResourceManager getResourceManager(final String appServerRoot, final WebArchive archive) throws IOException { return new ResourceManager() { @@ -139,7 +137,6 @@ class UndertowDeployerHelper { throw UndertowMessages.MESSAGES.resourceChangeListenerNotSupported(); } - @Override public void close() throws IOException { // TODO: Should close open streams? @@ -148,7 +145,6 @@ class UndertowDeployerHelper { }; } - private Document loadXML(InputStream is) { try { DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); @@ -159,7 +155,6 @@ class UndertowDeployerHelper { } } - private void addAnnotatedServlets(DeploymentInfo di, Archive archive) { Map classNodes = archive.getContent((ArchivePath path) -> { @@ -170,25 +165,26 @@ class UndertowDeployerHelper { for (Map.Entry entry : classNodes.entrySet()) { Node n = entry.getValue(); - ClassAsset classAsset = (ClassAsset) n.getAsset(); - Class clazz = classAsset.getSource(); + if (n.getAsset() instanceof ClassAsset) { + ClassAsset classAsset = (ClassAsset) n.getAsset(); + Class clazz = classAsset.getSource(); - WebServlet annotation = clazz.getAnnotation(WebServlet.class); - if (annotation != null) { - ServletInfo undertowServlet = new ServletInfo(clazz.getSimpleName(), (Class) clazz); + WebServlet annotation = clazz.getAnnotation(WebServlet.class); + if (annotation != null) { + ServletInfo undertowServlet = new ServletInfo(clazz.getSimpleName(), (Class) clazz); - String[] mappings = annotation.value(); - if (mappings != null) { - for (String urlPattern : mappings) { - undertowServlet.addMapping(urlPattern); + String[] mappings = annotation.value(); + if (mappings != null) { + for (String urlPattern : mappings) { + undertowServlet.addMapping(urlPattern); + } } - } - di.addServlet(undertowServlet); + di.addServlet(undertowServlet); + } } } } - } diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index e079fdcc53..e74016d3cd 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -1,20 +1,20 @@ +~ 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. +--> diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractAuthTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractAuthTest.java index 570c9e8815..7fbab6a8b7 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractAuthTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractAuthTest.java @@ -68,11 +68,16 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest { testRealms.add(testRealmRep); } - @Before - public void beforeAuthTest() { + @Override + public void setDefaultPageUriParameters() { + super.setDefaultPageUriParameters(); + testRealmPage.setAuthRealm(TEST); testRealmLoginPage.setAuthRealm(testRealmPage); testRealmAccountPage.setAuthRealm(testRealmPage); + } + @Before + public void beforeAuthTest() { testUser = createUserRepresentation("test", "test@email.test", "test", "user", true); setPasswordFor(testUser, PASSWORD); @@ -82,6 +87,7 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest { deleteAllCookiesForTestRealm(); } + public void createTestUserWithAdminClient() { log.debug("creating test user"); String id = createUserAndResetPasswordWithAdminClient(testRealmResource(), testUser, PASSWORD); @@ -116,4 +122,4 @@ public abstract class AbstractAuthTest extends AbstractKeycloakTest { return adminClient.realm(testRealmPage.getAuthRealm()); } -} \ No newline at end of file +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java index 97edc00361..bf366cffe5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/ApiUtil.java @@ -53,8 +53,8 @@ public class ApiUtil { URI location = response.getLocation(); if (!response.getStatusInfo().equals(Status.CREATED)) { StatusType statusInfo = response.getStatusInfo(); - throw new WebApplicationException("Create method returned status " + - statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)", response); + throw new WebApplicationException("Create method returned status " + + statusInfo.getReasonPhrase() + " (Code: " + statusInfo.getStatusCode() + "); expected status: Created (201)", response); } if (location == null) { return null; @@ -118,14 +118,16 @@ public class ApiUtil { public static UserRepresentation findUserByUsername(RealmResource realm, String username) { UserRepresentation user = null; - List ur = realm.users().search(username, null, null); + List ur = realm.users().search(username, null, null, null, 0, Integer.MAX_VALUE); if (ur.size() == 1) { user = ur.get(0); } if (ur.size() > 1) { // try to be more specific for (UserRepresentation rep : ur) { - if (rep.getUsername().equalsIgnoreCase(username)) return rep; + if (rep.getUsername().equalsIgnoreCase(username)) { + return rep; + } } } @@ -157,6 +159,21 @@ public class ApiUtil { userResource.resetPassword(newCredential); } + public static void assignRealmRoles(RealmResource realm, String userId, String... roles) { + String realmName = realm.toRepresentation().getRealm(); + + List roleRepresentations = new ArrayList<>(); + for (String roleName : roles) { + RoleRepresentation role = realm.roles().get(roleName).toRepresentation(); + roleRepresentations.add(role); + } + + UserResource userResource = realm.users().get(userId); + log.info("assigning roles " + Arrays.toString(roles) + " to user: \"" + + userResource.toRepresentation().getUsername() + "\" in realm: \"" + realmName + "\""); + userResource.roles().realmLevel().add(roleRepresentations); + } + public static void assignClientRoles(RealmResource realm, String userId, String clientName, String... roles) { String realmName = realm.toRepresentation().getRealm(); String clientId = ""; @@ -176,7 +193,7 @@ public class ApiUtil { } UserResource userResource = realm.users().get(userId); - log.debug("assigning role: " + Arrays.toString(roles) + " to user: \"" + log.info("assigning role: " + Arrays.toString(roles) + " to user: \"" + userResource.toRepresentation().getUsername() + "\" of client: \"" + clientName + "\" in realm: \"" + realmName + "\""); userResource.roles().clientLevel(clientId).add(roleRepresentations); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java index eed784ba9f..c247009141 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/client/AbstractClientTest.java @@ -36,6 +36,7 @@ import org.keycloak.testsuite.util.RealmBuilder; import javax.ws.rs.core.Response; import java.util.List; +import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; /** * @@ -46,6 +47,14 @@ public abstract class AbstractClientTest extends AbstractAuthTest { @Rule public AssertAdminEvents assertAdminEvents = new AssertAdminEvents(this); + @Override + public void setDefaultPageUriParameters() { + super.setDefaultPageUriParameters(); + testRealmPage.setAuthRealm(MASTER); + testRealmLoginPage.setAuthRealm(testRealmPage); + testRealmAccountPage.setAuthRealm(testRealmPage); + } + @Before public void setupAdminEvents() { RealmRepresentation realm = testRealmResource().toRepresentation(); @@ -66,7 +75,7 @@ public abstract class AbstractClientTest extends AbstractAuthTest { } protected String getRealmId() { - return "master"; + return MASTER; } // returns UserRepresentation retrieved from server, with all fields, including id diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java index 03df30e9f2..9cb3c32ffe 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/event/AbstractEventTest.java @@ -14,7 +14,6 @@ * License for the specific language governing permissions and limitations under * the License. */ - package org.keycloak.testsuite.admin.event; import org.junit.Before; @@ -23,6 +22,7 @@ import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.testsuite.AbstractAuthTest; import java.util.Collections; +import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; /** * @@ -32,6 +32,14 @@ public abstract class AbstractEventTest extends AbstractAuthTest { protected RealmEventsConfigRepresentation configRep; + @Override + public void setDefaultPageUriParameters() { + super.setDefaultPageUriParameters(); + testRealmPage.setAuthRealm(MASTER); + testRealmLoginPage.setAuthRealm(testRealmPage); + testRealmAccountPage.setAuthRealm(testRealmPage); + } + @Before public void setConfigRep() { RealmResource testRsc = testRealmResource(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java index 6125ab3eb5..cdd8856fe4 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/partialimport/PartialImportTest.java @@ -339,9 +339,9 @@ public class PartialImportTest extends AbstractAuthTest { @Test public void testAddUsersWithDuplicateEmailsAllowed() { - RealmRepresentation realmRep = new RealmRepresentation(); + RealmRepresentation realmRep = testRealmResource().toRepresentation(); realmRep.setDuplicateEmailsAllowed(true); - adminClient.realm(realmId).update(realmRep); + testRealmResource().update(realmRep); assertAdminEvents.clear(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ComponentExportImportTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ComponentExportImportTest.java new file mode 100644 index 0000000000..cc57b8e1bf --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/ComponentExportImportTest.java @@ -0,0 +1,139 @@ +package org.keycloak.testsuite.federation.storage; + +import java.io.File; +import java.io.Serializable; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import javax.ws.rs.NotFoundException; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.exportimport.ExportImportConfig; +import org.keycloak.exportimport.ExportImportManager; +import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.testsuite.AbstractAuthTest; +import org.keycloak.testsuite.admin.ApiUtil; +import static org.keycloak.testsuite.auth.page.AuthRealm.TEST; +import org.keycloak.testsuite.federation.UserMapStorageFactory; +import org.keycloak.testsuite.runonserver.RunOnServerDeployment; + +/** + * + * @author tkyjovsk + */ +public class ComponentExportImportTest extends AbstractAuthTest implements Serializable { + + private File exportFile; + + @Deployment + public static WebArchive deploy() { + return RunOnServerDeployment.create(ComponentExportImportTest.class, AbstractAuthTest.class, RealmResource.class) + .addPackages(true, "org.keycloak.testsuite"); + } + + @Before + public void setDirs() { + exportFile = new File (new File(System.getProperty("auth.server.config.dir", "target")), "singleFile-full.json"); + log.infof("Export file: %s", exportFile); + } + + public void clearExportImportProperties() { + // Clear export/import properties after test + Properties systemProps = System.getProperties(); + Set propsToRemove = new HashSet<>(); + + for (Object key : systemProps.keySet()) { + if (key.toString().startsWith(ExportImportConfig.PREFIX)) { + propsToRemove.add(key.toString()); + } + } + + for (String propToRemove : propsToRemove) { + systemProps.remove(propToRemove); + } + } + + protected String addComponent(ComponentRepresentation component) { + return ApiUtil.getCreatedId(testRealmResource().components().add(component)); + } + + @Test + @Ignore + public void testSingleFile() { + clearExportImportProperties(); + + String realmId = testRealmResource().toRepresentation().getId(); + String realmName = testRealmResource().toRepresentation().getRealm(); + + ComponentRepresentation parentComponent = new ComponentRepresentation(); + parentComponent.setParentId(realmId); + parentComponent.setName("parent"); + parentComponent.setSubType("subtype"); + parentComponent.setProviderId(UserMapStorageFactory.PROVIDER_ID); + parentComponent.setProviderType(UserStorageProvider.class.getName()); + parentComponent.setConfig(new MultivaluedHashMap<>()); + parentComponent.getConfig().putSingle("priority", Integer.toString(0)); + parentComponent.getConfig().putSingle("attr", "value"); + String parentComponentId = addComponent(parentComponent); + + ComponentRepresentation subcomponent = new ComponentRepresentation(); + subcomponent.setParentId(parentComponentId); + subcomponent.setName("child"); + subcomponent.setSubType("subtype2"); + subcomponent.setProviderId(UserMapStorageFactory.PROVIDER_ID); + subcomponent.setProviderType(UserStorageProvider.class.getName()); + subcomponent.setConfig(new MultivaluedHashMap<>()); + subcomponent.getConfig().putSingle("priority", Integer.toString(0)); + subcomponent.getConfig().putSingle("attr", "value2"); + String subcomponentId = addComponent(subcomponent); + + // export + testingClient.server().run(session -> { + ExportImportConfig.setProvider(SingleFileExportProviderFactory.PROVIDER_ID); + ExportImportConfig.setFile(exportFile.getAbsolutePath()); + ExportImportConfig.setRealmName(realmName); + ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT); + new ExportImportManager(session).runExport(); + }); + + // import + testingClient.server().run(session -> { + Assert.assertNull(session.realms().getRealmByName(TEST)); + ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT); + new ExportImportManager(session).runImport(); + Assert.assertNotNull(session.realms().getRealmByName(TEST)); + }); + + try { + parentComponent = testRealmResource().components().component(parentComponentId).toRepresentation(); + subcomponent = testRealmResource().components().component(subcomponentId).toRepresentation(); + } catch (NotFoundException nfe) { + fail("Components not found after import."); + } + + Assert.assertEquals(parentComponent.getParentId(), realmId); + Assert.assertEquals(parentComponent.getName(), "parent"); + Assert.assertEquals(parentComponent.getSubType(), "subtype"); + Assert.assertEquals(parentComponent.getProviderId(), UserMapStorageFactory.PROVIDER_ID); + Assert.assertEquals(parentComponent.getProviderType(), UserStorageProvider.class.getName()); + Assert.assertEquals(parentComponent.getConfig().getFirst("attr"), "value"); + + Assert.assertEquals(subcomponent.getParentId(), realmId); + Assert.assertEquals(subcomponent.getName(), "child"); + Assert.assertEquals(subcomponent.getSubType(), "subtype2"); + Assert.assertEquals(subcomponent.getProviderId(), UserMapStorageFactory.PROVIDER_ID); + Assert.assertEquals(subcomponent.getProviderType(), UserStorageProvider.class.getName()); + Assert.assertEquals(subcomponent.getConfig().getFirst("attr"), "value2"); + + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java new file mode 100644 index 0000000000..a5f00a7ae3 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java @@ -0,0 +1,585 @@ +package org.keycloak.testsuite.federation.storage; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import static java.util.Calendar.DAY_OF_WEEK; +import static java.util.Calendar.HOUR_OF_DAY; +import static java.util.Calendar.MINUTE; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.ws.rs.NotFoundException; +import org.apache.commons.io.FileUtils; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Assert; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import static org.keycloak.models.UserModel.RequiredAction.UPDATE_PROFILE; +import org.keycloak.models.cache.CachedUserModel; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.GroupRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.storage.UserStorageProvider; +import static org.keycloak.storage.UserStorageProviderModel.CACHE_POLICY; +import org.keycloak.storage.UserStorageProviderModel.CachePolicy; +import static org.keycloak.storage.UserStorageProviderModel.EVICTION_DAY; +import static org.keycloak.storage.UserStorageProviderModel.EVICTION_HOUR; +import static org.keycloak.storage.UserStorageProviderModel.EVICTION_MINUTE; +import static org.keycloak.storage.UserStorageProviderModel.MAX_LIFESPAN; +import org.keycloak.testsuite.AbstractAuthTest; +import org.keycloak.testsuite.admin.ApiUtil; +import org.keycloak.testsuite.federation.UserMapStorage; +import org.keycloak.testsuite.federation.UserMapStorageFactory; +import org.keycloak.testsuite.federation.UserPropertyFileStorageFactory; +import org.keycloak.testsuite.runonserver.RunOnServerDeployment; +import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlDoesntStartWith; +import static org.keycloak.testsuite.util.URLAssert.assertCurrentUrlStartsWith; + +/** + * + * @author tkyjovsk + */ +public class UserStorageTest extends AbstractAuthTest { + + private String memProviderId; + private String propProviderROId; + private String propProviderRWId; + + private static final File CONFIG_DIR = new File(System.getProperty("auth.server.config.dir", "")); + + @Before + public void addProvidersBeforeTest() throws URISyntaxException, IOException { + ComponentRepresentation memProvider = new ComponentRepresentation(); + memProvider.setName("memory"); + memProvider.setProviderId(UserMapStorageFactory.PROVIDER_ID); + memProvider.setProviderType(UserStorageProvider.class.getName()); + memProvider.setConfig(new MultivaluedHashMap<>()); + memProvider.getConfig().putSingle("priority", Integer.toString(0)); + + memProviderId = addComponent(memProvider); + + // copy files used by the following RO/RW user providers + File stResDir = new File(getClass().getResource("/storage-test").toURI()); + if (stResDir.exists() && stResDir.isDirectory() && CONFIG_DIR.exists() && CONFIG_DIR.isDirectory()) { + for (File f : stResDir.listFiles()) { + log.infof("Copying %s to %s", f.getName(), CONFIG_DIR.getAbsolutePath()); + FileUtils.copyFileToDirectory(f, CONFIG_DIR); + } + } else { + throw new RuntimeException("Property `auth.server.config.dir` must be set to run UserStorageTests."); + } + + ComponentRepresentation propProviderRO = new ComponentRepresentation(); + propProviderRO.setName("read-only-user-props"); + propProviderRO.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + propProviderRO.setProviderType(UserStorageProvider.class.getName()); + propProviderRO.setConfig(new MultivaluedHashMap<>()); + propProviderRO.getConfig().putSingle("priority", Integer.toString(1)); + propProviderRO.getConfig().putSingle("propertyFile", + CONFIG_DIR.getAbsolutePath() + File.separator + "read-only-user-password.properties"); + + propProviderROId = addComponent(propProviderRO); + + propProviderRWId = addComponent(newPropProviderRW()); + + } + + protected ComponentRepresentation newPropProviderRW() { + ComponentRepresentation propProviderRW = new ComponentRepresentation(); + propProviderRW.setName("user-props"); + propProviderRW.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + propProviderRW.setProviderType(UserStorageProvider.class.getName()); + propProviderRW.setConfig(new MultivaluedHashMap<>()); + propProviderRW.getConfig().putSingle("priority", Integer.toString(2)); + propProviderRW.getConfig().putSingle("propertyFile", CONFIG_DIR.getAbsolutePath() + File.separator + "user-password.properties"); + propProviderRW.getConfig().putSingle("federatedStorage", "true"); + return propProviderRW; + } + + protected String addComponent(ComponentRepresentation component) { + return ApiUtil.getCreatedId(testRealmResource().components().add(component)); + } + + private void loginSuccessAndLogout(String username, String password) { + testRealmAccountPage.navigateTo(); + testRealmLoginPage.form().login(username, password); + assertCurrentUrlStartsWith(testRealmAccountPage); + testRealmAccountPage.logOut(); + } + + public void loginBadPassword(String username) { + testRealmAccountPage.navigateTo(); + testRealmLoginPage.form().login(username, "badpassword"); + assertCurrentUrlDoesntStartWith(testRealmAccountPage); + } + +// @Test + public void listComponents() { + log.info("COMPONENTS:"); + testRealmResource().components().query().forEach((c) -> { + log.infof("%s - %s - %s", c.getId(), c.getProviderType(), c.getName()); + }); + } + + @Test + public void testLoginSuccess() { + loginSuccessAndLogout("tbrady", "goat"); + loginSuccessAndLogout("thor", "hammer"); + loginBadPassword("tbrady"); + } + + @Test + public void testUpdate() { + UserRepresentation thor = ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + // update entity + thor.setFirstName("Stian"); + thor.setLastName("Thorgersen"); + thor.setEmailVerified(true); + long thorCreated = System.currentTimeMillis() - 100; + thor.setCreatedTimestamp(thorCreated); + thor.setEmail("thor@hammer.com"); + thor.setAttributes(new HashMap<>()); + thor.getAttributes().put("test-attribute", Arrays.asList("value")); + thor.setRequiredActions(new ArrayList<>()); + thor.getRequiredActions().add(UPDATE_PROFILE.name()); + testRealmResource().users().get(thor.getId()).update(thor); + + // check entity + thor = ApiUtil.findUserByUsername(testRealmResource(), "thor"); + Assert.assertEquals("Stian", thor.getFirstName()); + Assert.assertEquals("Thorgersen", thor.getLastName()); + Assert.assertEquals("thor@hammer.com", thor.getEmail()); + Assert.assertTrue(thor.getAttributes().containsKey("test-attribute")); + Assert.assertEquals(1, thor.getAttributes().get("test-attribute").size()); + Assert.assertEquals("value", thor.getAttributes().get("test-attribute").get(0)); + Assert.assertTrue(thor.isEmailVerified()); + + // update group + GroupRepresentation g = new GroupRepresentation(); + g.setName("my-group"); + String gid = ApiUtil.getCreatedId(testRealmResource().groups().add(g)); + + testRealmResource().users().get(thor.getId()).joinGroup(gid); + + // check group + boolean foundGroup = false; + for (GroupRepresentation ug : testRealmResource().users().get(thor.getId()).groups()) { + if (ug.getId().equals(gid)) { + foundGroup = true; + } + } + Assert.assertTrue(foundGroup); + + // check required actions + assertTrue(thor.getRequiredActions().contains(UPDATE_PROFILE.name())); + // remove req. actions + thor.getRequiredActions().remove(UPDATE_PROFILE.name()); + testRealmResource().users().get(thor.getId()).update(thor); + + // change pass + ApiUtil.resetUserPassword(testRealmResource().users().get(thor.getId()), "lightning", false); + loginSuccessAndLogout("thor", "lightning"); + + // update role + RoleRepresentation r = new RoleRepresentation("foo-role", "foo role", false); + testRealmResource().roles().create(r); + ApiUtil.assignRealmRoles(testRealmResource(), thor.getId(), "foo-role"); + + // check role + boolean foundRole = false; + for (RoleRepresentation rr : user(thor.getId()).roles().getAll().getRealmMappings()) { + if ("foo-role".equals(rr.getName())) { + foundRole = true; + break; + } + } + assertTrue(foundRole); + + // test removal of provider + testRealmResource().components().component(propProviderRWId).remove(); + propProviderRWId = addComponent(newPropProviderRW()); + loginSuccessAndLogout("thor", "hammer"); + + thor = ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + Assert.assertNull(thor.getFirstName()); + Assert.assertNull(thor.getLastName()); + Assert.assertNull(thor.getEmail()); + Assert.assertNull(thor.getAttributes()); + Assert.assertFalse(thor.isEmailVerified()); + + foundGroup = false; + for (GroupRepresentation ug : testRealmResource().users().get(thor.getId()).groups()) { + if (ug.getId().equals(gid)) { + foundGroup = true; + } + } + Assert.assertFalse(foundGroup); + + foundRole = false; + for (RoleRepresentation rr : user(thor.getId()).roles().getAll().getRealmMappings()) { + if ("foo-role".equals(rr.getName())) { + foundRole = true; + break; + } + } + assertFalse(foundRole); + } + + public UserResource user(String userId) { + return testRealmResource().users().get(userId); + } + + @Test + public void testRegistration() { + UserRepresentation memuser = new UserRepresentation(); + memuser.setUsername("memuser"); + String uid = ApiUtil.createUserAndResetPasswordWithAdminClient(testRealmResource(), memuser, "password"); + loginSuccessAndLogout("memuser", "password"); + loginSuccessAndLogout("memuser", "password"); + loginSuccessAndLogout("memuser", "password"); + + memuser = user(uid).toRepresentation(); + assertNotNull(memuser); + assertNotNull(memuser.getOrigin()); + ComponentRepresentation origin = testRealmResource().components().component(memuser.getOrigin()).toRepresentation(); + Assert.assertEquals("memory", origin.getName()); + + testRealmResource().users().get(memuser.getId()).remove(); + try { + user(uid).toRepresentation(); // provider doesn't implement UserQueryProvider --> have to lookup by uid + fail("`memuser` wasn't removed"); + } catch (NotFoundException nfe) { + // expected + } + } + + @Test + public void testQuery() { + Set queried = new HashSet<>(); + int first = 0; + while (queried.size() < 8) { + List results = testRealmResource().users().search("", first, 3); + log.debugf("first=%s, results: %s", first, results.size()); + if (results.isEmpty()) { + break; + } + first += results.size(); + queried.addAll(results); + } + Set usernames = new HashSet<>(); + for (UserRepresentation user : queried) { + usernames.add(user.getUsername()); + log.info(user.getUsername()); + } + Assert.assertEquals(8, queried.size()); + Assert.assertTrue(usernames.contains("thor")); + Assert.assertTrue(usernames.contains("zeus")); + Assert.assertTrue(usernames.contains("apollo")); + Assert.assertTrue(usernames.contains("perseus")); + Assert.assertTrue(usernames.contains("tbrady")); + Assert.assertTrue(usernames.contains("rob")); + Assert.assertTrue(usernames.contains("jules")); + Assert.assertTrue(usernames.contains("danny")); + + // test searchForUser + List users = testRealmResource().users().search("tbrady", 0, Integer.MAX_VALUE); + Assert.assertTrue(users.size() == 1); + Assert.assertTrue(users.get(0).getUsername().equals("tbrady")); + + // test getGroupMembers() + GroupRepresentation g = new GroupRepresentation(); + g.setName("gods"); + String gid = ApiUtil.getCreatedId(testRealmResource().groups().add(g)); + + UserRepresentation user = ApiUtil.findUserByUsername(testRealmResource(), "apollo"); + testRealmResource().users().get(user.getId()).joinGroup(gid); + user = ApiUtil.findUserByUsername(testRealmResource(), "zeus"); + testRealmResource().users().get(user.getId()).joinGroup(gid); + user = ApiUtil.findUserByUsername(testRealmResource(), "thor"); + testRealmResource().users().get(user.getId()).joinGroup(gid); + queried.clear(); + usernames.clear(); + + first = 0; + while (queried.size() < 8) { + List results = testRealmResource().groups().group(gid).members(first, 1); + log.debugf("first=%s, results: %s", first, results.size()); + if (results.isEmpty()) { + break; + } + first += results.size(); + queried.addAll(results); + } + for (UserRepresentation u : queried) { + usernames.add(u.getUsername()); + log.info(u.getUsername()); + } + Assert.assertEquals(3, queried.size()); + Assert.assertTrue(usernames.contains("apollo")); + Assert.assertTrue(usernames.contains("zeus")); + Assert.assertTrue(usernames.contains("thor")); + + // search by single attribute + // FIXME - no equivalent for model in REST + } + + @Deployment + public static WebArchive deploy() { + return RunOnServerDeployment.create(UserResource.class) + .addPackages(true, "org.keycloak.testsuite"); + } + + @Test + public void testDailyEviction() { + ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + // set eviction to 1 hour from now + Calendar eviction = Calendar.getInstance(); + eviction.add(Calendar.HOUR, 1); + ComponentRepresentation propProviderRW = testRealmResource().components().component(propProviderRWId).toRepresentation(); + propProviderRW.getConfig().putSingle(CACHE_POLICY, CachePolicy.EVICT_DAILY.name()); + propProviderRW.getConfig().putSingle(EVICTION_HOUR, Integer.toString(eviction.get(HOUR_OF_DAY))); + propProviderRW.getConfig().putSingle(EVICTION_MINUTE, Integer.toString(eviction.get(MINUTE))); + testRealmResource().components().component(propProviderRWId).update(propProviderRW); + + // now + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertTrue(user instanceof CachedUserModel); // should still be cached + }); + + setTimeOffset(2 * 60 * 60); // 2 hours in future + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertFalse(user instanceof CachedUserModel); // should be evicted + }); + + } + + @Test + public void testWeeklyEviction() { + ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + // set eviction to 4 days from now + Calendar eviction = Calendar.getInstance(); + eviction.add(Calendar.HOUR, 4 * 24); + ComponentRepresentation propProviderRW = testRealmResource().components().component(propProviderRWId).toRepresentation(); + propProviderRW.getConfig().putSingle(CACHE_POLICY, CachePolicy.EVICT_WEEKLY.name()); + propProviderRW.getConfig().putSingle(EVICTION_DAY, Integer.toString(eviction.get(DAY_OF_WEEK))); + propProviderRW.getConfig().putSingle(EVICTION_HOUR, Integer.toString(eviction.get(HOUR_OF_DAY))); + propProviderRW.getConfig().putSingle(EVICTION_MINUTE, Integer.toString(eviction.get(MINUTE))); + testRealmResource().components().component(propProviderRWId).update(propProviderRW); + + // now + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertTrue(user instanceof CachedUserModel); // should still be cached + }); + + setTimeOffset(2 * 24 * 60 * 60); // 2 days in future + + // now + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertTrue(user instanceof CachedUserModel); // should still be cached + }); + + setTimeOffset(5 * 24 * 60 * 60); // 5 days in future + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertFalse(user instanceof CachedUserModel); // should be evicted + }); + + } + + @Test + @Ignore + public void testMaxLifespan() { + ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + // set eviction to 1 hour from now + ComponentRepresentation propProviderRW = testRealmResource().components().component(propProviderRWId).toRepresentation(); + propProviderRW.getConfig().putSingle(CACHE_POLICY, CachePolicy.MAX_LIFESPAN.name()); + propProviderRW.getConfig().putSingle(MAX_LIFESPAN, Long.toString(1 * 60 * 60 * 1000)); // 1 hour in milliseconds + testRealmResource().components().component(propProviderRWId).update(propProviderRW); + + // now + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertTrue(user instanceof CachedUserModel); // should still be cached + }); + + setTimeOffset(1/2 * 60 * 60); // 1/2 hour in future + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertTrue(user instanceof CachedUserModel); // should still be cached + }); + + setTimeOffset(2 * 60 * 60); // 2 hours in future + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertFalse(user instanceof CachedUserModel); // should be evicted + }); + + } + + @Test + public void testNoCache() { + ApiUtil.findUserByUsername(testRealmResource(), "thor"); + + // set NO_CACHE policy + ComponentRepresentation propProviderRW = testRealmResource().components().component(propProviderRWId).toRepresentation(); + propProviderRW.getConfig().putSingle(CACHE_POLICY, CachePolicy.NO_CACHE.name()); + testRealmResource().components().component(propProviderRWId).update(propProviderRW); + + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("thor", realm); + System.out.println("User class: " + user.getClass()); + Assert.assertFalse(user instanceof CachedUserModel); // should be evicted + }); + } + + @Test + public void testLifecycle() { + + testingClient.server().run(session -> { + UserMapStorage.allocations.set(0); + UserMapStorage.closings.set(0); + + RealmModel realm = session.realms().getRealmByName("test"); + UserModel user = session.users().addUser(realm, "memuser"); + Assert.assertNotNull(user); + user = session.users().getUserByUsername("nonexistent", realm); + Assert.assertNull(user); + + Assert.assertEquals(1, UserMapStorage.allocations.get()); + Assert.assertEquals(0, UserMapStorage.closings.get()); + }); + + testingClient.server().run(session -> { + Assert.assertEquals(1, UserMapStorage.allocations.get()); + Assert.assertEquals(1, UserMapStorage.closings.get()); + }); + + } + + @Test + public void testEntityRemovalHooks() { + testingClient.server().run(session -> { + UserMapStorage.realmRemovals.set(0); + UserMapStorage.groupRemovals.set(0); + UserMapStorage.roleRemovals.set(0); + }); + + // remove group + GroupRepresentation g1 = new GroupRepresentation(); + g1.setName("group1"); + GroupRepresentation g2 = new GroupRepresentation(); + g2.setName("group2"); + String gid1 = ApiUtil.getCreatedId(testRealmResource().groups().add(g1)); + String gid2 = ApiUtil.getCreatedId(testRealmResource().groups().add(g2)); + testRealmResource().groups().group(gid1).remove(); + testRealmResource().groups().group(gid2).remove(); + testingClient.server().run(session -> { + Assert.assertEquals(2, UserMapStorage.groupRemovals.get()); + UserMapStorage.realmRemovals.set(0); + }); + + // remove role + RoleRepresentation role1 = new RoleRepresentation(); + role1.setName("role1"); + RoleRepresentation role2 = new RoleRepresentation(); + role2.setName("role2"); + testRealmResource().roles().create(role1); + testRealmResource().roles().create(role2); + testRealmResource().roles().get("role1").remove(); + testRealmResource().roles().get("role2").remove(); + testingClient.server().run(session -> { + Assert.assertEquals(2, UserMapStorage.roleRemovals.get()); + UserMapStorage.realmRemovals.set(0); + }); + + // remove realm + RealmRepresentation testRealmRepresentation = testRealmResource().toRepresentation(); + testRealmResource().remove(); + testingClient.server().run(session -> { + Assert.assertEquals(1, UserMapStorage.realmRemovals.get()); + UserMapStorage.realmRemovals.set(0); + }); + + } + + @Test + @Ignore + public void testEntityRemovalHooksCascade() { + testingClient.server().run(session -> { + UserMapStorage.realmRemovals.set(0); + UserMapStorage.groupRemovals.set(0); + UserMapStorage.roleRemovals.set(0); + }); + + GroupRepresentation g1 = new GroupRepresentation(); + g1.setName("group1"); + GroupRepresentation g2 = new GroupRepresentation(); + g2.setName("group2"); + String gid1 = ApiUtil.getCreatedId(testRealmResource().groups().add(g1)); + String gid2 = ApiUtil.getCreatedId(testRealmResource().groups().add(g2)); + + RoleRepresentation role1 = new RoleRepresentation(); + role1.setName("role1"); + RoleRepresentation role2 = new RoleRepresentation(); + role2.setName("role2"); + testRealmResource().roles().create(role1); + testRealmResource().roles().create(role2); + + // remove realm with groups and roles in it + testRealmResource().remove(); + testingClient.server().run(session -> { + Assert.assertEquals(1, UserMapStorage.realmRemovals.get()); + Assert.assertEquals(2, UserMapStorage.groupRemovals.get()); // check if group removal hooks were called + Assert.assertEquals(2, UserMapStorage.roleRemovals.get()); // check if role removal hooks were called + }); + + } + +} diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/read-only-user-password.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/read-only-user-password.properties new file mode 100644 index 0000000000..c0b76abe79 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/read-only-user-password.properties @@ -0,0 +1,4 @@ +tbrady=goat +rob=pw +jules=pw +danny=pw diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/user-password.properties b/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/user-password.properties new file mode 100644 index 0000000000..a6e28c1405 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/storage-test/user-password.properties @@ -0,0 +1,4 @@ +thor=hammer +zeus=pw +apollo=pw +perseus=pw \ No newline at end of file