001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     François Maturel
016 */
017
018package org.nuxeo.ecm.platform.ui.web.keycloak;
019
020import java.io.Serializable;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.nuxeo.ecm.core.api.DocumentModel;
026import org.nuxeo.ecm.core.api.DocumentModelList;
027import org.nuxeo.ecm.core.api.NuxeoException;
028import org.nuxeo.ecm.core.api.NuxeoPrincipal;
029import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo;
030import org.nuxeo.ecm.platform.usermanager.UserManager;
031import org.nuxeo.runtime.api.Framework;
032import org.nuxeo.usermapper.extension.UserMapper;
033import org.slf4j.Logger;
034import org.slf4j.LoggerFactory;
035
036/**
037 * Plugin for the UserMapper to manage mapping between Ketcloack user and Nuxeo counterpart
038 *
039 * @since 7.4
040 */
041public class KeycloakUserMapper implements UserMapper {
042
043    private static final Logger log = LoggerFactory.getLogger(KeycloakUserMapper.class);
044
045    protected static String userSchemaName = "user";
046
047    protected static String groupSchemaName = "group";
048
049    protected UserManager userManager;
050
051    @Override
052    public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject) {
053        return getOrCreateAndUpdateNuxeoPrincipal(userObject, true, true, null);
054    }
055
056    @Override
057    public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject, boolean createIfNeeded, boolean update,
058            Map<String, Serializable> params) {
059
060        KeycloakUserInfo userInfo = (KeycloakUserInfo) userObject;
061        for (String role : userInfo.getRoles()) {
062            findOrCreateGroup(role, userInfo.getUserName());
063        }
064
065        // Remember that username is email by default
066        DocumentModel userDoc = findUser(userInfo);
067        if (userDoc == null) {
068            userDoc = createUser(userInfo);
069        }
070
071        userDoc = updateUser(userDoc, userInfo);
072
073        String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField());
074        return userManager.getPrincipal(userId);
075    }
076
077    @Override
078    public void init(Map<String, String> params) throws Exception {
079        userManager = Framework.getLocalService(UserManager.class);
080        userSchemaName = userManager.getUserSchemaName();
081        groupSchemaName = userManager.getGroupSchemaName();
082    }
083
084    private DocumentModel findOrCreateGroup(String role, String userName) {
085        DocumentModel groupDoc = findGroup(role);
086        if (groupDoc == null) {
087            groupDoc = userManager.getBareGroupModel();
088            groupDoc.setPropertyValue(userManager.getGroupIdField(), role);
089            groupDoc.setProperty(groupSchemaName, "groupname", role);
090            groupDoc.setProperty(groupSchemaName, "grouplabel", role + " group");
091            groupDoc.setProperty(groupSchemaName, "description",
092                    "Group automatically created by Keycloak based on user role [" + role + "]");
093            groupDoc = userManager.createGroup(groupDoc);
094        }
095        List<String> users = userManager.getUsersInGroupAndSubGroups(role);
096        if (!users.contains(userName)) {
097            users.add(userName);
098            groupDoc.setProperty(groupSchemaName, userManager.getGroupMembersField(), users);
099            userManager.updateGroup(groupDoc);
100        }
101        return groupDoc;
102    }
103
104    private DocumentModel findGroup(String role) {
105        Map<String, Serializable> query = new HashMap<>();
106        query.put(userManager.getGroupIdField(), role);
107        DocumentModelList groups = userManager.searchGroups(query, null);
108
109        if (groups.isEmpty()) {
110            return null;
111        }
112        return groups.get(0);
113    }
114
115    private DocumentModel findUser(UserIdentificationInfo userInfo) {
116        Map<String, Serializable> query = new HashMap<>();
117        query.put(userManager.getUserIdField(), userInfo.getUserName());
118        DocumentModelList users = userManager.searchUsers(query, null);
119
120        if (users.isEmpty()) {
121            return null;
122        }
123        return users.get(0);
124    }
125
126    private DocumentModel createUser(KeycloakUserInfo userInfo) {
127        DocumentModel userDoc;
128        try {
129            userDoc = userManager.getBareUserModel();
130            userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
131            userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
132            userManager.createUser(userDoc);
133        } catch (NuxeoException e) {
134            String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager";
135            log.error(message, e);
136            throw new RuntimeException(message);
137        }
138        return userDoc;
139    }
140
141    private DocumentModel updateUser(DocumentModel userDoc, KeycloakUserInfo userInfo) {
142        userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
143        userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
144        userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName());
145        userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName());
146        userDoc.setProperty(userSchemaName, "password", userInfo.getPassword());
147        userDoc.setProperty(userSchemaName, "company", userInfo.getCompany());
148        userManager.updateUser(userDoc);
149        return userDoc;
150    }
151
152    @Override
153    public Object wrapNuxeoPrincipal(NuxeoPrincipal principal, Object nativePrincipal, Map<String, Serializable> params) {
154        throw new UnsupportedOperationException();
155    }
156
157    @Override
158    public void release() {
159    }
160
161}