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