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        return  Framework.doPrivileged(() -> {
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            updateUser(userDoc, userInfo);
074
075            String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField());
076            return userManager.getPrincipal(userId);
077        });
078    }
079
080    @Override
081    public void init(Map<String, String> params) throws Exception {
082        userManager = Framework.getService(UserManager.class);
083        userSchemaName = userManager.getUserSchemaName();
084        groupSchemaName = userManager.getGroupSchemaName();
085    }
086
087    private DocumentModel findOrCreateGroup(String role, String userName) {
088        DocumentModel groupDoc = findGroup(role);
089        if (groupDoc == null) {
090            groupDoc = userManager.getBareGroupModel();
091            groupDoc.setPropertyValue(userManager.getGroupIdField(), role);
092            groupDoc.setProperty(groupSchemaName, "groupname", role);
093            groupDoc.setProperty(groupSchemaName, "grouplabel", role + " group");
094            groupDoc.setProperty(groupSchemaName, "description",
095                    "Group automatically created by Keycloak based on user role [" + role + "]");
096            groupDoc = userManager.createGroup(groupDoc);
097        }
098        List<String> users = userManager.getUsersInGroupAndSubGroups(role);
099        if (!users.contains(userName)) {
100            users.add(userName);
101            groupDoc.setProperty(groupSchemaName, userManager.getGroupMembersField(), users);
102            userManager.updateGroup(groupDoc);
103        }
104        return groupDoc;
105    }
106
107    private DocumentModel findGroup(String role) {
108        Map<String, Serializable> query = new HashMap<>();
109        query.put(userManager.getGroupIdField(), role);
110        DocumentModelList groups = userManager.searchGroups(query, null);
111
112        if (groups.isEmpty()) {
113            return null;
114        }
115        return groups.get(0);
116    }
117
118    private DocumentModel findUser(UserIdentificationInfo userInfo) {
119        Map<String, Serializable> query = new HashMap<>();
120        query.put(userManager.getUserIdField(), userInfo.getUserName());
121        DocumentModelList users = userManager.searchUsers(query, null);
122
123        if (users.isEmpty()) {
124            return null;
125        }
126        return users.get(0);
127    }
128
129    private DocumentModel createUser(KeycloakUserInfo userInfo) {
130        try {
131            DocumentModel userDoc = userManager.getBareUserModel();
132            userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
133            userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
134            return 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    }
141
142    private void updateUser(DocumentModel userDoc, KeycloakUserInfo userInfo) {
143        userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
144        userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
145        userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName());
146        userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName());
147        userDoc.setProperty(userSchemaName, "password", userInfo.getPassword());
148        userDoc.setProperty(userSchemaName, "company", userInfo.getCompany());
149        userManager.updateUser(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}