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 * Vladimir Pasquier <vpasquier@nuxeo.com> 016 */ 017 018package org.nuxeo.shibboleth.invitation; 019 020import java.io.Serializable; 021import java.util.HashMap; 022import java.util.Map; 023 024import org.nuxeo.ecm.core.api.DocumentModel; 025import org.nuxeo.ecm.core.api.DocumentModelList; 026import org.nuxeo.ecm.core.api.NuxeoException; 027import org.nuxeo.ecm.core.api.NuxeoPrincipal; 028import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner; 029import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl; 030import org.nuxeo.ecm.core.api.repository.RepositoryManager; 031import org.nuxeo.ecm.core.api.security.ACE; 032import org.nuxeo.ecm.core.api.security.ACL; 033import org.nuxeo.ecm.platform.usermanager.UserManager; 034import org.nuxeo.ecm.user.invite.UserInvitationService; 035import org.nuxeo.ecm.user.registration.UserRegistrationService; 036import org.nuxeo.runtime.api.Framework; 037import org.nuxeo.usermapper.extension.UserMapper; 038import org.slf4j.Logger; 039import org.slf4j.LoggerFactory; 040 041/** 042 * User mapper for handling user post creation when authenticating with Shibboleth (by invitation) 043 * 044 * @since 7.4 045 */ 046public class ShibbolethUserMapper implements UserMapper { 047 048 private static final Logger log = LoggerFactory.getLogger(ShibbolethUserMapper.class); 049 050 051 public static final String DEFAULT_REGISTRATION = "default_registration"; 052 053 protected static String userSchemaName = "user"; 054 055 protected static String groupSchemaName = "group"; 056 057 protected UserManager userManager; 058 059 @Override 060 public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject) { 061 return getOrCreateAndUpdateNuxeoPrincipal(userObject, true, true, null); 062 } 063 064 protected UserInvitationService fetchService() { 065 return Framework.getLocalService(UserRegistrationService.class); 066 } 067 068 @Override 069 public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject, boolean createIfNeeded, boolean update, 070 Map<String, Serializable> params) { 071 072 ShibbolethUserInfo userInfo = new ShibbolethUserInfo((String) ((Map) userObject).get("username"), 073 (String) ((Map) userObject).get("password"), (String) ((Map) userObject).get("firstName"), 074 (String) ((Map) userObject).get("lastName"), (String) ((Map) userObject).get("company")); 075 076 // Check if email has been provided and if invitation has been assigned to a user with email as username 077 DocumentModel userDoc = null; 078 String mail = (String) ((Map) userObject).get("email"); 079 String userName = userInfo.getUserName(); 080 if (mail != null && !mail.isEmpty()) { 081 userDoc = findUser(mail); 082 } 083 if (userDoc != null) { 084 // Update target document ACLs; 085 DocumentModelList targetDocs = getTargetDocuments(mail); 086 for (DocumentModel targetDoc : targetDocs) { 087 for (ACL acl : targetDoc.getACP().getACLs()) { 088 for (ACE oldACE : acl.getACEs()) { 089 if (oldACE.getUsername().equals(mail)) { 090 ACE newACE = ACE.builder(userName, oldACE.getPermission()) 091 .creator(oldACE.getCreator()) 092 .begin(oldACE.getBegin()) 093 .end(oldACE.getEnd()) 094 .build(); 095 new UnrestrictedSessionRunner(getTargetRepositoryName()) { 096 @Override 097 public void run() { 098 session.replaceACE(targetDoc.getRef(), acl.getName(), oldACE, newACE); 099 } 100 }.runUnrestricted(); 101 } 102 } 103 } 104 } 105 } else { 106 userDoc = findUser(userInfo.getUserName()); 107 } 108 109 if (userDoc == null) { 110 userDoc = createUser(userInfo); 111 } 112 // Update user with related infos 113 userDoc = updateUser(userDoc, userInfo); 114 115 String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField()); 116 return userManager.getPrincipal(userId); 117 } 118 119 protected DocumentModel createUser(ShibbolethUserInfo userInfo) { 120 DocumentModel userDoc; 121 try { 122 userDoc = userManager.getBareUserModel(); 123 userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); 124 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); 125 userManager.createUser(userDoc); 126 } catch (NuxeoException e) { 127 String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager"; 128 log.error(message, e); 129 throw new RuntimeException(message); 130 } 131 return userDoc; 132 } 133 134 protected DocumentModelList getTargetDocuments(String userId) { 135 UserInvitationService userInvitationService = Framework.getLocalService(UserRegistrationService.class); 136 final DocumentModelList registrationDocs = new DocumentModelListImpl(); 137 new UnrestrictedSessionRunner(getTargetRepositoryName()) { 138 @Override 139 public void run() { 140 String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'validated' AND" 141 + " ecm:mixinType = '" 142 + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getRequestDocType() + "' AND" 143 + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getUserInfoUsernameField() 144 + " = '%s' AND ecm:isCheckedInVersion = 0"; 145 query = String.format(query, userId); 146 registrationDocs.addAll(session.query(query)); 147 } 148 }.runUnrestricted(); 149 return registrationDocs; 150 } 151 152 @Override 153 public void init(Map<String, String> params) throws Exception { 154 userManager = Framework.getLocalService(UserManager.class); 155 userSchemaName = userManager.getUserSchemaName(); 156 groupSchemaName = userManager.getGroupSchemaName(); 157 } 158 159 private DocumentModel findUser(String userName) { 160 Map<String, Serializable> query = new HashMap<>(); 161 query.put(userManager.getUserIdField(), userName); 162 DocumentModelList users = userManager.searchUsers(query, null); 163 164 if (users.isEmpty()) { 165 return null; 166 } 167 return users.get(0); 168 } 169 170 private DocumentModel updateUser(DocumentModel userDoc, ShibbolethUserInfo userInfo) { 171 userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); 172 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); 173 userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName()); 174 userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName()); 175 userDoc.setProperty(userSchemaName, "password", userInfo.getPassword()); 176 userDoc.setProperty(userSchemaName, "company", userInfo.getCompany()); 177 userManager.updateUser(userDoc); 178 return userDoc; 179 } 180 181 @Override 182 public Object wrapNuxeoPrincipal(NuxeoPrincipal principal, Object nativePrincipal, Map<String, Serializable> params) { 183 throw new UnsupportedOperationException(); 184 } 185 186 @Override 187 public void release() { 188 } 189 190 public String getTargetRepositoryName() { 191 return Framework.getService(RepositoryManager.class).getDefaultRepositoryName(); 192 } 193}