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