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