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}