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.ShibbolethAuthenticationService; 042import org.nuxeo.ecm.platform.usermanager.UserManager; 043import org.nuxeo.ecm.user.invite.UserInvitationService; 044import org.nuxeo.ecm.user.registration.UserRegistrationService; 045import org.nuxeo.runtime.api.Framework; 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.getLocalService(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.getUserEmailField(), email); 100 } 101 if (userDoc != null && userName != null) { 102 updateACP(userName, email, userDoc); 103 } else { 104 userDoc = findUser(userManager.getUserIdField(), userInfo.getUserName()); 105 } 106 if (userDoc == null) { 107 userDoc = createUser(userInfo); 108 } else { 109 userDoc = updateUser(userDoc, userInfo); 110 } 111 112 String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField()); 113 return userManager.getPrincipal(userId); 114 } 115 116 protected void updateACP(String userName, String email, DocumentModel userDoc) { 117 new UnrestrictedSessionRunner(getTargetRepositoryName()) { 118 @Override 119 public void run() { 120 121 NuxeoPrincipal principal = userManager.getPrincipal( 122 (String) userDoc.getProperty(userSchemaName, "username")); 123 ArrayList<String> groups = new ArrayList<>(principal.getGroups()); 124 125 userManager.deleteUser(userDoc); 126 userDoc.setPropertyValue("user:username", userName); 127 userDoc.setPropertyValue("user:groups", groups); 128 userManager.createUser(userDoc); 129 // Fetching the registrations 130 UserInvitationService userInvitationService = Framework.getLocalService(UserRegistrationService.class); 131 DocumentModelList registrationDocuments = new DocumentModelListImpl(); 132 String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'validated' AND " 133 + "ecm:mixinType = '" 134 + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getRequestDocType() + "' AND " 135 + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getUserInfoUsernameField() 136 + " = '%s' AND ecm:isCheckedInVersion = 0"; 137 query = String.format(query, email); 138 registrationDocuments.addAll(session.query(query)); 139 Map<String, DocumentModel> targetDocuments = new HashMap<>(); 140 // Fetching the target documents 141 for (DocumentModel doc : registrationDocuments) { 142 String docId = (String) doc.getPropertyValue("docinfo:documentId"); 143 if (docId != null && !targetDocuments.keySet().contains(docId)) 144 targetDocuments.put(docId, session.getDocument(new IdRef(docId))); 145 } 146 // Update target document ACLs; 147 List<DocumentModel> targetDocs = new ArrayList<>(targetDocuments.values()); 148 for (DocumentModel targetDoc : targetDocs) { 149 for (ACL acl : targetDoc.getACP().getACLs()) { 150 for (ACE oldACE : acl.getACEs()) { 151 if (oldACE.getUsername().equals(email)) { 152 ACE newACE = ACE.builder(userName, oldACE.getPermission()) 153 .creator(oldACE.getCreator()) 154 .begin(oldACE.getBegin()) 155 .end(oldACE.getEnd()) 156 .build(); 157 session.replaceACE(targetDoc.getRef(), acl.getName(), oldACE, newACE); 158 } 159 } 160 } 161 } 162 } 163 }.runUnrestricted(); 164 } 165 166 protected DocumentModel createUser(ShibbolethUserInfo userInfo) { 167 DocumentModel userDoc; 168 try { 169 userDoc = userManager.getBareUserModel(); 170 userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName()); 171 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName()); 172 userManager.createUser(userDoc); 173 } catch (NuxeoException e) { 174 String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager"; 175 log.error(message, e); 176 throw new RuntimeException(message); 177 } 178 return userDoc; 179 } 180 181 @Override 182 public void init(Map<String, String> params) throws Exception { 183 userManager = Framework.getLocalService(UserManager.class); 184 userSchemaName = userManager.getUserSchemaName(); 185 groupSchemaName = userManager.getGroupSchemaName(); 186 } 187 188 private DocumentModel findUser(String field, String userName) { 189 Map<String, Serializable> query = new HashMap<>(); 190 query.put(field, userName); 191 DocumentModelList users = userManager.searchUsers(query, null); 192 193 if (users.isEmpty()) { 194 return null; 195 } 196 return users.get(0); 197 } 198 199 private DocumentModel updateUser(DocumentModel userDoc, ShibbolethUserInfo userInfo) { 200 userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getEmail()); 201 userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName()); 202 userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName()); 203 userDoc.setProperty(userSchemaName, "password", userInfo.getPassword()); 204 userDoc.setProperty(userSchemaName, "company", userInfo.getCompany()); 205 userManager.updateUser(userDoc); 206 return 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}