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