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}