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}