001/*
002 * (C) Copyright 2015-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 *     dmetzler
018 *     Vladimir Pasquier <vpasquier@nuxeo.com>
019 *     Mincong Huang <mhuang@nuxeo.com>
020 *     Nuno Cunha <ncunha@nuxeo.com>
021 */
022
023package org.nuxeo.ecm.automation.core.operations.document;
024
025import static java.util.stream.Collectors.toList;
026import static org.nuxeo.ecm.core.api.NuxeoPrincipal.computeTransientUsername;
027import static org.nuxeo.ecm.core.api.NuxeoPrincipal.isTransientUsername;
028
029import java.io.Serializable;
030import java.util.Arrays;
031import java.util.Calendar;
032import java.util.HashMap;
033import java.util.List;
034import java.util.Map;
035
036import org.nuxeo.ecm.automation.core.Constants;
037import org.nuxeo.ecm.automation.core.annotations.Context;
038import org.nuxeo.ecm.automation.core.annotations.Operation;
039import org.nuxeo.ecm.automation.core.annotations.OperationMethod;
040import org.nuxeo.ecm.automation.core.annotations.Param;
041import org.nuxeo.ecm.automation.core.collectors.DocumentModelCollector;
042import org.nuxeo.ecm.automation.core.util.StringList;
043import org.nuxeo.ecm.core.api.CoreSession;
044import org.nuxeo.ecm.core.api.DocumentModel;
045import org.nuxeo.ecm.core.api.DocumentRef;
046import org.nuxeo.ecm.core.api.security.ACE;
047import org.nuxeo.ecm.core.api.security.ACL;
048import org.nuxeo.ecm.core.api.security.ACP;
049import org.nuxeo.ecm.core.api.security.PermissionProvider;
050import org.nuxeo.ecm.core.api.security.impl.ACPImpl;
051import org.nuxeo.ecm.platform.usermanager.UserManager;
052import org.nuxeo.ecm.webengine.model.exceptions.IllegalParameterException;
053import org.nuxeo.runtime.api.Framework;
054import org.nuxeo.runtime.services.config.ConfigurationService;
055
056/**
057 * Operation that adds a permission to a given ACL for a given user.
058 *
059 * @since 5.7.3
060 */
061@Operation(id = AddPermission.ID, category = Constants.CAT_DOCUMENT, label = "Add Permission", description = "Add Permission on the input document(s). Returns the document(s).", aliases = {
062        "Document.AddACL" })
063public class AddPermission {
064
065    public static final String ID = "Document.AddPermission";
066
067    public static final String NOTIFY_KEY = "notify";
068
069    public static final String COMMENT_KEY = "comment";
070
071    /**
072     * Configuration property name, which defines whether virtual user (non-existent user) is allowed in Nuxeo
073     * automation. If allowed, Nuxeo server will not check the user existence during automation execution. Set this
074     * property to true if you use Nuxeo computed user or computed group.
075     *
076     * @since 9.1
077     */
078    public static final String ALLOW_VIRTUAL_USER = "nuxeo.automation.allowVirtualUser";
079
080    @Context
081    protected CoreSession session;
082
083    /**
084     * @since 10.3
085     */
086    @Param(name = "users", required = false, alias = "users", description = "ACE target set of users and/or groups.")
087    protected StringList users;
088
089    /**
090     * @deprecated since 10.3, use {@link #users} instead.
091     */
092    @Deprecated
093    @Param(name = "username", required = false, alias = "user", description = "ACE target user/group.")
094    protected String user;
095
096    /**
097     * @since 8.1
098     */
099    @Param(name = "email", required = false, description = "ACE target user/group.")
100    protected String email;
101
102    @Param(name = "permission", description = "ACE permission.")
103    protected String permission;
104
105    @Param(name = "acl", required = false, values = { ACL.LOCAL_ACL }, description = "ACL name.")
106    protected String aclName = ACL.LOCAL_ACL;
107
108    @Param(name = "begin", required = false, description = "ACE begin date.")
109    protected Calendar begin;
110
111    @Param(name = "end", required = false, description = "ACE end date.")
112    protected Calendar end;
113
114    @Param(name = "blockInheritance", required = false, description = "Block inheritance or not.")
115    protected boolean blockInheritance = false;
116
117    @Param(name = "notify", required = false, description = "Notify the user or not")
118    protected boolean notify = false;
119
120    @Param(name = "comment", required = false, description = "Comment")
121    protected String comment;
122
123    @OperationMethod(collector = DocumentModelCollector.class)
124    public DocumentModel run(DocumentModel doc) {
125        validateParameters();
126        addPermission(doc);
127        return doc;
128    }
129
130    @OperationMethod(collector = DocumentModelCollector.class)
131    public DocumentModel run(DocumentRef docRef) {
132        DocumentModel doc = session.getDocument(docRef);
133        validateParameters();
134        addPermission(doc);
135        return doc;
136    }
137
138    protected void addPermission(DocumentModel doc) {
139        ACP acp = doc.getACP() != null ? doc.getACP() : new ACPImpl();
140        Map<String, Serializable> contextData = new HashMap<>();
141        contextData.put(NOTIFY_KEY, notify);
142        contextData.put(COMMENT_KEY, comment);
143
144        String creator = session.getPrincipal().getName();
145        boolean permissionChanged = false;
146        if (blockInheritance) {
147            permissionChanged = acp.blockInheritance(aclName, creator);
148        }
149
150        for (String username : users) {
151            ACE ace = ACE.builder(username, permission)
152                         .creator(creator)
153                         .begin(begin)
154                         .end(end)
155                         .contextData(contextData)
156                         .build();
157            permissionChanged = acp.addACE(aclName, ace) || permissionChanged;
158        }
159
160        if (permissionChanged) {
161            doc.setACP(acp, true);
162        }
163    }
164
165    protected void validateParameters() {
166        if (user == null && (users == null || users.isEmpty()) && email == null) {
167            throw new IllegalParameterException("'users' or 'email' parameters must be set");
168        } else if (email != null && end == null) {
169            throw new IllegalParameterException("'end' parameter must be set when adding a permission for an 'email'");
170        }
171
172        ensureUserListIsUsed();
173
174        ConfigurationService configService = Framework.getService(ConfigurationService.class);
175        if (configService.isBooleanFalse(ALLOW_VIRTUAL_USER)) {
176            UserManager userManager = Framework.getService(UserManager.class);
177
178            List<String> unknownNames = users.stream()
179                                             .filter(userOrGroupName -> !isTransientUsername(userOrGroupName)
180                                                     && userManager.getUserModel(userOrGroupName) == null
181                                                     && userManager.getGroupModel(userOrGroupName) == null)
182                                             .collect(toList());
183
184            if (!unknownNames.isEmpty()) {
185                String errorMsg = String.format(
186                        "The following set of User or Group names do not exist: [%s]. Please provide valid ones.",
187                        String.join(",", unknownNames));
188                throw new IllegalParameterException(errorMsg);
189            }
190        }
191
192        // validate permission
193        if (!Arrays.asList(Framework.getService(PermissionProvider.class).getPermissions()).contains(permission)) {
194            throw new IllegalParameterException(String.format("Permission %s is invalid.", permission));
195        }
196    }
197
198    /**
199     * Method to help deprecating {@link #user} parameter.
200     */
201    protected void ensureUserListIsUsed() {
202        users = users == null ? new StringList() : new StringList(users);
203
204        if (user != null && !users.contains(user)) {
205            users.add(user);
206        } else if (email != null && users.isEmpty()) {
207            // share a document with someone not registered in Nuxeo, by using only an email
208            users.add(computeTransientUsername(email));
209        }
210    }
211
212}