/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * François Maturel */ package org.nuxeo.ecm.platform.ui.web.keycloak; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.NuxeoException; import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.platform.api.login.UserIdentificationInfo; import org.nuxeo.ecm.platform.usermanager.UserManager; import org.nuxeo.runtime.api.Framework; import org.nuxeo.usermapper.extension.UserMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Plugin for the UserMapper to manage mapping between Ketcloack user and Nuxeo counterpart * * @since 7.4 */ public class KeycloakUserMapper implements UserMapper { private static final Logger log = LoggerFactory.getLogger(KeycloakUserMapper.class); protected static String userSchemaName = "user"; protected static String groupSchemaName = "group"; protected UserManager userManager; @Override public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject) { return getOrCreateAndUpdateNuxeoPrincipal(userObject, true, true, null); } @Override public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject, boolean createIfNeeded, boolean update, Map<String, Serializable> params) { KeycloakUserInfo userInfo = (KeycloakUserInfo) userObject; for (String role : userInfo.getRoles()) { findOrCreateGroup(role, userInfo.getUserName()); } // Remember that username is email by default DocumentModel userDoc = findUser(userInfo); if (userDoc == null) { userDoc = createUser(userInfo); } userDoc = updateUser(userDoc, userInfo); String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField()); return userManager.getPrincipal(userId); } @Override public void init(Map<String, String> params) throws Exception { userManager = Framework.getLocalService(UserManager.class); userSchemaName = userManager.getUserSchemaName(); groupSchemaName = userManager.getGroupSchemaName(); } private DocumentModel findOrCreateGroup(String role, String userName) { DocumentModel groupDoc = findGroup(role); if (groupDoc == null) { groupDoc = userManager.getBareGroupModel(); groupDoc.setPropertyValue(userManager.getGroupIdField(), role); groupDoc.setProperty(groupSchemaName, "groupname", role); groupDoc.setProperty(groupSchemaName, "grouplabel", role + " group"); groupDoc.setProperty(groupSchemaName, "description", "Group automatically created by Keycloak based on user role [" + role + "]"); groupDoc = userManager.createGroup(groupDoc); } List<String> users = userManager.getUsersInGroupAndSubGroups(role); if (!users.contains(userName)) { users.add(userName); groupDoc.setProperty(groupSchemaName, userManager.getGroupMembersField(), users); userManager.updateGroup(groupDoc); } return groupDoc; } private DocumentModel findGroup(String role) { Map<String, Serializable> query = new HashMap<>(); query.put(userManager.getGroupIdField(), role); DocumentModelList groups = userManager.searchGroups(query, null); if (groups.isEmpty()) { return null; } return groups.get(0); } private DocumentModel findUser(UserIdentificationInfo userInfo) { Map<String, Serializable> query = new HashMap<>(); query.put(userManager.getUserIdField(), userInfo.getUserName()); DocumentModelList users = userManager.searchUsers(query, null); if (users.isEmpty()) { return null; } return users.get(0); } private DocumentModel createUser(KeycloakUserInfo userInfo) { DocumentModel userDoc; try { userDoc = userManager.getBareUserModel(); userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); userManager.createUser(userDoc); } catch (NuxeoException e) { String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager"; log.error(message, e); throw new RuntimeException(message); } return userDoc; } private DocumentModel updateUser(DocumentModel userDoc, KeycloakUserInfo userInfo) { userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName()); userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName()); userDoc.setProperty(userSchemaName, "password", userInfo.getPassword()); userDoc.setProperty(userSchemaName, "company", userInfo.getCompany()); userManager.updateUser(userDoc); return userDoc; } @Override public Object wrapNuxeoPrincipal(NuxeoPrincipal principal, Object nativePrincipal, Map<String, Serializable> params) { throw new UnsupportedOperationException(); } @Override public void release() { } }