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