View Javadoc
1   /**
2    * This file Copyright (c) 2014-2016 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.security.app.action;
35  
36  import info.magnolia.cms.security.Group;
37  import info.magnolia.cms.security.MgnlGroupManager;
38  import info.magnolia.cms.security.SecuritySupport;
39  import info.magnolia.cms.security.User;
40  import info.magnolia.cms.security.UserManager;
41  import info.magnolia.commands.CommandsManager;
42  import info.magnolia.event.EventBus;
43  import info.magnolia.i18nsystem.SimpleTranslator;
44  import info.magnolia.jcr.util.NodeTypes;
45  import info.magnolia.jcr.util.NodeUtil;
46  import info.magnolia.jcr.util.NodeVisitor;
47  import info.magnolia.ui.api.action.ActionExecutionException;
48  import info.magnolia.ui.api.context.UiContext;
49  import info.magnolia.ui.api.event.AdmincentralEventBus;
50  import info.magnolia.ui.api.overlay.ConfirmationCallback;
51  import info.magnolia.ui.framework.action.DeleteAction;
52  import info.magnolia.ui.vaadin.integration.jcr.JcrItemAdapter;
53  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
54  
55  import java.util.ArrayList;
56  import java.util.Collection;
57  import java.util.HashMap;
58  import java.util.List;
59  import java.util.Map;
60  
61  import javax.inject.Named;
62  import javax.jcr.Node;
63  import javax.jcr.RepositoryException;
64  
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  
68  import com.google.inject.Inject;
69  
70  /**
71   * Action that will only delete a folder if sub nodes are not in use.
72   *
73   * @see DeleteFolderActionDefinition
74   */
75  public class DeleteFolderAction extends DeleteAction<DeleteFolderActionDefinition> {
76  
77      private static final Logger log = LoggerFactory.getLogger(DeleteFolderAction.class);
78  
79      private final SecuritySupport securitySupport;
80  
81      @Inject
82      public DeleteFolderAction(DeleteFolderActionDefinition definition, JcrItemAdapter item, CommandsManager commandsManager, @Named(AdmincentralEventBus.NAME) EventBus eventBus, UiContext uiContext, SimpleTranslator i18n, SecuritySupport securitySupport) {
83          super(definition, item, commandsManager, eventBus, uiContext, i18n);
84          this.securitySupport = securitySupport;
85      }
86  
87      @Inject
88      public DeleteFolderAction(DeleteFolderActionDefinition definition, List<JcrItemAdapter> items, CommandsManager commandsManager, @Named(AdmincentralEventBus.NAME) EventBus eventBus, UiContext uiContext, SimpleTranslator i18n, SecuritySupport securitySupport) {
89          super(definition, items, commandsManager, eventBus, uiContext, i18n);
90          this.securitySupport = securitySupport;
91      }
92  
93      @Override
94      public void execute() throws ActionExecutionException {
95          try {
96              executeOnConfirmation();
97          } catch (RepositoryException e) {
98              throw new ActionExecutionException(getVerificationErrorMessage() + e.getMessage());
99          }
100     }
101 
102     private String getConfirmationDialogStatement() throws RepositoryException {
103         StringBuilder confirmMessage = new StringBuilder("<ul>");
104         final Map<String, List<String>> assignedTo = new HashMap<String, List<String>>();
105         for (JcrItemAdapter item : getSortedItems(getItemComparator())) {
106             final Map<String, List<String>> assignedToItem = new HashMap<String, List<String>>();
107             try {
108                 Map<String, List<String>> dependenciesMap = getAssignedUsersAndGroupsMap(item);
109                 if (!dependenciesMap.isEmpty()) {
110                     confirmMessage.append("<li>");
111                     confirmMessage.append(item.getJcrItem().getName());
112                     confirmMessage.append("</li>");
113                     assignedToItem.putAll(dependenciesMap);
114                 }
115             } catch (RepositoryException e) {
116                 throw new RepositoryException("Cannot get the users/groups the group or role is assigned to.", e);
117             }
118             confirmMessage.append(formatUserAndGroupList(assignedToItem));
119             assignedTo.putAll(assignedToItem);
120         }
121         confirmMessage.append("</ul>");
122         return !assignedTo.isEmpty() ? confirmMessage.toString() : "";
123     }
124 
125     private void executeOnConfirmation() throws RepositoryException {
126         final String message = getConfirmationDialogStatement();
127         getUiContext().openConfirmation(MessageStyleTypeEnum.WARNING,
128                 getConfirmationDialogTitle(),
129                 (!message.isEmpty() ? "<br />" + getI18n().translate("security-app.delete.confirmationDialog.body.label", message) + "<br />" : "") + getConfirmationDialogBody(),
130                 getConfirmationDialogProceedLabel(),
131                 getConfirmationDialogCancelLabel(),
132                 true,
133                 new ConfirmationCallback() {
134                     @Override
135                     public void onCancel() {
136                         // do nothing
137                     }
138 
139                     @Override
140                     public void onSuccess() {
141                         try {
142                             DeleteFolderAction.super.execute();
143                         } catch (Exception e) {
144                             onError(e);
145                         }
146                     }
147                 });
148     }
149 
150     @Override
151     protected void onPreExecute() throws Exception {
152         super.onPreExecute();
153 
154         final Map<String, List<String>> assignedTo = getAssignedUsersAndGroupsMap();
155         if (!assignedTo.isEmpty()) {
156             if (getCurrentItem().isNode()) {
157                 Node folder = (Node) getCurrentItem().getJcrItem();
158 
159                 NodeUtil.visit(folder, new NodeVisitor() {
160                     @Override
161                     public void visit(Node node) throws RepositoryException {
162                         if (NodeUtil.isNodeType(node, NodeTypes.Role.NAME) || NodeUtil.isNodeType(node, NodeTypes.Group.NAME)) {
163                             try {
164                                 removeDependencies(node);
165                             } catch (Exception e) {
166                                 onError(e);
167                             }
168                         }
169                     }
170                 });
171             }
172         }
173     }
174 
175     /**
176      * @return the list of user- and group-names this item (group or role) is directly assigned to.
177      */
178     private List<String> getAssignedUsersAndGroups(Node node) throws RepositoryException {
179         List<String> assignedTo = new ArrayList<String>();
180 
181         final String groupOrRoleName = node.getName();
182 
183         final String translatedUserString = getI18n().translate("security.delete.userIdentifier");
184 
185         final String translatedGroupString = getI18n().translate("security.delete.groupIdentifier");
186 
187         if (NodeUtil.isNodeType(node, NodeTypes.Group.NAME)) {
188             // group - user, group - group
189             for (String user : securitySupport.getUserManager().getUsersWithGroup(groupOrRoleName)) {
190                 assignedTo.add(translatedUserString + ":" + user);
191             }
192             for (String group : securitySupport.getGroupManager().getGroupsWithGroup(groupOrRoleName)) {
193                 assignedTo.add(translatedGroupString + ":" + group);
194             }
195         } else if (NodeUtil.isNodeType(node, NodeTypes.Role.NAME)) {
196             // role - user, role - group
197             for (String user : securitySupport.getUserManager().getUsersWithRole(groupOrRoleName)) {
198                 assignedTo.add(translatedUserString + ":" + user);
199             }
200             for (String group : securitySupport.getGroupManager().getGroupsWithRole(groupOrRoleName)) {
201                 assignedTo.add(translatedGroupString + ":" + group);
202             }
203         }
204 
205         return assignedTo;
206     }
207 
208     protected String getVerificationErrorMessage() {
209         return getI18n().translate("security.delete.folder.cannotVerifyError");
210     }
211 
212     /**
213      * @deprecated since 5.3.6 - will be removed without replacement
214      */
215     @Deprecated
216     protected Collection<String> getGroupsOrRoles(User user) {
217         List<String> groupsAndRoles = new ArrayList<String>();
218         groupsAndRoles.addAll(user.getGroups());
219         groupsAndRoles.addAll(user.getRoles());
220         return groupsAndRoles;
221     }
222 
223     /**
224      * @deprecated since 5.3.6 - will be removed without replacement
225      */
226     @Deprecated
227     protected Collection<String> getGroupsOrRoles(Group group) {
228         List<String> groupsAndRoles = new ArrayList<String>();
229         groupsAndRoles.addAll(group.getGroups());
230         groupsAndRoles.addAll(group.getRoles());
231         return groupsAndRoles;
232     }
233 
234     /**
235      * @deprecated since 5.3.10 - use {@link #formatUserAndGroupList(Map<String, List<String>>)} instead.
236      */
237     @Deprecated
238     protected String getUserAndGroupListForErrorMessage(List<String> usersAndGroups) {
239         Map<String, List<String>> usersAndGroupsMap = new HashMap<String, List<String>>();
240         usersAndGroupsMap.put("dependencies", usersAndGroups);
241         return formatUserAndGroupList(usersAndGroupsMap);
242     }
243 
244     protected String formatUserAndGroupList(Map<String, List<String>> usersAndGroups) {
245         StringBuilder message = new StringBuilder("<ul>");
246         for (String key : usersAndGroups.keySet()) {
247             int i = 0;
248             message.append("<li>").append(key).append("</li>");
249             message.append("<ul>");
250             for (String name : usersAndGroups.get(key)) {
251                 message.append("<li>").append(name).append("</li>");
252                 if (i > 4) {
253                     message.append("<li>...</li>");
254                     break;
255                 }
256                 i++;
257             }
258             message.append("</ul>");
259         }
260         message.append("</ul>");
261         return message.toString();
262     }
263 
264     protected String getConfirmationDialogTitle() {
265         return getI18n().translate("security.folders.actions.confirmDeleteFolder.confirmationHeader");
266     }
267 
268     protected String getConfirmationDialogBody() {
269         return getI18n().translate("security.folders.actions.confirmDeleteFolder.confirmationMessage");
270     }
271 
272     protected String getConfirmationDialogProceedLabel() {
273         return getI18n().translate("security.folders.actions.confirmDeleteFolder.proceedLabel");
274     }
275 
276     protected String getConfirmationDialogCancelLabel() {
277         return getI18n().translate("security.folders.actions.confirmDeleteFolder.cancelLabel");
278     }
279 
280     protected String getBaseErrorMessage() {
281         return getI18n().translate("security.delete.folder.roleOrGroupInfolderStillInUse");
282     }
283 
284     private void removeDependencies(Node node) throws RepositoryException, ActionExecutionException {
285         final String groupOrRoleName = node.getName();
286         final UserManager mgnlUserManager = securitySupport.getUserManager();
287         final MgnlGroupManager mgnlGroupManager = securitySupport.getGroupManager() instanceof MgnlGroupManager ? (MgnlGroupManager) securitySupport.getGroupManager() : null;
288         if (NodeUtil.isNodeType(node, NodeTypes.Group.NAME)) {
289             // group - user, group - group
290             for (String user : securitySupport.getUserManager().getUsersWithGroup(groupOrRoleName)) {
291                 mgnlUserManager.removeGroup(mgnlUserManager.getUser(user), groupOrRoleName);
292             }
293 
294             if (mgnlGroupManager != null) {
295                 for (String group : securitySupport.getGroupManager().getGroupsWithGroup(groupOrRoleName)) {
296                     mgnlGroupManager.removeGroup(mgnlGroupManager.getGroup(group), groupOrRoleName);
297                 }
298             }
299         } else if (NodeUtil.isNodeType(node, NodeTypes.Role.NAME)) {
300             // role - user, role - group
301             for (String user : securitySupport.getUserManager().getUsersWithRole(groupOrRoleName)) {
302                 mgnlUserManager.removeRole(mgnlUserManager.getUser(user), groupOrRoleName);
303             }
304 
305             if (mgnlGroupManager != null) {
306                 for (String group : securitySupport.getGroupManager().getGroupsWithRole(groupOrRoleName)) {
307                     mgnlGroupManager.removeRole(mgnlGroupManager.getGroup(group), groupOrRoleName);
308                 }
309             }
310         }
311         if (mgnlGroupManager == null) {
312             final Map<String, List<String>> assignedTo = getAssignedUsersAndGroupsMap();
313             String errorMessage = formatUserAndGroupList(assignedTo);
314             log.error("Cannot get MgnlGroupManager, dependencies in groups cannot be removed. {}", errorMessage);
315             throw new ActionExecutionException(getBaseErrorMessage() + errorMessage);
316         }
317     }
318 
319     private Map<String, List<String>> getAssignedUsersAndGroupsMap() throws RepositoryException {
320         return getAssignedUsersAndGroupsMap(getCurrentItem());
321     }
322     private Map<String, List<String>> getAssignedUsersAndGroupsMap(JcrItemAdapter jcrItemAdapter) throws RepositoryException {
323         final Map<String, List<String>> assignedTo = new HashMap<String, List<String>>();
324         try {
325             if (jcrItemAdapter.isNode()) {
326                 Node folder = (Node) jcrItemAdapter.getJcrItem();
327 
328                 NodeUtil.visit(folder, new NodeVisitor() {
329                     @Override
330                     public void visit(Node node) throws RepositoryException {
331                         if (NodeUtil.isNodeType(node, NodeTypes.Role.NAME) || NodeUtil.isNodeType(node, NodeTypes.Group.NAME)) {
332                             List<String> assignedToItem = getAssignedUsersAndGroups(node);
333                             if (!assignedToItem.isEmpty()) {
334                                 assignedTo.put(node.getName(), assignedToItem);
335                             }
336                         }
337                     }
338                 });
339             }
340         } catch (RepositoryException e) {
341             throw new RepositoryException("Cannot get the users/groups the group or role is assigned to.", e);
342         }
343         return assignedTo;
344     }
345 }