001/*
002 * (C) Copyright 2016-2018 Nuxeo (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 *     Michael Vachette
018 *     Florent Guillaume
019 */
020package org.nuxeo.ecm.automation.core.operations.users;
021
022import static org.nuxeo.ecm.platform.usermanager.UserConfig.COMPANY_COLUMN;
023import static org.nuxeo.ecm.platform.usermanager.UserConfig.EMAIL_COLUMN;
024import static org.nuxeo.ecm.platform.usermanager.UserConfig.FIRSTNAME_COLUMN;
025import static org.nuxeo.ecm.platform.usermanager.UserConfig.GROUPS_COLUMN;
026import static org.nuxeo.ecm.platform.usermanager.UserConfig.LASTNAME_COLUMN;
027import static org.nuxeo.ecm.platform.usermanager.UserConfig.PASSWORD_COLUMN;
028import static org.nuxeo.ecm.platform.usermanager.UserConfig.SCHEMA_NAME;
029import static org.nuxeo.ecm.platform.usermanager.UserConfig.TENANT_ID_COLUMN;
030import static org.nuxeo.ecm.platform.usermanager.UserConfig.USERNAME_COLUMN;
031
032import java.util.AbstractMap.SimpleEntry;
033import java.util.Arrays;
034import java.util.Map.Entry;
035
036import javax.servlet.http.HttpServletResponse;
037
038import org.apache.commons.lang3.StringUtils;
039import org.nuxeo.ecm.automation.OperationContext;
040import org.nuxeo.ecm.automation.OperationException;
041import org.nuxeo.ecm.automation.core.Constants;
042import org.nuxeo.ecm.automation.core.annotations.Context;
043import org.nuxeo.ecm.automation.core.annotations.Operation;
044import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
045import org.nuxeo.ecm.automation.core.annotations.Param;
046import org.nuxeo.ecm.automation.core.util.Properties;
047import org.nuxeo.ecm.automation.core.util.StringList;
048import org.nuxeo.ecm.core.api.DocumentModel;
049import org.nuxeo.ecm.core.api.NuxeoException;
050import org.nuxeo.ecm.core.api.NuxeoPrincipal;
051import org.nuxeo.ecm.platform.usermanager.NuxeoPrincipalImpl;
052import org.nuxeo.ecm.platform.usermanager.UserManager;
053
054/**
055 * Operation to create or update a user.
056 *
057 * @since 9.1
058 */
059@Operation(id = CreateOrUpdateUser.ID, //
060        aliases = { "Services.CreateUser" }, //
061        category = Constants.CAT_USERS_GROUPS, //
062        label = "Create or Update User", //
063        description = "Create or Update User.")
064public class CreateOrUpdateUser {
065
066    public static final String ID = "User.CreateOrUpdate";
067
068    public static final String CREATE_OR_UPDATE = "createOrUpdate";
069
070    public static final String CREATE = "create";
071
072    public static final String UPDATE = "update";
073
074    protected static final String USER_COLON = SCHEMA_NAME + ':';
075
076    @Context
077    protected UserManager userManager;
078
079    @Context
080    protected OperationContext ctx;
081
082    @Param(name = "username")
083    protected String username;
084
085    @Param(name = "password", required = false)
086    protected String password;
087
088    @Param(name = "email", required = false)
089    protected String email;
090
091    @Param(name = "firstName", required = false)
092    protected String firstName;
093
094    @Param(name = "lastName", required = false)
095    protected String lastName;
096
097    @Param(name = "company", required = false)
098    protected String company;
099
100    @Param(name = "tenantId", required = false)
101    protected String tenantId;
102
103    @Param(name = "groups", required = false)
104    protected StringList groups;
105
106    @Param(name = "properties", required = false)
107    protected Properties properties = new Properties();
108
109    @Param(name = "mode", required = false, values = { CREATE_OR_UPDATE, CREATE, UPDATE })
110    protected String mode;
111
112    @OperationMethod
113    public void run() throws OperationException {
114        boolean create;
115        DocumentModel userDoc = userManager.getUserModel(username);
116        if (userDoc == null) {
117            if (UPDATE.equals(mode)) {
118                throw new OperationException("Cannot update non-existent user: " + username);
119            }
120            create = true;
121            userDoc = userManager.getBareUserModel();
122            userDoc.setProperty(SCHEMA_NAME, USERNAME_COLUMN, username);
123        } else {
124            if (CREATE.equals(mode)) {
125                throw new OperationException("Cannot create already-existing user: " + username);
126            }
127            create = false;
128
129            // make sure the user can be updated
130            checkCanCreateOrUpdateUser(userDoc);
131        }
132        if (groups != null) {
133            userDoc.setProperty(SCHEMA_NAME, GROUPS_COLUMN, groups);
134        }
135        for (Entry<String, String> entry : Arrays.asList( //
136                new SimpleEntry<>(TENANT_ID_COLUMN, tenantId), //
137                new SimpleEntry<>(PASSWORD_COLUMN, password), //
138                new SimpleEntry<>(EMAIL_COLUMN, email), //
139                new SimpleEntry<>(FIRSTNAME_COLUMN, firstName), //
140                new SimpleEntry<>(LASTNAME_COLUMN, lastName), //
141                new SimpleEntry<>(COMPANY_COLUMN, company))) {
142            String key = entry.getKey();
143            String value = entry.getValue();
144            if (StringUtils.isNotBlank(value)) {
145                properties.put(key, value);
146            }
147        }
148        for (Entry<String, String> entry : properties.entrySet()) {
149            String key = entry.getKey();
150            String value = entry.getValue();
151            if (key.startsWith(USER_COLON)) {
152                key = key.substring(USER_COLON.length());
153            }
154            userDoc.setProperty(SCHEMA_NAME, key, value);
155        }
156
157        // make sure the new user can be created or updated
158        checkCanCreateOrUpdateUser(userDoc);
159
160        if (create) {
161            userDoc = userManager.createUser(userDoc);
162        } else {
163            userManager.updateUser(userDoc);
164            userDoc = userManager.getUserModel(username);
165        }
166    }
167
168    protected void checkCanCreateOrUpdateUser(DocumentModel userDoc) {
169        NuxeoPrincipal currentUser = ctx.getPrincipal();
170        if (!currentUser.isAdministrator()
171                && (!currentUser.isMemberOf("powerusers") || !canCreateOrUpdateUser(userDoc))) {
172            throw new NuxeoException("User is not allowed to create or edit users", HttpServletResponse.SC_FORBIDDEN);
173        }
174    }
175
176    protected boolean canCreateOrUpdateUser(DocumentModel userDoc) {
177        NuxeoPrincipal principal = new NuxeoPrincipalImpl((String) userDoc.getProperty(SCHEMA_NAME, USERNAME_COLUMN));
178        principal.setModel(userDoc);
179        return userManager.getAdministratorsGroups().stream().noneMatch(principal.getAllGroups()::contains);
180    }
181
182}