From 09693eb10830af6eb2e383635138259f7223bf77 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Mon, 1 Aug 2016 11:18:58 -0400 Subject: [PATCH] component model --- .../idm/ComponentRepresentation.java | 101 ++++++ .../idm/ComponentTypeRepresentation.java | 57 ++++ .../idm/StorageProviderRepresentation.java | 51 ++- .../info/ServerInfoRepresentation.java | 9 + examples/providers/pom.xml | 1 + .../user/ExampleUserStorageProvider.java | 118 +++++++ .../ExampleUserStorageProviderFactory.java | 60 ++++ .../examples/storage/user/UserAdapter.java | 135 ++++++++ .../examples/storage/user/UserEntity.java | 82 +++++ .../main/resources/META-INF/persistence.xml | 18 ++ ...eycloak.storage.UserStorageProviderFactory | 1 + .../mappers/TxAwareLDAPUserModelDelegate.java | 2 +- .../UserAttributeLDAPFederationMapper.java | 2 +- .../InfinispanStoreFactoryProvider.java | 2 +- .../models/cache/infinispan/RealmAdapter.java | 91 +++--- .../cache/infinispan/RealmCacheSession.java | 4 +- .../cache/infinispan/UserCacheSession.java | 9 +- .../infinispan/entities/CachedRealm.java | 26 +- .../InfinispanUserSessionProvider.java | 2 +- .../DefaultJpaConnectionProviderFactory.java | 2 +- .../jpa/JndiEntityManagerLookup.java | 25 +- .../keycloak/models/jpa/JpaRealmProvider.java | 8 +- .../keycloak/models/jpa/JpaUserProvider.java | 4 +- .../org/keycloak/models/jpa/RealmAdapter.java | 305 ++++++++---------- .../jpa/entities/ComponentConfigEntity.java | 110 +++++++ ...oviderEntity.java => ComponentEntity.java} | 107 +++--- .../models/jpa/entities/RealmEntity.java | 11 - .../jpa/JpaUserFederatedStorageProvider.java | 8 +- .../META-INF/jpa-changelog-2.1.0.xml | 40 +-- .../main/resources/META-INF/persistence.xml | 3 +- ...DefaultMongoConnectionFactoryProvider.java | 3 +- .../keycloak/adapters/MongoUserProvider.java | 4 +- .../mongo/keycloak/adapters/RealmAdapter.java | 276 +++++++--------- .../ComponentFactory.java} | 39 +-- .../keycloak/component/ComponentModel.java | 101 ++++++ .../ComponentValidationException.java} | 33 +- .../ConfiguredComponent.java} | 12 +- .../org/keycloak/models/KeycloakSession.java | 58 +++- .../java/org/keycloak/models/RealmModel.java | 29 +- .../models/UserFederationManager.java | 12 +- .../org/keycloak/models/UserProvider.java | 16 +- .../models/entities/ComponentEntity.java | 75 +++++ .../keycloak/models/entities/RealmEntity.java | 10 +- .../models/utils/KeycloakModelUtils.java | 40 +-- .../models/utils/ModelToRepresentation.java | 29 +- .../models/utils/RepresentationToModel.java | 11 + .../java/org/keycloak/storage/StorageId.java | 34 +- .../storage/StorageProviderModel.java | 97 ------ .../keycloak/storage/UserStorageManager.java | 58 ++-- .../keycloak/storage/UserStorageProvider.java | 9 +- .../storage/UserStorageProviderFactory.java | 85 +++++ .../storage/UserStorageProviderModel.java | 62 ++++ ...erSpi.java => UserStorageProviderSpi.java} | 6 +- .../storage/adapter/AbstractUserAdapter.java | 6 +- .../AbstractUserAdapterFederatedStorage.java | 7 +- .../storage/changeset/UserDataQuery.java | 46 --- .../storage/changeset/UserDataStore.java | 32 -- .../UserFederatedStorageProvider.java | 6 +- .../UserFederatedStorageProviderSpi.java | 2 - .../user/UserRegistrationProvider.java | 1 - .../services/org.keycloak.provider.Spi | 2 +- .../partialimport/PartialImportManager.java | 8 +- .../services/DefaultKeycloakSession.java | 18 +- .../filters/KeycloakSessionServletFilter.java | 6 +- .../managers/DefaultBruteForceProtector.java | 6 +- .../resources/IdentityBrokerService.java | 10 +- .../resources/KeycloakApplication.java | 32 +- .../AuthenticationManagementResource.java | 10 +- .../admin/ClientInitialAccessResource.java | 4 +- .../resources/admin/ComponentResource.java | 152 +++++++++ .../resources/admin/RealmAdminResource.java | 2 +- .../resources/admin/UsersResource.java | 18 +- .../admin/info/ServerInfoAdminResource.java | 28 +- .../ClusterAwareScheduledTaskRunner.java | 4 +- .../scheduled/ScheduledTaskRunner.java | 6 +- .../keycloak/testsuite/KeycloakServer.java | 9 +- .../adapter/AdapterTestStrategy.java | 14 +- .../adapter/CookieTokenStoreAdapterTest.java | 4 +- .../AbstractAuthorizationTest.java | 4 +- .../federation/storage/UserMapStorage.java | 29 +- .../storage/UserMapStorageFactory.java | 16 +- .../storage/UserPropertyFileStorage.java | 18 +- .../UserPropertyFileStorageFactory.java | 20 +- ...nStorageTest.java => UserStorageTest.java} | 64 ++-- .../testsuite/model/AbstractModelTest.java | 8 +- .../keycloak/testsuite/model/AdapterTest.java | 2 +- .../keycloak/testsuite/model/CacheTest.java | 8 +- .../testsuite/model/TransactionsTest.java | 14 +- .../testsuite/model/UserModelTest.java | 4 +- .../testsuite/rule/AbstractKeycloakRule.java | 24 +- .../keycloak/testsuite/rule/KeycloakRule.java | 4 +- ...ycloak.storage.UserStorageProviderFactory} | 0 92 files changed, 2097 insertions(+), 1044 deletions(-) create mode 100755 core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java create mode 100644 core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java rename server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java => core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java (62%) create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java create mode 100644 examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java create mode 100644 examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml create mode 100644 examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory rename server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java => model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java (51%) create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java rename model/jpa/src/main/java/org/keycloak/models/jpa/entities/{StorageProviderEntity.java => ComponentEntity.java} (52%) rename server-spi/src/main/java/org/keycloak/{storage/StorageProviderFactory.java => component/ComponentFactory.java} (53%) mode change 100755 => 100644 create mode 100755 server-spi/src/main/java/org/keycloak/component/ComponentModel.java rename server-spi/src/main/java/org/keycloak/{storage/StorageProvider.java => component/ComponentValidationException.java} (54%) rename server-spi/src/main/java/org/keycloak/{storage/changeset/UserDataLookup.java => component/ConfiguredComponent.java} (66%) create mode 100755 server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java delete mode 100755 server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java rename server-spi/src/main/java/org/keycloak/storage/{StorageProviderSpi.java => UserStorageProviderSpi.java} (89%) delete mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java delete mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java create mode 100644 services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java rename testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/{UserFederationStorageTest.java => UserStorageTest.java} (80%) rename testsuite/integration/src/test/resources/META-INF/services/{org.keycloak.storage.StorageProviderFactory => org.keycloak.storage.UserStorageProviderFactory} (100%) diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java new file mode 100755 index 0000000000..bbc4a42b97 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentRepresentation.java @@ -0,0 +1,101 @@ +/* + * 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.representations.idm; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class ComponentRepresentation { + + private String id; + private String name; + private String providerId; + private String providerType; + private String parentId; + private MultivaluedHashMap config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ComponentRepresentation that = (ComponentRepresentation) o; + + if (!id.equals(that.id)) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java new file mode 100644 index 0000000000..8ba0da6e41 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/ComponentTypeRepresentation.java @@ -0,0 +1,57 @@ +/* + * 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.representations.idm; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class ComponentTypeRepresentation { + protected String id; + protected String helpText; + protected List properties; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getHelpText() { + return helpText; + } + + public void setHelpText(String helpText) { + this.helpText = helpText; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java b/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java similarity index 62% rename from server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java rename to core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java index 3845ba0d61..04bf174636 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java +++ b/core/src/main/java/org/keycloak/representations/idm/StorageProviderRepresentation.java @@ -15,20 +15,36 @@ * limitations under the License. */ -package org.keycloak.models.entities; +package org.keycloak.representations.idm; import java.util.Map; /** - * @author Bill Burke - * @version $Revision: 1 $ + * @author Marek Posolda */ -public class StorageProviderEntity extends AbstractIdentifiableEntity { - protected String providerName; - protected Map config; - protected int priority; - protected String displayName; +public class StorageProviderRepresentation { + private String id; + private String displayName; + private String providerName; + private Map config; + private int priority; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } public String getProviderName() { return providerName; @@ -38,6 +54,7 @@ public class StorageProviderEntity extends AbstractIdentifiableEntity { this.providerName = providerName; } + public Map getConfig() { return config; } @@ -54,12 +71,20 @@ public class StorageProviderEntity extends AbstractIdentifiableEntity { this.priority = priority; } - public String getDisplayName() { - return displayName; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + StorageProviderRepresentation that = (StorageProviderRepresentation) o; + + if (!id.equals(that.id)) return false; + + return true; } - public void setDisplayName(String displayName) { - this.displayName = displayName; + @Override + public int hashCode() { + return id.hashCode(); } - } diff --git a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java index c3aeeeb328..59d400e485 100755 --- a/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/info/ServerInfoRepresentation.java @@ -17,6 +17,7 @@ package org.keycloak.representations.info; +import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.ProtocolMapperTypeRepresentation; @@ -43,6 +44,7 @@ public class ServerInfoRepresentation { private Map> protocolMapperTypes; private Map> builtinProtocolMappers; private Map> clientInstallations; + private Map> componentTypes; private List passwordPolicies; @@ -144,4 +146,11 @@ public class ServerInfoRepresentation { this.passwordPolicies = passwordPolicies; } + public Map> getComponentTypes() { + return componentTypes; + } + + public void setComponentTypes(Map> componentTypes) { + this.componentTypes = componentTypes; + } } diff --git a/examples/providers/pom.xml b/examples/providers/pom.xml index 20b4eaa7e9..fcde8b08e3 100755 --- a/examples/providers/pom.xml +++ b/examples/providers/pom.xml @@ -37,5 +37,6 @@ authenticator rest domain-extension + user-storage-jpa 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 new file mode 100644 index 0000000000..07053339d2 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProvider.java @@ -0,0 +1,118 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.examples.storage.user; + +import org.keycloak.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.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.user.UserLookupProvider; +import org.keycloak.storage.user.UserRegistrationProvider; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ExampleUserStorageProvider implements UserStorageProvider, UserLookupProvider, UserRegistrationProvider { + protected EntityManager em; + protected ComponentModel model; + protected KeycloakSession session; + + public ExampleUserStorageProvider(EntityManager em, ComponentModel model, KeycloakSession session) { + this.em = em; + this.model = model; + this.session = session; + } + + @Override + public void preRemove(RealmModel realm) { + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + + } + + @Override + public void close() { + em.close(); + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + String persistenceId = StorageId.externalId(id); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) return null; + return new UserAdapter(session, realm, model, entity); + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + TypedQuery query = em.createNamedQuery("getUserByUsername", UserEntity.class); + query.setParameter("username", username); + List result = query.getResultList(); + if (result.isEmpty()) return null; + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + TypedQuery query = em.createNamedQuery("getUserByEmail", UserEntity.class); + query.setParameter("email", email); + List result = query.getResultList(); + if (result.isEmpty()) return null; + return new UserAdapter(session, realm, model, result.get(0)); + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + UserEntity entity = new UserEntity(); + entity.setId(KeycloakModelUtils.generateId()); + entity.setUsername(username); + em.persist(entity); + return new UserAdapter(session, realm, model, entity); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + String persistenceId = StorageId.externalId(user.getId()); + UserEntity entity = em.find(UserEntity.class, persistenceId); + if (entity == null) return false; + em.remove(entity); + return true; + } + + @Override + public void grantToAllUsers(RealmModel realm, RoleModel role) { + + } +} 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 new file mode 100644 index 0000000000..963a558227 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/ExampleUserStorageProviderFactory.java @@ -0,0 +1,60 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.examples.storage.user; + +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.connections.jpa.JndiEntityManagerLookup; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.UserStorageProviderFactory; + +import javax.persistence.EntityManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ExampleUserStorageProviderFactory implements UserStorageProviderFactory { + protected String jndiName = "java:jboss/ExampleUserEntityManagerFactory"; + + @Override + public ExampleUserStorageProvider create(KeycloakSession session, ComponentModel model) { + EntityManager em = JndiEntityManagerLookup.getSessionEntityManager(session, jndiName); + return new ExampleUserStorageProvider(em, model, session); + } + + @Override + public String getId() { + return "example-user-storage"; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void 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 new file mode 100644 index 0000000000..f851904d52 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserAdapter.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.examples.storage.user; + +import org.keycloak.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.storage.StorageId; +import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserAdapter extends AbstractUserAdapterFederatedStorage { + protected UserEntity entity; + protected String keycloakId; + + public UserAdapter(KeycloakSession session, RealmModel realm, ComponentModel model, UserEntity entity) { + super(session, realm, model); + this.entity = entity; + keycloakId = StorageId.keycloakId(model, entity.getId()); + } + + @Override + public String getUsername() { + return entity.getUsername(); + } + + @Override + public void setUsername(String username) { + entity.setUsername(username); + + } + + @Override + public void setEmail(String email) { + entity.setEmail(email); + } + + @Override + public String getEmail() { + return entity.getEmail(); + } + + @Override + public String getId() { + return keycloakId; + } + + @Override + public void updateCredential(UserCredentialModel cred) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + entity.setPassword(cred.getValue()); + } else { + super.updateCredential(cred); + } + } + + @Override + public void setSingleAttribute(String name, String value) { + if (name.equals("phone")) { + entity.setPhone(value); + } else { + super.setSingleAttribute(name, value); + } + } + + @Override + public void removeAttribute(String name) { + if (name.equals("phone")) { + entity.setPhone(null); + } else { + super.removeAttribute(name); + } + } + + @Override + public void setAttribute(String name, List values) { + if (name.equals("phone")) { + entity.setPhone(values.get(0)); + } else { + super.setAttribute(name, values); + } + } + + @Override + public String getFirstAttribute(String name) { + if (name.equals("phone")) { + return entity.getPhone(); + } else { + return super.getFirstAttribute(name); + } + } + + @Override + public Map> getAttributes() { + Map> attrs = super.getAttributes(); + MultivaluedHashMap all = new MultivaluedHashMap<>(); + all.putAll(attrs); + all.add("phone", entity.getPhone()); + return all; + } + + @Override + public List getAttribute(String name) { + if (name.equals("phone")) { + List phone = new LinkedList<>(); + phone.add(entity.getPhone()); + return phone; + } else { + return super.getAttribute(name); + } + } +} 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 new file mode 100644 index 0000000000..c9227d2368 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/java/org/keycloak/examples/storage/user/UserEntity.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.examples.storage.user; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@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"), +}) +@Entity +public class UserEntity { + @Id + private String id; + + + private String username; + private String email; + private String password; + private String phone; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getPhone() { + return phone; + } + + public void setPhone(String phone) { + this.phone = phone; + } +} 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 new file mode 100644 index 0000000000..5af362d198 --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/persistence.xml @@ -0,0 +1,18 @@ + + + + java:jboss/datasources/ExampleUserDS + + org.keycloak.examples.storage.user.UserEntity + + + + + + + + diff --git a/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory b/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory new file mode 100644 index 0000000000..1634d11e1b --- /dev/null +++ b/examples/providers/user-storage-jpa/src/main/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.examples.storage.user.ExampleUserStorageProviderFactory \ No newline at end of file diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java index 622ebb683b..9126a668b8 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/TxAwareLDAPUserModelDelegate.java @@ -70,7 +70,7 @@ public abstract class TxAwareLDAPUserModelDelegate extends UserModelDelegate { logger.trace("Starting and enlisting transaction for object " + ldapUser.getDn().toString()); } - this.provider.getSession().getTransaction().enlistAfterCompletion(transaction); + this.provider.getSession().getTransactionManager().enlistAfterCompletion(transaction); } } diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java index af910a7c2c..b577326a0f 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapper.java @@ -163,7 +163,7 @@ public class UserAttributeLDAPFederationMapper extends AbstractLDAPFederationMap UserModel that = session.userStorage().getUserByEmail(email, realm); if (that != null && !that.getId().equals(user.getId())) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); String exceptionMessage = String.format("Can't import user '%s' from LDAP because email '%s' already exists in Keycloak. Existing user with this email is '%s'", user.getUsername(), email, that.getUsername()); throw new ModelDuplicateException(exceptionMessage, UserModel.EMAIL); } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index cea4388fb3..56a385fe12 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -40,7 +40,7 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide InfinispanStoreFactoryProvider(KeycloakSession delegate) { this.session = delegate; this.transaction = new CacheTransaction(); - this.session.getTransaction().enlistAfterCompletion(transaction); + this.session.getTransactionManager().enlistAfterCompletion(transaction); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 69f0510dec..ffaf46911f 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -19,11 +19,10 @@ package org.keycloak.models.cache.infinispan; import org.keycloak.Config; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.models.*; -import org.keycloak.models.cache.CacheRealmProvider; import org.keycloak.models.cache.infinispan.entities.CachedRealm; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import java.security.Key; import java.security.PrivateKey; @@ -746,48 +745,6 @@ public class RealmAdapter implements RealmModel { } - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - return updated.addStorageProvider(provider); - } - - @Override - public void updateStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - updated.updateStorageProvider(provider); - - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - getDelegateForUpdate(); - updated.removeStorageProvider(provider); - - } - - @Override - public void setStorageProviders(List providers) { - getDelegateForUpdate(); - updated.setStorageProviders(providers); - - } - - @Override - public List getStorageProviders() { - if (isUpdated()) return updated.getStorageProviders(); - return cached.getStorageProviders(); - } - - @Override - public StorageProviderModel getStorageProvider(String id) { - if (isUpdated()) return updated.getStorageProvider(id); - for (StorageProviderModel model : cached.getStorageProviders()) { - if (model.getId().equals(id)) return model; - } - return null; - } - @Override public String getLoginTheme() { if (isUpdated()) return updated.getLoginTheme(); @@ -1451,6 +1408,52 @@ public class RealmAdapter implements RealmModel { return cacheSession.getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + getDelegateForUpdate(); + return updated.addComponentModel(model); + } + @Override + public void updateComponent(ComponentModel component) { + getDelegateForUpdate(); + updated.updateComponent(component); + } + + @Override + public void removeComponent(ComponentModel component) { + getDelegateForUpdate(); + updated.removeComponent(component); + + } + + @Override + public void removeComponents(String parentId) { + getDelegateForUpdate(); + updated.removeComponents(parentId); + + } + + @Override + public List getComponents(String parentId, String providerType) { + if (isUpdated()) return updated.getComponents(parentId, providerType); + List components = cached.getComponentsByParent().getList(parentId + providerType); + if (components == null) return Collections.EMPTY_LIST; + return Collections.unmodifiableList(components); + } + + @Override + public List getComponents() { + if (isUpdated()) return updated.getComponents(); + List results = new LinkedList<>(); + results.addAll(cached.getComponents().values()); + return Collections.unmodifiableList(results); + } + + @Override + public ComponentModel getComponent(String id) { + if (isUpdated()) return updated.getComponent(id); + return cached.getComponents().get(id); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java index 2a3880d67b..7a72964c63 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmCacheSession.java @@ -132,8 +132,8 @@ public class RealmCacheSession implements CacheRealmProvider { this.cache = cache; this.session = session; this.startupRevision = cache.getCurrentCounter(); - session.getTransaction().enlistPrepare(getPrepareTransaction()); - session.getTransaction().enlistAfterCompletion(getAfterTransaction()); + session.getTransactionManager().enlistPrepare(getPrepareTransaction()); + session.getTransactionManager().enlistAfterCompletion(getAfterTransaction()); } public long getStartupRevision() { 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 c5e4c302b1..3016ae8435 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 @@ -19,6 +19,7 @@ package org.keycloak.models.cache.infinispan; import org.jboss.logging.Logger; import org.keycloak.common.constants.ServiceAccountConstants; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -39,7 +40,6 @@ import org.keycloak.models.cache.infinispan.entities.CachedUser; import org.keycloak.models.cache.infinispan.entities.CachedUserConsent; import org.keycloak.models.cache.infinispan.entities.CachedUserConsents; import org.keycloak.models.cache.infinispan.entities.UserListQuery; -import org.keycloak.storage.StorageProviderModel; import java.util.*; @@ -65,7 +65,7 @@ public class UserCacheSession implements CacheUserProvider { this.cache = cache; this.session = session; this.startupRevision = cache.getCurrentCounter(); - session.getTransaction().enlistAfterCompletion(getTransaction()); + session.getTransactionManager().enlistAfterCompletion(getTransaction()); } @Override @@ -665,7 +665,8 @@ public class UserCacheSession implements CacheUserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel provider) { - getDelegate().preRemove(realm, provider); + public void preRemove(RealmModel realm, ComponentModel component) { + getDelegate().preRemove(realm, component); + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java index 6dcc0d9670..80997c3e77 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedRealm.java @@ -18,6 +18,7 @@ package org.keycloak.models.cache.infinispan.entities; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.models.AuthenticationExecutionModel; import org.keycloak.models.AuthenticationFlowModel; import org.keycloak.models.AuthenticatorConfigModel; @@ -29,20 +30,14 @@ import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; -import org.keycloak.models.RealmProvider; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RequiredCredentialModel; -import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.cache.infinispan.RealmCache; import org.keycloak.common.util.MultivaluedHashMap; -import org.keycloak.storage.StorageProviderModel; -import java.io.Serializable; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collections; @@ -111,7 +106,8 @@ public class CachedRealm extends AbstractRevisioned { protected List requiredCredentials; protected List userFederationProviders; - protected List storageProviders; + protected MultivaluedHashMap componentsByParent = new MultivaluedHashMap<>(); + protected Map components = new HashMap<>(); protected MultivaluedHashMap userFederationMappers = new MultivaluedHashMap(); protected Set userFederationMapperSet; protected List identityProviders; @@ -208,7 +204,6 @@ public class CachedRealm extends AbstractRevisioned { requiredCredentials = model.getRequiredCredentials(); userFederationProviders = model.getUserFederationProviders(); - storageProviders = model.getStorageProviders(); userFederationMapperSet = model.getUserFederationMappers(); for (UserFederationMapperModel mapper : userFederationMapperSet) { this.userFederationMappers.add(mapper.getFederationProviderId(), mapper); @@ -279,6 +274,13 @@ public class CachedRealm extends AbstractRevisioned { resetCredentialsFlow = model.getResetCredentialsFlow(); clientAuthenticationFlow = model.getClientAuthenticationFlow(); + for (ComponentModel component : model.getComponents()) { + componentsByParent.add(component.getParentId() + component.getProviderType(), component); + } + for (ComponentModel component : model.getComponents()) { + components.put(component.getId(), component); + } + } protected void cacheClientTemplates(RealmModel model) { @@ -602,7 +604,11 @@ public class CachedRealm extends AbstractRevisioned { return requiredActionProviderList; } - public List getStorageProviders() { - return storageProviders; + public MultivaluedHashMap getComponentsByParent() { + return componentsByParent; + } + + public Map getComponents() { + return components; } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java index 960ea250b0..614fa9fa9d 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java @@ -81,7 +81,7 @@ public class InfinispanUserSessionProvider implements UserSessionProvider { this.loginFailureCache = loginFailureCache; this.tx = new InfinispanKeycloakTransaction(); - session.getTransaction().enlistAfterCompletion(tx); + session.getTransactionManager().enlistAfterCompletion(tx); } protected Cache getCache(boolean offline) { diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java index 431a638f09..4272fb8869 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/DefaultJpaConnectionProviderFactory.java @@ -63,7 +63,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide EntityManager em = emf.createEntityManager(); em = PersistenceExceptionConverter.create(em); - session.getTransaction().enlist(new JpaKeycloakTransaction(em)); + session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); return new DefaultJpaConnectionProvider(em); } diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java similarity index 51% rename from server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java rename to model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java index 741f3b7546..5291793edf 100644 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/JndiEntityManagerLookup.java @@ -14,20 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage.changeset; +package org.keycloak.connections.jpa; import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; -import java.util.List; +import javax.naming.InitialContext; +import javax.naming.NamingException; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserDataCredentialValidator { - boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input); - boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input); +public class JndiEntityManagerLookup { + public static EntityManager getSessionEntityManager(KeycloakSession session, String entityManagerFactoryJndiName) { + EntityManagerFactory factory = null; + try { + factory = (EntityManagerFactory)new InitialContext().lookup(entityManagerFactoryJndiName); + } catch (NamingException e) { + throw new RuntimeException(e); + } + EntityManager em = factory.createEntityManager(); + session.getTransactionManager().enlist(new JpaKeycloakTransaction(em)); + return em; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java index 2cf74ad270..f5d266602d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java @@ -34,7 +34,6 @@ import org.keycloak.models.jpa.entities.GroupEntity; import org.keycloak.models.jpa.entities.RealmEntity; import org.keycloak.models.jpa.entities.RoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -128,9 +127,6 @@ public class JpaRealmProvider implements RealmProvider { em.refresh(realm); final RealmAdapter adapter = new RealmAdapter(session, em, realm); session.users().preRemove(adapter); - for (StorageProviderModel provider : adapter.getStorageProviders()) { - adapter.removeStorageProvider(provider); - } realm.getDefaultGroups().clear(); em.flush(); @@ -141,6 +137,10 @@ public class JpaRealmProvider implements RealmProvider { .setParameter("realm", realm).executeUpdate(); num = em.createNamedQuery("deleteGroupsByRealm") .setParameter("realm", realm).executeUpdate(); + num = em.createNamedQuery("deleteComponentConfigByRealm") + .setParameter("realm", realm).executeUpdate(); + num = em.createNamedQuery("deleteComponentByRealm") + .setParameter("realm", realm).executeUpdate(); TypedQuery query = em.createNamedQuery("getClientIdsByRealm", String.class); query.setParameter("realm", realm.getId()); 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 02403ee3aa..a928e58feb 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 @@ -17,6 +17,7 @@ package org.keycloak.models.jpa; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -42,7 +43,6 @@ import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -712,7 +712,7 @@ public class JpaUserProvider implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index d6837aa20e..093ebf342d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -18,7 +18,8 @@ package org.keycloak.models.jpa; import org.jboss.logging.Logger; -import org.keycloak.connections.jpa.util.JpaUtils; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.common.enums.SslRequired; import org.keycloak.jose.jwk.JWKBuilder; import org.keycloak.models.AuthenticationExecutionModel; @@ -43,8 +44,6 @@ import org.keycloak.models.UserFederationProviderCreationEventImpl; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.jpa.entities.*; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; @@ -1003,179 +1002,6 @@ public class RealmAdapter implements RealmModel, JpaModel { return null; } - @Override - public StorageProviderModel getStorageProvider(String id) { - StorageProviderEntity entity = em.find(StorageProviderEntity.class, id); - if (entity == null) return null; - return toModel(entity); - } - - @Override - public List getStorageProviders() { - List entities = realm.getStorageProviders(); - if (entities.isEmpty()) return Collections.EMPTY_LIST; - List copy = new LinkedList<>(); - for (StorageProviderEntity entity : entities) { - copy.add(entity); - - } - List result = new LinkedList<>(); - for (StorageProviderEntity entity : copy) { - result.add(toModel(entity)); - } - Collections.sort(result, StorageProviderModel.comparator); - - return Collections.unmodifiableList(result); - } - - protected StorageProviderModel toModel(StorageProviderEntity entity) { - StorageProviderModel model = new StorageProviderModel(); - model.setId(entity.getId()); - model.setProviderName(entity.getProviderName()); - model.getConfig().putAll(entity.getConfig()); - model.setPriority(entity.getPriority()); - model.setDisplayName(entity.getDisplayName()); - return model; - } - - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders()); - - String id = KeycloakModelUtils.generateId(); - StorageProviderEntity entity = new StorageProviderEntity(); - entity.setId(id); - entity.setRealm(realm); - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (model.getDisplayName() == null) { - displayName = id; - } - entity.setDisplayName(displayName); - em.persist(entity); - realm.getStorageProviders().add(entity); - em.flush(); - StorageProviderModel providerModel = toModel(entity); - - return providerModel; - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - - session.users().preRemove(this, provider); - - it.remove(); - em.remove(entity); - return; - } - } - } - @Override - public void updateStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getStorageProviders()); - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(model.getId())) { - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(model.getDisplayName()); - } - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setPriority(model.getPriority()); - break; - } - } - } - - @Override - public void setStorageProviders(List providers) { - for (StorageProviderModel currentProvider : providers) { - KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); - } - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - boolean found = false; - for (StorageProviderModel model : providers) { - if (entity.getId().equals(model.getId())) { - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(displayName); - } - found = true; - break; - } - - } - if (found) continue; - session.users().preRemove(this, toModel(entity)); - removeFederationMappersForProvider(entity.getId()); - - it.remove(); - em.remove(entity); - } - - List add = new LinkedList<>(); - for (StorageProviderModel model : providers) { - boolean found = false; - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(model.getId())) { - found = true; - break; - } - } - if (!found) add.add(model); - } - - for (StorageProviderModel model : add) { - StorageProviderEntity entity = new StorageProviderEntity(); - if (model.getId() != null) { - entity.setId(model.getId()); - } else { - String id = KeycloakModelUtils.generateId(); - entity.setId(id); - model.setId(id); - } - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - entity.setRealm(realm); - em.persist(entity); - realm.getStorageProviders().add(entity); - - } - } - - protected StorageProviderEntity getStorageProviderEntityById(String id) { - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(id)) { - return entity; - } - } - return null; - } - @Override public RoleModel getRole(String name) { return session.realms().getRealmRole(this, name); @@ -2280,4 +2106,131 @@ public class RealmAdapter implements RealmModel, JpaModel { return session.realms().getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + ComponentEntity c = new ComponentEntity(); + if (model.getId() == null) { + c.setId(KeycloakModelUtils.generateId()); + } else { + c.setId(model.getId()); + } + c.setName(model.getName()); + c.setParentId(model.getParentId()); + c.setProviderType(model.getProviderType()); + c.setProviderId(model.getProviderId()); + c.setRealm(realm); + em.persist(c); + setConfig(model, c); + model.setId(c.getId()); + return model; + } + + protected void setConfig(ComponentModel model, ComponentEntity c) { + for (String key : model.getConfig().keySet()) { + List vals = model.getConfig().get(key); + for (String val : vals) { + ComponentConfigEntity config = new ComponentConfigEntity(); + config.setId(KeycloakModelUtils.generateId()); + config.setName(key); + config.setValue(val); + config.setComponent(c); + em.persist(config); + } + } + } + + @Override + public void updateComponent(ComponentModel component) { + ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + if (c == null) return; + c.setName(component.getName()); + c.setProviderId(component.getProviderId()); + c.setProviderType(component.getProviderType()); + c.setParentId(component.getParentId()); + em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate(); + em.flush(); + setConfig(component, c); + + + } + + @Override + public void removeComponent(ComponentModel component) { + ComponentEntity c = em.find(ComponentEntity.class, component.getId()); + if (c == null) return; + session.users().preRemove(this, component); + em.createNamedQuery("deleteComponentConfigByComponent").setParameter("component", c).executeUpdate(); + em.remove(c); + } + + @Override + public void removeComponents(String parentId) { + TypedQuery query = em.createNamedQuery("getComponentIdsByParent", String.class) + .setParameter("realm", realm) + .setParameter("parentId", parentId); + List results = query.getResultList(); + if (results.isEmpty()) return; + for (String id : results) { + session.users().preRemove(this, getComponent(id)); + } + em.createNamedQuery("deleteComponentConfigByParent").setParameter("parentId", parentId).executeUpdate(); + em.createNamedQuery("deleteComponentByParent").setParameter("parentId", parentId).executeUpdate(); + + } + + @Override + public List getComponents(String parentId, String providerType) { + if (parentId == null) parentId = getId(); + TypedQuery query = em.createNamedQuery("getComponentsByParentAndType", ComponentEntity.class) + .setParameter("realm", realm) + .setParameter("parentId", parentId) + .setParameter("providerType", providerType); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (ComponentEntity c : results) { + ComponentModel model = entityToModel(c); + rtn.add(model); + + } + return rtn; + } + + protected ComponentModel entityToModel(ComponentEntity c) { + ComponentModel model = new ComponentModel(); + model.setId(c.getId()); + model.setName(c.getName()); + model.setProviderType(c.getProviderType()); + model.setProviderId(c.getProviderId()); + model.setParentId(c.getParentId()); + MultivaluedHashMap config = new MultivaluedHashMap<>(); + TypedQuery configQuery = em.createNamedQuery("getComponentConfig", ComponentConfigEntity.class) + .setParameter("component", c); + List configResults = configQuery.getResultList(); + for (ComponentConfigEntity configEntity : configResults) { + config.add(configEntity.getName(), configEntity.getValue()); + } + model.setConfig(config); + return model; + } + + @Override + public List getComponents() { + TypedQuery query = em.createNamedQuery("getComponents", ComponentEntity.class) + .setParameter("realm", realm); + List results = query.getResultList(); + List rtn = new LinkedList<>(); + for (ComponentEntity c : results) { + ComponentModel model = entityToModel(c); + rtn.add(model); + + } + return rtn; + } + + @Override + public ComponentModel getComponent(String id) { + ComponentEntity c = em.find(ComponentEntity.class, id); + if (c == null) return null; + return entityToModel(c); + } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java new file mode 100755 index 0000000000..bf141a8f94 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentConfigEntity.java @@ -0,0 +1,110 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.models.jpa.entities; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getComponentConfig", query="select attr from ComponentConfigEntity attr where attr.component = :component"), + @NamedQuery(name="deleteComponentConfigByComponent", query="delete from ComponentConfigEntity attr where attr.component = :component"), + @NamedQuery(name="deleteComponentConfigByRealm", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.realm=:realm)"), + @NamedQuery(name="deleteComponentConfigByParent", query="delete from ComponentConfigEntity attr where attr.component IN (select u from ComponentEntity u where u.parentId=:parentId)"), +}) +@Table(name="COMPONENT_CONFIG") +@Entity +public class ComponentConfigEntity { + + @Id + @Column(name="ID", length = 36) + @Access(AccessType.PROPERTY) // we do this because relationships often fetch id, but not entity. This avoids an extra SQL + protected String id; + + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "COMPONENT_ID") + protected ComponentEntity component; + + @Column(name = "NAME") + protected String name; + @Column(name = "VALUE") + protected String value; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public ComponentEntity getComponent() { + return component; + } + + public void setComponent(ComponentEntity component) { + this.component = component; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof ComponentConfigEntity)) return false; + + ComponentConfigEntity that = (ComponentConfigEntity) o; + + if (!id.equals(that.getId())) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java similarity index 52% rename from model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java rename to model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java index f663795007..69dad0da7d 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ComponentEntity.java @@ -19,6 +19,7 @@ package org.keycloak.models.jpa.entities; import javax.persistence.Access; import javax.persistence.AccessType; +import javax.persistence.CascadeType; import javax.persistence.CollectionTable; import javax.persistence.Column; import javax.persistence.ElementCollection; @@ -28,16 +29,28 @@ import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.MapKeyColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; import javax.persistence.Table; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map; /** - * @author Marek Posolda * @author Bill Burke */ +@NamedQueries({ + @NamedQuery(name="getComponents", query="select attr from ComponentEntity attr where attr.realm = :realm"), + @NamedQuery(name="getComponentsByParentAndType", query="select attr from ComponentEntity attr where attr.realm = :realm and attr.providerType = :providerType and attr.parentId = :parentId"), + @NamedQuery(name="getComponentIdsByParent", query="select attr.id from ComponentEntity attr where attr.realm = :realm and attr.parentId = :parentId"), + @NamedQuery(name="deleteComponentByRealm", query="delete from ComponentEntity c where c.realm = :realm"), + @NamedQuery(name="deleteComponentByParent", query="delete from ComponentEntity c where c.parentId = :parentId") +}) @Entity -@Table(name="STORAGE_PROVIDER") -public class StorageProviderEntity { +@Table(name="COMPONENT") +public class ComponentEntity { @Id @Column(name="ID", length = 36) @@ -48,19 +61,17 @@ public class StorageProviderEntity { @JoinColumn(name = "REALM_ID") protected RealmEntity realm; - @Column(name="PROVIDER_NAME") - private String providerName; - @Column(name="PRIORITY") - private int priority; + @Column(name="NAME") + protected String name; - @ElementCollection - @MapKeyColumn(name="name") - @Column(name="VALUE") - @CollectionTable(name="STORAGE_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="STORAGE_PROVIDER_ID") }) - private Map config; + @Column(name="PROVIDER_TYPE") + protected String providerType; - @Column(name="DISPLAY_NAME") - private String displayName; + @Column(name="PROVIDER_ID") + protected String providerId; + + @Column(name="PARENT_ID") + protected String parentId; public String getId() { return id; @@ -70,6 +81,38 @@ public class StorageProviderEntity { this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + public RealmEntity getRealm() { return realm; } @@ -78,45 +121,13 @@ public class StorageProviderEntity { this.realm = realm; } - public String getProviderName() { - return providerName; - } - - public void setProviderName(String providerName) { - this.providerName = providerName; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - @Override public boolean equals(Object o) { if (this == o) return true; if (o == null) return false; - if (!(o instanceof StorageProviderEntity)) return false; + if (!(o instanceof ComponentEntity)) return false; - StorageProviderEntity that = (StorageProviderEntity) o; + ComponentEntity that = (ComponentEntity) o; if (!id.equals(that.getId())) return false; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index 3c9ae5fd22..e45d819800 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -144,9 +144,6 @@ public class RealmEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") List userFederationProviders = new ArrayList<>(); - @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - List storageProviders = new ArrayList<>(); - @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") Collection userFederationMappers = new ArrayList(); @@ -554,14 +551,6 @@ public class RealmEntity { this.userFederationProviders = userFederationProviders; } - public List getStorageProviders() { - return storageProviders; - } - - public void setStorageProviders(List storageProviders) { - this.storageProviders = storageProviders; - } - public Collection getUserFederationMappers() { return userFederationMappers; } diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java index d64a3ead7e..f6710dd3d5 100644 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -17,6 +17,7 @@ package org.keycloak.storage.jpa; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; @@ -31,11 +32,10 @@ import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; -import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.utils.FederatedCredentials; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.federated.UserAttributeFederatedStorage; import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage; import org.keycloak.storage.federated.UserConsentFederatedStorage; @@ -719,7 +719,9 @@ public class JpaUserFederatedStorageProvider implements } @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { + public void preRemove(RealmModel realm, ComponentModel model) { + if (!model.getProviderType().equals(UserStorageProvider.class.getName())) return; + em.createNamedQuery("deleteBrokerLinkByStorageProvider") .setParameter("storageProviderId", model.getId()) .executeUpdate(); diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml index 988662d1e2..9b01aceeef 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml @@ -149,22 +149,26 @@ - - - - - - - - - - + - - - + + + + + + + + + + + + + + + + @@ -185,10 +189,10 @@ - - - + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml index d912033b8a..0b2ff231ed 100755 --- a/model/jpa/src/main/resources/META-INF/persistence.xml +++ b/model/jpa/src/main/resources/META-INF/persistence.xml @@ -25,7 +25,8 @@ org.keycloak.models.jpa.entities.RealmEntity org.keycloak.models.jpa.entities.RealmAttributeEntity org.keycloak.models.jpa.entities.RequiredCredentialEntity - org.keycloak.models.jpa.entities.StorageProviderEntity + org.keycloak.models.jpa.entities.ComponentConfigEntity + org.keycloak.models.jpa.entities.ComponentEntity org.keycloak.models.jpa.entities.UserFederationProviderEntity org.keycloak.models.jpa.entities.UserFederationMapperEntity org.keycloak.models.jpa.entities.RoleEntity diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index feabf8e742..440886f18d 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -22,7 +22,6 @@ import java.net.UnknownHostException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; import javax.net.ssl.SSLSocketFactory; @@ -142,7 +141,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro lazyInit(session); TransactionMongoStoreInvocationContext invocationContext = new TransactionMongoStoreInvocationContext(mongoStore); - session.getTransaction().enlist(new MongoKeycloakTransaction(invocationContext)); + session.getTransactionManager().enlist(new MongoKeycloakTransaction(invocationContext)); return new DefaultMongoConnectionProvider(db, mongoStore, invocationContext); } 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 ad811d4e6a..96d1f91ea4 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 @@ -21,6 +21,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.models.ClientModel; @@ -44,7 +45,6 @@ import org.keycloak.models.entities.UserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity; import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity; import org.keycloak.models.utils.CredentialValidation; -import org.keycloak.storage.StorageProviderModel; import java.util.ArrayList; import java.util.Collections; @@ -632,7 +632,7 @@ public class MongoUserProvider implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index cb6bba7c67..8b945effd7 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -20,6 +20,8 @@ package org.keycloak.models.mongo.keycloak.adapters; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; import org.keycloak.common.enums.SslRequired; import org.keycloak.jose.jwk.JWKBuilder; @@ -47,20 +49,18 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.entities.AuthenticationExecutionEntity; import org.keycloak.models.entities.AuthenticationFlowEntity; import org.keycloak.models.entities.AuthenticatorConfigEntity; +import org.keycloak.models.entities.ComponentEntity; import org.keycloak.models.entities.IdentityProviderEntity; import org.keycloak.models.entities.IdentityProviderMapperEntity; import org.keycloak.models.entities.RequiredActionProviderEntity; import org.keycloak.models.entities.RequiredCredentialEntity; -import org.keycloak.models.entities.StorageProviderEntity; import org.keycloak.models.entities.UserFederationMapperEntity; import org.keycloak.models.entities.UserFederationProviderEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientTemplateEntity; -import org.keycloak.models.mongo.keycloak.entities.MongoGroupEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRoleEntity; import org.keycloak.models.utils.KeycloakModelUtils; -import org.keycloak.storage.StorageProviderModel; import java.security.Key; import java.security.PrivateKey; @@ -1170,166 +1170,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme updateRealm(); } - @Override - public StorageProviderModel addStorageProvider(StorageProviderModel model) { - KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), null, getStorageProviders()); - - StorageProviderEntity entity = new StorageProviderEntity(); - entity.setId(KeycloakModelUtils.generateId()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - realm.getStorageProviders().add(entity); - updateRealm(); - - StorageProviderModel providerModel = new StorageProviderModel(entity.getId(), model.getProviderName(), - model.getConfig(), model.getPriority(), displayName); - - - return providerModel; - } - - @Override - public void updateStorageProvider(StorageProviderModel provider) { - KeycloakModelUtils.ensureUniqueDisplayName(provider.getDisplayName(), provider, getStorageProviders()); - - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - entity.setProviderName(provider.getProviderName()); - entity.setConfig(provider.getConfig()); - entity.setPriority(provider.getPriority()); - String displayName = provider.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(provider.getDisplayName()); - } - } - } - updateRealm(); - - } - - @Override - public void removeStorageProvider(StorageProviderModel provider) { - Iterator it = realm.getStorageProviders().iterator(); - while (it.hasNext()) { - StorageProviderEntity entity = it.next(); - if (entity.getId().equals(provider.getId())) { - session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName() - )); - - it.remove(); - } - } - updateRealm(); - - } - - @Override - public void setStorageProviders(List providers) { - for (StorageProviderModel currentProvider : providers) { - KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); - } - - List existingProviders = realm.getStorageProviders(); - List toRemove = new LinkedList<>(); - for (StorageProviderEntity entity : existingProviders) { - boolean found = false; - for (StorageProviderModel model : providers) { - if (entity.getId().equals(model.getId())) { - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - entity.setProviderName(model.getProviderName()); - String displayName = model.getDisplayName(); - if (displayName != null) { - entity.setDisplayName(displayName); - } - found = true; - break; - } - - } - if (found) continue; - session.users().preRemove(this, new StorageProviderModel(entity.getId(), entity.getProviderName(), - entity.getConfig(), entity.getPriority(), entity.getDisplayName())); - toRemove.add(entity); - } - - for (StorageProviderEntity entity : toRemove) { - realm.getStorageProviders().remove(entity); - } - - List add = new LinkedList<>(); - for (StorageProviderModel model : providers) { - boolean found = false; - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(model.getId())) { - found = true; - break; - } - } - if (!found) add.add(model); - } - - for (StorageProviderModel model : add) { - StorageProviderEntity entity = new StorageProviderEntity(); - if (model.getId() != null) { - entity.setId(model.getId()); - } else { - String id = KeycloakModelUtils.generateId(); - entity.setId(id); - model.setId(id); - } - entity.setProviderName(model.getProviderName()); - entity.setConfig(model.getConfig()); - entity.setPriority(model.getPriority()); - String displayName = model.getDisplayName(); - if (displayName == null) { - displayName = entity.getId(); - } - entity.setDisplayName(displayName); - realm.getStorageProviders().add(entity); - - } - - updateRealm(); - - } - - @Override - public List getStorageProviders() { - List entities = realm.getStorageProviders(); - if (entities.isEmpty()) return Collections.EMPTY_LIST; - List copy = new LinkedList<>(); - for (StorageProviderEntity entity : entities) { - copy.add(entity); - - } - List result = new LinkedList<>(); - for (StorageProviderEntity entity : copy) { - result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName() - )); - } - - Collections.sort(result, StorageProviderModel.comparator); - return Collections.unmodifiableList(result); - } - - @Override - public StorageProviderModel getStorageProvider(String id) { - for (StorageProviderEntity entity : realm.getStorageProviders()) { - if (entity.getId().equals(id)) return new StorageProviderModel(entity.getId(), entity.getProviderName(), - entity.getConfig(), entity.getPriority(), entity.getDisplayName()); - } - return null; - } - @Override public boolean isEventsEnabled() { return realm.isEventsEnabled(); @@ -2217,5 +2057,115 @@ public class RealmAdapter extends AbstractMongoAdapter impleme return model.getClientTemplateById(id, this); } + @Override + public ComponentModel addComponentModel(ComponentModel model) { + ComponentEntity entity = new ComponentEntity(); + if (model.getId() == null) { + entity.setId(KeycloakModelUtils.generateId()); + } else { + entity.setId(model.getId()); + } + entity.setConfig(model.getConfig()); + entity.setId(model.getId()); + entity.setParentId(model.getParentId()); + entity.setProviderType(model.getProviderType()); + entity.setProviderId(model.getProviderId()); + entity.setName(model.getName()); + model.setId(entity.getId()); + realm.getComponentEntities().add(entity); + updateRealm(); + return model; + } + + @Override + public void updateComponent(ComponentModel model) { + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getId().equals(model.getId())) { + entity.setConfig(model.getConfig()); + entity.setId(model.getId()); + entity.setParentId(model.getParentId()); + entity.setProviderType(model.getProviderType()); + entity.setProviderId(model.getProviderId()); + entity.setName(model.getName()); + + } + } + updateRealm(); + + } + + @Override + public void removeComponent(ComponentModel component) { + Iterator it = realm.getComponentEntities().iterator(); + while(it.hasNext()) { + if (it.next().getId().equals(component.getId())) { + session.users().preRemove(this, component); + it.remove(); + break; + } + } + updateRealm(); + + } + + @Override + public void removeComponents(String parentId) { + Iterator it = realm.getComponentEntities().iterator(); + while(it.hasNext()) { + ComponentEntity next = it.next(); + if (next.getParentId().equals(parentId)) { + session.users().preRemove(this, entityToModel(next)); + it.remove(); + } + } + updateRealm(); + + } + + @Override + public List getComponents(String parentId, String providerType) { + List results = new LinkedList<>(); + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getParentId().equals(parentId) && entity.getProviderType().equals(providerType)) { + ComponentModel model = entityToModel(entity); + results.add(model); + } + + } + return results; + } + + protected ComponentModel entityToModel(ComponentEntity entity) { + ComponentModel model = new ComponentModel(); + model.setId(entity.getId()); + model.setName(entity.getName()); + model.setParentId(entity.getParentId()); + model.setProviderId(entity.getProviderId()); + model.setProviderType(entity.getProviderType()); + MultivaluedHashMap map = new MultivaluedHashMap<>(); + map.putAll(entity.getConfig()); + model.setConfig(map); + return model; + } + + @Override + public List getComponents() { + List results = new LinkedList<>(); + for (ComponentEntity entity : realm.getComponentEntities()) { + ComponentModel model = entityToModel(entity); + results.add(model); + } + return results; + } + + @Override + public ComponentModel getComponent(String id) { + for (ComponentEntity entity : realm.getComponentEntities()) { + if (entity.getId() == entity.getId()) { + return entityToModel(entity); + } + } + return null; + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java old mode 100755 new mode 100644 similarity index 53% rename from server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java rename to server-spi/src/main/java/org/keycloak/component/ComponentFactory.java index e4f27171b0..b107d5a59a --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentFactory.java @@ -14,41 +14,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package org.keycloak.storage; +package org.keycloak.component; import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderFactory; +import org.keycloak.storage.UserStorageProviderModel; -import java.util.Set; +import java.util.Collections; +import java.util.List; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface StorageProviderFactory extends ProviderFactory { - /** - * called per Keycloak transaction. - * - * @param session - * @param model - * @return - */ - T getInstance(KeycloakSession session, StorageProviderModel model); +public interface ComponentFactory extends ProviderFactory, ConfiguredProvider { + CreatedType create(KeycloakSession session, ComponentModel model); - /** - * This is the name of the provider and will be showed in the admin console as an option. - * - * @return - */ @Override - String getId(); + default ProviderType create(KeycloakSession session) { + return null; + } + + void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException; - /** - * This method is never called and is only an artifact of ProviderFactory. Returning null with no implementation is recommended. - * @param session - * @return - */ - @Override - StorageProvider create(KeycloakSession session); } diff --git a/server-spi/src/main/java/org/keycloak/component/ComponentModel.java b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java new file mode 100755 index 0000000000..338bc4b8af --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/component/ComponentModel.java @@ -0,0 +1,101 @@ +/* + * 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.component; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Stored configuration of a User Storage provider instance. + * + * @author Marek Posolda + * @author Bill Burke + */ +public class ComponentModel implements Serializable { + + private String id; + private String name; + private String providerId; + private String providerType; + private String parentId; + private MultivaluedHashMap config = new MultivaluedHashMap<>(); + + public ComponentModel() {} + + public ComponentModel(ComponentModel copy) { + this.id = copy.id; + this.name = copy.name; + this.providerId = copy.providerId; + this.providerType = copy.providerType; + this.config = copy.config; + } + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public MultivaluedHashMap getConfig() { + return config; + } + + public void setConfig(MultivaluedHashMap config) { + this.config = config; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java similarity index 54% rename from server-spi/src/main/java/org/keycloak/storage/StorageProvider.java rename to server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java index 56de3ab82c..d1d707c778 100644 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/component/ComponentValidationException.java @@ -14,26 +14,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage; - -import org.keycloak.models.ClientModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserModel; -import org.keycloak.provider.Provider; +package org.keycloak.component; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface StorageProvider extends Provider { +public class ComponentValidationException extends RuntimeException { + public ComponentValidationException() { + } - void preRemove(RealmModel realm); - void preRemove(RealmModel realm, GroupModel group); - void preRemove(RealmModel realm, RoleModel role); - void preRemove(RealmModel realm, StorageProviderModel model); + public ComponentValidationException(String message) { + super(message); + } + public ComponentValidationException(String message, Throwable cause) { + super(message, cause); + } + + public ComponentValidationException(Throwable cause) { + super(cause); + } + + public ComponentValidationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } } - diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java b/server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java similarity index 66% rename from server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java rename to server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java index 5e80d5d51c..835cd3986a 100644 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java +++ b/server-spi/src/main/java/org/keycloak/component/ConfiguredComponent.java @@ -14,18 +14,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.keycloak.storage.changeset; +package org.keycloak.component; -import org.keycloak.models.RealmModel; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.storage.StorageId; +import org.keycloak.models.KeycloakSession; +import org.keycloak.provider.ConfiguredProvider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserDataLookup { - UserData getUserById(RealmModel realm, StorageId id); - UserData getUserByUsername(RealmModel realm, String username); - UserData getUserByEmail(RealmModel realm, String email); +public interface ConfiguredComponent extends ConfiguredProvider { } diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 1d71b1f63b..8b6dcd31d0 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -32,16 +32,48 @@ public interface KeycloakSession { KeycloakContext getContext(); - KeycloakTransactionManager getTransaction(); + KeycloakTransactionManager getTransactionManager(); + /** + * Get dedicated provider instance of provider type clazz that was created for this session. If one hasn't been created yet, + * find the factory and allocate by calling ProviderFactory.create(KeycloakSession). The provider to use is determined + * by the "provider" config entry in keycloak-server boot configuration. (keycloak-server.json) + * + * + * + * @param clazz + * @param + * @return + */ T getProvider(Class clazz); + /** + * Get dedicated provider instance for a specific provider factory of id of provider type clazz that was created for this session. + * If one hasn't been created yet, + * find the factory and allocate by calling ProviderFactory.create(KeycloakSession). + + * @param clazz + * @param id + * @param + * @return + */ T getProvider(Class clazz, String id); + /** + * Get all provider factories that manage provider instances of class. + * + * @param clazz + * @param + * @return + */ Set listProviderIds(Class clazz); Set getAllProviders(Class clazz); + Object getAttribute(String attribute); + Object removeAttribute(String attribute); + void setAttribute(String name, Object value); + void enlistForClose(Provider provider); KeycloakSessionFactory getKeycloakSessionFactory(); @@ -69,22 +101,40 @@ public interface KeycloakSession { void close(); /** - * Possibly both cached and federated view of users depending on configuration. + * A cached view of all users in system. * * @return */ UserFederationManager users(); + + /** + * Un-cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. + * + * @return + */ UserProvider userStorageManager(); /** - * Keycloak user storage. Non-federated, but possibly cache (if it is on) view of users. + * A cached view of all users in system that does NOT include users available from the deprecated UserFederationProvider SPI. */ UserProvider userStorage(); - UserFederatedStorageProvider userFederatedStorage(); + /** + * Keycloak specific local storage for users. No cache in front, this api talks directly to database. + * + * @return + */ UserProvider userLocalStorage(); + /** + * Hybrid storage for UserStorageProviders that can't store a specific piece of keycloak data in their external storage. + * + * @return + */ + UserFederatedStorageProvider userFederatedStorage(); + + /** * Keycloak scripting support. */ diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index af4d6d746d..47692ac652 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -18,13 +18,17 @@ package org.keycloak.models; import org.keycloak.common.enums.SslRequired; +import org.keycloak.component.ComponentModel; import org.keycloak.provider.ProviderEvent; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; import java.security.Key; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -282,12 +286,23 @@ public interface RealmModel extends RoleContainerModel { public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name); - StorageProviderModel addStorageProvider(StorageProviderModel model); - void updateStorageProvider(StorageProviderModel provider); - void removeStorageProvider(StorageProviderModel provider); - void setStorageProviders(List providers); - List getStorageProviders(); - StorageProviderModel getStorageProvider(String id); + ComponentModel addComponentModel(ComponentModel model); + void updateComponent(ComponentModel component); + void removeComponent(ComponentModel component); + void removeComponents(String parentId); + List getComponents(String parentId, String providerType); + List getComponents(); + ComponentModel getComponent(String id); + + default + List getUserStorageProviders() { + List list = new LinkedList<>(); + for (ComponentModel component : getComponents(getId(), UserStorageProvider.class.getName())) { + list.add(new UserStorageProviderModel(component)); + } + Collections.sort(list, UserStorageProviderModel.comparator); + return list; + } // Should return list sorted by UserFederationProviderModel.priority List getUserFederationProviders(); 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 19db2e178f..c48ae4d75f 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java @@ -18,11 +18,11 @@ package org.keycloak.models; import org.jboss.logging.Logger; +import org.keycloak.component.ComponentModel; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.policy.PasswordPolicyManagerProvider; import org.keycloak.policy.PolicyError; import org.keycloak.services.managers.UserManager; -import org.keycloak.storage.StorageProviderModel; import java.util.ArrayList; import java.util.Arrays; @@ -487,11 +487,6 @@ public class UserFederationManager implements UserProvider { session.userStorage().preRemove(protocolMapper); } - @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { - - } - public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (realm.getPasswordPolicy() != null) { @@ -604,6 +599,11 @@ public class UserFederationManager implements UserProvider { return (result != null) ? result : CredentialValidationOutput.failed(); } + @Override + public void preRemove(RealmModel realm, ComponentModel component) { + + } + @Override public void close() { } diff --git a/server-spi/src/main/java/org/keycloak/models/UserProvider.java b/server-spi/src/main/java/org/keycloak/models/UserProvider.java index 24b61066ee..d6ef2cd360 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -17,8 +17,8 @@ package org.keycloak.models; +import org.keycloak.component.ComponentModel; import org.keycloak.provider.Provider; -import org.keycloak.storage.StorageProviderModel; import org.keycloak.storage.user.UserCredentialValidatorProvider; import org.keycloak.storage.user.UserLookupProvider; import org.keycloak.storage.user.UserQueryProvider; @@ -56,10 +56,20 @@ public interface UserProvider extends Provider, List getUsers(RealmModel realm, boolean includeServiceAccounts); List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); + /** + * only used for local storage + * + * @param realm + * @param id + * @param username + * @param addDefaultRoles + * @param addDefaultRequiredActions + * @return + */ + UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); void preRemove(RealmModel realm); void preRemove(RealmModel realm, UserFederationProviderModel link); - void preRemove(RealmModel realm, StorageProviderModel link); void preRemove(RealmModel realm, RoleModel role); void preRemove(RealmModel realm, GroupModel group); @@ -73,4 +83,6 @@ public interface UserProvider extends Provider, void close(); + + void preRemove(RealmModel realm, ComponentModel component); } diff --git a/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java new file mode 100755 index 0000000000..f873761596 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/entities/ComponentEntity.java @@ -0,0 +1,75 @@ +/* + * 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.models.entities; + +import org.keycloak.common.util.MultivaluedHashMap; + +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ComponentEntity extends AbstractIdentifiableEntity { + protected String name; + protected String providerType; + protected String providerId; + protected String parentId; + protected Map> config = new MultivaluedHashMap<>(); + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getProviderType() { + return providerType; + } + + public void setProviderType(String providerType) { + this.providerType = providerType; + } + + public String getProviderId() { + return providerId; + } + + public void setProviderId(String providerId) { + this.providerId = providerId; + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public Map> getConfig() { + return config; + } + + public void setConfig(Map> config) { + this.config = config; + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java index 6780498abb..c5a6ecf8fe 100755 --- a/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/server-spi/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -85,7 +85,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List defaultGroups = new LinkedList(); private List requiredCredentials = new LinkedList<>(); - private List storageProviders = new LinkedList<>(); + private List componentEntities = new LinkedList<>(); private List userFederationProviders = new LinkedList(); private List userFederationMappers = new LinkedList(); private List identityProviders = new LinkedList(); @@ -685,12 +685,12 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.defaultGroups = defaultGroups; } - public List getStorageProviders() { - return storageProviders; + public List getComponentEntities() { + return componentEntities; } - public void setStorageProviders(List storageProviders) { - this.storageProviders = storageProviders; + public void setComponentEntities(List componentEntities) { + this.componentEntities = componentEntities; } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 55a7c02d16..bc20d49042 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -44,7 +44,6 @@ import org.keycloak.models.UserModel; import org.keycloak.representations.idm.CertificateRepresentation; import org.keycloak.common.util.CertificateUtils; import org.keycloak.common.util.PemUtils; -import org.keycloak.storage.StorageProviderModel; import javax.crypto.spec.SecretKeySpec; import java.io.IOException; @@ -282,7 +281,7 @@ public final class KeycloakModelUtils { */ public static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) { KeycloakSession session = factory.create(); - KeycloakTransaction tx = session.getTransaction(); + KeycloakTransaction tx = session.getTransactionManager(); try { tx.begin(); task.run(session); @@ -342,43 +341,6 @@ public final class KeycloakModelUtils { } // USER FEDERATION RELATED STUFF - /** - * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list. - * - * @param displayName to check for duplications - * @param myProvider provider, which is excluded from the list (if present) - * @param federationProviders - * @throws ModelDuplicateException if there is other provider with same displayName - */ - public static void ensureUniqueDisplayName(String displayName, StorageProviderModel myProvider, List federationProviders) throws ModelDuplicateException { - if (displayName != null) { - - for (StorageProviderModel federationProvider : federationProviders) { - if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) { - continue; - } - - if (displayName.equals(federationProvider.getDisplayName())) { - throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName); - } - } - } - } - - - public static StorageProviderModel findStorageProviderByDisplayName(String displayName, RealmModel realm) { - if (displayName == null) { - return null; - } - - for (StorageProviderModel provider : realm.getStorageProviders()) { - if (displayName.equals(provider.getDisplayName())) { - return provider; - } - } - return null; - } - /** * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list. * diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 203736f893..f0c75b7fa9 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -17,6 +17,7 @@ package org.keycloak.models.utils; +import org.keycloak.component.ComponentModel; import org.keycloak.events.Event; import org.keycloak.events.admin.AdminEvent; import org.keycloak.events.admin.AuthDetails; @@ -44,6 +45,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.representations.idm.AdminEventRepresentation; import org.keycloak.representations.idm.AuthDetailsRepresentation; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; @@ -51,6 +53,8 @@ import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.representations.idm.ConfigPropertyRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.EventRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; @@ -565,7 +569,7 @@ public class ModelToRepresentation { return rep; } - public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) { + public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) { UserFederationMapperRepresentation rep = new UserFederationMapperRepresentation(); rep.setId(model.getId()); rep.setName(model.getName()); @@ -736,4 +740,27 @@ public class ModelToRepresentation { return rep; } + public static List toRepresentation(List configProperties) { + List propertiesRep = new LinkedList<>(); + for (ProviderConfigProperty prop : configProperties) { + ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation(); + propRep.setName(prop.getName()); + propRep.setLabel(prop.getLabel()); + propRep.setType(prop.getType()); + propRep.setDefaultValue(prop.getDefaultValue()); + propRep.setHelpText(prop.getHelpText()); + propertiesRep.add(propRep); + } + return propertiesRep; + } + + public static ComponentRepresentation toRepresentation(ComponentModel component) { + ComponentRepresentation rep = new ComponentRepresentation(); + rep.setId(component.getId()); + rep.setName(component.getName()); + rep.setProviderId(component.getProviderId()); + rep.setProviderType(component.getProviderType()); + rep.setConfig(component.getConfig()); + return rep; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 2be6720e54..a20adcf4f2 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -20,6 +20,7 @@ package org.keycloak.models.utils; import org.keycloak.authorization.AuthorizationProvider; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; +import org.keycloak.component.ComponentModel; import org.keycloak.hash.Pbkdf2PasswordHashProvider; import org.keycloak.migration.migrators.MigrationUtils; import org.keycloak.models.ClientTemplateModel; @@ -62,6 +63,7 @@ import org.keycloak.representations.idm.AuthenticatorConfigRepresentation; import org.keycloak.representations.idm.ClaimRepresentation; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.GroupRepresentation; @@ -1618,4 +1620,13 @@ public class RepresentationToModel { } + public static ComponentModel toModel(ComponentRepresentation rep) { + ComponentModel model = new ComponentModel(); + model.setParentId(rep.getParentId()); + model.setProviderType(rep.getProviderType()); + model.setProviderId(rep.getProviderId()); + model.setConfig(rep.getConfig()); + model.setName(rep.getName()); + return model; + } } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageId.java b/server-spi/src/main/java/org/keycloak/storage/StorageId.java index 56207403e3..ab44e47fed 100644 --- a/server-spi/src/main/java/org/keycloak/storage/StorageId.java +++ b/server-spi/src/main/java/org/keycloak/storage/StorageId.java @@ -16,6 +16,7 @@ */ package org.keycloak.storage; +import org.keycloak.component.ComponentModel; import org.keycloak.models.UserModel; import java.io.Serializable; @@ -27,27 +28,44 @@ import java.io.Serializable; public class StorageId implements Serializable { private String id; private String providerId; - private String storageId; + private String externalId; public StorageId(String id) { this.id = id; if (!id.startsWith("f:")) { - storageId = id; + externalId = id; return; } int providerIndex = id.indexOf(':', 2); providerId = id.substring(2, providerIndex); - storageId = id.substring(providerIndex + 1); + externalId = id.substring(providerIndex + 1); } - public StorageId(String providerId, String storageId) { - this.id = "f:" + providerId + ":" + storageId; + public StorageId(String providerId, String externalId) { + this.id = "f:" + providerId + ":" + externalId; this.providerId = providerId; - this.storageId = storageId; + this.externalId = externalId; } + /** + * generate the id string that should be returned by UserModel.getId() + * + * @param model + * @param externalId id used to resolve user in external storage + * @return + */ + public static String keycloakId(ComponentModel model, String externalId) { + return new StorageId(model.getId(), externalId).getId(); + } + + public static String externalId(String keycloakId) { + return new StorageId(keycloakId).getExternalId(); + } + + + public static String resolveProviderId(UserModel user) { return new StorageId(user.getId()).getProviderId(); } @@ -63,8 +81,8 @@ public class StorageId implements Serializable { return providerId; } - public String getStorageId() { - return storageId; + public String getExternalId() { + return externalId; } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java deleted file mode 100755 index 823a500abd..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.keycloak.storage; - -import java.io.Serializable; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; - -/** - * Stored configuration of a User Storage provider instance. - * - * @author Marek Posolda - * @author Bill Burke - */ -public class StorageProviderModel implements Serializable { - - public static Comparator comparator = new Comparator() { - @Override - public int compare(StorageProviderModel o1, StorageProviderModel o2) { - return o1.priority - o2.priority; - } - }; - - private String id; - private String providerName; - private Map config = new HashMap(); - private int priority; - private String displayName; - - public StorageProviderModel() {} - - public StorageProviderModel(String id, String providerName, Map config, int priority, String displayName) { - this.id = id; - this.providerName = providerName; - if (config != null) { - this.config.putAll(config); - } - this.priority = priority; - this.displayName = displayName; - } - - public String getId() { - return id; - } - - public void setId(String id) { - this.id = id; - } - - public String getProviderName() { - return providerName; - } - - public void setProviderName(String providerName) { - this.providerName = providerName; - } - - public Map getConfig() { - return config; - } - - public void setConfig(Map config) { - this.config = config; - } - - public int getPriority() { - return priority; - } - - public void setPriority(int priority) { - this.priority = priority; - } - - public String getDisplayName() { - return displayName; - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } -} 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 b6e6028cd9..72906b01f1 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -19,6 +19,7 @@ package org.keycloak.storage; import org.jboss.logging.Logger; import org.keycloak.common.util.reflections.Types; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; @@ -62,10 +63,6 @@ public class UserStorageManager implements UserProvider { protected KeycloakSession session; - // Set of already validated/proxied federation users during this session. Key is user ID - private Map managedUsers = new HashMap<>(); - private UserProvider localStorage = null; - public UserStorageManager(KeycloakSession session) { this.session = session; } @@ -74,28 +71,37 @@ public class UserStorageManager implements UserProvider { return session.userLocalStorage(); } - protected List getStorageProviders(RealmModel realm) { - return realm.getStorageProviders(); + protected List getStorageProviders(RealmModel realm) { + return realm.getUserStorageProviders(); } protected T getFirstStorageProvider(RealmModel realm, Class type) { - for (StorageProviderModel model : getStorageProviders(realm)) { - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + for (UserStorageProviderModel model : getStorageProviders(realm)) { + UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); - if (Types.supports(type, factory, StorageProviderFactory.class)) { - return type.cast(factory.getInstance(session, model)); + if (Types.supports(type, factory, UserStorageProviderFactory.class)) { + return type.cast(getStorageProviderInstance(model, factory)); } } return null; } + private UserStorageProvider getStorageProviderInstance(UserStorageProviderModel model, UserStorageProviderFactory factory) { + UserStorageProvider instance = (UserStorageProvider)session.getAttribute(model.getId()); + if (instance != null) return instance; + instance = factory.create(session, model); + session.enlistForClose(instance); + session.setAttribute(model.getId(), instance); + return instance; + } + protected List getStorageProviders(RealmModel realm, Class type) { List list = new LinkedList<>(); - for (StorageProviderModel model : getStorageProviders(realm)) { - StorageProviderFactory factory = (StorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); - if (Types.supports(type, factory, StorageProviderFactory.class)) { - list.add(type.cast(factory.getInstance(session, model))); + for (UserStorageProviderModel model : getStorageProviders(realm)) { + UserStorageProviderFactory factory = (UserStorageProviderFactory) session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); + if (Types.supports(type, factory, UserStorageProviderFactory.class)) { + list.add(type.cast(getStorageProviderInstance(model, factory))); } @@ -106,10 +112,6 @@ public class UserStorageManager implements UserProvider { @Override public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - UserRegistrationProvider registry = getFirstStorageProvider(realm, UserRegistrationProvider.class); - if (registry != null) { - return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions); - } return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions); } @@ -122,14 +124,14 @@ public class UserStorageManager implements UserProvider { return localStorage().addUser(realm, username.toLowerCase()); } - public StorageProvider getStorageProvider(RealmModel realm, String providerId) { - StorageProviderModel model = realm.getStorageProvider(providerId); + public UserStorageProvider getStorageProvider(RealmModel realm, String componentId) { + ComponentModel model = realm.getComponent(componentId); if (model == null) return null; - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + UserStorageProviderFactory factory = (UserStorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserStorageProvider.class, model.getProviderId()); if (factory == null) { - throw new ModelException("Could not find StorageProviderFactory for: " + model.getProviderName()); + throw new ModelException("Could not find UserStorageProviderFactory for: " + model.getProviderId()); } - return factory.getInstance(session, model); + return getStorageProviderInstance(new UserStorageProviderModel(model), factory); } @Override @@ -481,7 +483,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm) { localStorage().preRemove(realm); getFederatedStorage().preRemove(realm); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm); } } @@ -496,7 +498,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm, GroupModel group) { localStorage().preRemove(realm, group); getFederatedStorage().preRemove(realm, group); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm, group); } } @@ -505,7 +507,7 @@ public class UserStorageManager implements UserProvider { public void preRemove(RealmModel realm, RoleModel role) { localStorage().preRemove(realm, role); getFederatedStorage().preRemove(realm, role); - for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + for (UserStorageProvider provider : getStorageProviders(realm, UserStorageProvider.class)) { provider.preRemove(realm, role); } } @@ -557,7 +559,7 @@ public class UserStorageManager implements UserProvider { if (toValidate.isEmpty()) return true; - StorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); + UserStorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); if (!(provider instanceof UserCredentialValidatorProvider)) { return false; } @@ -601,7 +603,7 @@ public class UserStorageManager implements UserProvider { } @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { + public void preRemove(RealmModel realm, ComponentModel component) { } diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java index 58695c306b..dbb4b3c91e 100644 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java @@ -16,13 +16,18 @@ */ package org.keycloak.storage; +import org.keycloak.models.GroupModel; import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.provider.Provider; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserStorageProvider { +public interface UserStorageProvider extends Provider { void preRemove(RealmModel realm); - + void preRemove(RealmModel realm, GroupModel group); + void preRemove(RealmModel realm, RoleModel role); } + diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java new file mode 100755 index 0000000000..5102355982 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderFactory.java @@ -0,0 +1,85 @@ +/* + * 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.storage; + +import org.keycloak.Config; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.Collections; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserStorageProviderFactory extends ComponentFactory { + + + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + T create(KeycloakSession session, ComponentModel model); + + /** + * This is the name of the provider and will be showed in the admin console as an option. + * + * @return + */ + @Override + String getId(); + + @Override + default void init(Config.Scope config) { + + } + + @Override + default void postInit(KeycloakSessionFactory factory) { + + } + + @Override + default void close() { + + } + + @Override + default String getHelpText() { + return ""; + } + + @Override + default List getConfigProperties() { + return Collections.EMPTY_LIST; + } + + @Override + default void validateConfiguration(KeycloakSession session, ComponentModel config) throws ComponentValidationException { + + } + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java new file mode 100755 index 0000000000..351107f94f --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderModel.java @@ -0,0 +1,62 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.storage; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.RealmModel; + +import java.io.Serializable; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +/** + * Stored configuration of a User Storage provider instance. + * + * @author Marek Posolda + * @author Bill Burke + */ +public class UserStorageProviderModel extends ComponentModel { + + public static Comparator comparator = new Comparator() { + @Override + public int compare(UserStorageProviderModel o1, UserStorageProviderModel o2) { + return o1.getPriority() - o2.getPriority(); + } + }; + + public UserStorageProviderModel() { + setProviderType(UserStorageProvider.class.getName()); + } + + public UserStorageProviderModel(ComponentModel copy) { + super(copy); + } + + public int getPriority() { + String priority = getConfig().getFirst("priority"); + if (priority == null) return 0; + return Integer.valueOf(priority); + + } + + public void setPriority(int priority) { + getConfig().putSingle("priority", Integer.toString(priority)); + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java similarity index 89% rename from server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java rename to server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java index 5d6d0da687..344e0a0680 100755 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProviderSpi.java @@ -24,7 +24,7 @@ import org.keycloak.provider.Spi; /** * @author Stian Thorgersen */ -public class StorageProviderSpi implements Spi { +public class UserStorageProviderSpi implements Spi { @Override public boolean isInternal() { @@ -38,12 +38,12 @@ public class StorageProviderSpi implements Spi { @Override public Class getProviderClass() { - return StorageProvider.class; + return UserStorageProvider.class; } @Override public Class getProviderFactoryClass() { - return StorageProviderFactory.class; + return UserStorageProviderFactory.class; } } diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java index 45c616891d..6a4e7d2be8 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -17,6 +17,7 @@ package org.keycloak.storage.adapter; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -29,7 +30,6 @@ import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; import java.util.Collections; import java.util.HashSet; @@ -58,9 +58,9 @@ public abstract class AbstractUserAdapter implements UserModel { } protected KeycloakSession session; protected RealmModel realm; - protected StorageProviderModel storageProviderModel; + protected ComponentModel storageProviderModel; - public AbstractUserAdapter(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) { + public AbstractUserAdapter(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) { this.session = session; this.realm = realm; this.storageProviderModel = storageProviderModel; diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index b09ceae6ca..bfe0b95f6e 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -16,6 +16,7 @@ */ package org.keycloak.storage.adapter; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; @@ -28,11 +29,9 @@ import org.keycloak.models.UserModel; import org.keycloak.models.utils.DefaultRoles; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import java.util.Collections; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -60,9 +59,9 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel { protected KeycloakSession session; protected RealmModel realm; - protected StorageProviderModel storageProviderModel; + protected ComponentModel storageProviderModel; - public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, StorageProviderModel storageProviderModel) { + public AbstractUserAdapterFederatedStorage(KeycloakSession session, RealmModel realm, ComponentModel storageProviderModel) { this.session = session; this.realm = realm; this.storageProviderModel = storageProviderModel; diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java deleted file mode 100644 index e0ef387e9e..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.storage.changeset; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.entities.UserEntity; - -import java.util.List; -import java.util.Map; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface UserDataQuery { - - // Service account is included for counts - int getUsersCount(RealmModel realm); - - List getUsers(RealmModel realm); - List searchForUser(String search, RealmModel realm); - List searchForUserByAttributes(Map attributes, RealmModel realm); - - List getUsers(RealmModel realm, int firstResult, int maxResults); - List searchForUser(String search, RealmModel realm, int firstResult, int maxResults); - List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults); - - - - // Searching by UserModel.attribute (not property) - List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm); -} diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java deleted file mode 100644 index e896b00e9b..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.storage.changeset; - -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.entities.UserEntity; -import org.keycloak.storage.StorageId; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public interface UserDataStore { - void updateUser(RealmModel realm, UserData user); - void addUser(RealmModel realm, UserData user); - boolean removeUser(RealmModel realm, StorageId store); -} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java index 46ae9db649..b60499199c 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java @@ -17,6 +17,7 @@ package org.keycloak.storage.federated; +import org.keycloak.component.ComponentModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; import org.keycloak.models.ProtocolMapperModel; @@ -25,7 +26,6 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.provider.Provider; -import org.keycloak.storage.StorageProviderModel; /** * @author Bill Burke @@ -44,7 +44,7 @@ public interface UserFederatedStorageProvider extends Provider, void preRemove(RealmModel realm, UserFederationProviderModel link); - public void preRemove(RealmModel realm, GroupModel group); + void preRemove(RealmModel realm, GroupModel group); void preRemove(RealmModel realm, RoleModel role); @@ -54,5 +54,5 @@ public interface UserFederatedStorageProvider extends Provider, void preRemove(RealmModel realm, UserModel user); - void preRemove(RealmModel realm, StorageProviderModel model); + void preRemove(RealmModel realm, ComponentModel model); } diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java index 5475422d00..21b1ad0396 100755 --- a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java @@ -20,8 +20,6 @@ package org.keycloak.storage.federated; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; /** * @author Stian Thorgersen diff --git a/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java b/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java index 7b6922d6e6..7697042f87 100644 --- a/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java +++ b/server-spi/src/main/java/org/keycloak/storage/user/UserRegistrationProvider.java @@ -25,7 +25,6 @@ import org.keycloak.models.UserModel; * @version $Revision: 1 $ */ public interface UserRegistrationProvider { - UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); UserModel addUser(RealmModel realm, String username); diff --git a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 696bb4d2e3..d2431d84b0 100755 --- a/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -16,7 +16,7 @@ # org.keycloak.models.UserFederationSpi -org.keycloak.storage.StorageProviderSpi +org.keycloak.storage.UserStorageProviderSpi org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.mappers.UserFederationMapperSpi org.keycloak.models.RealmSpi diff --git a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java index 59d6217d15..705d23048f 100644 --- a/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java +++ b/services/src/main/java/org/keycloak/partialimport/PartialImportManager.java @@ -62,7 +62,7 @@ public class PartialImportManager { try { partialImport.prepare(rep, realm, session); } catch (ErrorResponseException error) { - if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly(); return error.getResponse(); } } @@ -72,7 +72,7 @@ public class PartialImportManager { partialImport.removeOverwrites(realm, session); results.addAllResults(partialImport.doImport(rep, realm, session)); } catch (ErrorResponseException error) { - if (session.getTransaction().isActive()) session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) session.getTransactionManager().setRollbackOnly(); return error.getResponse(); } } @@ -84,8 +84,8 @@ public class PartialImportManager { } } - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.ok(results).build(); diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 8eb2a56b6b..1fae787117 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -36,6 +36,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private final Map providers = new HashMap<>(); private final List closable = new LinkedList(); private final DefaultKeycloakTransactionManager transactionManager; + private final Map attributes = new HashMap<>(); private RealmProvider model; private UserProvider userModel; private UserStorageManager userStorageManager; @@ -81,7 +82,22 @@ public class DefaultKeycloakSession implements KeycloakSession { } @Override - public KeycloakTransactionManager getTransaction() { + public Object getAttribute(String attribute) { + return attributes.get(attribute); + } + + @Override + public Object removeAttribute(String attribute) { + return attributes.remove(attribute); + } + + @Override + public void setAttribute(String name, Object value) { + attributes.put(name, value); + } + + @Override + public KeycloakTransactionManager getTransactionManager() { return transactionManager; } diff --git a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java index 40c1ac6d2c..41876455c8 100755 --- a/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java +++ b/services/src/main/java/org/keycloak/services/filters/KeycloakSessionServletFilter.java @@ -82,7 +82,7 @@ public class KeycloakSessionServletFilter implements Filter { session.getContext().setConnection(connection); ResteasyProviderFactory.pushContext(ClientConnection.class, connection); - KeycloakTransaction tx = session.getTransaction(); + KeycloakTransaction tx = session.getTransactionManager(); ResteasyProviderFactory.pushContext(KeycloakTransaction.class, tx); tx.begin(); @@ -123,8 +123,8 @@ public class KeycloakSessionServletFilter implements Filter { private void closeSession(KeycloakSession session) { // KeycloakTransactionCommitter is responsible for committing the transaction, but if an exception is thrown it's not invoked and transaction // should be rolled back - if (session.getTransaction() != null && session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager() != null && session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } session.close(); diff --git a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java index fa3b1d5d6b..28fc29d308 100644 --- a/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java +++ b/services/src/main/java/org/keycloak/services/managers/DefaultBruteForceProtector.java @@ -177,7 +177,7 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector queue.drainTo(events, TRANSACTION_SIZE); Collections.sort(events); // we sort to avoid deadlock due to ordered updates. Maybe I'm overthinking this. KeycloakSession session = factory.create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { for (LoginEvent event : events) { if (event instanceof FailedLogin) { @@ -186,9 +186,9 @@ public class DefaultBruteForceProtector implements Runnable, BruteForceProtector run = false; } } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Exception e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); throw e; } finally { for (LoginEvent event : events) { diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index c7cbc62709..91818176c2 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -835,17 +835,17 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal private void fireErrorEvent(String message, Throwable throwable) { if (!this.event.getEvent().getType().toString().endsWith("_ERROR")) { - boolean newTransaction = !this.session.getTransaction().isActive(); + boolean newTransaction = !this.session.getTransactionManager().isActive(); try { if (newTransaction) { - this.session.getTransaction().begin(); + this.session.getTransactionManager().begin(); } this.event.error(message); if (newTransaction) { - this.session.getTransaction().commit(); + this.session.getTransactionManager().commit(); } } catch (Exception e) { logger.couldNotFireEvent(e); @@ -869,8 +869,8 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } private void rollback() { - if (this.session.getTransaction().isActive()) { - this.session.getTransaction().rollback(); + if (this.session.getTransactionManager().isActive()) { + this.session.getTransactionManager().rollback(); } } diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java index 038528156e..5e1cd9f876 100644 --- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java +++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java @@ -119,10 +119,10 @@ public class KeycloakApplication extends Application { boolean bootstrapAdminUser = false; KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); bootstrapAdminUser = new ApplianceBootstrap(session).isNoMasterUser(); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -142,7 +142,7 @@ public class KeycloakApplication extends Application { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); ApplianceBootstrap applianceBootstrap = new ApplianceBootstrap(session); exportImportManager = new ExportImportManager(session); @@ -155,10 +155,10 @@ public class KeycloakApplication extends Application { if (createMasterRealm) { applianceBootstrap.createMasterRealm(contextPath); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (RuntimeException re) { - if (session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } throw re; } finally { @@ -180,11 +180,11 @@ public class KeycloakApplication extends Application { protected void migrateModel() { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); MigrationModelManager.migrate(session); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Exception e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.migrationFailure(e); throw e; } finally { @@ -294,7 +294,7 @@ public class KeycloakApplication extends Application { KeycloakSession session = sessionFactory.create(); boolean exists = false; try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -313,9 +313,9 @@ public class KeycloakApplication extends Application { RealmModel realm = manager.importRealm(rep); logger.importedRealm(realm.getName(), from); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } catch (Throwable t) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); if (!exists) { logger.unableToImportRealm(t, rep.getRealm(), from); } @@ -345,7 +345,7 @@ public class KeycloakApplication extends Application { for (UserRepresentation userRep : realmRep.getUsers()) { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); RealmModel realm = session.realms().getRealmByName(realmRep.getRealm()); if (realm == null) { @@ -357,13 +357,13 @@ public class KeycloakApplication extends Application { RepresentationToModel.createRoleMappings(userRep, user, realm); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); logger.addUserSuccess(userRep.getUsername(), realmRep.getRealm()); } catch (ModelDuplicateException e) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.addUserFailedUserExists(userRep.getUsername(), realmRep.getRealm()); } catch (Throwable t) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); logger.addUserFailed(t, userRep.getUsername(), realmRep.getRealm()); } finally { session.close(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 94203d7893..82e65fb623 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -536,7 +536,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(rep.getId()); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -596,7 +596,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -642,7 +642,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -682,7 +682,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } @@ -718,7 +718,7 @@ public class AuthenticationManagementResource { AuthenticationExecutionModel model = realm.getAuthenticationExecutionById(execution); if (model == null) { - session.getTransaction().setRollbackOnly(); + session.getTransactionManager().setRollbackOnly(); throw new NotFoundException("Illegal execution"); } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java index a2c35d8e60..de4d49b8dc 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientInitialAccessResource.java @@ -73,8 +73,8 @@ public class ClientInitialAccessResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, clientInitialAccessModel.getId()).representation(config).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } ClientInitialAccessPresentation rep = wrap(clientInitialAccessModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java new file mode 100644 index 0000000000..0097b191ff --- /dev/null +++ b/services/src/main/java/org/keycloak/services/resources/admin/ComponentResource.java @@ -0,0 +1,152 @@ +/* + * 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.services.resources.admin; + +import org.jboss.resteasy.spi.NotFoundException; +import org.keycloak.common.ClientConnection; +import org.keycloak.component.ComponentModel; +import org.keycloak.events.admin.OperationType; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.utils.ModelToRepresentation; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.protocol.oidc.TokenManager; +import org.keycloak.representations.idm.ComponentRepresentation; +import org.keycloak.services.ServicesLogger; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class ComponentResource { + protected static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER; + + protected RealmModel realm; + + private RealmAuth auth; + + private AdminEventBuilder adminEvent; + + @Context + protected ClientConnection clientConnection; + + @Context + protected UriInfo uriInfo; + + @Context + protected KeycloakSession session; + + @Context + protected HttpHeaders headers; + + public ComponentResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { + this.auth = auth; + this.realm = realm; + this.adminEvent = adminEvent; + + auth.init(RealmAuth.Resource.USER); + } + + @GET + @Produces(MediaType.APPLICATION_JSON) + public List getComponents(@QueryParam("parent") String parent, @QueryParam("type") String type) { + auth.requireManage(); + if (parent == null) parent = realm.getId(); + List components = realm.getComponents(parent, type); + List reps = new LinkedList<>(); + for (ComponentModel component : components) { + ComponentRepresentation rep = ModelToRepresentation.toRepresentation(component); + reps.add(rep); + } + return reps; + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + public Response create(ComponentRepresentation rep) { + auth.requireManage(); + ComponentModel model = RepresentationToModel.toModel(rep); + if (model.getParentId() == null) model.setParentId(realm.getId()); + adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); + + + + model = realm.addComponentModel(model); + return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build(); + } + + @GET + @Path("{id}") + public ComponentRepresentation getComponent(@PathParam("id") String id) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + return ModelToRepresentation.toRepresentation(model); + + + } + + @PUT + @Path("{id}") + @Consumes(MediaType.APPLICATION_JSON) + public void updateComponent(@PathParam("id") String id, ComponentRepresentation rep) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + model = RepresentationToModel.toModel(rep); + model.setId(id); + adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo, model.getId()).representation(rep).success(); + realm.updateComponent(model); + + } + @DELETE + @Path("{id}") + public void removeComponent(@PathParam("id") String id) { + auth.requireManage(); + ComponentModel model = realm.getComponent(id); + if (model == null) { + throw new NotFoundException("Could not find component"); + } + adminEvent.operation(OperationType.DELETE).resourcePath(uriInfo, model.getId()).success(); + realm.removeComponent(model); + + } + + + +} diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java index 4594bdd87e..8270ed4c86 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java @@ -321,7 +321,7 @@ public class RealmAdminResource { */ @Path("users") public UsersResource users() { - UsersResource users = new UsersResource(realm, auth, tokenManager, adminEvent); + UsersResource users = new UsersResource(realm, auth, adminEvent); ResteasyProviderFactory.getInstance().injectProperties(users); //resourceContext.initResource(users); return users; 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 5788b42e7e..aa6aa688e7 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 @@ -127,7 +127,7 @@ public class UsersResource { @Context protected HttpHeaders headers; - public UsersResource(RealmModel realm, RealmAuth auth, TokenManager tokenManager, AdminEventBuilder adminEvent) { + public UsersResource(RealmModel realm, RealmAuth auth, AdminEventBuilder adminEvent) { this.auth = auth; this.realm = realm; this.adminEvent = adminEvent; @@ -172,8 +172,8 @@ public class UsersResource { updateUserFromRep(user, rep, attrsToRemove, realm, session, true); adminEvent.operation(OperationType.UPDATE).resourcePath(uriInfo).representation(rep).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.noContent().build(); } catch (ModelDuplicateException e) { @@ -214,19 +214,19 @@ public class UsersResource { adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, user.getId()).representation(rep).success(); - if (session.getTransaction().isActive()) { - session.getTransaction().commit(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().commit(); } return Response.created(uriInfo.getAbsolutePathBuilder().path(user.getId()).build()).build(); } catch (ModelDuplicateException e) { - if (session.getTransaction().isActive()) { - session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().setRollbackOnly(); } return ErrorResponse.exists("User exists with same username or email"); } catch (ModelException me){ - if (session.getTransaction().isActive()) { - session.getTransaction().setRollbackOnly(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().setRollbackOnly(); } return ErrorResponse.exists("Could not create user"); } 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 1a67fb0bf7..d7ee1085c9 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 @@ -25,7 +25,6 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.ServiceLoader; import javax.ws.rs.GET; import javax.ws.rs.WebApplicationException; @@ -33,12 +32,13 @@ import javax.ws.rs.core.Context; import org.keycloak.broker.provider.IdentityProvider; import org.keycloak.broker.provider.IdentityProviderFactory; +import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.events.EventType; import org.keycloak.events.admin.OperationType; -import org.keycloak.models.PasswordPolicy; import org.keycloak.policy.PasswordPolicyProvider; import org.keycloak.policy.PasswordPolicyProviderFactory; import org.keycloak.provider.*; +import org.keycloak.representations.idm.ComponentTypeRepresentation; import org.keycloak.representations.idm.PasswordPolicyTypeRepresentation; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; @@ -115,12 +115,26 @@ public class ServerInfoAdminResource { Map 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); if (ServerInfoAwareProviderFactory.class.isAssignableFrom(pi.getClass())) { provider.setOperationalInfo(((ServerInfoAwareProviderFactory) pi).getOperationalInfo()); } + if (pi instanceof ConfiguredProvider) { + ComponentTypeRepresentation rep = new ComponentTypeRepresentation(); + rep.setId(pi.getId()); + ConfiguredProvider configured = (ConfiguredProvider)pi; + rep.setHelpText(configured.getHelpText()); + rep.setProperties(ModelToRepresentation.toRepresentation(configured.getConfigProperties())); + List reps = info.getComponentTypes().get(spi.getProviderClass().getName()); + if (reps == null) { + reps = new LinkedList<>(); + info.getComponentTypes().put(spi.getProviderClass().getName(), reps); + } + reps.add(rep); + } providers.put(name, provider); } } @@ -225,15 +239,7 @@ public class ServerInfoAdminResource { rep.setCategory(mapper.getDisplayCategory()); rep.setProperties(new LinkedList()); List configProperties = mapper.getConfigProperties(); - for (ProviderConfigProperty prop : configProperties) { - ConfigPropertyRepresentation propRep = new ConfigPropertyRepresentation(); - propRep.setName(prop.getName()); - propRep.setLabel(prop.getLabel()); - propRep.setType(prop.getType()); - propRep.setDefaultValue(prop.getDefaultValue()); - propRep.setHelpText(prop.getHelpText()); - rep.getProperties().add(propRep); - } + rep.setProperties(ModelToRepresentation.toRepresentation(configProperties)); types.add(rep); } } diff --git a/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java b/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java index 94db9c971e..b2a8bf7d15 100644 --- a/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java +++ b/services/src/main/java/org/keycloak/services/scheduled/ClusterAwareScheduledTaskRunner.java @@ -41,7 +41,7 @@ public class ClusterAwareScheduledTaskRunner extends ScheduledTaskRunner { @Override protected void runTask(final KeycloakSession session) { - session.getTransaction().begin(); + session.getTransactionManager().begin(); ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class); String taskKey = task.getClass().getSimpleName(); @@ -56,7 +56,7 @@ public class ClusterAwareScheduledTaskRunner extends ScheduledTaskRunner { }); - session.getTransaction().commit(); + session.getTransactionManager().commit(); if (result.isExecuted()) { logger.debugf("Executed scheduled task %s", taskKey); diff --git a/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java b/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java index b49300f8a1..81ff5f6ae2 100644 --- a/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java +++ b/services/src/main/java/org/keycloak/services/scheduled/ScheduledTaskRunner.java @@ -45,7 +45,7 @@ public class ScheduledTaskRunner implements Runnable { } catch (Throwable t) { logger.failedToRunScheduledTask(t, task.getClass().getSimpleName()); - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); } finally { try { session.close(); @@ -56,9 +56,9 @@ public class ScheduledTaskRunner implements Runnable { } protected void runTask(KeycloakSession session) { - session.getTransaction().begin(); + session.getTransactionManager().begin(); task.run(session); - session.getTransaction().commit(); + session.getTransactionManager().commit(); logger.debug("Executed scheduled task " + task.getClass().getSimpleName()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java index 7791c9139c..3b8d68b9b2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/KeycloakServer.java @@ -39,7 +39,6 @@ import org.keycloak.testsuite.util.cli.TestsuiteCLI; import org.keycloak.util.JsonSerialization; import javax.servlet.DispatcherType; -import javax.ws.rs.core.Application; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -249,7 +248,7 @@ public class KeycloakServer { public void importRealm(RealmRepresentation rep) { KeycloakSession session = sessionFactory.create();; - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -268,7 +267,7 @@ public class KeycloakServer { info("Imported realm " + realm.getName()); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -278,11 +277,11 @@ public class KeycloakServer { if (System.getProperty("keycloak.createAdminUser", "true").equals("true")) { KeycloakSession session = sessionFactory.create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); if (new ApplianceBootstrap(session).isNoMasterUser()) { new ApplianceBootstrap(session).createMasterRealmUser("admin", "admin"); } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java index 507c0fee58..adea44e0b3 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/AdapterTestStrategy.java @@ -265,7 +265,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int originalIdle = realm.getSsoSessionIdleTimeout(); realm.setSsoSessionIdleTimeout(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -278,7 +278,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setSsoSessionIdleTimeout(originalIdle); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); @@ -300,7 +300,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int originalIdle = realm.getSsoSessionIdleTimeout(); realm.setSsoSessionIdleTimeout(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -308,7 +308,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); session.sessions().removeExpired(realm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); // test SSO @@ -321,7 +321,7 @@ public class AdapterTestStrategy extends ExternalResource { UserModel user = session.users().getUserByUsername("bburke@redhat.com", realm); new ResourceAdminManager(session).logoutUser(null, realm, user, session); realm.setSsoSessionIdleTimeout(originalIdle); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); @@ -343,7 +343,7 @@ public class AdapterTestStrategy extends ExternalResource { RealmModel realm = session.realms().getRealmByName("demo"); int original = realm.getSsoSessionMaxLifespan(); realm.setSsoSessionMaxLifespan(1); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(2); @@ -356,7 +356,7 @@ public class AdapterTestStrategy extends ExternalResource { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setSsoSessionMaxLifespan(original); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); Time.setOffset(0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java index 936dfe921a..02e34a7bcc 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/adapter/CookieTokenStoreAdapterTest.java @@ -125,7 +125,7 @@ public class CookieTokenStoreAdapterTest { RealmModel realm = session.realms().getRealmByName("demo"); int originalTokenTimeout = realm.getAccessTokenLifespan(); realm.setAccessTokenLifespan(3); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); // login to customer-cookie-portal @@ -164,7 +164,7 @@ public class CookieTokenStoreAdapterTest { session = keycloakRule.startSession(); realm = session.realms().getRealmByName("demo"); realm.setAccessTokenLifespan(originalTokenTimeout); - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } finally { Time.setOffset(0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java index 301adcae3a..fbf602ec4b 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/AbstractAuthorizationTest.java @@ -58,7 +58,7 @@ public abstract class AbstractAuthorizationTest { protected R onAuthorizationSession(Function function) { KeycloakSession keycloakSession = startKeycloakSession(); - KeycloakTransactionManager transaction = keycloakSession.getTransaction(); + KeycloakTransactionManager transaction = keycloakSession.getTransactionManager(); try { AuthorizationProvider authorizationProvider = keycloakSession.getProvider(AuthorizationProvider.class); @@ -80,7 +80,7 @@ public abstract class AbstractAuthorizationTest { protected void onAuthorizationSession(Consumer consumer) { KeycloakSession keycloakSession = startKeycloakSession(); - KeycloakTransactionManager transaction = keycloakSession.getTransaction(); + KeycloakTransactionManager transaction = keycloakSession.getTransactionManager(); try { AuthorizationProvider authorizationProvider = keycloakSession.getProvider(AuthorizationProvider.class); 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 daed4adac9..a53479c15b 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 @@ -16,6 +16,7 @@ */ package org.keycloak.testsuite.federation.storage; +import org.keycloak.component.ComponentModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -23,8 +24,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserModel; import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProvider; import org.keycloak.storage.adapter.AbstractUserAdapterFederatedStorage; import org.keycloak.storage.user.UserCredentialValidatorProvider; import org.keycloak.storage.user.UserLookupProvider; @@ -32,28 +32,33 @@ import org.keycloak.storage.user.UserRegistrationProvider; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserMapStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider { +public class UserMapStorage implements UserLookupProvider, UserStorageProvider, UserCredentialValidatorProvider, UserRegistrationProvider { protected Map userPasswords; - protected StorageProviderModel model; + protected ComponentModel model; protected KeycloakSession session; - public UserMapStorage(KeycloakSession session, StorageProviderModel model, Map userPasswords) { + public static final AtomicInteger allocations = new AtomicInteger(0); + public static final AtomicInteger closings = 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.getStorageId(); + final String username = storageId.getExternalId(); if (!userPasswords.containsKey(username)) return null; return createUser(realm, username); @@ -94,12 +99,6 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User return null; } - @Override - public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - userPasswords.put(username, ""); - return createUser(realm, username); - } - @Override public UserModel addUser(RealmModel realm, String username) { userPasswords.put(username, ""); @@ -131,11 +130,6 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User } - @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { - - } - @Override public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { for (UserCredentialModel cred : input) { @@ -151,6 +145,7 @@ public class UserMapStorage implements UserLookupProvider, StorageProvider, User @Override public void close() { + closings.incrementAndGet(); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java index f80a11dea7..79e9261384 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserMapStorageFactory.java @@ -17,22 +17,19 @@ package org.keycloak.testsuite.federation.storage; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProviderFactory; -import java.io.IOException; import java.util.Hashtable; import java.util.Map; -import java.util.Properties; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserMapStorageFactory implements StorageProviderFactory { +public class UserMapStorageFactory implements UserStorageProviderFactory { public static final String PROVIDER_ID = "user-password-map"; @@ -40,7 +37,7 @@ public class UserMapStorageFactory implements StorageProviderFactory userPasswords = new Hashtable<>(); @Override - public UserMapStorage getInstance(KeycloakSession session, StorageProviderModel model) { + public UserMapStorage create(KeycloakSession session, ComponentModel model) { return new UserMapStorage(session, model, userPasswords); } @@ -49,11 +46,6 @@ public class UserMapStorageFactory implements StorageProviderFactoryBill Burke * @version $Revision: 1 $ */ -public class UserPropertyFileStorage implements UserLookupProvider, StorageProvider, UserCredentialValidatorProvider, UserQueryProvider { +public class UserPropertyFileStorage implements UserLookupProvider, UserStorageProvider, UserCredentialValidatorProvider, UserQueryProvider { protected Properties userPasswords; - protected StorageProviderModel model; + protected ComponentModel model; protected KeycloakSession session; protected boolean federatedStorageEnabled; - public UserPropertyFileStorage(KeycloakSession session, StorageProviderModel model, Properties userPasswords) { + public UserPropertyFileStorage(KeycloakSession session, ComponentModel model, Properties userPasswords) { this.session = session; this.model = model; this.userPasswords = userPasswords; @@ -60,7 +59,7 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi @Override public UserModel getUserById(String id, RealmModel realm) { StorageId storageId = new StorageId(id); - final String username = storageId.getStorageId(); + final String username = storageId.getExternalId(); if (!userPasswords.containsKey(username)) return null; return createUser(realm, username); @@ -116,11 +115,6 @@ public class UserPropertyFileStorage implements UserLookupProvider, StorageProvi } - @Override - public void preRemove(RealmModel realm, StorageProviderModel model) { - - } - @Override public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { for (UserCredentialModel cred : input) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java index 82d91cfd4f..0dec2bb400 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserPropertyFileStorageFactory.java @@ -17,33 +17,28 @@ package org.keycloak.testsuite.federation.storage; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; -import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.UserStorageProviderFactory; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Properties; -import java.util.Set; /** * @author Bill Burke * @version $Revision: 1 $ */ -public class UserPropertyFileStorageFactory implements StorageProviderFactory { +public class UserPropertyFileStorageFactory implements UserStorageProviderFactory { public static final String PROVIDER_ID = "user-password-props"; @Override - public UserPropertyFileStorage getInstance(KeycloakSession session, StorageProviderModel model) { + public UserPropertyFileStorage create(KeycloakSession session, ComponentModel model) { Properties props = new Properties(); try { - props.load(getClass().getResourceAsStream(model.getConfig().get("property.file"))); + props.load(getClass().getResourceAsStream(model.getConfig().getFirst("property.file"))); } catch (IOException e) { throw new RuntimeException(e); } @@ -55,11 +50,6 @@ public class UserPropertyFileStorageFactory implements StorageProviderFactoryBill Burke * @version $Revision: 1 $ */ -public class UserFederationStorageTest { - public static StorageProviderModel memoryProvider = null; +public class UserStorageTest { + public static ComponentModel memoryProvider = null; @ClassRule public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { - StorageProviderModel model = new StorageProviderModel(); - model.setDisplayName("memory"); + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setName("memory"); model.setPriority(0); - model.setProviderName(UserMapStorageFactory.PROVIDER_ID); - memoryProvider = appRealm.addStorageProvider(model); - model = new StorageProviderModel(); - model.setDisplayName("read-only-user-props"); + model.setProviderId(UserMapStorageFactory.PROVIDER_ID); + model.setParentId(appRealm.getId()); + memoryProvider = appRealm.addComponentModel(model); + + model = new UserStorageProviderModel(); + model.setName("read-only-user-props"); model.setPriority(1); - model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID); - model.getConfig().put("property.file", "/storage-test/read-only-user-password.properties"); - appRealm.addStorageProvider(model); - model = new StorageProviderModel(); - model.setDisplayName("user-props"); + model.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + model.setParentId(appRealm.getId()); + model.getConfig().putSingle("property.file", "/storage-test/read-only-user-password.properties"); + appRealm.addComponentModel(model); + model = new UserStorageProviderModel(); + model.setName("user-props"); model.setPriority(2); - model.setProviderName(UserPropertyFileStorageFactory.PROVIDER_ID); - model.getConfig().put("property.file", "/storage-test/user-password.properties"); - model.getConfig().put("USER_FEDERATED_STORAGE", "true"); - appRealm.addStorageProvider(model); + model.setParentId(appRealm.getId()); + model.setProviderId(UserPropertyFileStorageFactory.PROVIDER_ID); + model.getConfig().putSingle("property.file", "/storage-test/user-password.properties"); + model.getConfig().putSingle("USER_FEDERATED_STORAGE", "true"); + appRealm.addComponentModel(model); } }); @Rule @@ -265,4 +270,27 @@ public class UserFederationStorageTest { } + @Test + public void testLifecycle() { + UserMapStorage.allocations.set(0); + UserMapStorage.closings.set(0); + KeycloakSession session = keycloakRule.startSession(); + 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); + keycloakRule.stopSession(session, true); + Assert.assertEquals(1, UserMapStorage.allocations.get()); + Assert.assertEquals(1, UserMapStorage.closings.get()); + + session = keycloakRule.startSession(); + realm = session.realms().getRealmByName("test"); + user = session.users().getUserByUsername("memuser", realm); + 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/AbstractModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java index 0befa7ca16..f1409f42b4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java @@ -81,16 +81,16 @@ public class AbstractModelTest { protected void commit(boolean rollback) { if (rollback) { - session.getTransaction().rollback(); + session.getTransactionManager().rollback(); } else { - session.getTransaction().commit(); + session.getTransactionManager().commit(); } resetSession(); } protected void resetSession() { - if (session.getTransaction().isActive()) { - session.getTransaction().rollback(); + if (session.getTransactionManager().isActive()) { + session.getTransactionManager().rollback(); } kc.stopSession(session, false); session = kc.startSession(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index a28af56688..eecf8c9034 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -74,7 +74,7 @@ public class AdapterTest extends AbstractModelTest { realmModel.setAccessTokenLifespan(1000); realmModel.addDefaultRole("foo"); - session.getTransaction().commit(); + session.getTransactionManager().commit(); resetSession(); realmModel = realmManager.getRealm(realmModel.getId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java index 0d7c9b08c6..c478375e21 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/CacheTest.java @@ -105,7 +105,7 @@ public class CacheTest { assertNotNull(user2.getLastName()); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } } @@ -121,7 +121,7 @@ public class CacheTest { RoleModel fooRole = client.addRole("foo-role"); user.grantRole(fooRole); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } @@ -136,7 +136,7 @@ public class CacheTest { ClientModel client = realm.getClientByClientId("foo"); realm.removeClient(client.getId()); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } @@ -152,7 +152,7 @@ public class CacheTest { Assert.assertEquals(roles.size(), grantedRolesCount - 1); } finally { - session.getTransaction().commit(); + session.getTransactionManager().commit(); session.close(); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java index 0d7e9e27c2..2edcfaa31e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/TransactionsTest.java @@ -35,14 +35,14 @@ public class TransactionsTest { public void testTransactionActive() { KeycloakSession session = kc.startSession(); - Assert.assertTrue(session.getTransaction().isActive()); - session.getTransaction().commit(); - Assert.assertFalse(session.getTransaction().isActive()); + Assert.assertTrue(session.getTransactionManager().isActive()); + session.getTransactionManager().commit(); + Assert.assertFalse(session.getTransactionManager().isActive()); - session.getTransaction().begin(); - Assert.assertTrue(session.getTransaction().isActive()); - session.getTransaction().rollback(); - Assert.assertFalse(session.getTransaction().isActive()); + session.getTransactionManager().begin(); + Assert.assertTrue(session.getTransactionManager().isActive()); + session.getTransactionManager().rollback(); + Assert.assertFalse(session.getTransactionManager().isActive()); session.close(); } 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 83ea5adb99..01f99fb3c3 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 @@ -217,7 +217,7 @@ public class UserModelTest extends AbstractModelTest { UserModel user1 = session.users().addUser(realm, "user1"); commit(); - + realm = session.realms().getRealmByName("original"); List users = session.users().searchForUser("user", realm, 0, 7); Assert.assertTrue(users.contains(user1)); } @@ -238,6 +238,7 @@ public class UserModelTest extends AbstractModelTest { user3.setSingleAttribute("key2", "value21"); commit(); + realm = session.realms().getRealmByName("original"); List users = session.users().searchForUserByUserAttribute("key1", "value1", realm); Assert.assertEquals(2, users.size()); @@ -284,6 +285,7 @@ public class UserModelTest extends AbstractModelTest { // Search and assert service account user not found realm = realmManager.getRealmByName("original"); + client = realm.getClientByClientId("foo"); UserModel searched = session.users().getServiceAccount(client); Assert.assertEquals(searched, user1); users = session.users().searchForUser("John Doe", realm); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java index 172c51796e..8315331918 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/AbstractKeycloakRule.java @@ -85,12 +85,12 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public UserRepresentation getUser(String realm, String name) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserModel user = session.users().getUserByUsername(name, realmByName); UserRepresentation userRep = user != null ? ModelToRepresentation.toRepresentation(user) : null; - session.getTransaction().commit(); + session.getTransactionManager().commit(); return userRep; } finally { session.close(); @@ -99,11 +99,11 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public UserRepresentation getUserById(String realm, String id) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmModel realmByName = session.realms().getRealmByName(realm); UserRepresentation userRep = ModelToRepresentation.toRepresentation(session.users().getUserById(id, realmByName)); - session.getTransaction().commit(); + session.getTransactionManager().commit(); return userRep; } finally { session.close(); @@ -112,7 +112,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { protected void setupKeycloak() { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -121,7 +121,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { configure(session, manager, adminstrationRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -129,7 +129,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public void update(KeycloakRule.KeycloakSetup configurer, String realmId) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -141,7 +141,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { configurer.session = session; configurer.config(manager, adminstrationRealm, appRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -221,7 +221,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { protected void removeTestRealms() { KeycloakSession session = server.getSessionFactory().create(); try { - session.getTransaction().begin(); + session.getTransactionManager().begin(); RealmManager realmManager = new RealmManager(session); for (String realmName : getTestRealms()) { RealmModel realm = realmManager.getRealmByName(realmName); @@ -229,7 +229,7 @@ public abstract class AbstractKeycloakRule extends ExternalResource { realmManager.removeRealm(realm); } } - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } @@ -248,12 +248,12 @@ public abstract class AbstractKeycloakRule extends ExternalResource { public KeycloakSession startSession() { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); return session; } public void stopSession(KeycloakSession session, boolean commit) { - KeycloakTransaction transaction = session.getTransaction(); + KeycloakTransaction transaction = session.getTransactionManager(); if (commit && !transaction.getRollbackOnly()) { transaction.commit(); } else { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java index 8e481778d6..050bcf3ece 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/KeycloakRule.java @@ -58,7 +58,7 @@ public class KeycloakRule extends AbstractKeycloakRule { public void configure(KeycloakSetup configurer) { KeycloakSession session = server.getSessionFactory().create(); - session.getTransaction().begin(); + session.getTransactionManager().begin(); try { RealmManager manager = new RealmManager(session); @@ -70,7 +70,7 @@ public class KeycloakRule extends AbstractKeycloakRule { configurer.session = session; configurer.config(manager, adminstrationRealm, appRealm); - session.getTransaction().commit(); + session.getTransactionManager().commit(); } finally { session.close(); } diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.StorageProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory similarity index 100% rename from testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.StorageProviderFactory rename to testsuite/integration/src/test/resources/META-INF/services/org.keycloak.storage.UserStorageProviderFactory