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}