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