From f51098c50b21ab6228ae594b70c272b99ccf53d7 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Wed, 29 Jun 2016 15:37:22 -0400 Subject: [PATCH 1/4] user fed refactor --- .../models/cache/infinispan/RealmAdapter.java | 43 + .../models/cache/infinispan/UserAdapter.java | 64 -- .../cache/infinispan/UserCacheSession.java | 120 ++- .../infinispan/entities/CachedRealm.java | 7 + .../cache/infinispan/entities/CachedUser.java | 11 - .../entities/CachedUserConsents.java | 53 ++ .../keycloak/models/jpa/JpaUserProvider.java | 183 ++++- .../org/keycloak/models/jpa/RealmAdapter.java | 200 ++++- .../org/keycloak/models/jpa/UserAdapter.java | 157 ---- .../models/jpa/entities/RealmEntity.java | 17 +- .../jpa/entities/StorageProviderEntity.java | 131 ++++ .../jpa/JpaUserFederatedStorageProvider.java | 732 ++++++++++++++++++ ...paUserFederatedStorageProviderFactory.java | 58 ++ .../storage/jpa/entity/BrokerLinkEntity.java | 191 +++++ .../entity/FederatedUserAttributeEntity.java | 133 ++++ .../entity/FederatedUserConsentEntity.java | 148 ++++ ...eratedUserConsentProtocolMapperEntity.java | 135 ++++ .../FederatedUserConsentRoleEntity.java | 133 ++++ .../entity/FederatedUserCredentialEntity.java | 222 ++++++ .../FederatedUserGroupMembershipEntity.java | 161 ++++ .../FederatedUserRequiredActionEntity.java | 160 ++++ .../FederatedUserRoleMappingEntity.java | 160 ++++ .../keycloak/adapters/MongoUserProvider.java | 127 ++- .../mongo/keycloak/adapters/RealmAdapter.java | 185 ++++- .../mongo/keycloak/adapters/UserAdapter.java | 102 --- .../mappers/UserFederationMapper.java | 6 +- .../mappers/UserFederationMapperFactory.java | 4 +- .../migration/migrators/MigrateTo1_3_0.java | 2 +- .../models/FederatedIdentityModel.java | 21 + .../org/keycloak/models/KeycloakSession.java | 7 + .../java/org/keycloak/models/RealmModel.java | 10 +- .../UserCredentialAuthenticationProvider.java | 28 + .../UserCredentialValidatorProvider.java | 28 + .../models/UserCredentialValueModel.java | 10 +- .../models/UserFederationManager.java | 52 +- .../keycloak/models/UserLookupProvider.java | 29 + .../java/org/keycloak/models/UserModel.java | 6 - .../org/keycloak/models/UserProvider.java | 43 +- .../keycloak/models/UserQueryProvider.java | 44 ++ .../keycloak/models/UserUpdateProvider.java | 32 + .../keycloak/models/entities/RealmEntity.java | 22 +- .../entities/StorageProviderEntity.java | 65 ++ .../models/utils/CredentialValidation.java | 4 +- .../utils/FederatedCredentialValidation.java | 166 ++++ .../models/utils/FederatedCredentials.java | 186 +++++ .../models/utils/KeycloakModelUtils.java | 39 +- .../models/utils/RepresentationToModel.java | 2 +- .../models/utils/UserModelDelegate.java | 25 - .../java/org/keycloak/storage/StorageId.java | 65 ++ .../org/keycloak/storage/StorageProvider.java | 40 + .../storage/StorageProviderFactory.java | 62 ++ .../storage/StorageProviderModel.java | 89 +++ .../keycloak/storage/StorageProviderSpi.java | 49 ++ .../keycloak/storage/UserStorageManager.java | 549 +++++++++++++ .../keycloak/storage/UserStorageProvider.java | 28 + .../storage/adapter/AbstractUserAdapter.java | 162 ++++ .../keycloak/storage/changeset/UserData.java | 387 +++++++++ .../storage/changeset/UserDataAdapter.java | 340 ++++++++ .../UserDataCredentialValidator.java | 33 + .../storage/changeset/UserDataLookup.java | 31 + .../storage/changeset/UserDataQuery.java | 46 ++ .../storage/changeset/UserDataStore.java | 32 + .../storage/changeset/UserStorageManager.java | 341 ++++++++ .../storage/federated/CredentialModel.java | 24 + .../UserAttributeFederatedStorage.java | 35 + .../UserBrokerLinkFederatedStorage.java | 36 + .../UserConsentFederatedStorage.java | 35 + .../UserCredentialsFederatedStorage.java | 35 + .../UserFederatedStorageProvider.java | 58 ++ .../UserFederatedStorageProviderFactory.java | 26 + .../UserFederatedStorageProviderSpi.java | 51 ++ .../UserGroupMembershipFederatedStorage.java | 36 + .../UserRequiredActionsFederatedStorage.java | 32 + .../UserRoleMappingsFederatedStorage.java | 36 + .../services/org.keycloak.provider.Spi | 1 + .../exportimport/util/ExportUtils.java | 2 +- .../freemarker/model/ApplicationsBean.java | 2 +- .../oidc/endpoints/TokenEndpoint.java | 4 +- .../services/DefaultKeycloakSession.java | 19 + .../managers/AuthenticationManager.java | 4 +- .../services/managers/ClientManager.java | 4 +- .../services/resources/AccountService.java | 2 +- .../resources/LoginActionsService.java | 6 +- .../resources/admin/ClientResource.java | 4 +- .../resources/admin/UsersResource.java | 4 +- .../keycloak/testsuite/model/ImportTest.java | 10 +- .../testsuite/model/UserConsentModelTest.java | 38 +- .../testsuite/model/UserModelTest.java | 4 +- 88 files changed, 6747 insertions(+), 479 deletions(-) create mode 100644 model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java create mode 100755 model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java create mode 100644 model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java create mode 100644 model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java create mode 100755 model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java create mode 100644 server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java create mode 100755 server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java create mode 100755 server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java create mode 100644 server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/StorageId.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/StorageProvider.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java create mode 100755 server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java create mode 100644 server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java 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 be67c49ad7..3bfaad10ad 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 @@ -23,6 +23,7 @@ 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; @@ -739,6 +740,48 @@ 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(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 2b02cee883..d697ad236e 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -388,70 +388,6 @@ public class UserAdapter implements UserModel { return KeycloakModelUtils.isMember(roles, group); } - @Override - public void addConsent(UserConsentModel consent) { - getDelegateForUpdate(); - updated.addConsent(consent); - } - - @Override - public UserConsentModel getConsentByClient(String clientId) { - if (updated != null) return updated.getConsentByClient(clientId); - CachedUserConsent cachedConsent = cached.getConsents().get(clientId); - if (cachedConsent == null) { - return null; - } - - return toConsentModel(cachedConsent); - } - - @Override - public List getConsents() { - if (updated != null) return updated.getConsents(); - Collection cachedConsents = cached.getConsents().values(); - - List result = new LinkedList<>(); - for (CachedUserConsent cachedConsent : cachedConsents) { - UserConsentModel consent = toConsentModel(cachedConsent); - if (consent != null) { - result.add(consent); - } - } - return result; - } - - @Override - public void updateConsent(UserConsentModel consent) { - getDelegateForUpdate(); - updated.updateConsent(consent); - } - - @Override - public boolean revokeConsentForClient(String clientId) { - getDelegateForUpdate(); - return updated.revokeConsentForClient(clientId); - } - - private UserConsentModel toConsentModel(CachedUserConsent cachedConsent) { - ClientModel client = keycloakSession.realms().getClientById(cachedConsent.getClientDbId(), realm); - if (client == null) { - return null; - } - - UserConsentModel consentModel = new UserConsentModel(client); - - for (String roleId : cachedConsent.getRoleIds()) { - RoleModel role = keycloakSession.realms().getRoleById(roleId, realm); - if (role != null) { - consentModel.addGrantedRole(role); - } - } - for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) { - consentModel.addGrantedProtocolMapper(protocolMapper); - } - return consentModel; - } - @Override public boolean equals(Object o) { if (this == o) return true; 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 1a489ad698..c5e4c302b1 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 @@ -28,6 +28,7 @@ import org.keycloak.models.KeycloakTransaction; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; @@ -35,7 +36,10 @@ import org.keycloak.models.UserProvider; import org.keycloak.models.cache.CacheUserProvider; import org.keycloak.models.cache.infinispan.entities.CachedFederatedIdentityLinks; 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.*; @@ -73,7 +77,7 @@ public class UserCacheSession implements CacheUserProvider { public UserProvider getDelegate() { if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction"); if (delegate != null) return delegate; - delegate = session.getProvider(UserProvider.class); + delegate = session.userStorageManager(); return delegate; } @@ -342,7 +346,7 @@ public class UserCacheSession implements CacheUserProvider { } @Override - public UserModel getUserByServiceAccountClient(ClientModel client) { + public UserModel getServiceAccount(ClientModel client) { // Just an attempt to find the user from cache by default serviceAccount username String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId(); UserModel user = getUserByUsername(username, client.getRealm()); @@ -350,7 +354,7 @@ public class UserCacheSession implements CacheUserProvider { return user; } - return getDelegate().getUserByServiceAccountClient(client); + return getDelegate().getServiceAccount(client); } @Override @@ -368,6 +372,16 @@ public class UserCacheSession implements CacheUserProvider { return getDelegate().getUsers(realm, firstResult, maxResults, includeServiceAccounts); } + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, false); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return getUsers(realm, firstResult, maxResults, false); + } + @Override public List searchForUser(String search, RealmModel realm) { return getDelegate().searchForUser(search, realm); @@ -433,6 +447,101 @@ public class UserCacheSession implements CacheUserProvider { return null; } + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + invalidations.add(getConsentCacheKey(user.getId())); + getDelegate().updateConsent(realm, user, consent); + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { + invalidations.add(getConsentCacheKey(user.getId())); + return getDelegate().revokeConsentForClient(realm, user, clientInternalId); + } + + public String getConsentCacheKey(String userId) { + return userId + ".consents"; + } + + + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + invalidations.add(getConsentCacheKey(user.getId())); + getDelegate().addConsent(realm, user, consent); + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) { + logger.tracev("getConsentByClient: {0}", user.getUsername()); + + String cacheKey = getConsentCacheKey(user.getId()); + if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) { + return getDelegate().getConsentByClient(realm, user, clientId); + } + + CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class); + + if (cached == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List consents = getDelegate().getConsents(realm, user); + cached = new CachedUserConsents(loaded, cacheKey, realm, consents); + cache.addRevisioned(cached, startupRevision); + } + CachedUserConsent cachedConsent = cached.getConsents().get(clientId); + if (cachedConsent == null) return null; + return toConsentModel(realm, cachedConsent); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + logger.tracev("getConsents: {0}", user.getUsername()); + + String cacheKey = getConsentCacheKey(user.getId()); + if (realmInvalidations.contains(realm.getId()) || invalidations.contains(user.getId()) || invalidations.contains(cacheKey)) { + return getDelegate().getConsents(realm, user); + } + + CachedUserConsents cached = cache.get(cacheKey, CachedUserConsents.class); + + if (cached == null) { + Long loaded = cache.getCurrentRevision(cacheKey); + List consents = getDelegate().getConsents(realm, user); + cached = new CachedUserConsents(loaded, cacheKey, realm, consents); + cache.addRevisioned(cached, startupRevision); + return consents; + } else { + List result = new LinkedList<>(); + for (CachedUserConsent cachedConsent : cached.getConsents().values()) { + UserConsentModel consent = toConsentModel(realm, cachedConsent); + if (consent != null) { + result.add(consent); + } + } + return result; + } + } + + private UserConsentModel toConsentModel(RealmModel realm, CachedUserConsent cachedConsent) { + ClientModel client = session.realms().getClientById(cachedConsent.getClientDbId(), realm); + if (client == null) { + return null; + } + + UserConsentModel consentModel = new UserConsentModel(client); + + for (String roleId : cachedConsent.getRoleIds()) { + RoleModel role = session.realms().getRoleById(roleId, realm); + if (role != null) { + consentModel.addGrantedRole(role); + } + } + for (ProtocolMapperModel protocolMapper : cachedConsent.getProtocolMappers()) { + consentModel.addGrantedProtocolMapper(protocolMapper); + } + return consentModel; + } + + @Override public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { UserModel user = getDelegate().addUser(realm, id, username, addDefaultRoles, addDefaultRoles); @@ -554,4 +663,9 @@ public class UserCacheSession implements CacheUserProvider { public void preRemove(ProtocolMapperModel protocolMapper) { getDelegate().preRemove(protocolMapper); } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel provider) { + getDelegate().preRemove(realm, provider); + } } 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 b93da32933..0181ad7a06 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 @@ -37,6 +37,7 @@ 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; @@ -109,6 +110,7 @@ public class CachedRealm extends AbstractRevisioned { protected List requiredCredentials; protected List userFederationProviders; + protected List storageProviders; protected MultivaluedHashMap userFederationMappers = new MultivaluedHashMap(); protected Set userFederationMapperSet; protected List identityProviders; @@ -204,6 +206,7 @@ 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); @@ -592,4 +595,8 @@ public class CachedRealm extends AbstractRevisioned { public List getRequiredActionProviderList() { return requiredActionProviderList; } + + public List getStorageProviders() { + return storageProviders; + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java index f37ecf1936..89049cae99 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUser.java @@ -53,7 +53,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm { private Set requiredActions = new HashSet<>(); private Set roleMappings = new HashSet<>(); private Set groups = new HashSet<>(); - private Map consents = new HashMap<>(); // Key is client DB Id @@ -82,13 +81,6 @@ public class CachedUser extends AbstractRevisioned implements InRealm { groups.add(group.getId()); } } - - List consents = user.getConsents(); - if (consents != null) { - for (UserConsentModel consent : consents) { - this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent)); - } - } } public String getRealm() { @@ -155,7 +147,4 @@ public class CachedUser extends AbstractRevisioned implements InRealm { return groups; } - public Map getConsents() { - return consents; - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java new file mode 100644 index 0000000000..eaca957688 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsents.java @@ -0,0 +1,53 @@ +/* + * 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.cache.infinispan.entities; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserConsentModel; + +import java.util.HashMap; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CachedUserConsents extends AbstractRevisioned implements InRealm { + private HashMap consents = new HashMap<>(); + private final String realmId; + + public CachedUserConsents(Long revision, String id, RealmModel realm, + List consents) { + super(revision, id); + this.realmId = realm.getId(); + if (consents != null) { + for (UserConsentModel consent : consents) { + this.consents.put(consent.getClient().getId(), new CachedUserConsent(consent)); + } + } + } + + @Override + public String getRealm() { + return realmId; + } + + + public HashMap getConsents() { + return consents; + } +} 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 040d6eb38e..0349a678d6 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 @@ -22,23 +22,31 @@ import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.jpa.entities.FederatedIdentityEntity; import org.keycloak.models.jpa.entities.UserAttributeEntity; +import org.keycloak.models.jpa.entities.UserConsentEntity; +import org.keycloak.models.jpa.entities.UserConsentProtocolMapperEntity; +import org.keycloak.models.jpa.entities.UserConsentRoleEntity; import org.keycloak.models.jpa.entities.UserEntity; import org.keycloak.models.utils.CredentialValidation; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.StorageProviderModel; import javax.persistence.EntityManager; import javax.persistence.TypedQuery; import java.util.ArrayList; +import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -173,6 +181,164 @@ public class JpaUserProvider implements UserProvider { } } + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + + UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId); + if (consentEntity != null) { + throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); + } + + consentEntity = new UserConsentEntity(); + consentEntity.setId(KeycloakModelUtils.generateId()); + consentEntity.setUser(em.getReference(UserEntity.class, user.getId())); + consentEntity.setClientId(clientId); + em.persist(consentEntity); + em.flush(); + + updateGrantedConsentEntity(consentEntity, consent); + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) { + UserConsentEntity entity = getGrantedConsentEntity(user, clientId); + return toConsentModel(realm, entity); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class); + query.setParameter("userId", user.getId()); + List results = query.getResultList(); + + List consents = new ArrayList(); + for (UserConsentEntity entity : results) { + UserConsentModel model = toConsentModel(realm, entity); + consents.add(model); + } + return consents; + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + + UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId); + if (consentEntity == null) { + throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]"); + } + + updateGrantedConsentEntity(consentEntity, consent); + } + + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) { + UserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId); + if (consentEntity == null) return false; + + em.remove(consentEntity); + em.flush(); + return true; + } + + + private UserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) { + TypedQuery query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class); + query.setParameter("userId", user.getId()); + query.setParameter("clientId", clientId); + List results = query.getResultList(); + if (results.size() > 1) { + throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]"); + } else if (results.size() == 1) { + return results.get(0); + } else { + return null; + } + } + + private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) { + if (entity == null) { + return null; + } + + ClientModel client = realm.getClientById(entity.getClientId()); + if (client == null) { + throw new ModelException("Client with id " + entity.getClientId() + " is not available"); + } + UserConsentModel model = new UserConsentModel(client); + + Collection grantedRoleEntities = entity.getGrantedRoles(); + if (grantedRoleEntities != null) { + for (UserConsentRoleEntity grantedRole : grantedRoleEntities) { + RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId()); + if (grantedRoleModel != null) { + model.addGrantedRole(grantedRoleModel); + } + } + } + + Collection grantedProtocolMapperEntities = entity.getGrantedProtocolMappers(); + if (grantedProtocolMapperEntities != null) { + for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) { + ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId()); + model.addGrantedProtocolMapper(protocolMapper ); + } + } + + return model; + } + + // Update roles and protocolMappers to given consentEntity from the consentModel + private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) { + Collection grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers(); + Collection mappersToRemove = new HashSet(grantedProtocolMapperEntities); + + for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) { + UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity(); + grantedProtocolMapperEntity.setUserConsent(consentEntity); + grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId()); + + // Check if it's already there + if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) { + em.persist(grantedProtocolMapperEntity); + em.flush(); + grantedProtocolMapperEntities.add(grantedProtocolMapperEntity); + } else { + mappersToRemove.remove(grantedProtocolMapperEntity); + } + } + // Those mappers were no longer on consentModel and will be removed + for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) { + grantedProtocolMapperEntities.remove(toRemove); + em.remove(toRemove); + } + + Collection grantedRoleEntities = consentEntity.getGrantedRoles(); + Set rolesToRemove = new HashSet(grantedRoleEntities); + for (RoleModel role : consentModel.getGrantedRoles()) { + UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity(); + consentRoleEntity.setUserConsent(consentEntity); + consentRoleEntity.setRoleId(role.getId()); + + // Check if it's already there + if (!grantedRoleEntities.contains(consentRoleEntity)) { + em.persist(consentRoleEntity); + em.flush(); + grantedRoleEntities.add(consentRoleEntity); + } else { + rolesToRemove.remove(consentRoleEntity); + } + } + // Those roles were no longer on consentModel and will be removed + for (UserConsentRoleEntity toRemove : rolesToRemove) { + grantedRoleEntities.remove(toRemove); + em.remove(toRemove); + } + + em.flush(); + } + + @Override public void grantToAllUsers(RealmModel realm, RoleModel role) { int num = em.createNamedQuery("grantRoleToAllUsers") @@ -324,7 +490,7 @@ public class JpaUserProvider implements UserProvider { } @Override - public UserModel getUserByServiceAccountClient(ClientModel client) { + public UserModel getServiceAccount(ClientModel client) { TypedQuery query = em.createNamedQuery("getRealmUserByServiceAccount", UserEntity.class); query.setParameter("realmId", client.getRealm().getId()); query.setParameter("clientInternalId", client.getId()); @@ -353,6 +519,16 @@ public class JpaUserProvider implements UserProvider { return ((Number)count).intValue(); } + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, false); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return getUsers(realm, firstResult, maxResults, false); + } + @Override public List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { String queryName = includeServiceAccounts ? "getAllUsersByRealm" : "getAllUsersByRealmExcludeServiceAccount" ; @@ -529,4 +705,9 @@ public class JpaUserProvider implements UserProvider { // Not supported yet return null; } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel link) { + + } } 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 4167ebc1a0..bfdc6012d2 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 @@ -42,6 +42,8 @@ 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; @@ -805,6 +807,15 @@ public class RealmAdapter implements RealmModel, JpaModel { em.flush(); } + + private void removeFederationMappersForProvider(String federationProviderId) { + Set mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); + for (UserFederationMapperEntity mapper : mappers) { + realm.getUserFederationMappers().remove(mapper); + em.remove(mapper); + } + } + @Override public List getUserFederationProviders() { List entities = realm.getUserFederationProviders(); @@ -875,15 +886,6 @@ public class RealmAdapter implements RealmModel, JpaModel { } } } - - private void removeFederationMappersForProvider(String federationProviderId) { - Set mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); - for (UserFederationMapperEntity mapper : mappers) { - realm.getUserFederationMappers().remove(mapper); - em.remove(mapper); - } - } - @Override public void updateUserFederationProvider(UserFederationProviderModel model) { KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); @@ -994,6 +996,186 @@ 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); + + } + Collections.sort(copy, new Comparator() { + + @Override + public int compare(StorageProviderEntity o1, StorageProviderEntity o2) { + return o1.getPriority() - o2.getPriority(); + } + + }); + List result = new LinkedList<>(); + for (StorageProviderEntity entity : copy) { + result.add(toModel(entity)); + } + + 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); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 9cce4044f5..5d51fc939e 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -668,163 +668,6 @@ public class UserAdapter implements UserModel, JpaModel { user.setServiceAccountClientLink(clientInternalId); } - @Override - public void addConsent(UserConsentModel consent) { - String clientId = consent.getClient().getId(); - - UserConsentEntity consentEntity = getGrantedConsentEntity(clientId); - if (consentEntity != null) { - throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); - } - - consentEntity = new UserConsentEntity(); - consentEntity.setId(KeycloakModelUtils.generateId()); - consentEntity.setUser(user); - consentEntity.setClientId(clientId); - em.persist(consentEntity); - em.flush(); - - updateGrantedConsentEntity(consentEntity, consent); - } - - @Override - public UserConsentModel getConsentByClient(String clientId) { - UserConsentEntity entity = getGrantedConsentEntity(clientId); - return toConsentModel(entity); - } - - @Override - public List getConsents() { - TypedQuery query = em.createNamedQuery("userConsentsByUser", UserConsentEntity.class); - query.setParameter("userId", getId()); - List results = query.getResultList(); - - List consents = new ArrayList(); - for (UserConsentEntity entity : results) { - UserConsentModel model = toConsentModel(entity); - consents.add(model); - } - return consents; - } - - @Override - public void updateConsent(UserConsentModel consent) { - String clientId = consent.getClient().getId(); - - UserConsentEntity consentEntity = getGrantedConsentEntity(clientId); - if (consentEntity == null) { - throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]"); - } - - updateGrantedConsentEntity(consentEntity, consent); - } - - @Override - public boolean revokeConsentForClient(String clientId) { - UserConsentEntity consentEntity = getGrantedConsentEntity(clientId); - if (consentEntity == null) return false; - - em.remove(consentEntity); - em.flush(); - return true; - } - - - private UserConsentEntity getGrantedConsentEntity(String clientId) { - TypedQuery query = em.createNamedQuery("userConsentByUserAndClient", UserConsentEntity.class); - query.setParameter("userId", getId()); - query.setParameter("clientId", clientId); - List results = query.getResultList(); - if (results.size() > 1) { - throw new ModelException("More results found for user [" + getUsername() + "] and client [" + clientId + "]"); - } else if (results.size() == 1) { - return results.get(0); - } else { - return null; - } - } - - private UserConsentModel toConsentModel(UserConsentEntity entity) { - if (entity == null) { - return null; - } - - ClientModel client = realm.getClientById(entity.getClientId()); - if (client == null) { - throw new ModelException("Client with id " + entity.getClientId() + " is not available"); - } - UserConsentModel model = new UserConsentModel(client); - - Collection grantedRoleEntities = entity.getGrantedRoles(); - if (grantedRoleEntities != null) { - for (UserConsentRoleEntity grantedRole : grantedRoleEntities) { - RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId()); - if (grantedRoleModel != null) { - model.addGrantedRole(grantedRoleModel); - } - } - } - - Collection grantedProtocolMapperEntities = entity.getGrantedProtocolMappers(); - if (grantedProtocolMapperEntities != null) { - for (UserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) { - ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId()); - model.addGrantedProtocolMapper(protocolMapper ); - } - } - - return model; - } - - // Update roles and protocolMappers to given consentEntity from the consentModel - private void updateGrantedConsentEntity(UserConsentEntity consentEntity, UserConsentModel consentModel) { - Collection grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers(); - Collection mappersToRemove = new HashSet(grantedProtocolMapperEntities); - - for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) { - UserConsentProtocolMapperEntity grantedProtocolMapperEntity = new UserConsentProtocolMapperEntity(); - grantedProtocolMapperEntity.setUserConsent(consentEntity); - grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId()); - - // Check if it's already there - if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) { - em.persist(grantedProtocolMapperEntity); - em.flush(); - grantedProtocolMapperEntities.add(grantedProtocolMapperEntity); - } else { - mappersToRemove.remove(grantedProtocolMapperEntity); - } - } - // Those mappers were no longer on consentModel and will be removed - for (UserConsentProtocolMapperEntity toRemove : mappersToRemove) { - grantedProtocolMapperEntities.remove(toRemove); - em.remove(toRemove); - } - - Collection grantedRoleEntities = consentEntity.getGrantedRoles(); - Set rolesToRemove = new HashSet(grantedRoleEntities); - for (RoleModel role : consentModel.getGrantedRoles()) { - UserConsentRoleEntity consentRoleEntity = new UserConsentRoleEntity(); - consentRoleEntity.setUserConsent(consentEntity); - consentRoleEntity.setRoleId(role.getId()); - - // Check if it's already there - if (!grantedRoleEntities.contains(consentRoleEntity)) { - em.persist(consentRoleEntity); - em.flush(); - grantedRoleEntities.add(consentRoleEntity); - } else { - rolesToRemove.remove(consentRoleEntity); - } - } - // Those roles were no longer on consentModel and will be removed - for (UserConsentRoleEntity toRemove : rolesToRemove) { - grantedRoleEntities.remove(toRemove); - em.remove(toRemove); - } - - em.flush(); - } @Override public boolean equals(Object o) { 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 5ad023afc4..3c9ae5fd22 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 @@ -136,13 +136,16 @@ public class RealmEntity { protected String emailTheme; @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - Collection attributes = new ArrayList(); + Collection attributes = new ArrayList<>(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - Collection requiredCredentials = new ArrayList(); + Collection requiredCredentials = new ArrayList<>(); @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") - List userFederationProviders = new ArrayList(); + 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(); @@ -551,6 +554,14 @@ 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/models/jpa/entities/StorageProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java new file mode 100755 index 0000000000..f663795007 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/StorageProviderEntity.java @@ -0,0 +1,131 @@ +/* + * 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.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.Table; +import java.util.Map; + +/** + * @author Marek Posolda + * @author Bill Burke + */ +@Entity +@Table(name="STORAGE_PROVIDER") +public class StorageProviderEntity { + + @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 = "REALM_ID") + protected RealmEntity realm; + + @Column(name="PROVIDER_NAME") + private String providerName; + @Column(name="PRIORITY") + private int priority; + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="VALUE") + @CollectionTable(name="STORAGE_PROVIDER_CONFIG", joinColumns={ @JoinColumn(name="STORAGE_PROVIDER_ID") }) + private Map config; + + @Column(name="DISPLAY_NAME") + private String displayName; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public RealmEntity getRealm() { + return realm; + } + + public void setRealm(RealmEntity realm) { + 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; + + StorageProviderEntity that = (StorageProviderEntity) 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/storage/jpa/JpaUserFederatedStorageProvider.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java new file mode 100644 index 0000000000..b206cc5073 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProvider.java @@ -0,0 +1,732 @@ +/* + * 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.jpa; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.ClientModel; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; +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.federated.UserAttributeFederatedStorage; +import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage; +import org.keycloak.storage.federated.UserConsentFederatedStorage; +import org.keycloak.storage.federated.UserCredentialsFederatedStorage; +import org.keycloak.storage.federated.UserFederatedStorageProvider; +import org.keycloak.storage.federated.UserGroupMembershipFederatedStorage; +import org.keycloak.storage.federated.UserRequiredActionsFederatedStorage; +import org.keycloak.storage.federated.UserRoleMappingsFederatedStorage; +import org.keycloak.storage.jpa.entity.BrokerLinkEntity; +import org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity; +import org.keycloak.storage.jpa.entity.FederatedUserConsentEntity; +import org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity; +import org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity; +import org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity; +import org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity; +import org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity; +import org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JpaUserFederatedStorageProvider implements + UserFederatedStorageProvider, + UserAttributeFederatedStorage, + UserBrokerLinkFederatedStorage, + UserConsentFederatedStorage, + UserCredentialsFederatedStorage, + UserGroupMembershipFederatedStorage, + UserRequiredActionsFederatedStorage, + UserRoleMappingsFederatedStorage { + + private final KeycloakSession session; + protected EntityManager em; + + public JpaUserFederatedStorageProvider(KeycloakSession session, EntityManager em) { + this.session = session; + this.em = em; + } + + @Override + public void close() { + + } + + + @Override + public void setAttribute(RealmModel realm, UserModel user, String name, List values) { + deleteAttribute(realm, user, name); + em.flush(); + for (String value : values) { + persistAttributeValue(realm, user, name, value); + } + } + + private void deleteAttribute(RealmModel realm, UserModel user, String name) { + em.createNamedQuery("deleteUserFederatedAttributesByUserAndName") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .setParameter("name", name) + .executeUpdate(); + } + + private void persistAttributeValue(RealmModel realm, UserModel user, String name, String value) { + FederatedUserAttributeEntity attr = new FederatedUserAttributeEntity(); + attr.setId(KeycloakModelUtils.generateId()); + attr.setName(name); + attr.setValue(value); + attr.setUserId(user.getId()); + attr.setRealmId(realm.getId()); + attr.setStorageProviderId(StorageId.resolveProviderId(user)); + em.persist(attr); + } + + @Override + public void setSingleAttribute(RealmModel realm, UserModel user, String name, String value) { + deleteAttribute(realm, user, name); + em.flush(); + persistAttributeValue(realm, user, name, value); + } + + @Override + public void removeAttribute(RealmModel realm, UserModel user, String name) { + deleteAttribute(realm, user, name); + em.flush(); + } + + @Override + public MultivaluedHashMap getAttributes(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("getFederatedAttributesByUser", FederatedUserAttributeEntity.class); + List list = query + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .getResultList(); + MultivaluedHashMap result = new MultivaluedHashMap<>(); + for (FederatedUserAttributeEntity entity : list) { + result.add(entity.getName(), entity.getValue()); + + } + return result; + } + + @Override + public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) { + TypedQuery query = em.createNamedQuery("findBrokerLinkByUserAndProvider", String.class) + .setParameter("realmid", realm.getId()) + .setParameter("identityProvider", link.getIdentityProvider()) + .setParameter("brokerUserId", link.getUserId()); + List results = query.getResultList(); + if (results.isEmpty()) { + return null; + } else if (results.size() > 1) { + throw new IllegalStateException("More results found for identityProvider=" + link.getIdentityProvider() + + ", userId=" + link.getUserId() + ", results=" + results); + } else { + return results.get(0); + } + } + + @Override + public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel link) { + BrokerLinkEntity entity = new BrokerLinkEntity(); + entity.setRealmId(realm.getId()); + entity.setUserId(user.getId()); + entity.setBrokerUserId(link.getUserId()); + entity.setIdentityProvider(link.getIdentityProvider()); + entity.setToken(link.getToken()); + entity.setBrokerUserName(link.getUserName()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + em.persist(entity); + + } + + @Override + public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { + BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider); + if (entity == null) return false; + em.remove(entity); + return true; + } + + private BrokerLinkEntity getBrokerLinkEntity(UserModel user, String socialProvider) { + TypedQuery query = em.createNamedQuery("findBrokerLinkByUserAndProvider", BrokerLinkEntity.class) + .setParameter("userId", user.getId()) + .setParameter("identityProvider", socialProvider); + List results = query.getResultList(); + return results.size() > 0 ? results.get(0) : null; + } + + @Override + public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) { + BrokerLinkEntity entity = getBrokerLinkEntity(user, model.getIdentityProvider()); + if (entity == null) return; + entity.setBrokerUserName(model.getUserName()); + entity.setBrokerUserId(model.getUserId()); + entity.setToken(model.getToken()); + em.persist(entity); + em.flush(); + + } + + @Override + public Set getFederatedIdentities(UserModel user, RealmModel realm) { + TypedQuery query = em.createNamedQuery("findBrokerLinkByUser", BrokerLinkEntity.class) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + Set set = new HashSet<>(); + for (BrokerLinkEntity entity : results) { + FederatedIdentityModel model = new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken()); + set.add(model); + } + return set; + } + + @Override + public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { + BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider); + if (entity == null) return null; + return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken()); + } + + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + + FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId); + if (consentEntity != null) { + throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); + } + + consentEntity = new FederatedUserConsentEntity(); + consentEntity.setId(KeycloakModelUtils.generateId()); + consentEntity.setUserId(user.getId()); + consentEntity.setClientId(clientId); + consentEntity.setStorageProviderId(StorageId.resolveProviderId(user)); + em.persist(consentEntity); + em.flush(); + + updateGrantedConsentEntity(consentEntity, consent); + + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { + FederatedUserConsentEntity entity = getGrantedConsentEntity(user, clientInternalId); + return toConsentModel(realm, entity); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("userFederatedConsentsByUser", FederatedUserConsentEntity.class); + query.setParameter("userId", user.getId()); + List results = query.getResultList(); + + List consents = new ArrayList(); + for (FederatedUserConsentEntity entity : results) { + UserConsentModel model = toConsentModel(realm, entity); + consents.add(model); + } + return consents; + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + + FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientId); + if (consentEntity == null) { + throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]"); + } + + updateGrantedConsentEntity(consentEntity, consent); + + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { + FederatedUserConsentEntity consentEntity = getGrantedConsentEntity(user, clientInternalId); + if (consentEntity == null) return false; + + em.remove(consentEntity); + em.flush(); + return true; + } + + private FederatedUserConsentEntity getGrantedConsentEntity(UserModel user, String clientId) { + TypedQuery query = em.createNamedQuery("userFederatedConsentByUserAndClient", FederatedUserConsentEntity.class); + query.setParameter("userId", user.getId()); + query.setParameter("clientId", clientId); + List results = query.getResultList(); + if (results.size() > 1) { + throw new ModelException("More results found for user [" + user.getUsername() + "] and client [" + clientId + "]"); + } else if (results.size() == 1) { + return results.get(0); + } else { + return null; + } + } + + + private UserConsentModel toConsentModel(RealmModel realm, FederatedUserConsentEntity entity) { + if (entity == null) { + return null; + } + + ClientModel client = realm.getClientById(entity.getClientId()); + if (client == null) { + throw new ModelException("Client with id " + entity.getClientId() + " is not available"); + } + UserConsentModel model = new UserConsentModel(client); + + Collection grantedRoleEntities = entity.getGrantedRoles(); + if (grantedRoleEntities != null) { + for (FederatedUserConsentRoleEntity grantedRole : grantedRoleEntities) { + RoleModel grantedRoleModel = realm.getRoleById(grantedRole.getRoleId()); + if (grantedRoleModel != null) { + model.addGrantedRole(grantedRoleModel); + } + } + } + + Collection grantedProtocolMapperEntities = entity.getGrantedProtocolMappers(); + if (grantedProtocolMapperEntities != null) { + for (FederatedUserConsentProtocolMapperEntity grantedProtMapper : grantedProtocolMapperEntities) { + ProtocolMapperModel protocolMapper = client.getProtocolMapperById(grantedProtMapper.getProtocolMapperId()); + model.addGrantedProtocolMapper(protocolMapper); + } + } + + return model; + } + + // Update roles and protocolMappers to given consentEntity from the consentModel + private void updateGrantedConsentEntity(FederatedUserConsentEntity consentEntity, UserConsentModel consentModel) { + Collection grantedProtocolMapperEntities = consentEntity.getGrantedProtocolMappers(); + Collection mappersToRemove = new HashSet<>(grantedProtocolMapperEntities); + + for (ProtocolMapperModel protocolMapper : consentModel.getGrantedProtocolMappers()) { + FederatedUserConsentProtocolMapperEntity grantedProtocolMapperEntity = new FederatedUserConsentProtocolMapperEntity(); + grantedProtocolMapperEntity.setUserConsent(consentEntity); + grantedProtocolMapperEntity.setProtocolMapperId(protocolMapper.getId()); + + // Check if it's already there + if (!grantedProtocolMapperEntities.contains(grantedProtocolMapperEntity)) { + em.persist(grantedProtocolMapperEntity); + em.flush(); + grantedProtocolMapperEntities.add(grantedProtocolMapperEntity); + } else { + mappersToRemove.remove(grantedProtocolMapperEntity); + } + } + // Those mappers were no longer on consentModel and will be removed + for (FederatedUserConsentProtocolMapperEntity toRemove : mappersToRemove) { + grantedProtocolMapperEntities.remove(toRemove); + em.remove(toRemove); + } + + Collection grantedRoleEntities = consentEntity.getGrantedRoles(); + Set rolesToRemove = new HashSet<>(grantedRoleEntities); + for (RoleModel role : consentModel.getGrantedRoles()) { + FederatedUserConsentRoleEntity consentRoleEntity = new FederatedUserConsentRoleEntity(); + consentRoleEntity.setUserConsent(consentEntity); + consentRoleEntity.setRoleId(role.getId()); + + // Check if it's already there + if (!grantedRoleEntities.contains(consentRoleEntity)) { + em.persist(consentRoleEntity); + em.flush(); + grantedRoleEntities.add(consentRoleEntity); + } else { + rolesToRemove.remove(consentRoleEntity); + } + } + // Those roles were no longer on consentModel and will be removed + for (FederatedUserConsentRoleEntity toRemove : rolesToRemove) { + grantedRoleEntities.remove(toRemove); + em.remove(toRemove); + } + + em.flush(); + } + + + + @Override + public List getCredentials(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("federatedUserCredentialByUser", FederatedUserCredentialEntity.class) + .setParameter("userId", user.getId()); + List results = query.getResultList(); + List list = new LinkedList<>(); + for (FederatedUserCredentialEntity credEntity : results) { + UserCredentialValueModel credModel = new UserCredentialValueModel(); + credModel.setId(credEntity.getId()); + credModel.setType(credEntity.getType()); + credModel.setDevice(credEntity.getDevice()); + credModel.setValue(credEntity.getValue()); + credModel.setCreatedDate(credEntity.getCreatedDate()); + credModel.setSalt(credEntity.getSalt()); + credModel.setHashIterations(credEntity.getHashIterations()); + credModel.setCounter(credEntity.getCounter()); + credModel.setAlgorithm(credEntity.getAlgorithm()); + credModel.setDigits(credEntity.getDigits()); + credModel.setPeriod(credEntity.getPeriod()); + + list.add(credModel); + } + return list; + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred) { + FederatedCredentials.updateCredential(session, this, realm, user, cred); + + } + + @Override + public void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) { + FederatedUserCredentialEntity entity = null; + if (cred.getId() != null) entity = em.find(FederatedUserCredentialEntity.class, cred.getId()); + boolean newEntity = false; + if (entity == null) { + entity = new FederatedUserCredentialEntity(); + entity.setId(KeycloakModelUtils.generateId()); + newEntity = true; + } + entity.setUserId(user.getId()); + entity.setRealmId(realm.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + entity.setAlgorithm(cred.getAlgorithm()); + entity.setCounter(cred.getCounter()); + Long createdDate = cred.getCreatedDate(); + if (createdDate == null) createdDate = System.currentTimeMillis(); + entity.setCreatedDate(createdDate); + entity.setDevice(cred.getDevice()); + entity.setDigits(cred.getDigits()); + entity.setHashIterations(cred.getHashIterations()); + entity.setPeriod(cred.getPeriod()); + entity.setSalt(cred.getSalt()); + entity.setType(cred.getType()); + entity.setValue(cred.getValue()); + if (newEntity) { + em.persist(entity); + } + + } + + @Override + public void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred) { + FederatedUserCredentialEntity entity = em.find(FederatedUserCredentialEntity.class, cred.getId()); + em.remove(entity); + } + + @Override + public Set getGroups(RealmModel realm, UserModel user) { + Set set = new HashSet<>(); + TypedQuery query = em.createNamedQuery("feduserGroupMembership", FederatedUserGroupMembershipEntity.class); + query.setParameter("userId", user.getId()); + List results = query.getResultList(); + if (results.size() == 0) return set; + for (FederatedUserGroupMembershipEntity entity : results) { + GroupModel group = realm.getGroupById(entity.getGroupId()); + set.add(group); + } + return set; + } + + @Override + public void joinGroup(RealmModel realm, UserModel user, GroupModel group) { + if (isMemberOf(realm, user, group)) return; + FederatedUserGroupMembershipEntity entity = new FederatedUserGroupMembershipEntity(); + entity.setUserId(user.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + entity.setGroupId(group.getId()); + entity.setRealmId(realm.getId()); + em.persist(entity); + + } + + public boolean isMemberOf(RealmModel realm, UserModel user, GroupModel group) { + Set roles = user.getGroups(); + return KeycloakModelUtils.isMember(roles, group); + } + + + @Override + public void leaveGroup(RealmModel realm, UserModel user, GroupModel group) { + if (user == null || group == null) return; + + TypedQuery query1 = em.createNamedQuery("feduserMemberOf", FederatedUserGroupMembershipEntity.class); + query1.setParameter("userId", user.getId()); + query1.setParameter("groupId", group.getId()); + TypedQuery query = query1; + List results = query.getResultList(); + if (results.size() == 0) return; + for (FederatedUserGroupMembershipEntity entity : results) { + em.remove(entity); + } + em.flush(); + + } + + + @Override + public Set getRequiredActions(RealmModel realm, UserModel user) { + Set set = new HashSet<>(); + List values = getRequiredActionEntities(realm, user); + for (FederatedUserRequiredActionEntity entity : values) { + set.add(entity.getAction()); + } + + return set; + + } + + private List getRequiredActionEntities(RealmModel realm, UserModel user) { + TypedQuery query = em.createNamedQuery("getFederatedUserRequiredActionsByUser", FederatedUserRequiredActionEntity.class) + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()); + return query.getResultList(); + } + + @Override + public void addRequiredAction(RealmModel realm, UserModel user, String action) { + if (user.getRequiredActions().contains(action)) return; + FederatedUserRequiredActionEntity entity = new FederatedUserRequiredActionEntity(); + entity.setUserId(user.getId()); + entity.setRealmId(realm.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + entity.setAction(action); + em.persist(entity); + + } + + @Override + public void removeRequiredAction(RealmModel realm, UserModel user, String action) { + List values = getRequiredActionEntities(realm, user); + for (FederatedUserRequiredActionEntity entity : values) { + if (action.equals(entity.getAction())) em.remove(entity); + } + em.flush(); + + } + + @Override + public void grantRole(RealmModel realm, UserModel user, RoleModel role) { + if (user.hasRole(role)) return; + FederatedUserRoleMappingEntity entity = new FederatedUserRoleMappingEntity(); + entity.setUserId(user.getId()); + entity.setStorageProviderId(StorageId.resolveProviderId(user)); + entity.setRealmId(realm.getId()); + entity.setRoleId(role.getId()); + em.persist(entity); + + } + + @Override + public Set getRoleMappings(RealmModel realm, UserModel user) { + Set set = new HashSet<>(); + TypedQuery query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class); + query.setParameter("userId", user.getId()); + List results = query.getResultList(); + if (results.size() == 0) return set; + for (FederatedUserRoleMappingEntity entity : results) { + RoleModel role = realm.getRoleById(entity.getRoleId()); + set.add(role); + } + return set; + } + + @Override + public void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role) { + TypedQuery query = em.createNamedQuery("feduserRoleMappings", FederatedUserRoleMappingEntity.class); + query.setParameter("userId", user.getId()); + List results = query.getResultList(); + for (FederatedUserRoleMappingEntity entity : results) { + if (entity.getRoleId().equals(role.getId())) em.remove(entity); + + } + em.flush(); + } + + @Override + public void preRemove(RealmModel realm) { + int num = em.createNamedQuery("deleteUserConsentRolesByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserConsentsByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteBrokerLinkByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserCredentialsByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteUserFederatedAttributesByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserGroupMembershipByRealm") + .setParameter("realmId", realm.getId()).executeUpdate(); + } + + @Override + public void preRemove(RealmModel realm, UserFederationProviderModel link) { + int num = em.createNamedQuery("deleteFederatedUserRoleMappingsByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserRequiredActionsByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); + num = em.createNamedQuery("deleteBrokerLinkByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); + num = em.createNamedQuery("deleteFederatedUserCredentialsByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); + num = em.createNamedQuery("deleteUserFederatedAttributesByRealmAndLink") + .setParameter("realmId", realm.getId()) + .setParameter("link", link.getId()) + .executeUpdate(); + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate(); + em.createNamedQuery("deleteFederatedUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate(); + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + em.createNamedQuery("deleteFederatedUserGroupMembershipsByGroup").setParameter("groupId", group.getId()).executeUpdate(); + } + + @Override + public void preRemove(RealmModel realm, ClientModel client) { + em.createNamedQuery("deleteFederatedUserConsentProtMappersByClient").setParameter("clientId", client.getId()).executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentRolesByClient").setParameter("clientId", client.getId()).executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentsByClient").setParameter("clientId", client.getId()).executeUpdate(); + } + + @Override + public void preRemove(ProtocolMapperModel protocolMapper) { + em.createNamedQuery("deleteFederatedUserConsentProtMappersByProtocolMapper") + .setParameter("protocolMapperId", protocolMapper.getId()) + .executeUpdate(); + } + + @Override + public void preRemove(RealmModel realm, UserModel user) { + em.createNamedQuery("deleteBrokerLinkByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteUserFederatedAttributesByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentProtMappersByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentRolesByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentsByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserCredentialByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserGroupMembershipsByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("getFederatedUserRequiredActionsByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserRoleMappingsByUser") + .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) + .executeUpdate(); + + } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel model) { + em.createNamedQuery("deleteBrokerLinkByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedAttributesByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentProtMappersByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserConsentsByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserCredentialsByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserGroupMembershipByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserRequiredActionsByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + em.createNamedQuery("deleteFederatedUserRoleMappingsByStorageProvider") + .setParameter("storageProviderId", model.getId()) + .executeUpdate(); + + } +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java new file mode 100644 index 0000000000..0a3218c57d --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/JpaUserFederatedStorageProviderFactory.java @@ -0,0 +1,58 @@ +/* + * 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.jpa; + +import org.keycloak.Config; +import org.keycloak.connections.jpa.JpaConnectionProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.storage.federated.UserFederatedStorageProvider; +import org.keycloak.storage.federated.UserFederatedStorageProviderFactory; + +import javax.persistence.EntityManager; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class JpaUserFederatedStorageProviderFactory implements UserFederatedStorageProviderFactory { + @Override + public UserFederatedStorageProvider create(KeycloakSession session) { + EntityManager em = session.getProvider(JpaConnectionProvider.class).getEntityManager(); + return new JpaUserFederatedStorageProvider(session, em); + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "jpa"; + } +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java new file mode 100755 index 0000000000..90ca5f1e9b --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java @@ -0,0 +1,191 @@ +/* + * 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.jpa.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name= "findBrokerLinkByUser", query="select link from BrokerLinkEntity link where link.userId = :userId"), + @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider"), + @NamedQuery(name= "findUserByBrokerLinkAndRealm", query="select link.userId from BrokerLinkEntity link where link.realmId = :realmId and link.identityProvider = :identityProvider and link.brokerUserId = :brokerUserId"), + @NamedQuery(name= "deleteBrokerLinkByStorageProvider", query="delete from BrokerLinkEntity social where social.storageProviderId = :storageProviderId"), + @NamedQuery(name= "deleteBrokerLinkByRealm", query="delete from BrokerLinkEntity social where social.realmId = :realmId"), + @NamedQuery(name= "deleteBrokerLinkByRealmAndLink", query="delete from BrokerLinkEntity social where social.userId IN (select u.id from UserEntity u where realmId=:realmId and u.federationLink=:link)"), + @NamedQuery(name= "deleteBrokerLinkByUser", query="delete from BrokerLinkEntity social where social.userId = :userId and social.realmId = :realmId") +}) +@Table(name="BROKER_LINK") +@Entity +@IdClass(BrokerLinkEntity.Key.class) +public class BrokerLinkEntity { + + @Id + private String userId; + + @Id + @Column(name = "IDENTITY_PROVIDER") + protected String identityProvider; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + @Column(name = "BROKER_USER_ID") + protected String brokerUserId; + @Column(name = "BROKER_USERNAME") + protected String brokerUserName; + + @Column(name = "TOKEN") + protected String token; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getIdentityProvider() { + return identityProvider; + } + + public void setIdentityProvider(String identityProvider) { + this.identityProvider = identityProvider; + } + + public String getBrokerUserId() { + return brokerUserId; + } + + public void setBrokerUserId(String brokerUserId) { + this.brokerUserId = brokerUserId; + } + + public String getBrokerUserName() { + return brokerUserName; + } + + public void setBrokerUserName(String brokerUserName) { + this.brokerUserName = brokerUserName; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public void setToken(String token) { + this.token = token; + } + + public String getToken() { + return token; + } + + public static class Key implements Serializable { + + protected String userId; + + protected String identityProvider; + + public Key() { + } + + public Key(String userId, String identityProvider) { + this.userId = userId; + this.identityProvider = identityProvider; + } + + public String getUserId() { + return userId; + } + + public String getIdentityProvider() { + return identityProvider; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null) + return false; + if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof BrokerLinkEntity)) return false; + + BrokerLinkEntity key = (BrokerLinkEntity) o; + + if (identityProvider != null ? !identityProvider.equals(key.identityProvider) : key.identityProvider != null) + return false; + if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + (identityProvider != null ? identityProvider.hashCode() : 0); + return result; + } + + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java new file mode 100755 index 0000000000..071eacdc75 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserAttributeEntity.java @@ -0,0 +1,133 @@ +/* + * 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.jpa.entity; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getFederatedAttributesByNameAndValue", query="select attr from FederatedUserAttributeEntity attr where attr.name = :name and attr.value = :value and attr.realmId=:realmId"), + @NamedQuery(name="getFederatedAttributesByUser", query="select attr from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"), + @NamedQuery(name="deleteUserFederatedAttributesByUser", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.realmId=:realmId"), + @NamedQuery(name="deleteUserFederatedAttributesByUserAndName", query="delete from FederatedUserAttributeEntity attr where attr.userId = :userId and attr.name=:name and attr.realmId=:realmId"), + @NamedQuery(name="deleteUserFederatedAttributesByRealm", query="delete from FederatedUserAttributeEntity attr where attr.realmId=:realmId"), + @NamedQuery(name="deleteFederatedAttributesByStorageProvider", query="delete from FederatedUserAttributeEntity e where e.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteUserFederatedAttributesByRealmAndLink", query="delete from FederatedUserAttributeEntity attr where attr.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") +}) +@Table(name="FED_USER_ATTRIBUTE") +@Entity +public class FederatedUserAttributeEntity { + + @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; + + @Column(name = "USER_ID") + protected String userId; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + @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 String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserAttributeEntity)) return false; + + FederatedUserAttributeEntity that = (FederatedUserAttributeEntity) 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/storage/jpa/entity/FederatedUserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java new file mode 100755 index 0000000000..18aab7f1d1 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java @@ -0,0 +1,148 @@ +/* + * 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.jpa.entity; + +import javax.persistence.Access; +import javax.persistence.AccessType; +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.util.ArrayList; +import java.util.Collection; + +/** + * @author Marek Posolda + */ +@Entity +@Table(name="FED_USER_CONSENT", uniqueConstraints = { + @UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"}) +}) +@NamedQueries({ + @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from UserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"), + @NamedQuery(name="userFederatedConsentsByUser", query="select consent from UserConsentEntity consent where consent.userId = :userId"), + @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from UserConsentEntity consent where consent.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserConsentsByStorageProvider", query="delete from FederatedUserConsentEntity e where e.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from UserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"), + @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from UserConsentEntity consent where consent.clientId = :clientId"), +}) +public class FederatedUserConsentEntity { + + @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; + + @Column(name = "USER_ID") + protected String userId; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + @Column(name="CLIENT_ID") + protected String clientId; + + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent") + Collection grantedRoles = new ArrayList(); + + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent") + Collection grantedProtocolMappers = new ArrayList(); + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public Collection getGrantedRoles() { + return grantedRoles; + } + + public void setGrantedRoles(Collection grantedRoles) { + this.grantedRoles = grantedRoles; + } + + public Collection getGrantedProtocolMappers() { + return grantedProtocolMappers; + } + + public void setGrantedProtocolMappers(Collection grantedProtocolMappers) { + this.grantedProtocolMappers = grantedProtocolMappers; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserConsentEntity)) return false; + + FederatedUserConsentEntity that = (FederatedUserConsentEntity) 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/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.java new file mode 100755 index 0000000000..f7da2cc310 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentProtocolMapperEntity.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.storage.jpa.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Marek Posolda + */ +@NamedQueries({ + @NamedQuery(name="deleteFederatedUserConsentProtMappersByRealm", query= + "delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"), + @NamedQuery(name="deleteFederatedUserConsentProtMappersByUser", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"), + @NamedQuery(name="deleteFederatedUserConsentProtMappersByStorageProvider", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"), + @NamedQuery(name="deleteFederatedUserConsentProtMappersByProtocolMapper", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.protocolMapperId = :protocolMapperId"), + @NamedQuery(name="deleteFederatedUserConsentProtMappersByClient", query="delete from FederatedUserConsentProtocolMapperEntity csm where csm.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"), +}) +@Entity +@Table(name="FED_USER_CONSENT_PROT_MAPPER") +@IdClass(FederatedUserConsentProtocolMapperEntity.Key.class) +public class FederatedUserConsentProtocolMapperEntity { + + @Id + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "USER_CONSENT_ID") + protected FederatedUserConsentEntity userConsent; + + @Id + @Column(name="PROTOCOL_MAPPER_ID") + protected String protocolMapperId; + + public FederatedUserConsentEntity getUserConsent() { + return userConsent; + } + + public void setUserConsent(FederatedUserConsentEntity userConsent) { + this.userConsent = userConsent; + } + + public String getProtocolMapperId() { + return protocolMapperId; + } + + public void setProtocolMapperId(String protocolMapperId) { + this.protocolMapperId = protocolMapperId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserConsentProtocolMapperEntity)) return false; + + FederatedUserConsentProtocolMapperEntity that = (FederatedUserConsentProtocolMapperEntity)o; + Key myKey = new Key(this.userConsent, this.protocolMapperId); + Key hisKey = new Key(that.userConsent, that.protocolMapperId); + return myKey.equals(hisKey); + } + + @Override + public int hashCode() { + Key myKey = new Key(this.userConsent, this.protocolMapperId); + return myKey.hashCode(); + } + + public static class Key implements Serializable { + + protected FederatedUserConsentEntity userConsent; + + protected String protocolMapperId; + + public Key() { + } + + public Key(FederatedUserConsentEntity userConsent, String protocolMapperId) { + this.userConsent = userConsent; + this.protocolMapperId = protocolMapperId; + } + + public FederatedUserConsentEntity getUserConsent() { + return userConsent; + } + + public String getProtocolMapperId() { + return protocolMapperId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false; + if (protocolMapperId != null ? !protocolMapperId.equals(key.protocolMapperId) : key.protocolMapperId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userConsent != null ? userConsent.getId().hashCode() : 0; + result = 31 * result + (protocolMapperId != null ? protocolMapperId.hashCode() : 0); + return result; + } + } + + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java new file mode 100755 index 0000000000..d74865d11d --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentRoleEntity.java @@ -0,0 +1,133 @@ +/* + * 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.jpa.entity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Marek Posolda + */ +@NamedQueries({ + @NamedQuery(name="deleteFederatedUserConsentRolesByRealm", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.realmId = :realmId)"), + @NamedQuery(name="deleteFederatedUserConsentRolesByUser", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId)"), + @NamedQuery(name="deleteFederatedUserConsentRolesByStorageProvider", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.storageProviderId = :storageProviderId)"), + @NamedQuery(name="deleteFederatedUserConsentRolesByRole", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.roleId = :roleId"), + @NamedQuery(name="deleteFederatedUserConsentRolesByClient", query="delete from FederatedUserConsentRoleEntity grantedRole where grantedRole.userConsent IN (select consent from FederatedUserConsentEntity consent where consent.clientId = :clientId)"), +}) +@Entity +@Table(name="FED_USER_CONSENT_ROLE") +@IdClass(FederatedUserConsentRoleEntity.Key.class) +public class FederatedUserConsentRoleEntity { + + @Id + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name = "USER_CONSENT_ID") + protected FederatedUserConsentEntity userConsent; + + @Id + @Column(name="ROLE_ID") + protected String roleId; + + public FederatedUserConsentEntity getUserConsent() { + return userConsent; + } + + public void setUserConsent(FederatedUserConsentEntity userConsent) { + this.userConsent = userConsent; + } + + public String getRoleId() { + return roleId; + } + + public void setRoleId(String roleId) { + this.roleId = roleId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserConsentRoleEntity)) return false; + + FederatedUserConsentRoleEntity that = (FederatedUserConsentRoleEntity)o; + Key myKey = new Key(this.userConsent, this.roleId); + Key hisKey = new Key(that.userConsent, that.roleId); + return myKey.equals(hisKey); + } + + @Override + public int hashCode() { + Key myKey = new Key(this.userConsent, this.roleId); + return myKey.hashCode(); + } + + public static class Key implements Serializable { + + protected FederatedUserConsentEntity userConsent; + + protected String roleId; + + public Key() { + } + + public Key(FederatedUserConsentEntity userConsent, String roleId) { + this.userConsent = userConsent; + this.roleId = roleId; + } + + public FederatedUserConsentEntity getUserConsent() { + return userConsent; + } + + public String getRoleId() { + return roleId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (userConsent != null ? !userConsent.getId().equals(key.userConsent != null ? key.userConsent.getId() : null) : key.userConsent != null) return false; + if (roleId != null ? !roleId.equals(key.roleId) : key.roleId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userConsent != null ? userConsent.getId().hashCode() : 0; + result = 31 * result + (roleId != null ? roleId.hashCode() : 0); + return result; + } + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java new file mode 100755 index 0000000000..996608b0b3 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserCredentialEntity.java @@ -0,0 +1,222 @@ +/* + * 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.jpa.entity; + +import org.keycloak.models.jpa.entities.UserEntity; + +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="federatedUserCredentialByUser", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId"), + @NamedQuery(name="federatedUserCredentialByUserAndType", query="select cred from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"), + @NamedQuery(name="deleteFederatedUserCredentialByUser", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.realmId = :realmId"), + @NamedQuery(name="deleteFederatedUserCredentialByUserAndType", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type"), + @NamedQuery(name="deleteFederatedUserCredentialByUserAndTypeAndDevice", query="delete from FederatedUserCredentialEntity cred where cred.userId = :userId and cred.type = :type and cred.device = :device"), + @NamedQuery(name="deleteFederatedUserCredentialsByRealm", query="delete from FederatedUserCredentialEntity cred where cred.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserCredentialsByStorageProvider", query="delete from FederatedUserCredentialEntity cred where cred.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteFederatedUserCredentialsByRealmAndLink", query="delete from FederatedUserCredentialEntity cred where cred.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") + +}) +@Table(name="FED_USER_CREDENTIAL") +@Entity +public class FederatedUserCredentialEntity { + @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; + + @Column(name="TYPE") + protected String type; + @Column(name="VALUE") + protected String value; + @Column(name="DEVICE") + protected String device; + @Column(name="SALT") + protected byte[] salt; + @Column(name="HASH_ITERATIONS") + protected int hashIterations; + @Column(name="CREATED_DATE") + protected Long createdDate; + + @Column(name="USER_ID") + protected String userId; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + + + @Column(name="COUNTER") + protected int counter; + + @Column(name="ALGORITHM") + protected String algorithm; + @Column(name="DIGITS") + protected int digits; + @Column(name="PERIOD") + protected int period; + + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDevice() { + return device; + } + + public void setDevice(String device) { + this.device = device; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public byte[] getSalt() { + return salt; + } + + public void setSalt(byte[] salt) { + this.salt = salt; + } + + public int getHashIterations() { + return hashIterations; + } + + public void setHashIterations(int hashIterations) { + this.hashIterations = hashIterations; + } + + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public int getDigits() { + return digits; + } + + public void setDigits(int digits) { + this.digits = digits; + } + + public int getPeriod() { + return period; + } + + public void setPeriod(int period) { + this.period = period; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserCredentialEntity)) return false; + + FederatedUserCredentialEntity that = (FederatedUserCredentialEntity) 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/storage/jpa/entity/FederatedUserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java new file mode 100755 index 0000000000..cbbdf4e5c9 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java @@ -0,0 +1,161 @@ +/* + * 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.jpa.entity; + +import org.keycloak.models.jpa.entities.UserEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"), + @NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"), + @NamedQuery(name="fedgroupMembership", query="select g.user from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"), + @NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"), + @NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteFederatedUserGroupMembershipsByRealmAndLink", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"), + @NamedQuery(name="deleteFederatedUserGroupMembershipsByGroup", query="delete from FederatedUserGroupMembershipEntity m where m.groupId = :groupId"), + @NamedQuery(name="deleteFederatedUserGroupMembershipsByUser", query="delete from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.realmId = :realmId") + +}) +@Table(name="FED_USER_GROUP_MEMBERSHIP") +@Entity +@IdClass(FederatedUserGroupMembershipEntity.Key.class) +public class FederatedUserGroupMembershipEntity { + + @Id + protected String userId; + + @Id + @Column(name = "GROUP_ID") + protected String groupId; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + public String getGroupId() { + return groupId; + } + + public void setGroupId(String groupId) { + this.groupId = groupId; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public static class Key implements Serializable { + + protected String userId; + + protected String groupId; + + public Key() { + } + + public Key(String userId, String groupId) { + this.userId = userId; + this.groupId = groupId; + } + + public String getUserId() { + return userId; + } + + public String getGroupId() { + return groupId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (!groupId.equals(key.groupId)) return false; + if (!userId.equals(key.userId)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId.hashCode(); + result = 31 * result + groupId.hashCode(); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserGroupMembershipEntity)) return false; + + FederatedUserGroupMembershipEntity key = (FederatedUserGroupMembershipEntity) o; + + if (!groupId.equals(key.groupId)) return false; + if (!userId.equals(key.userId)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId.hashCode(); + result = 31 * result + groupId.hashCode(); + return result; + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java new file mode 100755 index 0000000000..c39a311a4e --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java @@ -0,0 +1,160 @@ +/* + * 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.jpa.entity; + +import org.keycloak.models.jpa.entities.UserEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and attr.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") +}) +@Entity +@Table(name="FED_USER_REQUIRED_ACTION") +@IdClass(FederatedUserRequiredActionEntity.Key.class) +public class FederatedUserRequiredActionEntity { + + @Id + @Column(name="USER_ID") + protected String userId; + + @Id + @Column(name="REQUIRED_ACTION") + protected String action; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + public String getAction() { + return action; + } + + public void setAction(String action) { + this.action = action; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public static class Key implements Serializable { + + protected String userId; + + protected String action; + + public Key() { + } + + public Key(String user, String action) { + this.userId = user; + this.action = action; + } + + public String getUserId() { + return userId; + } + + public String getAction() { + return action; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (action != key.action) return false; + if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + (action != null ? action.hashCode() : 0); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserRequiredActionEntity)) return false; + + FederatedUserRequiredActionEntity key = (FederatedUserRequiredActionEntity) o; + + if (action != key.action) return false; + if (userId != null ? !userId.equals(key.userId != null ? key.userId : null) : key.userId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + (action != null ? action.hashCode() : 0); + return result; + } + + +} diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java new file mode 100755 index 0000000000..78ef29912f --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java @@ -0,0 +1,160 @@ +/* + * 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.jpa.entity; + +import org.keycloak.models.jpa.entities.UserEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.IdClass; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="feduserHasRole", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId and m.roleId = :roleId"), + @NamedQuery(name="feduserRoleMappings", query="select m from FederatedUserRoleMappingEntity m where m.userId = :userId"), + @NamedQuery(name="deleteFederatedUserRoleMappingsByRealm", query="delete from FederatedUserRoleMappingEntity mapping where mapping.realmId=:realmId"), + @NamedQuery(name="deleteFederatedUserRoleMappingsByStorageProvider", query="delete from FederatedUserRoleMappingEntity e where e.storageProviderId=:storageProviderId"), + @NamedQuery(name="deleteFederatedUserRoleMappingsByRealmAndLink", query="delete from FederatedUserRoleMappingEntity mapping where mapping.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)"), + @NamedQuery(name="deleteFederatedUserRoleMappingsByRole", query="delete from FederatedUserRoleMappingEntity m where m.roleId = :roleId"), + @NamedQuery(name="deleteFederatedUserRoleMappingsByUser", query="delete from FederatedUserRoleMappingEntity m where m.userId = :userId and m.realmId = :realmId"), + +}) +@Table(name="FED_USER_ROLE_MAPPING") +@Entity +@IdClass(FederatedUserRoleMappingEntity.Key.class) +public class FederatedUserRoleMappingEntity { + + @Id + protected String userId; + + @Id + @Column(name = "ROLE_ID") + protected String roleId; + + @Column(name = "REALM_ID") + protected String realmId; + + @Column(name = "STORAGE_PROVIDER_ID") + protected String storageProviderId; + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getRealmId() { + return realmId; + } + + public void setRealmId(String realmId) { + this.realmId = realmId; + } + + public String getStorageProviderId() { + return storageProviderId; + } + + public void setStorageProviderId(String storageProviderId) { + this.storageProviderId = storageProviderId; + } + + public String getRoleId() { + return roleId; + } + + public void setRoleId(String roleId) { + this.roleId = roleId; + } + + + public static class Key implements Serializable { + + protected String userId; + + protected String roleId; + + public Key() { + } + + public Key(String userId, String roleId) { + this.userId = userId; + this.roleId = roleId; + } + + public String getUserId() { + return userId; + } + + public String getRoleId() { + return roleId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Key key = (Key) o; + + if (!roleId.equals(key.roleId)) return false; + if (!userId.equals(key.userId)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId.hashCode(); + result = 31 * result + roleId.hashCode(); + return result; + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + if (!(o instanceof FederatedUserRoleMappingEntity)) return false; + + FederatedUserRoleMappingEntity key = (FederatedUserRoleMappingEntity) o; + + if (!roleId.equals(key.roleId)) return false; + if (!userId.equals(key.userId)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = userId.hashCode(); + result = 31 * result + roleId.hashCode(); + return result; + } + +} 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 1f2623902b..4f20a50f7b 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 @@ -28,22 +28,28 @@ import org.keycloak.models.CredentialValidationOutput; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.GroupModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredActionProviderModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.models.entities.FederatedIdentityEntity; +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; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -140,7 +146,7 @@ public class MongoUserProvider implements UserProvider { } @Override - public UserModel getUserByServiceAccountClient(ClientModel client) { + public UserModel getServiceAccount(ClientModel client) { DBObject query = new QueryBuilder() .and("serviceAccountClientLink").is(client.getId()) .and("realmId").is(client.getRealm().getId()) @@ -157,6 +163,17 @@ public class MongoUserProvider implements UserProvider { return userModels; } + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, false); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return getUsers(realm, firstResult, maxResults, false); + } + + @Override public List getUsers(RealmModel realm, boolean includeServiceAccounts) { @@ -509,4 +526,112 @@ public class MongoUserProvider implements UserProvider { // Not supported yet return null; } + + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + if (getConsentEntityByClientId(user, clientId) != null) { + throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); + } + + MongoUserConsentEntity consentEntity = new MongoUserConsentEntity(); + consentEntity.setUserId(user.getId()); + consentEntity.setClientId(clientId); + fillEntityFromModel(consent, consentEntity); + getMongoStore().insertEntity(consentEntity, invocationContext); + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientId) { + UserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId); + return consentEntity!=null ? toConsentModel(realm, consentEntity) : null; + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + List result = new ArrayList(); + + DBObject query = new QueryBuilder() + .and("userId").is(user.getId()) + .get(); + List grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext); + + for (UserConsentEntity consentEntity : grantedConsents) { + UserConsentModel model = toConsentModel(realm, consentEntity); + result.add(model); + } + + return result; + } + + private MongoUserConsentEntity getConsentEntityByClientId(UserModel user, String clientId) { + DBObject query = new QueryBuilder() + .and("userId").is(user.getId()) + .and("clientId").is(clientId) + .get(); + return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext); + } + + private UserConsentModel toConsentModel(RealmModel realm, UserConsentEntity entity) { + ClientModel client = realm.getClientById(entity.getClientId()); + if (client == null) { + throw new ModelException("Client with id " + entity.getClientId() + " is not available"); + } + UserConsentModel model = new UserConsentModel(client); + + for (String roleId : entity.getGrantedRoles()) { + RoleModel roleModel = realm.getRoleById(roleId); + if (roleModel != null) { + model.addGrantedRole(roleModel); + } + } + + for (String protMapperId : entity.getGrantedProtocolMappers()) { + ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId); + model.addGrantedProtocolMapper(protocolMapper); + } + return model; + } + + // Fill roles and protocolMappers to entity + private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) { + List roleIds = new LinkedList(); + for (RoleModel role : consent.getGrantedRoles()) { + roleIds.add(role.getId()); + } + consentEntity.setGrantedRoles(roleIds); + + List protMapperIds = new LinkedList(); + for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) { + protMapperIds.add(protMapperModel.getId()); + } + consentEntity.setGrantedProtocolMappers(protMapperIds); + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + String clientId = consent.getClient().getId(); + MongoUserConsentEntity consentEntity = getConsentEntityByClientId(user, clientId); + if (consentEntity == null) { + throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]"); + } else { + fillEntityFromModel(consent, consentEntity); + getMongoStore().updateEntity(consentEntity, invocationContext); + } + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientId) { + MongoUserConsentEntity entity = getConsentEntityByClientId(user, clientId); + if (entity == null) { + return false; + } + + return getMongoStore().removeEntity(entity, invocationContext); + } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel link) { + + } } 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 6fff8d5815..f5e0bb153a 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 @@ -50,6 +50,7 @@ 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; @@ -58,6 +59,7 @@ 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; @@ -984,6 +986,14 @@ public class RealmAdapter extends AbstractMongoAdapter impleme updateRealm(); } + + private void removeFederationMappersForProvider(String federationProviderId) { + Set mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); + for (UserFederationMapperEntity mapper : mappers) { + getMongoEntity().getUserFederationMappers().remove(mapper); + } + } + @Override public UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) { KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders()); @@ -1025,14 +1035,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } updateRealm(); } - - private void removeFederationMappersForProvider(String federationProviderId) { - Set mappers = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); - for (UserFederationMapperEntity mapper : mappers) { - getMongoEntity().getUserFederationMappers().remove(mapper); - } - } - @Override public void updateUserFederationProvider(UserFederationProviderModel model) { KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); @@ -1161,6 +1163,173 @@ 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); + + } + Collections.sort(copy, new Comparator() { + + @Override + public int compare(StorageProviderEntity o1, StorageProviderEntity o2) { + return o1.getPriority() - o2.getPriority(); + } + + }); + List result = new LinkedList<>(); + for (StorageProviderEntity entity : copy) { + result.add(new StorageProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName() + )); + } + + 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(); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 601bc4a803..b89ab0102a 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -566,108 +566,6 @@ public class UserAdapter extends AbstractMongoAdapter implement updateUser(); } - @Override - public void addConsent(UserConsentModel consent) { - String clientId = consent.getClient().getId(); - if (getConsentEntityByClientId(clientId) != null) { - throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); - } - - MongoUserConsentEntity consentEntity = new MongoUserConsentEntity(); - consentEntity.setUserId(getId()); - consentEntity.setClientId(clientId); - fillEntityFromModel(consent, consentEntity); - getMongoStore().insertEntity(consentEntity, invocationContext); - } - - @Override - public UserConsentModel getConsentByClient(String clientId) { - UserConsentEntity consentEntity = getConsentEntityByClientId(clientId); - return consentEntity!=null ? toConsentModel(consentEntity) : null; - } - - @Override - public List getConsents() { - List result = new ArrayList(); - - DBObject query = new QueryBuilder() - .and("userId").is(getId()) - .get(); - List grantedConsents = getMongoStore().loadEntities(MongoUserConsentEntity.class, query, invocationContext); - - for (UserConsentEntity consentEntity : grantedConsents) { - UserConsentModel model = toConsentModel(consentEntity); - result.add(model); - } - - return result; - } - - private MongoUserConsentEntity getConsentEntityByClientId(String clientId) { - DBObject query = new QueryBuilder() - .and("userId").is(getId()) - .and("clientId").is(clientId) - .get(); - return getMongoStore().loadSingleEntity(MongoUserConsentEntity.class, query, invocationContext); - } - - private UserConsentModel toConsentModel(UserConsentEntity entity) { - ClientModel client = realm.getClientById(entity.getClientId()); - if (client == null) { - throw new ModelException("Client with id " + entity.getClientId() + " is not available"); - } - UserConsentModel model = new UserConsentModel(client); - - for (String roleId : entity.getGrantedRoles()) { - RoleModel roleModel = realm.getRoleById(roleId); - if (roleModel != null) { - model.addGrantedRole(roleModel); - } - } - - for (String protMapperId : entity.getGrantedProtocolMappers()) { - ProtocolMapperModel protocolMapper = client.getProtocolMapperById(protMapperId); - model.addGrantedProtocolMapper(protocolMapper); - } - return model; - } - - // Fill roles and protocolMappers to entity - private void fillEntityFromModel(UserConsentModel consent, MongoUserConsentEntity consentEntity) { - List roleIds = new LinkedList(); - for (RoleModel role : consent.getGrantedRoles()) { - roleIds.add(role.getId()); - } - consentEntity.setGrantedRoles(roleIds); - - List protMapperIds = new LinkedList(); - for (ProtocolMapperModel protMapperModel : consent.getGrantedProtocolMappers()) { - protMapperIds.add(protMapperModel.getId()); - } - consentEntity.setGrantedProtocolMappers(protMapperIds); - } - - @Override - public void updateConsent(UserConsentModel consent) { - String clientId = consent.getClient().getId(); - MongoUserConsentEntity consentEntity = getConsentEntityByClientId(clientId); - if (consentEntity == null) { - throw new ModelException("Consent not found for client [" + clientId + "] and user [" + user.getId() + "]"); - } else { - fillEntityFromModel(consent, consentEntity); - getMongoStore().updateEntity(consentEntity, invocationContext); - } - } - - @Override - public boolean revokeConsentForClient(String clientId) { - MongoUserConsentEntity entity = getConsentEntityByClientId(clientId); - if (entity == null) { - return false; - } - - return getMongoStore().removeEntity(entity, invocationContext); - } @Override public boolean equals(Object o) { diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java index ce9c1992a9..98fa98d6c3 100644 --- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java +++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapper.java @@ -34,8 +34,8 @@ import org.keycloak.provider.Provider; public interface UserFederationMapper extends Provider { /** - * Sync data from federation storage to Keycloak. It's useful just if mapper needs some data preloaded from federation storage (For example - * load roles from federation provider and sync them to Keycloak database) + * Sync data from federated storage to Keycloak. It's useful just if mapper needs some data preloaded from federated storage (For example + * load roles from federated provider and sync them to Keycloak database) * * Applicable just if sync is supported (see UserFederationMapperFactory.getSyncConfig() ) * @@ -48,7 +48,7 @@ public interface UserFederationMapper extends Provider { UserFederationSyncResult syncDataFromFederationProviderToKeycloak(UserFederationMapperModel mapperModel, UserFederationProvider federationProvider, KeycloakSession session, RealmModel realm); /** - * Sync data from Keycloak back to federation storage + * Sync data from Keycloak back to federated storage * * @see UserFederationMapperFactory#getSyncConfig() * @param mapperModel diff --git a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java index 661462cc1f..0efcf2ea9f 100644 --- a/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java +++ b/server-spi/src/main/java/org/keycloak/mappers/UserFederationMapperFactory.java @@ -32,7 +32,7 @@ import org.keycloak.representations.idm.UserFederationMapperSyncConfigRepresenta public interface UserFederationMapperFactory extends ProviderFactory, ConfiguredProvider { /** - * Refers to providerName (type) of the federation provider, which this mapper can be used for. For example "ldap" or "kerberos" + * Refers to providerName (type) of the federated provider, which this mapper can be used for. For example "ldap" or "kerberos" * * @return providerName */ @@ -42,7 +42,7 @@ public interface UserFederationMapperFactory extends ProviderFactory config = fedProvider.getConfig(); - // Update config properties for LDAP federation provider + // Update config properties for LDAP federated provider if (config.get(LDAPConstants.SEARCH_SCOPE) == null) { config.put(LDAPConstants.SEARCH_SCOPE, String.valueOf(SearchControls.SUBTREE_SCOPE)); } diff --git a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java index dcda375358..e7e4ee63e9 100755 --- a/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java +++ b/server-spi/src/main/java/org/keycloak/models/FederatedIdentityModel.java @@ -57,4 +57,25 @@ public class FederatedIdentityModel { public void setToken(String token) { this.token = token; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FederatedIdentityModel that = (FederatedIdentityModel) o; + + if (userId != null ? !userId.equals(that.userId) : that.userId != null) return false; + if (!identityProvider.equals(that.identityProvider)) return false; + return userName != null ? userName.equals(that.userName) : that.userName == null; + + } + + @Override + public int hashCode() { + int result = userId != null ? userId.hashCode() : 0; + result = 31 * result + identityProvider.hashCode(); + result = 31 * result + (userName != null ? userName.hashCode() : 0); + return result; + } } 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 8521e30ba2..1d71b1f63b 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -19,6 +19,8 @@ package org.keycloak.models; import org.keycloak.provider.Provider; import org.keycloak.scripting.ScriptingProvider; +import org.keycloak.storage.federated.UserFederatedStorageProvider; +import org.keycloak.storage.federated.UserFederatedStorageProviderFactory; import java.util.Set; @@ -73,11 +75,16 @@ public interface KeycloakSession { */ UserFederationManager users(); + UserProvider userStorageManager(); + /** * Keycloak user storage. Non-federated, but possibly cache (if it is on) view of users. */ UserProvider userStorage(); + UserFederatedStorageProvider userFederatedStorage(); + UserProvider userLocalStorage(); + /** * 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 1c42e45e51..38f45bd0d1 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -19,6 +19,7 @@ package org.keycloak.models; import org.keycloak.common.enums.SslRequired; import org.keycloak.provider.ProviderEvent; +import org.keycloak.storage.StorageProviderModel; import java.security.Key; import java.security.PrivateKey; @@ -263,9 +264,16 @@ public interface RealmModel extends RoleContainerModel { public IdentityProviderMapperModel getIdentityProviderMapperById(String id); 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); + // Should return list sorted by UserFederationProviderModel.priority List getUserFederationProviders(); - UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync); void updateUserFederationProvider(UserFederationProviderModel provider); void removeUserFederationProvider(UserFederationProviderModel provider); diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java new file mode 100644 index 0000000000..a55145d08f --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialAuthenticationProvider.java @@ -0,0 +1,28 @@ +/* + * 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; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCredentialAuthenticationProvider { + Set getSupportedCredentialAuthenticationTypes(); + CredentialValidationOutput validCredential(KeycloakSession session, RealmModel realm, UserCredentialModel input); +} diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java new file mode 100644 index 0000000000..13fc0150cc --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValidatorProvider.java @@ -0,0 +1,28 @@ +/* + * 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; + +import java.util.List; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCredentialValidatorProvider { + boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input); +} diff --git a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java index 65d0c89201..5ef6071a1c 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserCredentialValueModel.java @@ -25,7 +25,7 @@ import java.io.Serializable; * @author Marek Posolda */ public class UserCredentialValueModel implements Serializable { - + private String id; private String type; private String value; private String device; @@ -40,6 +40,14 @@ public class UserCredentialValueModel implements Serializable { private int period; + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + public String getType() { return type; } 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 ef8d182534..08a070dce1 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/server-spi/src/main/java/org/keycloak/models/UserFederationManager.java @@ -20,6 +20,7 @@ package org.keycloak.models; import org.jboss.logging.Logger; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.services.managers.UserManager; +import org.keycloak.storage.StorageProviderModel; import java.util.ArrayList; import java.util.Arrays; @@ -174,6 +175,38 @@ public class UserFederationManager implements UserProvider { return session.userStorage().removeFederatedIdentity(realm, user, socialProvider); } + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + validateUser(realm, user); + session.userStorage().addConsent(realm, user, consent); + + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { + validateUser(realm, user); + return session.userStorage().getConsentByClient(realm, user, clientInternalId); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + validateUser(realm, user); + return session.userStorage().getConsents(realm, user); + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + validateUser(realm, user); + session.userStorage().updateConsent(realm, user, consent); + + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { + validateUser(realm, user); + return session.userStorage().revokeConsentForClient(realm, user, clientInternalId); + } + @Override public UserModel getUserById(String id, RealmModel realm) { UserModel user = session.userStorage().getUserById(id, realm); @@ -265,8 +298,8 @@ public class UserFederationManager implements UserProvider { } @Override - public UserModel getUserByServiceAccountClient(ClientModel client) { - UserModel user = session.userStorage().getUserByServiceAccountClient(client); + public UserModel getServiceAccount(ClientModel client) { + UserModel user = session.userStorage().getServiceAccount(client); if (user != null) { user = validateAndProxyUser(client.getRealm(), user); } @@ -279,6 +312,16 @@ public class UserFederationManager implements UserProvider { } + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, false); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return getUsers(realm, firstResult, maxResults, false); + } + @Override public int getUsersCount(RealmModel realm) { return session.userStorage().getUsersCount(realm); @@ -442,6 +485,11 @@ 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) { diff --git a/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java new file mode 100644 index 0000000000..3597f1e873 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserLookupProvider.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserLookupProvider { + UserModel getUserById(String id, RealmModel realm); + + UserModel getUserByUsername(String username, RealmModel realm); + + UserModel getUserByEmail(String email, RealmModel realm); +} diff --git a/server-spi/src/main/java/org/keycloak/models/UserModel.java b/server-spi/src/main/java/org/keycloak/models/UserModel.java index 0453977162..d74ccaff40 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserModel.java @@ -122,12 +122,6 @@ public interface UserModel extends RoleMapperModel { String getServiceAccountClientLink(); void setServiceAccountClientLink(String clientInternalId); - void addConsent(UserConsentModel consent); - UserConsentModel getConsentByClient(String clientInternalId); - List getConsents(); - void updateConsent(UserConsentModel consent); - boolean revokeConsentForClient(String clientInternalId); - public static enum RequiredAction { VERIFY_EMAIL, UPDATE_PROFILE, CONFIGURE_TOTP, UPDATE_PASSWORD } 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 67c887f619..16e6634ffe 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -18,56 +18,44 @@ package org.keycloak.models; import org.keycloak.provider.Provider; +import org.keycloak.storage.StorageProviderModel; import java.util.List; -import java.util.Map; import java.util.Set; /** * @author Bill Burke * @version $Revision: 1 $ */ -public interface UserProvider extends Provider { +public interface UserProvider extends Provider, UserLookupProvider, UserQueryProvider, UserCredentialValidatorProvider, UserUpdateProvider { // Note: The reason there are so many query methods here is for layering a cache on top of an persistent KeycloakSession - UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); - UserModel addUser(RealmModel realm, String username); - boolean removeUser(RealmModel realm, UserModel user); - public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink); public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider); void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel); - - UserModel getUserById(String id, RealmModel realm); - UserModel getUserByUsername(String username, RealmModel realm); - UserModel getUserByEmail(String email, RealmModel realm); - - List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults); - + Set getFederatedIdentities(UserModel user, RealmModel realm); + FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm); UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm); - UserModel getUserByServiceAccountClient(ClientModel client); - List getUsers(RealmModel realm, boolean includeServiceAccounts); - // Service account is included for counts - int getUsersCount(RealmModel realm); - List getGroupMembers(RealmModel realm, GroupModel group); + void addConsent(RealmModel realm, UserModel user, UserConsentModel consent); + UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId); + List getConsents(RealmModel realm, UserModel user); + void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent); + boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId); + + + UserModel getServiceAccount(ClientModel client); + List getUsers(RealmModel realm, boolean includeServiceAccounts); List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); List searchForUser(String search, RealmModel realm); - List searchForUser(String search, RealmModel realm, int firstResult, int maxResults); - List searchForUserByAttributes(Map attributes, RealmModel realm); - List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults); // Searching by UserModel.attribute (not property) List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm); - Set getFederatedIdentities(UserModel user, RealmModel realm); - FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm); - - void grantToAllUsers(RealmModel realm, RoleModel role); - 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); @@ -75,9 +63,10 @@ public interface UserProvider extends Provider { void preRemove(RealmModel realm, ClientModel client); void preRemove(ProtocolMapperModel protocolMapper); - boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input); + boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input); CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input); + void close(); } diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java new file mode 100644 index 0000000000..3f476bf91d --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java @@ -0,0 +1,44 @@ +/* + * 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; + +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserQueryProvider { + + // Service account is included for counts + int getUsersCount(RealmModel realm); + + List getUsers(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); + + + + + + List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults); + List getGroupMembers(RealmModel realm, GroupModel group); +} diff --git a/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java new file mode 100644 index 0000000000..a9efe970d2 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/UserUpdateProvider.java @@ -0,0 +1,32 @@ +/* + * 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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserUpdateProvider { + UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions); + + UserModel addUser(RealmModel realm, String username); + + boolean removeUser(RealmModel realm, UserModel user); + + void grantToAllUsers(RealmModel realm, RoleModel role); + +} 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 cc3356bb24..6780498abb 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 @@ -19,6 +19,7 @@ package org.keycloak.models.entities; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -80,13 +81,14 @@ public class RealmEntity extends AbstractIdentifiableEntity { private String emailTheme; // We are using names of defaultRoles (not ids) - private List defaultRoles = new ArrayList(); - private List defaultGroups = new ArrayList(); + private List defaultRoles = new LinkedList(); + private List defaultGroups = new LinkedList(); - private List requiredCredentials = new ArrayList(); - private List userFederationProviders = new ArrayList(); - private List userFederationMappers = new ArrayList(); - private List identityProviders = new ArrayList(); + private List requiredCredentials = new LinkedList<>(); + private List storageProviders = new LinkedList<>(); + private List userFederationProviders = new LinkedList(); + private List userFederationMappers = new LinkedList(); + private List identityProviders = new LinkedList(); private Map browserSecurityHeaders = new HashMap(); private Map smtpConfig = new HashMap(); @@ -682,6 +684,14 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setDefaultGroups(List defaultGroups) { this.defaultGroups = defaultGroups; } + + public List getStorageProviders() { + return storageProviders; + } + + public void setStorageProviders(List storageProviders) { + this.storageProviders = storageProviders; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java new file mode 100755 index 0000000000..3845ba0d61 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/entities/StorageProviderEntity.java @@ -0,0 +1,65 @@ +/* + * 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 java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class StorageProviderEntity extends AbstractIdentifiableEntity { + protected String providerName; + protected Map config; + protected int priority; + protected String displayName; + + + 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/models/utils/CredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java index 074949f4ad..c6b3762ddb 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/CredentialValidation.java @@ -48,7 +48,7 @@ public class CredentialValidation { } - /** + /** * Will update password if hash iteration policy has changed * * @param realm @@ -195,7 +195,7 @@ public class CredentialValidation { return true; } - private static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) { + public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential) { if (credential.getType().equals(UserCredentialModel.PASSWORD)) { if (!validPassword(session, realm, user, credential.getValue())) { return false; diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java new file mode 100755 index 0000000000..54df5762d3 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentialValidation.java @@ -0,0 +1,166 @@ +/* + * 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.utils; + +import org.keycloak.common.util.Time; +import org.keycloak.hash.PasswordHashManager; +import org.keycloak.jose.jws.JWSInput; +import org.keycloak.jose.jws.JWSInputException; +import org.keycloak.jose.jws.crypto.RSAProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OTPPolicy; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.representations.PasswordToken; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class FederatedCredentialValidation { + + private static int hashIterations(RealmModel realm) { + PasswordPolicy policy = realm.getPasswordPolicy(); + if (policy != null) { + return policy.getHashIterations(); + } + return -1; + + } + + /** + * Will update password if hash iteration policy has changed + * + * @param realm + * @param user + * @param password + * @return + */ + public static boolean validPassword(KeycloakSession session, RealmModel realm, UserModel user, String password, UserCredentialValueModel fedCred) { + return validateHashedCredential(session, realm, user, password, fedCred); + + } + + + public static boolean validateHashedCredential(KeycloakSession session, RealmModel realm, UserModel user, String unhashedCredValue, UserCredentialValueModel credential) { + if (unhashedCredValue == null || unhashedCredValue.isEmpty()) { + return false; + } + + boolean validated = PasswordHashManager.verify(session, realm, unhashedCredValue, credential); + + if (validated) { + int iterations = hashIterations(realm); + if (iterations > -1 && iterations != credential.getHashIterations()) { + + UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, unhashedCredValue); + session.userFederatedStorage().updateCredential(realm, user, newCred); + } + + } + return validated; + } + + public static boolean validPasswordToken(RealmModel realm, UserModel user, String encodedPasswordToken) { + try { + JWSInput jws = new JWSInput(encodedPasswordToken); + if (!RSAProvider.verify(jws, realm.getPublicKey())) { + return false; + } + PasswordToken passwordToken = jws.readJsonContent(PasswordToken.class); + if (!passwordToken.getRealm().equals(realm.getName())) { + return false; + } + if (!passwordToken.getUser().equals(user.getId())) { + return false; + } + if (Time.currentTime() - passwordToken.getTimestamp() > realm.getAccessCodeLifespanUserAction()) { + return false; + } + return true; + } catch (JWSInputException e) { + return false; + } + } + + public static boolean validHOTP(KeycloakSession session, RealmModel realm, UserModel user, String otp, List fedCreds) { + UserCredentialValueModel passwordCred = null; + OTPPolicy policy = realm.getOTPPolicy(); + HmacOTP validator = new HmacOTP(policy.getDigits(), policy.getAlgorithm(), policy.getLookAheadWindow()); + for (UserCredentialValueModel cred : fedCreds) { + if (cred.getType().equals(UserCredentialModel.HOTP)) { + int counter = validator.validateHOTP(otp, cred.getValue(), cred.getCounter()); + if (counter < 0) return false; + cred.setCounter(counter); + session.userFederatedStorage().updateCredential(realm, user, cred); + return true; + } + } + return false; + + } + + public static boolean validTOTP(RealmModel realm, UserModel user, String otp, List fedCreds) { + UserCredentialValueModel passwordCred = null; + OTPPolicy policy = realm.getOTPPolicy(); + TimeBasedOTP validator = new TimeBasedOTP(policy.getAlgorithm(), policy.getDigits(), policy.getPeriod(), policy.getLookAheadWindow()); + for (UserCredentialValueModel cred : fedCreds) { + if (validator.validateTOTP(otp, cred.getValue().getBytes())) { + return true; + } + } + return false; + + } + public static boolean validSecret(RealmModel realm, UserModel user, String secret, UserCredentialValueModel cred) { + return cred.getValue().equals(secret); + + } + + public static boolean validCredential(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel credential, List fedCreds) { + if (credential.getType().equals(UserCredentialModel.PASSWORD)) { + if (!validPassword(session, realm, user, credential.getValue(), fedCreds.get(0))) { + return false; + } + } else if (credential.getType().equals(UserCredentialModel.PASSWORD_TOKEN)) { + if (!validPasswordToken(realm, user, credential.getValue())) { + return false; + } + } else if (credential.getType().equals(UserCredentialModel.TOTP)) { + if (!validTOTP(realm, user, credential.getValue(), fedCreds)) { + return false; + } + } else if (credential.getType().equals(UserCredentialModel.HOTP)) { + if (!validHOTP(session, realm, user, credential.getValue(), fedCreds)) { + return false; + } + } else if (credential.getType().equals(UserCredentialModel.SECRET)) { + if (!validSecret(realm, user, credential.getValue(), fedCreds.get(0))) { + return false; + } + } else { + return false; + } + return true; + } +} diff --git a/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java new file mode 100644 index 0000000000..69a823c42e --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/models/utils/FederatedCredentials.java @@ -0,0 +1,186 @@ +/* + * 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.utils; + +import org.keycloak.common.util.Time; +import org.keycloak.hash.PasswordHashManager; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OTPPolicy; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.storage.federated.UserFederatedStorageProvider; + +import java.util.Collections; +import java.util.Comparator; +import java.util.LinkedList; +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class FederatedCredentials { + public static void updateCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) { + if (cred.getType().equals(UserCredentialModel.PASSWORD)) { + updatePasswordCredential(session, provider,realm, user, cred); + } else if (UserCredentialModel.isOtp(cred.getType())) { + updateOtpCredential(session, provider, realm, user, cred); + } else { + UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType()); + if (fedCred == null) { + fedCred.setCreatedDate(Time.toMillis(Time.currentTime())); + fedCred.setType(cred.getType()); + fedCred.setDevice(cred.getDevice()); + fedCred.setValue(cred.getValue()); + + } else { + fedCred.setValue(cred.getValue()); + } + provider.updateCredential(realm, user, fedCred); + } + } + + public static UserCredentialValueModel getCredentialByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) { + List creds = provider.getCredentials(realm, user); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) return cred; + } + return null; + } + + public static LinkedList getCredentialsByType(UserFederatedStorageProvider provider, RealmModel realm, UserModel user, String type) { + List creds = provider.getCredentials(realm, user); + LinkedList newCreds = new LinkedList<>(); + for (UserCredentialValueModel cred : creds) { + if (cred.getType().equals(type)) newCreds.add(cred); + } + return newCreds; + } + + public static void updatePasswordCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) { + UserCredentialValueModel fedCred = getCredentialByType(provider, realm, user, cred.getType()); + if (fedCred == null) { + UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue()); + newCred.setCreatedDate(Time.toMillis(Time.currentTime())); + newCred.setType(cred.getType()); + newCred.setDevice(cred.getDevice()); + provider.updateCredential(realm, user, newCred); + } else { + int expiredPasswordsPolicyValue = -1; + PasswordPolicy policy = realm.getPasswordPolicy(); + if(policy != null) { + expiredPasswordsPolicyValue = policy.getExpiredPasswords(); + } + + if (expiredPasswordsPolicyValue != -1) { + fedCred.setType(UserCredentialModel.PASSWORD_HISTORY); + + LinkedList credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY); + if (credentialEntities.size() > expiredPasswordsPolicyValue - 1) { + Collections.sort(credentialEntities, new Comparator() { + @Override + public int compare(UserCredentialValueModel o1, UserCredentialValueModel o2) { + if (o1.getCreatedDate().equals(o2.getCreatedDate())) return 0; + return o1.getCreatedDate() < o2.getCreatedDate() ? -1 : 1; + } + }); + while (credentialEntities.size() > expiredPasswordsPolicyValue - 1) { + UserCredentialValueModel model = credentialEntities.removeFirst(); + provider.removeCredential(realm, user, model); + } + + } + provider.updateCredential(realm, user, fedCred); + fedCred = PasswordHashManager.encode(session, realm, cred.getValue()); + fedCred.setCreatedDate(Time.toMillis(Time.currentTime())); + fedCred.setType(cred.getType()); + fedCred.setDevice(cred.getDevice()); + provider.updateCredential(realm, user, fedCred); + } else { + // clear password history as it is not required anymore + for (UserCredentialValueModel model : getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY)) { + provider.removeCredential(realm, user, model); + } + UserCredentialValueModel newCred = PasswordHashManager.encode(session, realm, cred.getValue()); + newCred.setCreatedDate(Time.toMillis(Time.currentTime())); + newCred.setType(cred.getType()); + newCred.setDevice(cred.getDevice()); + newCred.setId(fedCred.getId()); + provider.updateCredential(realm, user, newCred); + } + + + } + + + } + + public static void updateOtpCredential(KeycloakSession session, UserFederatedStorageProvider provider, RealmModel realm, UserModel user, UserCredentialModel cred) { + LinkedList credentialEntities = getCredentialsByType(provider, realm, user, UserCredentialModel.PASSWORD_HISTORY); + + if (credentialEntities.isEmpty()) { + UserCredentialValueModel fedCred = new UserCredentialValueModel(); + fedCred.setCreatedDate(Time.toMillis(Time.currentTime())); + fedCred.setType(cred.getType()); + fedCred.setDevice(cred.getDevice()); + fedCred.setValue(cred.getValue()); + OTPPolicy otpPolicy = realm.getOTPPolicy(); + fedCred.setAlgorithm(otpPolicy.getAlgorithm()); + fedCred.setDigits(otpPolicy.getDigits()); + fedCred.setCounter(otpPolicy.getInitialCounter()); + fedCred.setPeriod(otpPolicy.getPeriod()); + provider.updateCredential(realm, user, fedCred); + } else { + OTPPolicy policy = realm.getOTPPolicy(); + if (cred.getDevice() == null) { + for (UserCredentialValueModel model : credentialEntities) provider.removeCredential(realm, user, model); + UserCredentialValueModel fedCred = new UserCredentialValueModel(); + fedCred.setCreatedDate(Time.toMillis(Time.currentTime())); + fedCred.setType(cred.getType()); + fedCred.setDevice(cred.getDevice()); + fedCred.setDigits(policy.getDigits()); + fedCred.setCounter(policy.getInitialCounter()); + fedCred.setAlgorithm(policy.getAlgorithm()); + fedCred.setValue(cred.getValue()); + fedCred.setPeriod(policy.getPeriod()); + provider.updateCredential(realm, user, fedCred); + } else { + UserCredentialValueModel fedCred = new UserCredentialValueModel(); + for (UserCredentialValueModel model : credentialEntities) { + if (cred.getDevice().equals(model.getDevice())) { + fedCred = model; + break; + } + } + fedCred.setCreatedDate(Time.toMillis(Time.currentTime())); + fedCred.setType(cred.getType()); + fedCred.setDevice(cred.getDevice()); + fedCred.setDigits(policy.getDigits()); + fedCred.setCounter(policy.getInitialCounter()); + fedCred.setAlgorithm(policy.getAlgorithm()); + fedCred.setValue(cred.getValue()); + fedCred.setPeriod(policy.getPeriod()); + provider.updateCredential(realm, user, fedCred); + } + } + } + + +} 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 e0dc211448..98ef0e1418 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,6 +44,7 @@ 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; @@ -341,6 +342,43 @@ 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. * @@ -378,7 +416,6 @@ public final class KeycloakModelUtils { return null; } - public static UserFederationProviderModel findUserFederationProviderById(String fedProviderId, RealmModel realm) { for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) { if (fedProviderId.equals(fedProvider.getId())) { 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 24e3ac8b6a..dce700957e 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 @@ -1279,7 +1279,7 @@ public class RepresentationToModel { if (userRep.getClientConsents() != null) { for (UserConsentRepresentation consentRep : userRep.getClientConsents()) { UserConsentModel consentModel = toModel(newRealm, consentRep); - user.addConsent(consentModel); + session.userStorage().addConsent(newRealm, user, consentModel); } } if (userRep.getServiceAccountClientId() != null) { diff --git a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java index f2d66e2a03..9a8e4abcc7 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/UserModelDelegate.java @@ -235,31 +235,6 @@ public class UserModelDelegate implements UserModel { delegate.setServiceAccountClientLink(clientInternalId); } - @Override - public void addConsent(UserConsentModel consent) { - delegate.addConsent(consent); - } - - @Override - public UserConsentModel getConsentByClient(String clientId) { - return delegate.getConsentByClient(clientId); - } - - @Override - public List getConsents() { - return delegate.getConsents(); - } - - @Override - public void updateConsent(UserConsentModel consent) { - delegate.updateConsent(consent); - } - - @Override - public boolean revokeConsentForClient(String clientId) { - return delegate.revokeConsentForClient(clientId); - } - public UserModel getDelegate() { return delegate; } diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageId.java b/server-spi/src/main/java/org/keycloak/storage/StorageId.java new file mode 100644 index 0000000000..4ecac6eed8 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/StorageId.java @@ -0,0 +1,65 @@ +/* + * 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.models.UserModel; + +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class StorageId implements Serializable { + private String id; + private String providerId; + private String storageId; + + + public StorageId(String id) { + this.id = id; + if (!id.startsWith("f:")) { + storageId = id; + return; + } + int providerIndex = id.indexOf(':', 2); + providerId = id.substring(2, providerIndex); + storageId = id.substring(providerIndex + 1); + + } + + public static String resolveProviderId(UserModel user) { + return new StorageId(user.getId()).getProviderId(); + } + public static boolean isLocalStorage(UserModel user) { + return new StorageId(user.getId()).getProviderId() == null; + } + + public String getId() { + return id; + } + + public String getProviderId() { + return providerId; + } + + public String getStorageId() { + return storageId; + } + + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java new file mode 100644 index 0000000000..6746d5d8fa --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/StorageProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.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; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface StorageProvider extends Provider { + StorageProviderModel getModel(); + + void preRemove(RealmModel realm); + void preRemove(RealmModel realm, GroupModel group); + void preRemove(RealmModel realm, RoleModel role); + void preRemove(RealmModel realm, StorageProviderModel model); + +} + diff --git a/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.java new file mode 100755 index 0000000000..3369da2462 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderFactory.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.models.KeycloakSession; +import org.keycloak.provider.ProviderFactory; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface StorageProviderFactory extends ProviderFactory { + boolean supports(Class type); + /** + * called per Keycloak transaction. + * + * @param session + * @param model + * @return + */ + StorageProvider getInstance(KeycloakSession session, StorageProviderModel model); + + /** + * Config options to display in generic admin console page for federated + * + * @return + */ + Set getConfigurationOptions(); + + /** + * This is the name of the provider and will be showed in the admin console as an option. + * + * @return + */ + @Override + String getId(); + + /** + * 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/storage/StorageProviderModel.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java new file mode 100755 index 0000000000..910df8180b --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderModel.java @@ -0,0 +1,89 @@ +/* + * 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.HashMap; +import java.util.Map; + +/** + * Stored configuration of a User Storage provider instance. + * + * @author Marek Posolda + * @author Bill Burke + */ +public class StorageProviderModel implements Serializable { + + 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/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java new file mode 100755 index 0000000000..42a906037c --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.storage; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +/** + * @author Stian Thorgersen + */ +public class StorageProviderSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "userFederation"; + } + + @Override + public Class getProviderClass() { + return StorageProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return StorageProviderFactory.class; + } + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java new file mode 100755 index 0000000000..aa438a3789 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -0,0 +1,549 @@ +/* + * 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.jboss.logging.Logger; +import org.keycloak.models.ClientModel; +import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelException; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; +import org.keycloak.models.UserCredentialAuthenticationProvider; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValidatorProvider; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserLookupProvider; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserProvider; +import org.keycloak.models.UserQueryProvider; +import org.keycloak.models.UserUpdateProvider; +import org.keycloak.models.utils.CredentialValidation; +import org.keycloak.storage.federated.UserFederatedStorageProvider; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserStorageManager implements UserProvider { + + private static final Logger logger = Logger.getLogger(UserStorageManager.class); + + 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; + } + + protected UserProvider localStorage() { + return session.userLocalStorage(); + } + + protected List getStorageProviders(RealmModel realm) { + return realm.getStorageProviders(); + } + + protected T getFirstStorageProvider(RealmModel realm, Class type) { + for (StorageProviderModel model : getStorageProviders(realm)) { + StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + if (factory.supports(type)) { + return type.cast(factory.getInstance(session, model)); + } + } + return null; + } + + 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 (factory.supports(type)) { + list.add(type.cast(factory.getInstance(session, model))); + } + + + } + return list; + } + + + @Override + public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { + UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class); + if (registry != null) { + return registry.addUser(realm, id, username, addDefaultRoles, addDefaultRequiredActions); + } + return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions); + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class); + if (registry != null) { + return registry.addUser(realm, username); + } + return localStorage().addUser(realm, username.toLowerCase()); + } + + public StorageProvider getStorageProvider(StorageProviderModel model) { + StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + return factory.getInstance(session, model); + } + + public StorageProvider getStorageProvider(RealmModel realm, String providerId) { + StorageProviderModel model = realm.getStorageProvider(providerId); + if (model == null) return null; + StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + if (factory == null) { + throw new ModelException("Could not find StorageProviderFactory for: " + model.getProviderName()); + } + return factory.getInstance(session, model); + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + StorageId storageId = new StorageId(user.getId()); + if (storageId.getProviderId() == null) { + return localStorage().removeUser(realm, user); + } + UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(realm, storageId.getProviderId()); + if (registry == null) { + throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId()); + } + return registry.removeUser(realm, user); + + } + + public UserFederatedStorageProvider getFederatedStorage() { + return session.userFederatedStorage(); + } + + @Override + public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { + getFederatedStorage().addFederatedIdentity(realm, user, socialLink); + } + + public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { + getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); + } + + @Override + public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { + return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider); + } + + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + getFederatedStorage().addConsent(realm, user, consent); + + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { + return getFederatedStorage().getConsentByClient(realm, user, clientInternalId); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + return getFederatedStorage().getConsents(realm, user); + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + getFederatedStorage().updateConsent(realm, user, consent); + + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { + return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId); + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + StorageId storageId = new StorageId(id); + if (storageId.getProviderId() == null) { + return localStorage().getUserById(id, realm); + } + UserLookupProvider provider = (UserLookupProvider)getStorageProvider(realm, storageId.getProviderId()); + return provider.getUserById(id, realm); + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + return getGroupMembers(realm, group, -1, -1); + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + UserModel user = localStorage().getUserByUsername(username, realm); + if (user != null) return user; + for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) { + user = provider.getUserByUsername(username, realm); + if (user != null) return user; + } + return null; + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + UserModel user = localStorage().getUserByEmail(email, realm); + if (user != null) return user; + for (UserLookupProvider provider : getStorageProviders(realm, UserLookupProvider.class)) { + user = provider.getUserByEmail(email, realm); + if (user != null) return user; + } + return null; + } + + @Override + public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) { + UserModel user = localStorage().getUserByFederatedIdentity(socialLink, realm); + if (user != null) { + return user; + } + String id = getFederatedStorage().getUserByFederatedIdentity(socialLink, realm); + if (id != null) return getUserById(id, realm); + return null; + } + + @Override + public UserModel getServiceAccount(ClientModel client) { + return localStorage().getServiceAccount(client); + } + + @Override + public List getUsers(RealmModel realm, boolean includeServiceAccounts) { + return getUsers(realm, 0, Integer.MAX_VALUE - 1, includeServiceAccounts); + + } + + @Override + public List getUsers(RealmModel realm) { + return getUsers(realm, false); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return getUsers(realm, firstResult, maxResults, false); + } + + @Override + public int getUsersCount(RealmModel realm) { + int size = localStorage().getUsersCount(realm); + for (UserQueryProvider provider : getStorageProviders(realm, UserQueryProvider.class)) { + size += provider.getUsersCount(realm); + } + return size; + } + + interface PaginatedQuery { + List query(UserQueryProvider provider, int first, int max); + } + + protected List query(PaginatedQuery pagedQuery, RealmModel realm, int firstResult, int maxResults) { + List results = new LinkedList(); + if (maxResults == 0) return results; + + + List storageProviders = getStorageProviders(realm, UserQueryProvider.class); + LinkedList providers = new LinkedList<>(); + if (providers.isEmpty()) { + return pagedQuery.query(localStorage(), firstResult, maxResults); + } + providers.add(localStorage()); + providers.addAll(storageProviders); + + int leftToRead = maxResults; + int leftToFirstResult = firstResult; + + Iterator it = providers.iterator(); + while (it.hasNext() && leftToRead != 0) { + UserQueryProvider provider = it.next(); + boolean exhausted = false; + int index = 0; + if (leftToFirstResult > 0) { + do { + int toRead = Math.min(50, leftToFirstResult); + List tmp = pagedQuery.query(provider, index, toRead); + leftToFirstResult -= tmp.size(); + index += tmp.size(); + if (tmp.size() < toRead) { + exhausted = true; + break; + } + } while (leftToFirstResult > 0); + } + if (exhausted) continue; + List tmp = pagedQuery.query(provider, index, leftToRead); + results.addAll(tmp); + if (leftToRead > 0) leftToRead -= tmp.size(); + } + return results; + } + + @Override + public List getUsers(final RealmModel realm, int firstResult, int maxResults, final boolean includeServiceAccounts) { + return query(new PaginatedQuery() { + @Override + public List query(UserQueryProvider provider, int first, int max) { + if (provider instanceof UserProvider) { // it is local storage + return ((UserProvider)provider).getUsers(realm, first, max, includeServiceAccounts); + } + return provider.getUsers(realm, first, max); + } + }, realm, firstResult, maxResults); + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, 0, Integer.MAX_VALUE - 1); + } + + @Override + public List searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) { + final Map attributes = new HashMap(); + int spaceIndex = search.lastIndexOf(' '); + if (spaceIndex > -1) { + String firstName = search.substring(0, spaceIndex).trim(); + String lastName = search.substring(spaceIndex).trim(); + attributes.put(UserModel.FIRST_NAME, firstName); + attributes.put(UserModel.LAST_NAME, lastName); + } else if (search.indexOf('@') > -1) { + attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); + attributes.put(UserModel.EMAIL, search.trim().toLowerCase()); + } else { + attributes.put(UserModel.LAST_NAME, search.trim()); + attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); + } + return query(new PaginatedQuery() { + @Override + public List query(UserQueryProvider provider, int first, int max) { + return provider.searchForUserByAttributes(attributes, realm, first, max); + } + }, realm, firstResult, maxResults); + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm) { + return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE - 1); + } + + @Override + public List searchForUserByAttributes(final Map attributes, final RealmModel realm, int firstResult, int maxResults) { + return query(new PaginatedQuery() { + @Override + public List query(UserQueryProvider provider, int first, int max) { + return provider.searchForUserByAttributes(attributes, realm, first, max); + } + }, realm, firstResult, maxResults); + } + + @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + Map attributes = new HashMap<>(); + attributes.put(attrName, attrValue); + return searchForUserByAttributes(attributes, realm); + } + + @Override + public Set getFederatedIdentities(UserModel user, RealmModel realm) { + if (user == null) throw new IllegalStateException("Federated user no longer valid"); + if (StorageId.isLocalStorage(user)) { + return localStorage().getFederatedIdentities(user, realm); + } + return getFederatedStorage().getFederatedIdentities(user, realm); + } + + @Override + public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { + if (user == null) throw new IllegalStateException("Federated user no longer valid"); + if (StorageId.isLocalStorage(user)) { + return localStorage().getFederatedIdentity(user, socialProvider, realm); + } + return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm); + } + + @Override + public void grantToAllUsers(RealmModel realm, RoleModel role) { + // not federation-aware for now + List storageProviders = getStorageProviders(realm, UserUpdateProvider.class); + LinkedList providers = new LinkedList<>(); + providers.add(localStorage()); + providers.addAll(storageProviders); + for (UserUpdateProvider provider : providers) { + provider.grantToAllUsers(realm, role); + } + } + + @Override + public List getGroupMembers(final RealmModel realm, final GroupModel group, int firstResult, int maxResults) { + return query(new PaginatedQuery() { + @Override + public List query(UserQueryProvider provider, int first, int max) { + return provider.getGroupMembers(realm, group, first, max); + } + }, realm, firstResult, maxResults); + } + + + @Override + public void preRemove(RealmModel realm) { + localStorage().preRemove(realm); + getFederatedStorage().preRemove(realm); + for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + provider.preRemove(realm); + } + } + + @Override + public void preRemove(RealmModel realm, UserFederationProviderModel model) { + localStorage().preRemove(realm, model); + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + localStorage().preRemove(realm, group); + getFederatedStorage().preRemove(realm, group); + for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + provider.preRemove(realm, group); + } + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + localStorage().preRemove(realm, role); + getFederatedStorage().preRemove(realm, role); + for (StorageProvider provider : getStorageProviders(realm, StorageProvider.class)) { + provider.preRemove(realm, role); + } + } + + @Override + public void preRemove(RealmModel realm, ClientModel client) { + localStorage().preRemove(realm, client); + } + + @Override + public void preRemove(ProtocolMapperModel protocolMapper) { + localStorage().preRemove(protocolMapper); + } + + @Override + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { + if (StorageId.isLocalStorage(user)) { + return localStorage().validCredentials(session, realm, user, input); + } + // make sure we hit the cache here! + List userCreds = user.getCredentialsDirectly(); + + LinkedList toValidate = new LinkedList<>(); + toValidate.addAll(input); + Iterator it = toValidate.iterator(); + boolean failedStoredCredential = false; + // we allow for multiple credentials of same type, i.e. multiple OTP devices + while (it.hasNext()) { + UserCredentialModel cred = it.next(); + boolean credValidated = false; + for (UserCredentialValueModel userCred : userCreds) { + if (!userCred.getType().equals(cred.getType())) continue; + if (CredentialValidation.validCredential(session, realm, user, cred)) { + credValidated = true; + break; + } else { + failedStoredCredential = true; + } + } + if (credValidated) { + it.remove(); + } else if (failedStoredCredential) { + return false; + } + } + + if (toValidate.isEmpty()) return true; + + StorageProvider provider = getStorageProvider(realm, StorageId.resolveProviderId(user)); + if (!(provider instanceof UserCredentialValidatorProvider)) { + return false; + } + return ((UserCredentialValidatorProvider)provider).validCredentials(session, realm, user, toValidate); + } + + @Override + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return validCredentials(session, realm, user, Arrays.asList(input)); + } + + @Override + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { + List providers = getStorageProviders(realm, UserCredentialAuthenticationProvider.class); + if (providers.isEmpty()) return CredentialValidationOutput.failed(); + + CredentialValidationOutput result = null; + for (UserCredentialModel cred : input) { + UserCredentialAuthenticationProvider providerSupportingCreds = null; + + // Find first provider, which supports required credential type + for (UserCredentialAuthenticationProvider provider : providers) { + if (provider.getSupportedCredentialAuthenticationTypes().contains(cred.getType())) { + providerSupportingCreds = provider; + break; + } + } + + if (providerSupportingCreds == null) { + logger.warn("Don't have provider supporting credentials of type " + cred.getType()); + return CredentialValidationOutput.failed(); + } + + logger.debug("Found provider [" + providerSupportingCreds + "] supporting credentials of type " + cred.getType()); + CredentialValidationOutput currentResult = providerSupportingCreds.validCredential(session, realm, cred); + result = (result == null) ? currentResult : result.merge(currentResult); + } + + // For now, validCredentials(realm, input) is not supported for local userProviders + return (result != null) ? result : CredentialValidationOutput.failed(); + } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel link) { + + } + + @Override + public void close() { + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java new file mode 100644 index 0000000000..58695c306b --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageProvider.java @@ -0,0 +1,28 @@ +/* + * 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.models.RealmModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserStorageProvider { + void preRemove(RealmModel realm); + +} 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 new file mode 100644 index 0000000000..114b0c34c3 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -0,0 +1,162 @@ +/* + * 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.adapter; + +import org.keycloak.models.ClientModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.storage.federated.UserFederatedStorageProvider; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractUserAdapter implements UserModel { + protected KeycloakSession session; + protected RealmModel realm; + + public UserFederatedStorageProvider getFederatedStorage() { + return null; + } + + @Override + public Set getRequiredActions() { + return getFederatedStorage().getRequiredActions(realm, this); + } + + @Override + public void addRequiredAction(String action) { + getFederatedStorage().addRequiredAction(realm, this, action); + + } + + @Override + public void removeRequiredAction(String action) { + getFederatedStorage().removeRequiredAction(realm, this, action); + + } + + @Override + public void addRequiredAction(RequiredAction action) { + getFederatedStorage().addRequiredAction(realm, this, action.name()); + + } + + @Override + public void removeRequiredAction(RequiredAction action) { + getFederatedStorage().removeRequiredAction(realm, this, action.name()); + } + + @Override + public Set getGroups() { + return null; + } + + @Override + public void joinGroup(GroupModel group) { + + } + + @Override + public void leaveGroup(GroupModel group) { + + } + + @Override + public boolean isMemberOf(GroupModel group) { + return false; + } + + @Override + public String getFederationLink() { + return null; + } + + @Override + public void setFederationLink(String link) { + + } + + @Override + public String getServiceAccountClientLink() { + return null; + } + + @Override + public void setServiceAccountClientLink(String clientInternalId) { + + } + + @Override + public Set getRealmRoleMappings() { + return null; + } + + @Override + public Set getClientRoleMappings(ClientModel app) { + return null; + } + + @Override + public boolean hasRole(RoleModel role) { + return false; + } + + @Override + public void grantRole(RoleModel role) { + + } + + @Override + public Set getRoleMappings() { + return null; + } + + @Override + public void deleteRoleMapping(RoleModel role) { + + } + + @Override + public boolean isEnabled() { + return false; + } + + @Override + public void setEnabled(boolean enabled) { + + } + + @Override + public boolean isOtpEnabled() { + return false; + } + + @Override + public void setOtpEnabled(boolean totp) { + + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java new file mode 100755 index 0000000000..4b2813a87a --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserData.java @@ -0,0 +1,387 @@ +/* + * 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.common.util.MultivaluedHashMap; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.entities.AbstractIdentifiableEntity; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * @author Marek Posolda + */ +public class UserData { + + private String id; + private boolean idChanged; + private String username; + private boolean usernameChanged; + private Long createdTimestamp; + private boolean createdTimestampChanged; + private String firstName; + private boolean firstNameChanged; + private String lastName; + private boolean lastNameChanged; + private String email; + private boolean emailChanged; + private boolean emailVerified; + private boolean emailVerifiedChanged; + private boolean totp; + private boolean totpChanged; + private boolean enabled; + private boolean enabledChanged; + + private Set roleIds = new HashSet<>(); + private boolean rolesChanged; + private Set groupIds = new HashSet<>(); + private boolean groupsChanged; + + private MultivaluedHashMap attributes = new MultivaluedHashMap<>(); + private boolean attributesChanged; + private Set requiredActions = new HashSet<>(); + private boolean requiredActionsChanged; + private List credentials = new LinkedList<>(); + private boolean credentialsChanged; + + public void rememberState() { + original = new UserData(); + original.id = id; + original.username = username; + original.createdTimestamp = createdTimestamp; + original.firstName = firstName; + original.lastName = lastName; + original.email = email; + original.emailVerified = emailVerified; + original.totp = totp; + original.enabled = enabled; + original.attributes.putAll(attributes); + original.requiredActions.addAll(requiredActions); + original.credentials.addAll(credentials); + } + + private UserData original = null; + + public void clearChangeFlags() { + original = null; + idChanged = false; + usernameChanged = false; + createdTimestampChanged = false; + firstNameChanged = false; + lastNameChanged = false; + emailChanged = false; + emailVerifiedChanged = false; + totpChanged = false; + enabledChanged = false; + rolesChanged = false; + groupsChanged = false; + attributesChanged = false; + requiredActionsChanged = false; + credentialsChanged = false; + } + + public boolean isChanged() { + return !idChanged + && !usernameChanged + && !createdTimestampChanged + && !firstNameChanged + && !lastNameChanged + && !emailChanged + && !emailVerifiedChanged + && !totpChanged + && !enabledChanged + && !rolesChanged + && !groupsChanged + && !attributesChanged + && !requiredActionsChanged + && !credentialsChanged; + } + + public boolean isIdChanged() { + return idChanged; + } + + public boolean isUsernameChanged() { + return usernameChanged; + } + + public boolean isCreatedTimestampChanged() { + return createdTimestampChanged; + } + + public boolean isFirstNameChanged() { + return firstNameChanged; + } + + public boolean isLastNameChanged() { + return lastNameChanged; + } + + public boolean isEmailChanged() { + return emailChanged; + } + + public boolean isEmailVerifiedChanged() { + return emailVerifiedChanged; + } + + public boolean isTotpChanged() { + return totpChanged; + } + + public boolean isEnabledChanged() { + return enabledChanged; + } + + public boolean isRolesChanged() { + return rolesChanged; + } + + public boolean isGroupsChanged() { + return groupsChanged; + } + + public boolean isAttributesChanged() { + return attributesChanged; + } + + public boolean isRequiredActionsChanged() { + return requiredActionsChanged; + } + + public boolean isCredentialsChanged() { + return credentialsChanged; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + idChanged = true; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + usernameChanged = true; + } + + public Long getCreatedTimestamp() { + return createdTimestamp; + } + + public void setCreatedTimestamp(Long timestamp) { + this.createdTimestamp = timestamp; + createdTimestampChanged = true; + } + + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + firstNameChanged = true; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + lastNameChanged = true; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + emailChanged = true; + } + + public boolean isEmailVerified() { + return emailVerified; + } + + public void setEmailVerified(boolean emailVerified) { + this.emailVerified = emailVerified; + emailVerifiedChanged = true; + } + + public boolean isTotp() { + return totp; + } + + public void setTotp(boolean totp) { + this.totp = totp; + totpChanged = true; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + enabledChanged = true; + } + + public Set getRoleMappings() { + return Collections.unmodifiableSet(roleIds); + } + + public void grantRole(String roleId) { + if (roleIds.contains(roleId)) return; + roleIds.add(roleId); + rolesChanged = true; + } + + public void deleteRoleMapping(String roleId) { + if (!roleIds.contains(roleId)) return; + roleIds.remove(roleId); + rolesChanged = true; + } + + public MultivaluedHashMap getAttributes() { + return attributes; + } + + public void setSingleAttribute(String name, String value) { + attributes.putSingle(name, value); + attributesChanged = true; + + } + public void setAttribute(String name, List values) { + attributes.put(name, values); + attributesChanged = true; + } + public void removeAttribute(String name) { + attributes.remove(name); + attributesChanged = true; + } + + + + public Set getRequiredActions() { + return Collections.unmodifiableSet(requiredActions); + } + public void addRequiredAction(String action) { + if (requiredActions.contains(action)) return; + requiredActions.add(action); + requiredActionsChanged = true; + } + public void removeRequiredAction(String action) { + if (!requiredActions.contains(action)) return; + requiredActions.remove(action); + requiredActionsChanged = true; + } + + public List getCredentials() { + return Collections.unmodifiableList(credentials); + } + + public void removeCredentialType(String type) { + Iterator it = credentials.iterator(); + while (it.hasNext()) { + if (it.next().getType().equals(type)) { + it.remove(); + credentialsChanged = true; + } + } + + } + + public void removeCredentialDevice(String type, String device) { + Iterator it = credentials.iterator(); + while (it.hasNext()) { + UserCredentialValueModel next = it.next(); + if (next.getType().equals(type) && next.getDevice().equals(device)) { + it.remove(); + credentialsChanged = true; + } + } + + } + + public void setCredential(UserCredentialValueModel cred) { + removeCredentialType(cred.getType()); + addCredential(cred); + } + public void addCredential(UserCredentialValueModel cred) { + credentials.add(cred); + credentialsChanged = true; + } + + public Set getGroupIds() { + return Collections.unmodifiableSet(groupIds); + } + + public void joinGroup(String groupId) { + if (groupIds.contains(groupId)) return; + groupIds.add(groupId); + groupsChanged = true; + } + + public void leaveGroup(String groupId) { + if (!groupIds.contains(groupId)) return; + groupIds.remove(groupId); + groupsChanged = true; + } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + + if (this.id == null) return false; + + if (o == null || getClass() != o.getClass()) return false; + + AbstractIdentifiableEntity that = (AbstractIdentifiableEntity) o; + + if (!getId().equals(that.getId())) return false; + + return true; + + } + + @Override + public int hashCode() { + return id!=null ? id.hashCode() : super.hashCode(); + } + + @Override + public String toString() { + return String.format("%s [ id=%s ]", getClass().getSimpleName(), getId()); + } + +} + diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java new file mode 100644 index 0000000000..6ec78f25b6 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataAdapter.java @@ -0,0 +1,340 @@ +/* + * 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.common.util.Time; +import org.keycloak.hash.PasswordHashManager; +import org.keycloak.models.ClientModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.OTPPolicy; +import org.keycloak.models.PasswordPolicy; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleContainerModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * + * + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserDataAdapter implements UserModel { + protected UserData userData; + protected RealmModel realm; + protected KeycloakSession session; + protected Set managedCredentialTypes; + protected List updatedManagedCredentials = new LinkedList<>(); + + public UserDataAdapter(KeycloakSession session, RealmModel realm, UserData userData) { + this.session = session; + this.realm = realm; + this.userData = userData; + this.userData.rememberState(); + } + + @Override + public String getId() { + return userData.getId(); + } + + @Override + public String getUsername() { + return userData.getUsername(); + } + + @Override + public void setUsername(String username) { + userData.setUsername(username); + + } + + @Override + public Long getCreatedTimestamp() { + return userData.getCreatedTimestamp(); + } + + @Override + public void setCreatedTimestamp(Long timestamp) { + userData.setCreatedTimestamp(timestamp); + + } + + @Override + public boolean isEnabled() { + return userData.isEnabled(); + } + + @Override + public boolean isOtpEnabled() { + return userData.isTotp(); + } + + @Override + public void setEnabled(boolean enabled) { + userData.setEnabled(enabled); + + } + + @Override + public void setSingleAttribute(String name, String value) { + userData.setSingleAttribute(name, value); + + } + + @Override + public void setAttribute(String name, List values) { + userData.setAttribute(name, values); + + } + + @Override + public void removeAttribute(String name) { + userData.removeAttribute(name); + + } + + @Override + public String getFirstAttribute(String name) { + return userData.getAttributes().getFirst(name); + } + + @Override + public List getAttribute(String name) { + return userData.getAttributes().get(name); + } + + @Override + public Map> getAttributes() { + return userData.getAttributes(); + } + + @Override + public Set getRequiredActions() { + return userData.getRequiredActions(); + } + + @Override + public void addRequiredAction(String action) { + userData.addRequiredAction(action); + + } + + @Override + public void removeRequiredAction(String action) { + userData.removeRequiredAction(action); + + } + + @Override + public void addRequiredAction(RequiredAction action) { + userData.addRequiredAction(action.name()); + + } + + @Override + public void removeRequiredAction(RequiredAction action) { + userData.removeRequiredAction(action.name()); + + } + + @Override + public String getFirstName() { + return userData.getFirstName(); + } + + @Override + public void setFirstName(String firstName) { + userData.setFirstName(firstName); + + } + + @Override + public String getLastName() { + return userData.getLastName(); + } + + @Override + public void setLastName(String lastName) { + userData.setLastName(lastName); + + } + + @Override + public String getEmail() { + return userData.getEmail(); + } + + @Override + public void setEmail(String email) { + userData.setEmail(email); + + } + + @Override + public boolean isEmailVerified() { + return userData.isEmailVerified(); + } + + @Override + public void setEmailVerified(boolean verified) { + userData.setEmailVerified(verified); + + } + + @Override + public void setOtpEnabled(boolean totp) { + userData.setTotp(totp); + + } + + @Override + public void updateCredential(UserCredentialModel cred) { + + } + + @Override + public List getCredentialsDirectly() { + return null; + } + + @Override + public void updateCredentialDirectly(UserCredentialValueModel cred) { + + } + + @Override + public Set getGroups() { + Set groups = userData.getGroupIds(); + Set set = new HashSet<>(); + for (String id : groups) { + GroupModel group = realm.getGroupById(id); + if (group != null) set.add(group); + } + return set; + } + + @Override + public void joinGroup(GroupModel group) { + userData.joinGroup(group.getId()); + + } + + @Override + public void leaveGroup(GroupModel group) { + userData.leaveGroup(group.getId()); + + } + + @Override + public boolean isMemberOf(GroupModel group) { + Set roles = getGroups(); + return KeycloakModelUtils.isMember(roles, group); + } + + @Override + public String getFederationLink() { + return null; + } + + @Override + public void setFederationLink(String link) { + + } + + @Override + public String getServiceAccountClientLink() { + return null; + } + + @Override + public void setServiceAccountClientLink(String clientInternalId) { + + } + + @Override + public Set getRealmRoleMappings() { + Set roleMappings = getRoleMappings(); + + Set realmRoles = new HashSet(); + for (RoleModel role : roleMappings) { + RoleContainerModel container = role.getContainer(); + if (container instanceof RealmModel) { + realmRoles.add(role); + } + } + return realmRoles; + } + + @Override + public Set getClientRoleMappings(ClientModel app) { + Set roleMappings = getRoleMappings(); + + Set roles = new HashSet(); + for (RoleModel role : roleMappings) { + RoleContainerModel container = role.getContainer(); + if (container instanceof ClientModel) { + ClientModel appModel = (ClientModel)container; + if (appModel.getId().equals(app.getId())) { + roles.add(role); + } + } + } + return roles; + } + + @Override + public boolean hasRole(RoleModel role) { + Set roles = getRoleMappings(); + return KeycloakModelUtils.hasRole(roles, role); + } + + @Override + public void grantRole(RoleModel role) { + userData.grantRole(role.getId()); + + } + + @Override + public Set getRoleMappings() { + Set roles = userData.getRoleMappings(); + Set set = new HashSet<>(); + for (String id : roles) { + RoleModel role = realm.getRoleById(id); + if (role != null) set.add(role); + } + return set; + } + + @Override + public void deleteRoleMapping(RoleModel role) { + userData.deleteRoleMapping(role.getId()); + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java new file mode 100644 index 0000000000..741f3b7546 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataCredentialValidator.java @@ -0,0 +1,33 @@ +/* + * 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.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; + +import java.util.List; + +/** + * @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); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java new file mode 100644 index 0000000000..5e80d5d51c --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataLookup.java @@ -0,0 +1,31 @@ +/* + * 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 org.keycloak.storage.StorageId; + +/** + * @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); +} 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 new file mode 100644 index 0000000000..e0ef387e9e --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataQuery.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.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 new file mode 100644 index 0000000000..e896b00e9b --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserDataStore.java @@ -0,0 +1,32 @@ +/* + * 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/changeset/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java new file mode 100755 index 0000000000..01bac5080c --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java @@ -0,0 +1,341 @@ +/* + * 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.jboss.logging.Logger; +import org.keycloak.models.ClientModel; +import org.keycloak.models.CredentialValidationOutput; +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.GroupModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelException; +import org.keycloak.models.ProtocolMapperModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserConsentModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProviderModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.UserProvider; +import org.keycloak.models.UserUpdateProvider; +import org.keycloak.storage.StorageId; +import org.keycloak.storage.StorageProvider; +import org.keycloak.storage.StorageProviderFactory; +import org.keycloak.storage.StorageProviderModel; +import org.keycloak.storage.federated.UserFederatedStorageProvider; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UserStorageManager implements UserProvider { + + private static final Logger logger = Logger.getLogger(UserStorageManager.class); + + 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; + } + + protected UserProvider localStorage() { + if (localStorage == null) { + localStorage = session.getProvider(UserProvider.class); + } + return localStorage; + } + + protected List getStorageProviders(RealmModel realm) { + return null; + } + + protected T getFirstStorageProvider(RealmModel realm, Class type) { + for (StorageProviderModel model : getStorageProviders(realm)) { + StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + if (factory.supports(type)) { + return type.cast(factory.getInstance(session, model)); + } + } + return null; + } + + 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 (factory.supports(type)) { + list.add(type.cast(factory.getInstance(session, model))); + } + + + } + return list; + } + + + @Override + public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { + UserDataStore store = getFirstStorageProvider(realm, UserDataStore.class); + if (store != null) { + UserData data = new UserData(); + } + return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions); + } + + @Override + public UserModel addUser(RealmModel realm, String username) { + UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class); + if (registry != null) { + return registry.addUser(realm, username); + } + return localStorage().addUser(realm, username.toLowerCase()); + } + + public StorageProvider getStorageProvider(StorageProviderModel model) { + StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); + return factory.getInstance(session, model); + } + + public StorageProvider getStorageProvider(String providerId) { + return null; + } + + @Override + public boolean removeUser(RealmModel realm, UserModel user) { + StorageId storageId = new StorageId(user.getId()); + if (storageId.getProviderId() == null) { + return localStorage().removeUser(realm, user); + } + UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(storageId.getProviderId()); + if (registry == null) { + throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId()); + } + return registry.removeUser(realm, user); + + } + + public UserFederatedStorageProvider getFederatedStorage() { + return null; + } + + @Override + public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { + getFederatedStorage().addFederatedIdentity(realm, user, socialLink); + } + + public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { + getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); + } + + @Override + public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { + return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider); + } + + @Override + public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + getFederatedStorage().addConsent(realm, user, consent); + + } + + @Override + public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { + return getFederatedStorage().getConsentByClient(realm, user, clientInternalId); + } + + @Override + public List getConsents(RealmModel realm, UserModel user) { + return getFederatedStorage().getConsents(realm, user); + } + + @Override + public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { + getFederatedStorage().updateConsent(realm, user, consent); + + } + + @Override + public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { + return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId); + } + + @Override + public Set getFederatedIdentities(UserModel user, RealmModel realm) { + return null; + } + + @Override + public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { + return null; + } + + @Override + public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) { + return null; + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { + return null; + } + + @Override + public List getGroupMembers(RealmModel realm, GroupModel group) { + return null; + } + + @Override + public UserModel getServiceAccount(ClientModel client) { + return null; + } + + @Override + public List getUsers(RealmModel realm, boolean includeServiceAccounts) { + return null; + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { + return null; + } + + @Override + public void grantToAllUsers(RealmModel realm, RoleModel role) { + + } + + @Override + public void preRemove(RealmModel realm) { + + } + + @Override + public void preRemove(RealmModel realm, UserFederationProviderModel link) { + + } + + @Override + public void preRemove(RealmModel realm, RoleModel role) { + + } + + @Override + public void preRemove(RealmModel realm, GroupModel group) { + + } + + @Override + public void preRemove(RealmModel realm, ClientModel client) { + + } + + @Override + public void preRemove(ProtocolMapperModel protocolMapper) { + + } + + @Override + public void preRemove(RealmModel realm, StorageProviderModel link) { + + } + + @Override + public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { + return null; + } + + @Override + public void close() { + + } + + @Override + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { + return false; + } + + @Override + public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { + return false; + } + + @Override + public UserModel getUserById(String id, RealmModel realm) { + return null; + } + + @Override + public UserModel getUserByUsername(String username, RealmModel realm) { + return null; + } + + @Override + public UserModel getUserByEmail(String email, RealmModel realm) { + return null; + } + + @Override + public int getUsersCount(RealmModel realm) { + return 0; + } + + @Override + public List getUsers(RealmModel realm) { + return null; + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return null; + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm) { + return null; + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + return null; + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + return null; + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults) { + return null; + } + + @Override + public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { + return null; + } +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java new file mode 100644 index 0000000000..7c4b735322 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/CredentialModel.java @@ -0,0 +1,24 @@ +/* + * 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.federated; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class CredentialModel { +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java new file mode 100644 index 0000000000..12bcabfcef --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserAttributeFederatedStorage.java @@ -0,0 +1,35 @@ +/* + * 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.federated; + +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import java.util.List; +import java.util.Map; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserAttributeFederatedStorage { + void setSingleAttribute(RealmModel realm, UserModel user, String name, String value); + void setAttribute(RealmModel realm, UserModel user, String name, List values); + void removeAttribute(RealmModel realm, UserModel user, String name); + MultivaluedHashMap getAttributes(RealmModel realm, UserModel user); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java new file mode 100644 index 0000000000..6fcbabf1f2 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserBrokerLinkFederatedStorage.java @@ -0,0 +1,36 @@ +/* + * 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.federated; + +import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserBrokerLinkFederatedStorage { + String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm); + public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink); + public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider); + void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel); + Set getFederatedIdentities(UserModel user, RealmModel realm); + FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java new file mode 100644 index 0000000000..7a14b1450d --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserConsentFederatedStorage.java @@ -0,0 +1,35 @@ +/* + * 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.federated; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserConsentModel; +import org.keycloak.models.UserModel; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserConsentFederatedStorage { + void addConsent(RealmModel realm, UserModel user, UserConsentModel consent); + UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId); + List getConsents(RealmModel realm, UserModel user); + void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent); + boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java new file mode 100644 index 0000000000..7239182877 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserCredentialsFederatedStorage.java @@ -0,0 +1,35 @@ +/* + * 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.federated; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserModel; + +import java.util.List; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserCredentialsFederatedStorage { + void updateCredential(RealmModel realm, UserModel user, UserCredentialModel cred); + void updateCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred); + void removeCredential(RealmModel realm, UserModel user, UserCredentialValueModel cred); + List getCredentials(RealmModel realm, UserModel user); +} 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 new file mode 100755 index 0000000000..46ae9db649 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProvider.java @@ -0,0 +1,58 @@ +/* + * 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.federated; + +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.UserFederationProviderModel; +import org.keycloak.models.UserModel; +import org.keycloak.provider.Provider; +import org.keycloak.storage.StorageProviderModel; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserFederatedStorageProvider extends Provider, + UserAttributeFederatedStorage, + UserBrokerLinkFederatedStorage, + UserConsentFederatedStorage, + UserCredentialsFederatedStorage, + UserGroupMembershipFederatedStorage, + UserRequiredActionsFederatedStorage, + UserRoleMappingsFederatedStorage { + + void preRemove(RealmModel realm); + + void preRemove(RealmModel realm, UserFederationProviderModel link); + + public void preRemove(RealmModel realm, GroupModel group); + + void preRemove(RealmModel realm, RoleModel role); + + void preRemove(RealmModel realm, ClientModel client); + + void preRemove(ProtocolMapperModel protocolMapper); + + void preRemove(RealmModel realm, UserModel user); + + void preRemove(RealmModel realm, StorageProviderModel model); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java new file mode 100644 index 0000000000..667542c783 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.storage.federated; + +import org.keycloak.provider.ProviderFactory; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserFederatedStorageProviderFactory extends ProviderFactory { +} 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 new file mode 100755 index 0000000000..5475422d00 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserFederatedStorageProviderSpi.java @@ -0,0 +1,51 @@ +/* + * 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.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 + */ +public class UserFederatedStorageProviderSpi implements Spi { + + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "userFederatedStorage"; + } + + @Override + public Class getProviderClass() { + return UserFederatedStorageProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return UserFederatedStorageProviderFactory.class; + } + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java new file mode 100644 index 0000000000..fef3ceefcb --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserGroupMembershipFederatedStorage.java @@ -0,0 +1,36 @@ +/* + * 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.federated; + +import org.keycloak.models.GroupModel; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserGroupMembershipFederatedStorage { + Set getGroups(RealmModel realm, UserModel user); + + void joinGroup(RealmModel realm,UserModel user, GroupModel group); + + void leaveGroup(RealmModel realm,UserModel user, GroupModel group); + +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java new file mode 100644 index 0000000000..1e14f78a41 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRequiredActionsFederatedStorage.java @@ -0,0 +1,32 @@ +/* + * 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.federated; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserRequiredActionsFederatedStorage { + Set getRequiredActions(RealmModel realm, UserModel user); + void addRequiredAction(RealmModel realm,UserModel user, String action); + void removeRequiredAction(RealmModel realm,UserModel user, String action); +} diff --git a/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java new file mode 100644 index 0000000000..f63d9ecf36 --- /dev/null +++ b/server-spi/src/main/java/org/keycloak/storage/federated/UserRoleMappingsFederatedStorage.java @@ -0,0 +1,36 @@ +/* + * 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.federated; + +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserModel; + +import java.util.Set; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public interface UserRoleMappingsFederatedStorage { + + void grantRole(RealmModel realm,UserModel user, RoleModel role); + + Set getRoleMappings(RealmModel realm,UserModel user); + + void deleteRoleMapping(RealmModel realm, UserModel user, RoleModel role); +} 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 63015cfd3b..3ef7094d4b 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,6 +16,7 @@ # org.keycloak.models.UserFederationSpi +org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.mappers.UserFederationMapperSpi org.keycloak.models.RealmSpi org.keycloak.models.UserSessionSpi diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java index cf0c5ca2d9..01f1186462 100755 --- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java +++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java @@ -319,7 +319,7 @@ public class ExportUtils { userRep.setFederationLink(user.getFederationLink()); // Grants - List consents = user.getConsents(); + List consents = session.users().getConsents(realm, user); LinkedList consentReps = new LinkedList(); for (UserConsentModel consent : consents) { UserConsentRepresentation consentRep = ModelToRepresentation.toRepresentation(consent); diff --git a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java index 0b8ec67337..f845903467 100755 --- a/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java +++ b/services/src/main/java/org/keycloak/forms/account/freemarker/model/ApplicationsBean.java @@ -64,7 +64,7 @@ public class ApplicationsBean { MultivaluedHashMap resourceRolesGranted = new MultivaluedHashMap(); List claimsGranted = new LinkedList(); if (client.isConsentRequired()) { - UserConsentModel consent = user.getConsentByClient(client.getId()); + UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId()); if (consent != null) { processRoles(consent.getGrantedRoles(), realmRolesGranted, resourceRolesGranted); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index 1ff3da9d01..bf116508fe 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -420,13 +420,13 @@ public class TokenEndpoint { throw new ErrorResponseException("unauthorized_client", "Client not enabled to retrieve service account", Response.Status.UNAUTHORIZED); } - UserModel clientUser = session.users().getUserByServiceAccountClient(client); + UserModel clientUser = session.users().getServiceAccount(client); if (clientUser == null || client.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, ServiceAccountConstants.CLIENT_ID_PROTOCOL_MAPPER) == null) { // May need to handle bootstrap here as well logger.debugf("Service account user for client '%s' not found or default protocol mapper for service account not found. Creating now", client.getClientId()); new ClientManager(new RealmManager(session)).enableServiceAccount(client); - clientUser = session.users().getUserByServiceAccountClient(client); + clientUser = session.users().getServiceAccount(client); } String clientUsername = clientUser.getUsername(); diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 22f84d4f3d..3e5529e4a3 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -22,6 +22,8 @@ import org.keycloak.models.cache.CacheUserProvider; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.scripting.ScriptingProvider; +import org.keycloak.storage.UserStorageManager; +import org.keycloak.storage.federated.UserFederatedStorageProvider; import java.util.*; @@ -36,6 +38,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private final DefaultKeycloakTransactionManager transactionManager; private RealmProvider model; private UserProvider userModel; + private UserStorageManager userStorageManager; private ScriptingProvider scriptingProvider; private UserSessionProvider sessionProvider; private UserFederationManager federationManager; @@ -86,6 +89,22 @@ public class DefaultKeycloakSession implements KeycloakSession { return factory; } + @Override + public UserFederatedStorageProvider userFederatedStorage() { + return null; + } + + @Override + public UserProvider userLocalStorage() { + return getProvider(UserProvider.class); + } + + @Override + public UserProvider userStorageManager() { + if (userStorageManager == null) userStorageManager = new UserStorageManager(this); + return userStorageManager; + } + @Override public UserProvider userStorage() { if (userModel == null) { diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java index c2c774c071..875fd2b710 100755 --- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java +++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java @@ -443,7 +443,7 @@ public class AuthenticationManager { if (client.isConsentRequired()) { - UserConsentModel grantedConsent = user.getConsentByClient(client.getId()); + UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId()); ClientSessionCode accessCode = new ClientSessionCode(realm, clientSession); for (RoleModel r : accessCode.getRequestedRoles()) { @@ -497,7 +497,7 @@ public class AuthenticationManager { if (client.isConsentRequired()) { - UserConsentModel grantedConsent = user.getConsentByClient(client.getId()); + UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId()); List realmRoles = new LinkedList<>(); MultivaluedMap resourceRoles = new MultivaluedMapImpl<>(); diff --git a/services/src/main/java/org/keycloak/services/managers/ClientManager.java b/services/src/main/java/org/keycloak/services/managers/ClientManager.java index a8120e9640..7432e6fc04 100644 --- a/services/src/main/java/org/keycloak/services/managers/ClientManager.java +++ b/services/src/main/java/org/keycloak/services/managers/ClientManager.java @@ -102,7 +102,7 @@ public class ClientManager { sessionsPersister.onClientRemoved(realm, client); } - UserModel serviceAccountUser = realmManager.getSession().users().getUserByServiceAccountClient(client); + UserModel serviceAccountUser = realmManager.getSession().users().getServiceAccount(client); if (serviceAccountUser != null) { new UserManager(realmManager.getSession()).removeUser(realm, serviceAccountUser); } @@ -149,7 +149,7 @@ public class ClientManager { client.setServiceAccountsEnabled(true); // Add dedicated user for this service account - if (realmManager.getSession().users().getUserByServiceAccountClient(client) == null) { + if (realmManager.getSession().users().getServiceAccount(client) == null) { String username = ServiceAccountConstants.SERVICE_ACCOUNT_USER_PREFIX + client.getClientId(); logger.debugf("Creating service account user '%s'", username); diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java index 4907707d90..251cd8e569 100755 --- a/services/src/main/java/org/keycloak/services/resources/AccountService.java +++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java @@ -500,7 +500,7 @@ public class AccountService extends AbstractSecuredLocalService { // Revoke grant in UserModel UserModel user = auth.getUser(); - user.revokeConsentForClient(client.getId()); + session.users().revokeConsentForClient(realm, user, client.getId()); new UserSessionManager(session).revokeOfflineToken(user, client); // Logout clientSessions for this user and client diff --git a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java index fab58150ae..cf8791edd2 100755 --- a/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java +++ b/services/src/main/java/org/keycloak/services/resources/LoginActionsService.java @@ -667,10 +667,10 @@ public class LoginActionsService { return response; } - UserConsentModel grantedConsent = user.getConsentByClient(client.getId()); + UserConsentModel grantedConsent = session.users().getConsentByClient(realm, user, client.getId()); if (grantedConsent == null) { grantedConsent = new UserConsentModel(client); - user.addConsent(grantedConsent); + session.users().addConsent(realm, user, grantedConsent); } for (RoleModel role : accessCode.getRequestedRoles()) { grantedConsent.addGrantedRole(role); @@ -680,7 +680,7 @@ public class LoginActionsService { grantedConsent.addGrantedProtocolMapper(protocolMapper); } } - user.updateConsent(grantedConsent); + session.users().updateConsent(realm, user, grantedConsent); event.detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED); event.success(); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java index 08177b9887..9b5304e882 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientResource.java @@ -304,11 +304,11 @@ public class ClientResource { throw new NotFoundException("Could not find client"); } - UserModel user = session.users().getUserByServiceAccountClient(client); + UserModel user = session.users().getServiceAccount(client); if (user == null) { if (client.isServiceAccountsEnabled()) { new ClientManager(new RealmManager(session)).enableServiceAccount(client); - user = session.users().getUserByServiceAccountClient(client); + user = session.users().getServiceAccount(client); } else { throw new BadRequestException("Service account not enabled for the client '" + client.getClientId() + "'"); } 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 7441517756..2eb39bdd06 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 @@ -527,7 +527,7 @@ public class UsersResource { Set offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user); for (ClientModel client : realm.getClients()) { - UserConsentModel consent = user.getConsentByClient(client.getId()); + UserConsentModel consent = session.users().getConsentByClient(realm, user, client.getId()); boolean hasOfflineToken = offlineClients.contains(client); if (consent == null && !hasOfflineToken) { @@ -576,7 +576,7 @@ public class UsersResource { } ClientModel client = realm.getClientByClientId(clientId); - boolean revokedConsent = user.revokeConsentForClient(client.getId()); + boolean revokedConsent = session.users().revokeConsentForClient(realm, user, client.getId()); boolean revokedOfflineToken = new UserSessionManager(session).revokeOfflineToken(user, client); if (revokedConsent) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 136fc69175..bb7cbaffa2 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -351,15 +351,15 @@ public class ImportTest extends AbstractModelTest { // Test user consents admin = session.users().getUserByUsername("admin", realm); - Assert.assertEquals(2, admin.getConsents().size()); + Assert.assertEquals(2, session.users().getConsents(realm, admin).size()); - UserConsentModel appAdminConsent = admin.getConsentByClient(application.getId()); + UserConsentModel appAdminConsent = session.users().getConsentByClient(realm, admin, application.getId()); Assert.assertEquals(2, appAdminConsent.getGrantedRoles().size()); Assert.assertTrue(appAdminConsent.getGrantedProtocolMappers() == null || appAdminConsent.getGrantedProtocolMappers().isEmpty()); Assert.assertTrue(appAdminConsent.isRoleGranted(realm.getRole("admin"))); Assert.assertTrue(appAdminConsent.isRoleGranted(application.getRole("app-admin"))); - UserConsentModel otherAppAdminConsent = admin.getConsentByClient(otherApp.getId()); + UserConsentModel otherAppAdminConsent = session.users().getConsentByClient(realm, admin, otherApp.getId()); Assert.assertEquals(1, otherAppAdminConsent.getGrantedRoles().size()); Assert.assertEquals(1, otherAppAdminConsent.getGrantedProtocolMappers().size()); Assert.assertTrue(otherAppAdminConsent.isRoleGranted(realm.getRole("admin"))); @@ -376,8 +376,8 @@ public class ImportTest extends AbstractModelTest { // Test service accounts Assert.assertFalse(application.isServiceAccountsEnabled()); Assert.assertTrue(otherApp.isServiceAccountsEnabled()); - Assert.assertNull(session.users().getUserByServiceAccountClient(application)); - UserModel linked = session.users().getUserByServiceAccountClient(otherApp); + Assert.assertNull(session.users().getServiceAccount(application)); + UserModel linked = session.users().getServiceAccount(otherApp); Assert.assertNotNull(linked); Assert.assertEquals("my-service-user", linked.getUsername()); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java index 0a03398d87..2f71b2329e 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java @@ -67,7 +67,7 @@ public class UserConsentModelTest extends AbstractModelTest { johnFooGrant.addGrantedRole(realmRole); johnFooGrant.addGrantedRole(barClientRole); johnFooGrant.addGrantedProtocolMapper(fooMapper); - john.addConsent(johnFooGrant); + realmManager.getSession().users().addConsent(realm, john, johnFooGrant); UserConsentModel johnBarGrant = new UserConsentModel(barClient); johnBarGrant.addGrantedProtocolMapper(barMapper); @@ -75,17 +75,17 @@ public class UserConsentModelTest extends AbstractModelTest { // Update should fail as grant doesn't yet exists try { - john.updateConsent(johnBarGrant); + realmManager.getSession().users().updateConsent(realm, john, johnBarGrant); Assert.fail("Not expected to end here"); } catch (ModelException expected) { } - john.addConsent(johnBarGrant); + realmManager.getSession().users().addConsent(realm, john, johnBarGrant); UserConsentModel maryFooGrant = new UserConsentModel(fooClient); maryFooGrant.addGrantedRole(realmRole); maryFooGrant.addGrantedProtocolMapper(fooMapper); - mary.addConsent(maryFooGrant); + realmManager.getSession().users().addConsent(realm, mary, maryFooGrant); commit(); } @@ -99,27 +99,27 @@ public class UserConsentModelTest extends AbstractModelTest { UserModel john = session.users().getUserByUsername("john", realm); UserModel mary = session.users().getUserByUsername("mary", realm); - UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId()); + UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 2); Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1); Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent)); Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent)); Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent)); - UserConsentModel johnBarConsent = john.getConsentByClient(barClient.getId()); + UserConsentModel johnBarConsent = realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId()); Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1); Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1); Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent)); Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent)); - UserConsentModel maryConsent = mary.getConsentByClient(fooClient.getId()); + UserConsentModel maryConsent = realmManager.getSession().users().getConsentByClient(realm, mary, fooClient.getId()); Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1); Assert.assertEquals(maryConsent.getGrantedProtocolMappers().size(), 1); Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent)); Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent)); Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent)); - Assert.assertNull(mary.getConsentByClient(barClient.getId())); + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, mary, barClient.getId())); } @Test @@ -130,10 +130,10 @@ public class UserConsentModelTest extends AbstractModelTest { UserModel john = session.users().getUserByUsername("john", realm); UserModel mary = session.users().getUserByUsername("mary", realm); - List johnConsents = john.getConsents(); + List johnConsents = realmManager.getSession().users().getConsents(realm, john); Assert.assertEquals(2, johnConsents.size()); - List maryConsents = mary.getConsents(); + List maryConsents = realmManager.getSession().users().getConsents(realm, mary); Assert.assertEquals(1, maryConsents.size()); UserConsentModel maryConsent = maryConsents.get(0); Assert.assertEquals(maryConsent.getClient().getId(), fooClient.getId()); @@ -149,7 +149,7 @@ public class UserConsentModelTest extends AbstractModelTest { ClientModel fooClient = realm.getClientByClientId("foo-client"); UserModel john = session.users().getUserByUsername("john", realm); - UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId()); + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); // Remove foo protocol mapper from johnConsent ProtocolMapperModel protMapperModel = fooClient.getProtocolMapperByName(OIDCLoginProtocol.LOGIN_PROTOCOL, "foo"); @@ -162,14 +162,14 @@ public class UserConsentModelTest extends AbstractModelTest { RoleModel newRealmRole = realm.addRole("new-realm-role"); johnConsent.addGrantedRole(newRealmRole); - john.updateConsent(johnConsent); + realmManager.getSession().users().updateConsent(realm, john, johnConsent); commit(); realm = realmManager.getRealm("original"); fooClient = realm.getClientByClientId("foo-client"); john = session.users().getUserByUsername("john", realm); - johnConsent = john.getConsentByClient(fooClient.getId()); + johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2); Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0); @@ -184,13 +184,13 @@ public class UserConsentModelTest extends AbstractModelTest { ClientModel fooClient = realm.getClientByClientId("foo-client"); UserModel john = session.users().getUserByUsername("john", realm); - john.revokeConsentForClient(fooClient.getId()); + realmManager.getSession().users().revokeConsentForClient(realm, john, fooClient.getId()); commit(); realm = realmManager.getRealm("original"); john = session.users().getUserByUsername("john", realm); - Assert.assertNull(john.getConsentByClient(fooClient.getId())); + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId())); } @Test @@ -213,7 +213,7 @@ public class UserConsentModelTest extends AbstractModelTest { realm = realmManager.getRealm("original"); fooClient = realm.getClientByClientId("foo-client"); UserModel john = session.users().getUserByUsername("john", realm); - UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId()); + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); Assert.assertEquals(johnConsent.getGrantedRoles().size(), 2); Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 0); @@ -232,7 +232,7 @@ public class UserConsentModelTest extends AbstractModelTest { ClientModel fooClient = realm.getClientByClientId("foo-client"); ClientModel barClient = realm.getClientByClientId("bar-client"); UserModel john = session.users().getUserByUsername("john", realm); - UserConsentModel johnConsent = john.getConsentByClient(fooClient.getId()); + UserConsentModel johnConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); Assert.assertEquals(johnConsent.getGrantedRoles().size(), 1); Assert.assertEquals(johnConsent.getGrantedProtocolMappers().size(), 1); @@ -254,13 +254,13 @@ public class UserConsentModelTest extends AbstractModelTest { UserModel john = session.users().getUserByUsername("john", realm); - UserConsentModel johnFooConsent = john.getConsentByClient(fooClient.getId()); + UserConsentModel johnFooConsent = realmManager.getSession().users().getConsentByClient(realm, john, fooClient.getId()); Assert.assertEquals(johnFooConsent.getGrantedRoles().size(), 1); Assert.assertEquals(johnFooConsent.getGrantedProtocolMappers().size(), 1); Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent)); Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent)); - Assert.assertNull(john.getConsentByClient(barClient.getId())); + Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId())); } private boolean isRoleGranted(RoleContainerModel roleContainer, String roleName, UserConsentModel consentModel) { 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 0841c1f948..83ea5adb99 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 @@ -271,7 +271,7 @@ public class UserModelTest extends AbstractModelTest { user2.setLastName("Doe"); // Search - Assert.assertNull(session.users().getUserByServiceAccountClient(client)); + Assert.assertNull(session.users().getServiceAccount(client)); List users = session.users().searchForUser("John Doe", realm); Assert.assertEquals(2, users.size()); Assert.assertTrue(users.contains(user1)); @@ -284,7 +284,7 @@ public class UserModelTest extends AbstractModelTest { // Search and assert service account user not found realm = realmManager.getRealmByName("original"); - UserModel searched = session.users().getUserByServiceAccountClient(client); + UserModel searched = session.users().getServiceAccount(client); Assert.assertEquals(searched, user1); users = session.users().searchForUser("John Doe", realm); Assert.assertEquals(1, users.size()); From 3ba3be877ecc97d1b39a9f94ef26833e0ae8a644 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 30 Jun 2016 16:47:49 -0400 Subject: [PATCH 2/4] fixes --- .../META-INF/jpa-changelog-2.1.0.xml | 147 ++++++++++++++++++ .../admin/ResourceServerService.java | 2 +- .../common/KeycloakIdentity.java | 4 +- .../rest/TestingResourceProvider.java | 2 +- 4 files changed, 151 insertions(+), 4 deletions(-) create mode 100755 model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml 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 new file mode 100755 index 0000000000..79ba2fc3d1 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.1.0.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TYPE in ('password-history', 'password') AND ALGORITHM is NULL + + + + + + + + + + TYPE in ('password-history', 'password') AND ALGORITHM = 'HmacSHA1' + + + + + + + + + + \ No newline at end of file diff --git a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java index 84e52954c3..4f1e638c09 100644 --- a/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java +++ b/services/src/main/java/org/keycloak/authorization/admin/ResourceServerService.java @@ -500,7 +500,7 @@ public class ResourceServerService { umaProtectionRole = client.addRole(Constants.AUTHZ_UMA_PROTECTION); } - UserModel serviceAccount = this.session.users().getUserByServiceAccountClient(client); + UserModel serviceAccount = this.session.users().getServiceAccount(client); if (!serviceAccount.hasRole(umaProtectionRole)) { serviceAccount.grantRole(umaProtectionRole); diff --git a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java index 922785423e..5ddd431279 100644 --- a/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java +++ b/services/src/main/java/org/keycloak/authorization/common/KeycloakIdentity.java @@ -139,10 +139,10 @@ public class KeycloakIdentity implements Identity { if (this.accessToken.getClientSession() != null) { ClientSessionModel clientSession = this.keycloakSession.sessions().getClientSession(this.accessToken.getClientSession()); - clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientSession.getClient()); + clientUser = this.keycloakSession.users().getServiceAccount(clientSession.getClient()); } else if (this.accessToken.getIssuedFor() != null) { ClientModel clientModel = this.keycloakSession.realms().getClientById(this.accessToken.getIssuedFor(), this.realm); - clientUser = this.keycloakSession.users().getUserByServiceAccountClient(clientModel); + clientUser = this.keycloakSession.users().getServiceAccount(clientModel); } diff --git a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java index e484645326..bbc9f276ae 100644 --- a/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java +++ b/testsuite/integration-arquillian/servers/auth-server/services/testsuite-providers/src/main/java/org/keycloak/testsuite/rest/TestingResourceProvider.java @@ -635,7 +635,7 @@ public class TestingResourceProvider implements RealmResourceProvider { public UserRepresentation getUserByServiceAccountClient(@QueryParam("realmName") String realmName, @QueryParam("clientId") String clientId) { RealmModel realm = getRealmByName(realmName); ClientModel client = realm.getClientByClientId(clientId); - UserModel user = session.users().getUserByServiceAccountClient(client); + UserModel user = session.users().getServiceAccount(client); if (user == null) return null; return ModelToRepresentation.toRepresentation(user); } From 7e5a5f79cfd8adff3e2b79abc23a0dec3ee5827f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 7 Jul 2016 10:35:35 -0400 Subject: [PATCH 3/4] fixes for new user fed spi --- .../models/cache/infinispan/CacheManager.java | 34 +++ .../ClusteredCacheBehaviorTest.java | 5 +- .../jpa/JpaUserFederatedStorageProvider.java | 16 +- .../storage/jpa/entity/BrokerLinkEntity.java | 4 +- .../entity/FederatedUserConsentEntity.java | 10 +- .../FederatedUserGroupMembershipEntity.java | 3 +- .../FederatedUserRequiredActionEntity.java | 2 +- .../FederatedUserRoleMappingEntity.java | 1 + .../META-INF/jpa-changelog-2.1.0.xml | 239 +++++++++++------- .../META-INF/jpa-changelog-master.xml | 1 + .../main/resources/META-INF/persistence.xml | 12 + ...erated.UserFederatedStorageProviderFactory | 1 + .../keycloak/adapters/MongoUserProvider.java | 3 +- .../org/keycloak/models/UserProvider.java | 4 - .../keycloak/models/UserQueryProvider.java | 5 + .../keycloak/storage/StorageProviderSpi.java | 2 +- .../keycloak/storage/UserStorageManager.java | 93 ++++--- .../services/org.keycloak.provider.Spi | 1 + .../services/DefaultKeycloakSession.java | 6 +- .../DefaultKeycloakSessionFactory.java | 6 +- .../resources/META-INF/keycloak-server.json | 4 + .../AbstractKeycloakIdentityProviderTest.java | 12 +- .../OIDCKeyCloakServerBrokerBasicTest.java | 10 + .../src/test/resources/log4j.properties | 2 +- 24 files changed, 320 insertions(+), 156 deletions(-) create mode 100644 model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java index e41913d4f6..d272a5aed4 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/CacheManager.java @@ -16,6 +16,40 @@ import java.util.Set; import java.util.function.Predicate; /** + * + * Some notes on how this works: + + * This implementation manages optimistic locking and version checks itself. The reason is Infinispan just does behave + * the way we need it to. Not saying Infinispan is bad, just that we have specific caching requirements! + * + * This is an invalidation cache implementation and requires to caches: + * Cache 1 is an Invalidation Cache + * Cache 2 is a local-only revision number cache. + * + * + * Each node in the cluster maintains its own revision number cache for each entry in the main invalidation cache. This revision + * cache holds the version counter for each cached entity. + * + * Cache listeners do not receive a @CacheEntryInvalidated event if that node does not have an entry for that item. So, consider the following. + + 1. Node 1 gets current counter for user. There currently isn't one as this user isn't cached. + 2. Node 1 reads user from DB + 3. Node 2 updates user + 4. Node 2 calls cache.remove(user). This does not result in an invalidation listener event to node 1! + 5. node 1 checks version counter, checks pass. Stale entry is cached. + + The issue is that Node 1 doesn't have an entry for the user, so it never receives an invalidation listener event from Node 2 thus it can't bump the version. So, when node 1 goes to cache the user it is stale as the version number was never bumped. + + So how is this issue fixed? here is pseudo code: + + 1. Node 1 calls cacheManager.getCurrentRevision() to get the current local version counter of that User + 2. Node 1 getCurrentRevision() pulls current counter for that user + 3. Node 1 getCurrentRevision() adds a "invalidation.key.userid" to invalidation cache. Its just a marker. nothing else + 4. Node 2 update user + 5. Node 2 does a cache.remove(user) cache.remove(invalidation.key.userid) + 6. Node 1 receives invalidation event for invalidation.key.userid. Bumps the version counter for that user + 7. node 1 version check fails, it doesn't cache the user + * * @author Bill Burke * @version $Revision: 1 $ */ diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java index 3dcc913f9b..de6e587d0a 100755 --- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java +++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ClusteredCacheBehaviorTest.java @@ -29,6 +29,8 @@ import org.keycloak.connections.infinispan.InfinispanConnectionProvider; @Ignore public class ClusteredCacheBehaviorTest { public EmbeddedCacheManager createManager() { + System.setProperty("java.net.preferIPv4Stack", "true"); + System.setProperty("jgroups.tcp.port", "53715"); GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder(); boolean clustered = true; @@ -36,7 +38,8 @@ public class ClusteredCacheBehaviorTest { boolean allowDuplicateJMXDomains = true; if (clustered) { - gcb.transport().defaultTransport(); + gcb = gcb.clusteredDefault(); + gcb.transport().clusterName("test-clustering"); } gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains); 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 b206cc5073..4ec58bd02d 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 @@ -149,8 +149,8 @@ public class JpaUserFederatedStorageProvider implements @Override public String getUserByFederatedIdentity(FederatedIdentityModel link, RealmModel realm) { - TypedQuery query = em.createNamedQuery("findBrokerLinkByUserAndProvider", String.class) - .setParameter("realmid", realm.getId()) + TypedQuery query = em.createNamedQuery("findUserByBrokerLinkAndRealm", String.class) + .setParameter("realmId", realm.getId()) .setParameter("identityProvider", link.getIdentityProvider()) .setParameter("brokerUserId", link.getUserId()); List results = query.getResultList(); @@ -180,15 +180,16 @@ public class JpaUserFederatedStorageProvider implements @Override public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { - BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider); + BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider); if (entity == null) return false; em.remove(entity); return true; } - private BrokerLinkEntity getBrokerLinkEntity(UserModel user, String socialProvider) { + private BrokerLinkEntity getBrokerLinkEntity(RealmModel realm, UserModel user, String socialProvider) { TypedQuery query = em.createNamedQuery("findBrokerLinkByUserAndProvider", BrokerLinkEntity.class) .setParameter("userId", user.getId()) + .setParameter("realmId", realm.getId()) .setParameter("identityProvider", socialProvider); List results = query.getResultList(); return results.size() > 0 ? results.get(0) : null; @@ -196,7 +197,7 @@ public class JpaUserFederatedStorageProvider implements @Override public void updateFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel model) { - BrokerLinkEntity entity = getBrokerLinkEntity(user, model.getIdentityProvider()); + BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, model.getIdentityProvider()); if (entity == null) return; entity.setBrokerUserName(model.getUserName()); entity.setBrokerUserId(model.getUserId()); @@ -221,7 +222,7 @@ public class JpaUserFederatedStorageProvider implements @Override public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { - BrokerLinkEntity entity = getBrokerLinkEntity(user, socialProvider); + BrokerLinkEntity entity = getBrokerLinkEntity(realm, user, socialProvider); if (entity == null) return null; return new FederatedIdentityModel(entity.getIdentityProvider(), entity.getBrokerUserId(), entity.getBrokerUserName(), entity.getToken()); } @@ -239,6 +240,7 @@ public class JpaUserFederatedStorageProvider implements consentEntity.setId(KeycloakModelUtils.generateId()); consentEntity.setUserId(user.getId()); consentEntity.setClientId(clientId); + consentEntity.setRealmId(realm.getId()); consentEntity.setStorageProviderId(StorageId.resolveProviderId(user)); em.persist(consentEntity); em.flush(); @@ -588,7 +590,7 @@ public class JpaUserFederatedStorageProvider implements @Override public void preRemove(RealmModel realm) { - int num = em.createNamedQuery("deleteUserConsentRolesByRealm") + int num = em.createNamedQuery("deleteFederatedUserConsentRolesByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); num = em.createNamedQuery("deleteFederatedUserConsentProtMappersByRealm") .setParameter("realmId", realm.getId()).executeUpdate(); diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java index 90ca5f1e9b..a6b05cda11 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/BrokerLinkEntity.java @@ -21,6 +21,7 @@ import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; +import javax.persistence.JoinColumn; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.Table; @@ -32,7 +33,7 @@ import java.io.Serializable; */ @NamedQueries({ @NamedQuery(name= "findBrokerLinkByUser", query="select link from BrokerLinkEntity link where link.userId = :userId"), - @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider"), + @NamedQuery(name= "findBrokerLinkByUserAndProvider", query="select link from BrokerLinkEntity link where link.userId = :userId and link.identityProvider = :identityProvider and link.realmId = :realmId"), @NamedQuery(name= "findUserByBrokerLinkAndRealm", query="select link.userId from BrokerLinkEntity link where link.realmId = :realmId and link.identityProvider = :identityProvider and link.brokerUserId = :brokerUserId"), @NamedQuery(name= "deleteBrokerLinkByStorageProvider", query="delete from BrokerLinkEntity social where social.storageProviderId = :storageProviderId"), @NamedQuery(name= "deleteBrokerLinkByRealm", query="delete from BrokerLinkEntity social where social.realmId = :realmId"), @@ -45,6 +46,7 @@ import java.io.Serializable; public class BrokerLinkEntity { @Id + @Column(name = "USER_ID") private String userId; @Id diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java index 18aab7f1d1..8066310ffc 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserConsentEntity.java @@ -39,12 +39,12 @@ import java.util.Collection; @UniqueConstraint(columnNames = {"USER_ID", "CLIENT_ID"}) }) @NamedQueries({ - @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from UserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"), - @NamedQuery(name="userFederatedConsentsByUser", query="select consent from UserConsentEntity consent where consent.userId = :userId"), - @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from UserConsentEntity consent where consent.realmId=:realmId"), + @NamedQuery(name="userFederatedConsentByUserAndClient", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId and consent.clientId = :clientId"), + @NamedQuery(name="userFederatedConsentsByUser", query="select consent from FederatedUserConsentEntity consent where consent.userId = :userId"), + @NamedQuery(name="deleteFederatedUserConsentsByRealm", query="delete from FederatedUserConsentEntity consent where consent.realmId=:realmId"), @NamedQuery(name="deleteFederatedUserConsentsByStorageProvider", query="delete from FederatedUserConsentEntity e where e.storageProviderId=:storageProviderId"), - @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from UserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"), - @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from UserConsentEntity consent where consent.clientId = :clientId"), + @NamedQuery(name="deleteFederatedUserConsentsByUser", query="delete from FederatedUserConsentEntity consent where consent.userId = :userId and consent.realmId = :realmId"), + @NamedQuery(name="deleteFederatedUserConsentsByClient", query="delete from FederatedUserConsentEntity consent where consent.clientId = :clientId"), }) public class FederatedUserConsentEntity { diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java index cbbdf4e5c9..ecfaa49ed1 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserGroupMembershipEntity.java @@ -35,7 +35,7 @@ import java.io.Serializable; @NamedQueries({ @NamedQuery(name="feduserMemberOf", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId and m.groupId = :groupId"), @NamedQuery(name="feduserGroupMembership", query="select m from FederatedUserGroupMembershipEntity m where m.userId = :userId"), - @NamedQuery(name="fedgroupMembership", query="select g.user from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"), + @NamedQuery(name="fedgroupMembership", query="select g.userId from FederatedUserGroupMembershipEntity g where g.groupId = :groupId"), @NamedQuery(name="feduserGroupIds", query="select m.groupId from FederatedUserGroupMembershipEntity m where m.userId = :userId"), @NamedQuery(name="deleteFederatedUserGroupMembershipByRealm", query="delete from FederatedUserGroupMembershipEntity mapping where mapping.realmId=:realmId"), @NamedQuery(name="deleteFederatedUserGroupMembershipByStorageProvider", query="delete from FederatedUserGroupMembershipEntity e where e.storageProviderId=:storageProviderId"), @@ -50,6 +50,7 @@ import java.io.Serializable; public class FederatedUserGroupMembershipEntity { @Id + @Column(name = "USER_ID") protected String userId; @Id diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java index c39a311a4e..f555628344 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRequiredActionEntity.java @@ -36,7 +36,7 @@ import java.io.Serializable; * @version $Revision: 1 $ */ @NamedQueries({ - @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and attr.realmId=:realmId"), + @NamedQuery(name="getFederatedUserRequiredActionsByUser", query="select action from FederatedUserRequiredActionEntity action where action.userId = :userId and action.realmId=:realmId"), @NamedQuery(name="deleteFederatedUserRequiredActionsByRealm", query="delete from FederatedUserRequiredActionEntity action where action.realmId=:realmId"), @NamedQuery(name="deleteFederatedUserRequiredActionsByStorageProvider", query="delete from FederatedUserRequiredActionEntity e where e.storageProviderId=:storageProviderId"), @NamedQuery(name="deleteFederatedUserRequiredActionsByRealmAndLink", query="delete from FederatedUserRequiredActionEntity action where action.userId IN (select u.id from UserEntity u where u.realmId=:realmId and u.federationLink=:link)") diff --git a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java index 78ef29912f..719fce6dea 100755 --- a/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java +++ b/model/jpa/src/main/java/org/keycloak/storage/jpa/entity/FederatedUserRoleMappingEntity.java @@ -48,6 +48,7 @@ import java.io.Serializable; public class FederatedUserRoleMappingEntity { @Id + @Column(name = "USER_ID") protected String userId; @Id 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 79ba2fc3d1..79c3e9e568 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 @@ -17,131 +17,178 @@ --> - + - - - + + + - - - - + - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + - - - + + + - + + + + + + + + - - + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - TYPE in ('password-history', 'password') AND ALGORITHM is NULL - + + + + + + + + + + + + + + + + - - - - - - - TYPE in ('password-history', 'password') AND ALGORITHM = 'HmacSHA1' - - - - - - - - - \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index c8abe490b9..4dbc50bf3b 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -32,6 +32,7 @@ + diff --git a/model/jpa/src/main/resources/META-INF/persistence.xml b/model/jpa/src/main/resources/META-INF/persistence.xml index b90c0fc232..d912033b8a 100755 --- a/model/jpa/src/main/resources/META-INF/persistence.xml +++ b/model/jpa/src/main/resources/META-INF/persistence.xml @@ -25,6 +25,7 @@ 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.UserFederationProviderEntity org.keycloak.models.jpa.entities.UserFederationMapperEntity org.keycloak.models.jpa.entities.RoleEntity @@ -64,6 +65,17 @@ org.keycloak.authorization.jpa.entities.ResourceEntity org.keycloak.authorization.jpa.entities.ScopeEntity org.keycloak.authorization.jpa.entities.PolicyEntity + + + org.keycloak.storage.jpa.entity.BrokerLinkEntity + org.keycloak.storage.jpa.entity.FederatedUserAttributeEntity + org.keycloak.storage.jpa.entity.FederatedUserConsentEntity + org.keycloak.storage.jpa.entity.FederatedUserConsentRoleEntity + org.keycloak.storage.jpa.entity.FederatedUserConsentProtocolMapperEntity + org.keycloak.storage.jpa.entity.FederatedUserCredentialEntity + org.keycloak.storage.jpa.entity.FederatedUserGroupMembershipEntity + org.keycloak.storage.jpa.entity.FederatedUserRequiredActionEntity + org.keycloak.storage.jpa.entity.FederatedUserRoleMappingEntity true diff --git a/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory new file mode 100644 index 0000000000..c67277c549 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/services/org.keycloak.storage.federated.UserFederatedStorageProviderFactory @@ -0,0 +1 @@ +org.keycloak.storage.jpa.JpaUserFederatedStorageProviderFactory \ No newline at end of file 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 4f20a50f7b..ad811d4e6a 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 @@ -209,7 +209,8 @@ public class MongoUserProvider implements UserProvider { } @Override - public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + public List + searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { search = search.trim(); Pattern caseInsensitivePattern = Pattern.compile("(?i:" + search + ")"); 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 16e6634ffe..d903799948 100755 --- a/server-spi/src/main/java/org/keycloak/models/UserProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserProvider.java @@ -47,10 +47,6 @@ public interface UserProvider extends Provider, UserLookupProvider, UserQueryPro UserModel getServiceAccount(ClientModel client); List getUsers(RealmModel realm, boolean includeServiceAccounts); List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts); - List searchForUser(String search, RealmModel realm); - - // Searching by UserModel.attribute (not property) - List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm); void preRemove(RealmModel realm); diff --git a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java index 3f476bf91d..57a69ca356 100644 --- a/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java +++ b/server-spi/src/main/java/org/keycloak/models/UserQueryProvider.java @@ -41,4 +41,9 @@ public interface UserQueryProvider { List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults); List getGroupMembers(RealmModel realm, GroupModel group); + + List searchForUser(String search, RealmModel realm); + + // 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/StorageProviderSpi.java b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java index 42a906037c..5d6d0da687 100755 --- a/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java +++ b/server-spi/src/main/java/org/keycloak/storage/StorageProviderSpi.java @@ -33,7 +33,7 @@ public class StorageProviderSpi implements Spi { @Override public String getName() { - return "userFederation"; + return "storage"; } @Override 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 aa438a3789..f740f7689f 100755 --- a/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java +++ b/server-spi/src/main/java/org/keycloak/storage/UserStorageManager.java @@ -43,6 +43,7 @@ import org.keycloak.storage.federated.UserFederatedStorageProvider; import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -152,43 +153,77 @@ public class UserStorageManager implements UserProvider { @Override public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { - getFederatedStorage().addFederatedIdentity(realm, user, socialLink); + if (StorageId.isLocalStorage(user)) { + localStorage().addFederatedIdentity(realm, user, socialLink); + } else { + getFederatedStorage().addFederatedIdentity(realm, user, socialLink); + } } public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { - getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); + if (StorageId.isLocalStorage(federatedUser)) { + localStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); + + } else { + getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); + } } @Override public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { - return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider); + if (StorageId.isLocalStorage(user)) { + return localStorage().removeFederatedIdentity(realm, user, socialProvider); + } else { + return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider); + } } @Override public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { - getFederatedStorage().addConsent(realm, user, consent); + if (StorageId.isLocalStorage(user)) { + localStorage().addConsent(realm, user, consent); + } else { + getFederatedStorage().addConsent(realm, user, consent); + } } @Override public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { - return getFederatedStorage().getConsentByClient(realm, user, clientInternalId); + if (StorageId.isLocalStorage(user)) { + return localStorage().getConsentByClient(realm, user, clientInternalId); + } else { + return getFederatedStorage().getConsentByClient(realm, user, clientInternalId); + } } @Override public List getConsents(RealmModel realm, UserModel user) { - return getFederatedStorage().getConsents(realm, user); + if (StorageId.isLocalStorage(user)) { + return localStorage().getConsents(realm, user); + + } else { + return getFederatedStorage().getConsents(realm, user); + } } @Override public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { - getFederatedStorage().updateConsent(realm, user, consent); + if (StorageId.isLocalStorage(user)) { + localStorage().updateConsent(realm, user, consent); + } else { + getFederatedStorage().updateConsent(realm, user, consent); + } } @Override public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { - return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId); + if (StorageId.isLocalStorage(user)) { + return localStorage().revokeConsentForClient(realm, user, clientInternalId); + } else { + return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId); + } } @Override @@ -334,24 +369,10 @@ public class UserStorageManager implements UserProvider { @Override public List searchForUser(final String search, final RealmModel realm, int firstResult, int maxResults) { - final Map attributes = new HashMap(); - int spaceIndex = search.lastIndexOf(' '); - if (spaceIndex > -1) { - String firstName = search.substring(0, spaceIndex).trim(); - String lastName = search.substring(spaceIndex).trim(); - attributes.put(UserModel.FIRST_NAME, firstName); - attributes.put(UserModel.LAST_NAME, lastName); - } else if (search.indexOf('@') > -1) { - attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); - attributes.put(UserModel.EMAIL, search.trim().toLowerCase()); - } else { - attributes.put(UserModel.LAST_NAME, search.trim()); - attributes.put(UserModel.USERNAME, search.trim().toLowerCase()); - } - return query(new PaginatedQuery() { + return query(new PaginatedQuery() { @Override public List query(UserQueryProvider provider, int first, int max) { - return provider.searchForUserByAttributes(attributes, realm, first, max); + return provider.searchForUser(search, realm, first, max); } }, realm, firstResult, maxResults); } @@ -372,26 +393,32 @@ public class UserStorageManager implements UserProvider { } @Override - public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { - Map attributes = new HashMap<>(); - attributes.put(attrName, attrValue); - return searchForUserByAttributes(attributes, realm); + public List searchForUserByUserAttribute(final String attrName, final String attrValue, RealmModel realm) { + return query(new PaginatedQuery() { + @Override + public List query(UserQueryProvider provider, int first, int max) { + return provider.searchForUserByUserAttribute(attrName, attrValue, realm); + } + }, realm,0, Integer.MAX_VALUE - 1); } @Override public Set getFederatedIdentities(UserModel user, RealmModel realm) { if (user == null) throw new IllegalStateException("Federated user no longer valid"); + Set set = new HashSet<>(); if (StorageId.isLocalStorage(user)) { - return localStorage().getFederatedIdentities(user, realm); + set.addAll(localStorage().getFederatedIdentities(user, realm)); } - return getFederatedStorage().getFederatedIdentities(user, realm); + set.addAll(getFederatedStorage().getFederatedIdentities(user, realm)); + return set; } @Override public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { if (user == null) throw new IllegalStateException("Federated user no longer valid"); if (StorageId.isLocalStorage(user)) { - return localStorage().getFederatedIdentity(user, socialProvider, realm); + FederatedIdentityModel model = localStorage().getFederatedIdentity(user, socialProvider, realm); + if (model != null) return model; } return getFederatedStorage().getFederatedIdentity(user, socialProvider, realm); } @@ -430,6 +457,7 @@ public class UserStorageManager implements UserProvider { @Override public void preRemove(RealmModel realm, UserFederationProviderModel model) { + getFederatedStorage().preRemove(realm, model); localStorage().preRemove(realm, model); } @@ -454,11 +482,14 @@ public class UserStorageManager implements UserProvider { @Override public void preRemove(RealmModel realm, ClientModel client) { localStorage().preRemove(realm, client); + getFederatedStorage().preRemove(realm, client); + } @Override public void preRemove(ProtocolMapperModel protocolMapper) { localStorage().preRemove(protocolMapper); + getFederatedStorage().preRemove(protocolMapper); } @Override 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 89c6e2ddae..c5a5ebb3fd 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,6 +16,7 @@ # org.keycloak.models.UserFederationSpi +org.keycloak.storage.StorageProviderSpi org.keycloak.storage.federated.UserFederatedStorageProviderSpi org.keycloak.mappers.UserFederationMapperSpi org.keycloak.models.RealmSpi diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 3e5529e4a3..8eb2a56b6b 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -42,6 +42,7 @@ public class DefaultKeycloakSession implements KeycloakSession { private ScriptingProvider scriptingProvider; private UserSessionProvider sessionProvider; private UserFederationManager federationManager; + private UserFederatedStorageProvider userFederatedStorageProvider; private KeycloakContext context; public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) { @@ -91,7 +92,10 @@ public class DefaultKeycloakSession implements KeycloakSession { @Override public UserFederatedStorageProvider userFederatedStorage() { - return null; + if (userFederatedStorageProvider == null) { + userFederatedStorageProvider = getProvider(UserFederatedStorageProvider.class); + } + return userFederatedStorageProvider; } @Override diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 172de6ee48..b56af8de24 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -154,7 +154,11 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory { @Override public ProviderFactory getProviderFactory(Class clazz, String id) { - return factoriesMap.get(clazz).get(id); + Map map = factoriesMap.get(clazz); + if (map == null) { + return null; + } + return map.get(id); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json index c37291d81f..c42d254a3f 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json +++ b/testsuite/integration-arquillian/tests/base/src/test/resources/META-INF/keycloak-server.json @@ -23,6 +23,10 @@ "provider": "${keycloak.user.provider:jpa}" }, + "userFederatedStorage": { + "provider": "${keycloak.userFederatedStorage.provider:jpa}" + }, + "userSessionPersister": { "provider": "${keycloak.userSessionPersister.provider:jpa}" }, diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java index 1af21f51f0..7683d0c0f4 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/AbstractKeycloakIdentityProviderTest.java @@ -72,7 +72,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent @Test public void testDisabledUser() { + KeycloakSession session = brokerServerRule.startSession(); setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF); + brokerServerRule.stopSession(session, true); driver.navigate().to("http://localhost:8081/test-app"); loginPage.clickSocial(getProviderId()); @@ -81,7 +83,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent driver.navigate().to("http://localhost:8081/test-app/logout"); try { - KeycloakSession session = brokerServerRule.startSession(); + session = brokerServerRule.startSession(); session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(false); brokerServerRule.stopSession(session, true); @@ -93,7 +95,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent assertTrue(errorPage.isCurrent()); assertEquals("Account is disabled, contact admin.", errorPage.getError()); } finally { - KeycloakSession session = brokerServerRule.startSession(); + session = brokerServerRule.startSession(); session.users().getUserByUsername("test-user", session.realms().getRealmByName("realm-with-broker")).setEnabled(true); brokerServerRule.stopSession(session, true); } @@ -101,7 +103,9 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent @Test public void testTemporarilyDisabledUser() { + KeycloakSession session = brokerServerRule.startSession(); setUpdateProfileFirstLogin(session.realms().getRealmByName("realm-with-broker"), IdentityProviderRepresentation.UPFLM_OFF); + brokerServerRule.stopSession(session, true); driver.navigate().to("http://localhost:8081/test-app"); loginPage.clickSocial(getProviderId()); @@ -109,7 +113,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent driver.navigate().to("http://localhost:8081/test-app/logout"); try { - KeycloakSession session = brokerServerRule.startSession(); + session = brokerServerRule.startSession(); RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker"); brokerRealm.setBruteForceProtected(true); brokerRealm.setFailureFactor(2); @@ -129,7 +133,7 @@ public abstract class AbstractKeycloakIdentityProviderTest extends AbstractIdent assertTrue(errorPage.isCurrent()); assertEquals("Account is disabled, contact admin.", errorPage.getError()); } finally { - KeycloakSession session = brokerServerRule.startSession(); + session = brokerServerRule.startSession(); RealmModel brokerRealm = session.realms().getRealmByName("realm-with-broker"); brokerRealm.setBruteForceProtected(false); brokerRealm.setFailureFactor(0); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java index 28663ff269..d79d85f729 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/broker/OIDCKeyCloakServerBrokerBasicTest.java @@ -170,4 +170,14 @@ public class OIDCKeyCloakServerBrokerBasicTest extends AbstractKeycloakIdentityP public void testAccountManagementLinkIdentity() { super.testAccountManagementLinkIdentity(); } + + @Test + public void testWithLinkedFederationProvider() throws Exception { + super.testWithLinkedFederationProvider(); + } + + @Test + public void testAccountManagementLinkedIdentityAlreadyExists() { + super.testAccountManagementLinkedIdentityAlreadyExists(); + } } diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties index c2bc22c784..306d48ebea 100755 --- a/testsuite/integration/src/test/resources/log4j.properties +++ b/testsuite/integration/src/test/resources/log4j.properties @@ -21,7 +21,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n -log4j.logger.org.keycloak=info +log4j.logger.org.keycloak=debug # Enable to view events # log4j.logger.org.keycloak.events=debug From bf2ca4008eaa99ac72d47e374588e1f153d09642 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 7 Jul 2016 12:06:57 -0400 Subject: [PATCH 4/4] log level info --- .../storage/changeset/UserStorageManager.java | 341 ------------------ .../src/test/resources/log4j.properties | 2 +- 2 files changed, 1 insertion(+), 342 deletions(-) delete mode 100755 server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java diff --git a/server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java b/server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java deleted file mode 100755 index 01bac5080c..0000000000 --- a/server-spi/src/main/java/org/keycloak/storage/changeset/UserStorageManager.java +++ /dev/null @@ -1,341 +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.jboss.logging.Logger; -import org.keycloak.models.ClientModel; -import org.keycloak.models.CredentialValidationOutput; -import org.keycloak.models.FederatedIdentityModel; -import org.keycloak.models.GroupModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.ModelException; -import org.keycloak.models.ProtocolMapperModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserConsentModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserFederationProviderModel; -import org.keycloak.models.UserModel; -import org.keycloak.models.UserProvider; -import org.keycloak.models.UserUpdateProvider; -import org.keycloak.storage.StorageId; -import org.keycloak.storage.StorageProvider; -import org.keycloak.storage.StorageProviderFactory; -import org.keycloak.storage.StorageProviderModel; -import org.keycloak.storage.federated.UserFederatedStorageProvider; - -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** - * @author Bill Burke - * @version $Revision: 1 $ - */ -public class UserStorageManager implements UserProvider { - - private static final Logger logger = Logger.getLogger(UserStorageManager.class); - - 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; - } - - protected UserProvider localStorage() { - if (localStorage == null) { - localStorage = session.getProvider(UserProvider.class); - } - return localStorage; - } - - protected List getStorageProviders(RealmModel realm) { - return null; - } - - protected T getFirstStorageProvider(RealmModel realm, Class type) { - for (StorageProviderModel model : getStorageProviders(realm)) { - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); - if (factory.supports(type)) { - return type.cast(factory.getInstance(session, model)); - } - } - return null; - } - - 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 (factory.supports(type)) { - list.add(type.cast(factory.getInstance(session, model))); - } - - - } - return list; - } - - - @Override - public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles, boolean addDefaultRequiredActions) { - UserDataStore store = getFirstStorageProvider(realm, UserDataStore.class); - if (store != null) { - UserData data = new UserData(); - } - return localStorage().addUser(realm, id, username.toLowerCase(), addDefaultRoles, addDefaultRequiredActions); - } - - @Override - public UserModel addUser(RealmModel realm, String username) { - UserUpdateProvider registry = getFirstStorageProvider(realm, UserUpdateProvider.class); - if (registry != null) { - return registry.addUser(realm, username); - } - return localStorage().addUser(realm, username.toLowerCase()); - } - - public StorageProvider getStorageProvider(StorageProviderModel model) { - StorageProviderFactory factory = (StorageProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(StorageProvider.class, model.getProviderName()); - return factory.getInstance(session, model); - } - - public StorageProvider getStorageProvider(String providerId) { - return null; - } - - @Override - public boolean removeUser(RealmModel realm, UserModel user) { - StorageId storageId = new StorageId(user.getId()); - if (storageId.getProviderId() == null) { - return localStorage().removeUser(realm, user); - } - UserUpdateProvider registry = (UserUpdateProvider)getStorageProvider(storageId.getProviderId()); - if (registry == null) { - throw new ModelException("Could not resolve StorageProvider: " + storageId.getProviderId()); - } - return registry.removeUser(realm, user); - - } - - public UserFederatedStorageProvider getFederatedStorage() { - return null; - } - - @Override - public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIdentityModel socialLink) { - getFederatedStorage().addFederatedIdentity(realm, user, socialLink); - } - - public void updateFederatedIdentity(RealmModel realm, UserModel federatedUser, FederatedIdentityModel federatedIdentityModel) { - getFederatedStorage().updateFederatedIdentity(realm, federatedUser, federatedIdentityModel); - } - - @Override - public boolean removeFederatedIdentity(RealmModel realm, UserModel user, String socialProvider) { - return getFederatedStorage().removeFederatedIdentity(realm, user, socialProvider); - } - - @Override - public void addConsent(RealmModel realm, UserModel user, UserConsentModel consent) { - getFederatedStorage().addConsent(realm, user, consent); - - } - - @Override - public UserConsentModel getConsentByClient(RealmModel realm, UserModel user, String clientInternalId) { - return getFederatedStorage().getConsentByClient(realm, user, clientInternalId); - } - - @Override - public List getConsents(RealmModel realm, UserModel user) { - return getFederatedStorage().getConsents(realm, user); - } - - @Override - public void updateConsent(RealmModel realm, UserModel user, UserConsentModel consent) { - getFederatedStorage().updateConsent(realm, user, consent); - - } - - @Override - public boolean revokeConsentForClient(RealmModel realm, UserModel user, String clientInternalId) { - return getFederatedStorage().revokeConsentForClient(realm, user, clientInternalId); - } - - @Override - public Set getFederatedIdentities(UserModel user, RealmModel realm) { - return null; - } - - @Override - public FederatedIdentityModel getFederatedIdentity(UserModel user, String socialProvider, RealmModel realm) { - return null; - } - - @Override - public UserModel getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) { - return null; - } - - @Override - public List getGroupMembers(RealmModel realm, GroupModel group, int firstResult, int maxResults) { - return null; - } - - @Override - public List getGroupMembers(RealmModel realm, GroupModel group) { - return null; - } - - @Override - public UserModel getServiceAccount(ClientModel client) { - return null; - } - - @Override - public List getUsers(RealmModel realm, boolean includeServiceAccounts) { - return null; - } - - @Override - public List getUsers(RealmModel realm, int firstResult, int maxResults, boolean includeServiceAccounts) { - return null; - } - - @Override - public void grantToAllUsers(RealmModel realm, RoleModel role) { - - } - - @Override - public void preRemove(RealmModel realm) { - - } - - @Override - public void preRemove(RealmModel realm, UserFederationProviderModel link) { - - } - - @Override - public void preRemove(RealmModel realm, RoleModel role) { - - } - - @Override - public void preRemove(RealmModel realm, GroupModel group) { - - } - - @Override - public void preRemove(RealmModel realm, ClientModel client) { - - } - - @Override - public void preRemove(ProtocolMapperModel protocolMapper) { - - } - - @Override - public void preRemove(RealmModel realm, StorageProviderModel link) { - - } - - @Override - public CredentialValidationOutput validCredentials(KeycloakSession session, RealmModel realm, UserCredentialModel... input) { - return null; - } - - @Override - public void close() { - - } - - @Override - public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, List input) { - return false; - } - - @Override - public boolean validCredentials(KeycloakSession session, RealmModel realm, UserModel user, UserCredentialModel... input) { - return false; - } - - @Override - public UserModel getUserById(String id, RealmModel realm) { - return null; - } - - @Override - public UserModel getUserByUsername(String username, RealmModel realm) { - return null; - } - - @Override - public UserModel getUserByEmail(String email, RealmModel realm) { - return null; - } - - @Override - public int getUsersCount(RealmModel realm) { - return 0; - } - - @Override - public List getUsers(RealmModel realm) { - return null; - } - - @Override - public List searchForUser(String search, RealmModel realm) { - return null; - } - - @Override - public List searchForUserByAttributes(Map attributes, RealmModel realm) { - return null; - } - - @Override - public List getUsers(RealmModel realm, int firstResult, int maxResults) { - return null; - } - - @Override - public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { - return null; - } - - @Override - public List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults) { - return null; - } - - @Override - public List searchForUserByUserAttribute(String attrName, String attrValue, RealmModel realm) { - return null; - } -} diff --git a/testsuite/integration/src/test/resources/log4j.properties b/testsuite/integration/src/test/resources/log4j.properties index 306d48ebea..c2bc22c784 100755 --- a/testsuite/integration/src/test/resources/log4j.properties +++ b/testsuite/integration/src/test/resources/log4j.properties @@ -21,7 +21,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{HH:mm:ss,SSS} %-5p %t [%c] %m%n -log4j.logger.org.keycloak=debug +log4j.logger.org.keycloak=info # Enable to view events # log4j.logger.org.keycloak.events=debug