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 DocumentModel userDoc; 131 try { 132 userDoc = userManager.getBareUserModel(); 133 userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); 134 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); 135 userManager.createUser(userDoc); 136 } catch (NuxeoException e) { 137 String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager"; 138 log.error(message, e); 139 throw new RuntimeException(message); 140 } 141 return userDoc; 142 } 143 144 private void updateUser(DocumentModel userDoc, KeycloakUserInfo userInfo) { 145 userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); 146 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); 147 userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName()); 148 userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName()); 149 userDoc.setProperty(userSchemaName, "password", userInfo.getPassword()); 150 userDoc.setProperty(userSchemaName, "company", userInfo.getCompany()); 151 userManager.updateUser(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}