/*
* 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.mongo;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.QueryBuilder;
import org.keycloak.common.util.MultivaluedHashMap;
import org.keycloak.component.ComponentModel;
import org.keycloak.connections.mongo.api.MongoStore;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.credential.CredentialModel;
import org.keycloak.credential.UserCredentialStore;
import org.keycloak.models.ClientModel;
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
import org.keycloak.models.mongo.keycloak.entities.FederatedIdentityEntity;
import org.keycloak.models.mongo.keycloak.entities.MongoUserEntity;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.storage.StorageId;
import org.keycloak.storage.UserStorageProvider;
import org.keycloak.storage.federated.UserAttributeFederatedStorage;
import org.keycloak.storage.federated.UserBrokerLinkFederatedStorage;
import org.keycloak.storage.federated.UserConsentFederatedStorage;
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.mongo.entity.FederatedUser;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class MongoUserFederatedStorageProvider implements
UserFederatedStorageProvider,
UserCredentialStore {
private final MongoStoreInvocationContext invocationContext;
private final KeycloakSession session;
public MongoUserFederatedStorageProvider(KeycloakSession session, MongoStoreInvocationContext invocationContext) {
this.session = session;
this.invocationContext = invocationContext;
}
protected MongoStore getMongoStore() {
return invocationContext.getMongoStore();
}
protected FederatedUser addUserEntity(RealmModel realm, String id) {
FederatedUser userEntity = new FederatedUser();
userEntity.setId(id);
userEntity.setStorageId(StorageId.providerId(id));
userEntity.setRealmId(realm.getId());
getMongoStore().insertEntity(userEntity, invocationContext);
return userEntity;
}
protected FederatedUser getUserById(String id) {
return getMongoStore().loadEntity(FederatedUser.class, id, invocationContext);
}
protected FederatedUser findOrCreate(RealmModel realm, String id) {
FederatedUser user = getUserById(id);
if (user != null) return user;
return addUserEntity(realm, id);
}
@Override
public boolean removeStoredCredential(RealmModel realm, String userId, String id) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return false;
CredentialEntity ce = getCredentialEntity(id, userEntity);
if (ce != null) return getMongoStore().pullItemFromList(userEntity, "credentials", ce, invocationContext);
return false;
}
private CredentialEntity getCredentialEntity(String id, FederatedUser userEntity) {
CredentialEntity ce = null;
if (userEntity.getCredentials() != null) {
for (CredentialEntity credentialEntity : userEntity.getCredentials()) {
if (credentialEntity.getId().equals(id)) {
ce = credentialEntity;
break;
}
}
}
return ce;
}
protected CredentialModel toModel(CredentialEntity entity) {
CredentialModel model = new CredentialModel();
model.setId(entity.getId());
model.setHashIterations(entity.getHashIterations());
model.setType(entity.getType());
model.setValue(entity.getValue());
model.setAlgorithm(entity.getAlgorithm());
model.setSalt(entity.getSalt());
model.setPeriod(entity.getPeriod());
model.setCounter(entity.getCounter());
model.setCreatedDate(entity.getCreatedDate());
model.setDevice(entity.getDevice());
model.setDigits(entity.getDigits());
MultivaluedHashMap<String, String> config = new MultivaluedHashMap<>();
model.setConfig(config);
if (entity.getConfig() != null) {
config.putAll(entity.getConfig());
}
return model;
}
@Override
public CredentialModel getStoredCredentialById(RealmModel realm, String userId, String id) {
FederatedUser userEntity = getUserById(id);
if (userEntity != null && userEntity.getCredentials() != null) {
for (CredentialEntity credentialEntity : userEntity.getCredentials()) {
if (credentialEntity.getId().equals(id)) {
return toModel(credentialEntity);
}
}
}
return null;
}
@Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, String userId) {
FederatedUser userEntity = getUserById(userId);
if (userEntity != null && userEntity.getCredentials() != null) {
List<CredentialModel> list = new LinkedList<>();
for (CredentialEntity credentialEntity : userEntity.getCredentials()) {
list.add(toModel(credentialEntity));
}
return list;
}
return Collections.EMPTY_LIST;
}
@Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, String userId, String type) {
FederatedUser userEntity = getUserById(userId);
if (userEntity != null && userEntity.getCredentials() != null) {
List<CredentialModel> list = new LinkedList<>();
for (CredentialEntity credentialEntity : userEntity.getCredentials()) {
if (type.equals(credentialEntity.getType())) list.add(toModel(credentialEntity));
}
return list;
}
return Collections.EMPTY_LIST;
}
@Override
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, String userId, String name, String type) {
FederatedUser userEntity = getUserById(userId);
if (userEntity != null && userEntity.getCredentials() != null) {
for (CredentialEntity credentialEntity : userEntity.getCredentials()) {
if (credentialEntity.getDevice().equals(name) && type.equals(credentialEntity.getType())) {
return toModel(credentialEntity);
}
}
}
return null;
}
@Override
public List<String> getStoredUsers(RealmModel realm, int first, int max) {
QueryBuilder queryBuilder = new QueryBuilder()
.and("realmId").is(realm.getId());
DBObject query = queryBuilder.get();
List<FederatedUser> users = getMongoStore().loadEntities(FederatedUser.class, query, null, first, max, invocationContext);
List<String> ids = new LinkedList<>();
for (FederatedUser user : users) ids.add(user.getId());
return ids;
}
@Override
public void preRemove(RealmModel realm) {
DBObject query = new QueryBuilder()
.and("realmId").is(realm.getId())
.get();
getMongoStore().removeEntities(FederatedUser.class, query, true, invocationContext);
}
@Override
public void preRemove(RealmModel realm, UserFederationProviderModel link) {
}
@Override
public void preRemove(RealmModel realm, GroupModel group) {
DBObject query = new QueryBuilder()
.and("groupIds").is(group.getId())
.get();
DBObject pull = new BasicDBObject("$pull", query);
getMongoStore().updateEntities(FederatedUser.class, query, pull, invocationContext);
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
DBObject query = new QueryBuilder()
.and("roleIds").is(role.getId())
.get();
DBObject pull = new BasicDBObject("$pull", query);
getMongoStore().updateEntities(FederatedUser.class, query, pull, invocationContext);
}
@Override
public void preRemove(RealmModel realm, ClientModel client) {
}
@Override
public void preRemove(ProtocolMapperModel protocolMapper) {
}
@Override
public void preRemove(RealmModel realm, UserModel user) {
getMongoStore().removeEntity(FederatedUser.class, user.getId(), invocationContext);
}
@Override
public void preRemove(RealmModel realm, ComponentModel model) {
if (!model.getProviderType().equals(UserStorageProvider.class.getName())) return;
DBObject query = new QueryBuilder()
.and("storageId").is(model.getId())
.get();
getMongoStore().removeEntities(FederatedUser.class, query, true, invocationContext);
}
@Override
public void close() {
}
@Override
public void setSingleAttribute(RealmModel realm, String userId, String name, String value) {
FederatedUser userEntity = findOrCreate(realm, userId);
if (userEntity.getAttributes() == null) {
userEntity.setAttributes(new HashMap<>());
}
List<String> attrValues = new LinkedList<>();
attrValues.add(value);
userEntity.getAttributes().put(name, attrValues);
getMongoStore().updateEntity(userEntity, invocationContext);
}
@Override
public void setAttribute(RealmModel realm, String userId, String name, List<String> values) {
FederatedUser userEntity = findOrCreate(realm, userId);
if (userEntity.getAttributes() == null) {
userEntity.setAttributes(new HashMap<>());
}
userEntity.getAttributes().put(name, values);
getMongoStore().updateEntity(userEntity, invocationContext);
}
@Override
public void removeAttribute(RealmModel realm, String userId, String name) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getAttributes() == null) return;
userEntity.getAttributes().remove(name);
getMongoStore().updateEntity(userEntity, invocationContext);
}
@Override
public MultivaluedHashMap<String, String> getAttributes(RealmModel realm, String userId) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getAttributes() == null) return new MultivaluedHashMap<>();
MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
result.putAll(userEntity.getAttributes());
return result;
}
@Override
public List<String> getUsersByUserAttribute(RealmModel realm, String name, String value) {
QueryBuilder queryBuilder = new QueryBuilder()
.and("realmId").is(realm.getId());
queryBuilder.and("attributes." + name).is(value);
List<FederatedUser> users = getMongoStore().loadEntities(FederatedUser.class, queryBuilder.get(), invocationContext);
List<String> ids = new LinkedList<>();
for (FederatedUser user : users) ids.add(user.getId());
return ids;
}
@Override
public String getUserByFederatedIdentity(FederatedIdentityModel socialLink, RealmModel realm) {
DBObject query = new QueryBuilder()
.and("federatedIdentities.identityProvider").is(socialLink.getIdentityProvider())
.and("federatedIdentities.userId").is(socialLink.getUserId())
.and("realmId").is(realm.getId())
.get();
FederatedUser userEntity = getMongoStore().loadSingleEntity(FederatedUser.class, query, invocationContext);
return userEntity != null ? userEntity.getId() : null;
}
@Override
public void addFederatedIdentity(RealmModel realm, String userId, FederatedIdentityModel socialLink) {
FederatedUser userEntity = findOrCreate(realm, userId);
FederatedIdentityEntity federatedIdentityEntity = new FederatedIdentityEntity();
federatedIdentityEntity.setIdentityProvider(socialLink.getIdentityProvider());
federatedIdentityEntity.setUserId(socialLink.getUserId());
federatedIdentityEntity.setUserName(socialLink.getUserName().toLowerCase());
federatedIdentityEntity.setToken(socialLink.getToken());
getMongoStore().pushItemToList(userEntity, "federatedIdentities", federatedIdentityEntity, true, invocationContext);
}
@Override
public boolean removeFederatedIdentity(RealmModel realm, String userId, String socialProvider) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return false;
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
if (federatedIdentityEntity == null) {
return false;
}
return getMongoStore().pullItemFromList(userEntity, "federatedIdentities", federatedIdentityEntity, invocationContext); }
private FederatedIdentityEntity findFederatedIdentityLink(FederatedUser userEntity, String identityProvider) {
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
if (linkEntities == null) {
return null;
}
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
if (federatedIdentityEntity.getIdentityProvider().equals(identityProvider)) {
return federatedIdentityEntity;
}
}
return null;
}
@Override
public void updateFederatedIdentity(RealmModel realm, String userId, FederatedIdentityModel federatedIdentityModel) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return;
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, federatedIdentityModel.getIdentityProvider());
if (federatedIdentityEntity == null) return;
//pushItemToList updates the whole federatedIdentities array in Mongo so we just need to remove this object from the Java
//List and pushItemToList will handle the DB update.
userEntity.getFederatedIdentities().remove(federatedIdentityEntity);
federatedIdentityEntity.setToken(federatedIdentityModel.getToken());
getMongoStore().pushItemToList(userEntity, "federatedIdentities", federatedIdentityEntity, true, invocationContext);
}
@Override
public Set<FederatedIdentityModel> getFederatedIdentities(String userId, RealmModel realm) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return Collections.EMPTY_SET;
List<FederatedIdentityEntity> linkEntities = userEntity.getFederatedIdentities();
if (linkEntities == null) {
return Collections.EMPTY_SET;
}
Set<FederatedIdentityModel> result = new HashSet<FederatedIdentityModel>();
for (FederatedIdentityEntity federatedIdentityEntity : linkEntities) {
FederatedIdentityModel model = new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(),
federatedIdentityEntity.getUserId(), federatedIdentityEntity.getUserName(), federatedIdentityEntity.getToken());
result.add(model);
}
return result;
}
@Override
public FederatedIdentityModel getFederatedIdentity(String userId, String socialProvider, RealmModel realm) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return null;
FederatedIdentityEntity federatedIdentityEntity = findFederatedIdentityLink(userEntity, socialProvider);
return federatedIdentityEntity != null ? new FederatedIdentityModel(federatedIdentityEntity.getIdentityProvider(), federatedIdentityEntity.getUserId(),
federatedIdentityEntity.getUserName(), federatedIdentityEntity.getToken()) : null;
}
@Override
public void addConsent(RealmModel realm, String userId, UserConsentModel consent) {
session.userLocalStorage().addConsent(realm, userId, consent);
}
@Override
public UserConsentModel getConsentByClient(RealmModel realm, String userId, String clientInternalId) {
return session.userLocalStorage().getConsentByClient(realm, userId, clientInternalId);
}
@Override
public List<UserConsentModel> getConsents(RealmModel realm, String userId) {
return session.userLocalStorage().getConsents(realm, userId);
}
@Override
public void updateConsent(RealmModel realm, String userId, UserConsentModel consent) {
session.userLocalStorage().updateConsent(realm, userId, consent);
}
@Override
public boolean revokeConsentForClient(RealmModel realm, String userId, String clientInternalId) {
return session.userLocalStorage().revokeConsentForClient(realm, userId, clientInternalId);
}
@Override
public void updateCredential(RealmModel realm, String userId, CredentialModel cred) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null) return;
CredentialEntity entity = getCredentialEntity(cred.getId(), userEntity);
if (entity == null) return;
toEntity(cred, entity);
userEntity.getCredentials().remove(entity);
getMongoStore().pushItemToList(userEntity, "credentials", entity, true, invocationContext);
}
private void toEntity(CredentialModel cred, CredentialEntity entity) {
entity.setAlgorithm(cred.getAlgorithm());
entity.setCounter(cred.getCounter());
entity.setCreatedDate(cred.getCreatedDate());
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 (cred.getConfig() == null) entity.setConfig(null);
else {
MultivaluedHashMap<String, String> newConfig = new MultivaluedHashMap<>();
newConfig.putAll(cred.getConfig());
entity.setConfig(newConfig);
}
}
@Override
public CredentialModel createCredential(RealmModel realm, String userId, CredentialModel cred) {
FederatedUser userEntity = findOrCreate(realm, userId);
CredentialEntity entity = new CredentialEntity();
entity.setId(KeycloakModelUtils.generateId());
toEntity(cred, entity);
getMongoStore().pushItemToList(userEntity, "credentials", entity, true, invocationContext);
cred.setId(entity.getId());
return cred;
}
@Override
public Set<GroupModel> getGroups(RealmModel realm, String userId) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getGroupIds() == null || userEntity.getGroupIds().isEmpty()) return Collections.EMPTY_SET;
Set<GroupModel> groups = new HashSet<>();
for (String groupId : userEntity.getGroupIds()) {
GroupModel group = session.realms().getGroupById(groupId, realm);
if (group != null) groups.add(group);
}
return groups;
}
@Override
public void joinGroup(RealmModel realm, String userId, GroupModel group) {
FederatedUser userEntity = findOrCreate(realm, userId);
getMongoStore().pushItemToList(userEntity, "groupIds", group.getId(), true, invocationContext);
}
@Override
public void leaveGroup(RealmModel realm, String userId, GroupModel group) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || group == null) return;
getMongoStore().pullItemFromList(userEntity, "groupIds", group.getId(), invocationContext);
}
@Override
public List<String> getMembership(RealmModel realm, GroupModel group, int firstResult, int max) {
QueryBuilder queryBuilder = new QueryBuilder()
.and("realmId").is(realm.getId());
queryBuilder.and("groupIds").is(group.getId());
List<FederatedUser> users = getMongoStore().loadEntities(FederatedUser.class, queryBuilder.get(), null, firstResult, max, invocationContext);
List<String> ids = new LinkedList<>();
for (FederatedUser user : users) ids.add(user.getId());
return ids;
}
@Override
public Set<String> getRequiredActions(RealmModel realm, String userId) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getRequiredActions() == null || userEntity.getRequiredActions().isEmpty()) return Collections.EMPTY_SET;
Set<String> set = new HashSet<>();
set.addAll(userEntity.getRequiredActions());
return set;
}
@Override
public void addRequiredAction(RealmModel realm, String userId, String action) {
FederatedUser userEntity = findOrCreate(realm, userId);
getMongoStore().pushItemToList(userEntity, "requiredActions", action, true, invocationContext);
}
@Override
public void removeRequiredAction(RealmModel realm, String userId, String action) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getRequiredActions() == null || userEntity.getRequiredActions().isEmpty()) return;
getMongoStore().pullItemFromList(userEntity, "requiredActions", action, invocationContext);
}
@Override
public void grantRole(RealmModel realm, String userId, RoleModel role) {
FederatedUser userEntity = findOrCreate(realm, userId);
getMongoStore().pushItemToList(userEntity, "roleIds", role.getId(), true, invocationContext);
}
@Override
public Set<RoleModel> getRoleMappings(RealmModel realm, String userId) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getRoleIds() == null || userEntity.getRoleIds().isEmpty()) return Collections.EMPTY_SET;
Set<RoleModel> roles = new HashSet<>();
for (String roleId : userEntity.getRoleIds()) {
RoleModel role = realm.getRoleById(roleId);
if (role != null) roles.add(role);
}
return roles;
}
@Override
public void deleteRoleMapping(RealmModel realm, String userId, RoleModel role) {
FederatedUser userEntity = getUserById(userId);
if (userEntity == null || userEntity.getRoleIds() == null || userEntity.getRoleIds().isEmpty()) return;
getMongoStore().pullItemFromList(userEntity, "roleIds", role.getId(), invocationContext);
}
@Override
public void updateCredential(RealmModel realm, UserModel user, CredentialModel cred) {
updateCredential(realm, user.getId(), cred);
}
@Override
public CredentialModel createCredential(RealmModel realm, UserModel user, CredentialModel cred) {
return createCredential(realm, user.getId(), cred);
}
@Override
public boolean removeStoredCredential(RealmModel realm, UserModel user, String id) {
return removeStoredCredential(realm, user.getId(), id);
}
@Override
public CredentialModel getStoredCredentialById(RealmModel realm, UserModel user, String id) {
return getStoredCredentialById(realm, user.getId(), id);
}
@Override
public List<CredentialModel> getStoredCredentials(RealmModel realm, UserModel user) {
return getStoredCredentials(realm, user.getId());
}
@Override
public List<CredentialModel> getStoredCredentialsByType(RealmModel realm, UserModel user, String type) {
return getStoredCredentialsByType(realm, user.getId(), type);
}
@Override
public CredentialModel getStoredCredentialByNameAndType(RealmModel realm, UserModel user, String name, String type) {
return getStoredCredentialByNameAndType(realm, user.getId(), name, type);
}
@Override
public int getStoredUsersCount(RealmModel realm) {
DBObject query = new QueryBuilder()
.and("realmId").is(realm.getId())
.get();
return getMongoStore().countEntities(FederatedUser.class, query, invocationContext);
}
}