001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Vladimir Pasquier <vpasquier@nuxeo.com>
016 */
017
018package org.nuxeo.shibboleth.invitation;
019
020import java.io.Serializable;
021import java.util.HashMap;
022import java.util.Map;
023
024import org.nuxeo.ecm.core.api.DocumentModel;
025import org.nuxeo.ecm.core.api.DocumentModelList;
026import org.nuxeo.ecm.core.api.NuxeoException;
027import org.nuxeo.ecm.core.api.NuxeoPrincipal;
028import org.nuxeo.ecm.core.api.UnrestrictedSessionRunner;
029import org.nuxeo.ecm.core.api.impl.DocumentModelListImpl;
030import org.nuxeo.ecm.core.api.repository.RepositoryManager;
031import org.nuxeo.ecm.core.api.security.ACE;
032import org.nuxeo.ecm.core.api.security.ACL;
033import org.nuxeo.ecm.platform.usermanager.UserManager;
034import org.nuxeo.ecm.user.invite.UserInvitationService;
035import org.nuxeo.ecm.user.registration.UserRegistrationService;
036import org.nuxeo.runtime.api.Framework;
037import org.nuxeo.usermapper.extension.UserMapper;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041/**
042 * User mapper for handling user post creation when authenticating with Shibboleth (by invitation)
043 *
044 * @since 7.4
045 */
046public class ShibbolethUserMapper implements UserMapper {
047
048    private static final Logger log = LoggerFactory.getLogger(ShibbolethUserMapper.class);
049
050
051    public static final String DEFAULT_REGISTRATION = "default_registration";
052
053    protected static String userSchemaName = "user";
054
055    protected static String groupSchemaName = "group";
056
057    protected UserManager userManager;
058
059    @Override
060    public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject) {
061        return getOrCreateAndUpdateNuxeoPrincipal(userObject, true, true, null);
062    }
063
064    protected UserInvitationService fetchService() {
065        return Framework.getLocalService(UserRegistrationService.class);
066    }
067
068    @Override
069    public NuxeoPrincipal getOrCreateAndUpdateNuxeoPrincipal(Object userObject, boolean createIfNeeded, boolean update,
070            Map<String, Serializable> params) {
071
072        ShibbolethUserInfo userInfo = new ShibbolethUserInfo((String) ((Map) userObject).get("username"),
073                (String) ((Map) userObject).get("password"), (String) ((Map) userObject).get("firstName"),
074                (String) ((Map) userObject).get("lastName"), (String) ((Map) userObject).get("company"));
075
076        // Check if email has been provided and if invitation has been assigned to a user with email as username
077        DocumentModel userDoc = null;
078        String mail = (String) ((Map) userObject).get("email");
079        String userName = userInfo.getUserName();
080        if (mail != null && !mail.isEmpty()) {
081            userDoc = findUser(mail);
082        }
083        if (userDoc != null) {
084            // Update target document ACLs;
085            DocumentModelList targetDocs = getTargetDocuments(mail);
086            for (DocumentModel targetDoc : targetDocs) {
087                for (ACL acl : targetDoc.getACP().getACLs()) {
088                    for (ACE oldACE : acl.getACEs()) {
089                        if (oldACE.getUsername().equals(mail)) {
090                            ACE newACE = ACE.builder(userName, oldACE.getPermission())
091                                            .creator(oldACE.getCreator())
092                                            .begin(oldACE.getBegin())
093                                            .end(oldACE.getEnd())
094                                            .build();
095                            new UnrestrictedSessionRunner(getTargetRepositoryName()) {
096                                @Override
097                                public void run() {
098                                    session.replaceACE(targetDoc.getRef(), acl.getName(), oldACE, newACE);
099                                }
100                            }.runUnrestricted();
101                        }
102                    }
103                }
104            }
105        } else {
106            userDoc = findUser(userInfo.getUserName());
107        }
108
109        if (userDoc == null) {
110            userDoc = createUser(userInfo);
111        }
112        // Update user with related infos
113        userDoc = updateUser(userDoc, userInfo);
114
115        String userId = (String) userDoc.getPropertyValue(userManager.getUserIdField());
116        return userManager.getPrincipal(userId);
117    }
118
119    protected DocumentModel createUser(ShibbolethUserInfo userInfo) {
120        DocumentModel userDoc;
121        try {
122            userDoc = userManager.getBareUserModel();
123            userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
124            userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
125            userManager.createUser(userDoc);
126        } catch (NuxeoException e) {
127            String message = "Error while creating user [" + userInfo.getUserName() + "] in UserManager";
128            log.error(message, e);
129            throw new RuntimeException(message);
130        }
131        return userDoc;
132    }
133
134    protected DocumentModelList getTargetDocuments(String userId) {
135        UserInvitationService userInvitationService = Framework.getLocalService(UserRegistrationService.class);
136        final DocumentModelList registrationDocs = new DocumentModelListImpl();
137        new UnrestrictedSessionRunner(getTargetRepositoryName()) {
138            @Override
139            public void run() {
140                String query = "SELECT * FROM Document WHERE ecm:currentLifeCycleState != 'validated' AND"
141                        + " ecm:mixinType = '"
142                        + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getRequestDocType() + "' AND"
143                        + userInvitationService.getConfiguration(DEFAULT_REGISTRATION).getUserInfoUsernameField()
144                        + " = '%s' AND ecm:isCheckedInVersion = 0";
145                query = String.format(query, userId);
146                registrationDocs.addAll(session.query(query));
147            }
148        }.runUnrestricted();
149        return registrationDocs;
150    }
151
152    @Override
153    public void init(Map<String, String> params) throws Exception {
154        userManager = Framework.getLocalService(UserManager.class);
155        userSchemaName = userManager.getUserSchemaName();
156        groupSchemaName = userManager.getGroupSchemaName();
157    }
158
159    private DocumentModel findUser(String userName) {
160        Map<String, Serializable> query = new HashMap<>();
161        query.put(userManager.getUserIdField(), userName);
162        DocumentModelList users = userManager.searchUsers(query, null);
163
164        if (users.isEmpty()) {
165            return null;
166        }
167        return users.get(0);
168    }
169
170    private DocumentModel updateUser(DocumentModel userDoc, ShibbolethUserInfo userInfo) {
171        userDoc.setPropertyValue(userManager.getUserIdField(), userInfo.getUserName());
172        userDoc.setPropertyValue(userManager.getUserEmailField(), userInfo.getUserName());
173        userDoc.setProperty(userSchemaName, "firstName", userInfo.getFirstName());
174        userDoc.setProperty(userSchemaName, "lastName", userInfo.getLastName());
175        userDoc.setProperty(userSchemaName, "password", userInfo.getPassword());
176        userDoc.setProperty(userSchemaName, "company", userInfo.getCompany());
177        userManager.updateUser(userDoc);
178        return userDoc;
179    }
180
181    @Override
182    public Object wrapNuxeoPrincipal(NuxeoPrincipal principal, Object nativePrincipal, Map<String, Serializable> params) {
183        throw new UnsupportedOperationException();
184    }
185
186    @Override
187    public void release() {
188    }
189
190    public String getTargetRepositoryName() {
191        return Framework.getService(RepositoryManager.class).getDefaultRepositoryName();
192    }
193}