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 java.util.AbstractMap.SimpleEntry; 023import java.util.Arrays; 024import java.util.HashSet; 025import java.util.LinkedList; 026import java.util.List; 027import java.util.Map.Entry; 028import java.util.Queue; 029import java.util.Set; 030 031import javax.servlet.http.HttpServletResponse; 032 033import org.apache.commons.lang3.StringUtils; 034import org.nuxeo.ecm.automation.OperationContext; 035import org.nuxeo.ecm.automation.OperationException; 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.util.Properties; 042import org.nuxeo.ecm.automation.core.util.StringList; 043import org.nuxeo.ecm.core.api.DocumentModel; 044import org.nuxeo.ecm.core.api.NuxeoException; 045import org.nuxeo.ecm.core.api.NuxeoGroup; 046import org.nuxeo.ecm.core.api.NuxeoPrincipal; 047import org.nuxeo.ecm.directory.BaseSession; 048import org.nuxeo.ecm.platform.usermanager.GroupConfig; 049import org.nuxeo.ecm.platform.usermanager.NuxeoGroupImpl; 050import org.nuxeo.ecm.platform.usermanager.UserManager; 051 052/** 053 * Operation to create or update a group. 054 * 055 * @since 9.1 056 */ 057@Operation(id = CreateOrUpdateGroup.ID, // 058 category = Constants.CAT_USERS_GROUPS, // 059 label = "Create or Update Group", // 060 description = "Create or Update Group") 061public class CreateOrUpdateGroup { 062 063 public static final String ID = "Group.CreateOrUpdate"; 064 065 public static final String CREATE_OR_UPDATE = "createOrUpdate"; 066 067 public static final String CREATE = "create"; 068 069 public static final String UPDATE = "update"; 070 071 public static final String GROUP_SCHEMA = "group"; 072 073 protected static final String GROUP_COLON = GROUP_SCHEMA + ':'; 074 075 public static final String GROUP_NAME = "groupname"; 076 077 public static final String GROUP_LABEL = "grouplabel"; 078 079 public static final String GROUP_DESCRIPTION = "description"; 080 081 public static final String MEMBERS = "members"; 082 083 public static final String SUB_GROUPS = "subGroups"; 084 085 public static final String PARENT_GROUPS = "parentGroups"; 086 087 public static final String GROUP_TENANTID = "tenantId"; 088 089 @Context 090 protected UserManager userManager; 091 092 @Context 093 protected OperationContext ctx; 094 095 @Param(name = "groupname") 096 protected String groupName; 097 098 @Param(name = "tenantId", required = false) 099 protected String tenantId; 100 101 @Param(name = "grouplabel", required = false) 102 protected String groupLabel; 103 104 @Param(name = "description", required = false) 105 protected String groupDescription; 106 107 @Param(name = "members", required = false) 108 protected StringList members; 109 110 @Param(name = "subGroups", required = false) 111 protected StringList subGroups; 112 113 @Param(name = "parentGroups", required = false) 114 protected StringList parentGroups; 115 116 @Param(name = "properties", required = false) 117 protected Properties properties = new Properties(); 118 119 @Param(name = "mode", required = false, values = { CREATE_OR_UPDATE, CREATE, UPDATE }) 120 protected String mode; 121 122 @OperationMethod 123 public void run() throws OperationException { 124 String tenantGroupName = getTenantGroupName(groupName, tenantId); 125 boolean create; 126 DocumentModel groupDoc = userManager.getGroupModel(tenantGroupName); 127 if (groupDoc == null) { 128 if (UPDATE.equals(mode)) { 129 throw new OperationException("Cannot update non-existent group: " + groupName); 130 } 131 create = true; 132 groupDoc = userManager.getBareGroupModel(); 133 groupDoc.setProperty(GROUP_SCHEMA, GROUP_NAME, tenantGroupName); 134 } else { 135 if (CREATE.equals(mode)) { 136 throw new OperationException("Cannot create already-existing group: " + groupName); 137 } 138 create = false; 139 140 // make sure that the group can be updated 141 checkCanCreateOrUpdateGroup(groupDoc); 142 } 143 if (members != null) { 144 groupDoc.setProperty(GROUP_SCHEMA, MEMBERS, members); 145 } 146 if (subGroups != null) { 147 groupDoc.setProperty(GROUP_SCHEMA, SUB_GROUPS, subGroups); 148 } 149 if (parentGroups != null) { 150 groupDoc.setProperty(GROUP_SCHEMA, PARENT_GROUPS, parentGroups); 151 } 152 for (Entry<String, String> entry : Arrays.asList( // 153 new SimpleEntry<>(GROUP_TENANTID, tenantId), // 154 new SimpleEntry<>(GROUP_LABEL, groupLabel), // 155 new SimpleEntry<>(GROUP_DESCRIPTION, groupDescription))) { 156 String key = entry.getKey(); 157 String value = entry.getValue(); 158 if (StringUtils.isNotBlank(value)) { 159 properties.put(key, value); 160 } 161 } 162 for (Entry<String, String> entry : properties.entrySet()) { 163 String key = entry.getKey(); 164 String value = entry.getValue(); 165 if (key.startsWith(GROUP_COLON)) { 166 key = key.substring(GROUP_COLON.length()); 167 } 168 groupDoc.setProperty(GROUP_SCHEMA, key, value); 169 } 170 171 // make sure that the new group can be created or updated 172 checkCanCreateOrUpdateGroup(groupDoc); 173 174 if (create) { 175 userManager.createGroup(groupDoc); 176 } else { 177 userManager.updateGroup(groupDoc); 178 } 179 } 180 181 /** 182 * Use tenant_mytenant_mygroup instead of mygroup for groups having a tenant id. 183 * <p> 184 * This is done explicitly instead of the implicit computation done in SQLSession.createEntry which is based on a 185 * tenant id deduced from the logged-in user. 186 */ 187 public static String getTenantGroupName(String groupName, String tenantId) { 188 if (StringUtils.isBlank(tenantId)) { 189 return groupName; 190 } 191 return BaseSession.computeMultiTenantDirectoryId(tenantId, groupName); 192 } 193 194 protected void checkCanCreateOrUpdateGroup(DocumentModel groupDoc) { 195 NuxeoPrincipal currentUser = ctx.getPrincipal(); 196 if (!currentUser.isAdministrator() 197 && (!currentUser.isMemberOf("powerusers") || !canCreateOrUpdateGroup(groupDoc))) { 198 throw new NuxeoException("User is not allowed to create or edit groups", HttpServletResponse.SC_FORBIDDEN); 199 } 200 } 201 202 protected boolean canCreateOrUpdateGroup(DocumentModel groupDoc) { 203 GroupConfig groupConfig = userManager.getGroupConfig(); 204 NuxeoGroup group = new NuxeoGroupImpl(groupDoc, groupConfig); 205 Set<String> allGroups = computeAllGroups(group); 206 List<String> administratorsGroups = userManager.getAdministratorsGroups(); 207 return allGroups.stream().noneMatch(administratorsGroups::contains); 208 } 209 210 protected Set<String> computeAllGroups(NuxeoGroup group) { 211 Set<String> allGroups = new HashSet<>(); 212 Queue<NuxeoGroup> queue = new LinkedList<>(); 213 queue.add(group); 214 215 while (!queue.isEmpty()) { 216 NuxeoGroup nuxeoGroup = queue.poll(); 217 allGroups.add(nuxeoGroup.getName()); 218 nuxeoGroup.getParentGroups() 219 .stream() 220 .filter(pg -> !allGroups.contains(pg)) 221 .map(userManager::getGroup) 222 .forEach(queue::add); 223 } 224 225 return allGroups; 226 } 227 228}