View Javadoc
1   /**
2    * This file Copyright (c) 2013-2018 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.setup.migration;
35  
36  import info.magnolia.cms.util.PathUtil;
37  import info.magnolia.jcr.predicate.NodeNamePredicate;
38  import info.magnolia.jcr.predicate.NodePropertyNamePredicate;
39  import info.magnolia.jcr.util.NodeUtil;
40  import info.magnolia.module.InstallContext;
41  import info.magnolia.module.delta.AbstractRepositoryTask;
42  import info.magnolia.module.delta.TaskExecutionException;
43  import info.magnolia.repository.RepositoryConstants;
44  
45  import java.util.ArrayList;
46  import java.util.Iterator;
47  import java.util.List;
48  
49  import javax.jcr.Node;
50  import javax.jcr.Property;
51  import javax.jcr.RepositoryException;
52  import javax.jcr.Session;
53  
54  import org.apache.commons.lang3.StringUtils;
55  import org.slf4j.Logger;
56  import org.slf4j.LoggerFactory;
57  
58  /**
59   * Update ACL's from a workspace to another. <br>
60   * For example move ACL's from dms to dam workspace: <br>
61   * - rename nodes with name acl_dms to acl_dam <br>
62   * <br>
63   * Check the associated path and correct them if required. <br>
64   * - If the related path (/demo-project/image/icon) is no more valid on the target workspace.<br>
65   * -- check if the path prefixed with one of the element of 'subPaths' will match<br>
66   * --- If one match found and 'updatePath' is set to true, set this path to the ACL property.<br>
67   * - Else log a warning message.<br>
68   */
69  public class MoveAclPermissionsBetweenWorkspaces  extends AbstractRepositoryTask {
70  
71      private static final Logger log = LoggerFactory.getLogger(MoveAclPermissionsBetweenWorkspaces.class);
72  
73      private String everythingUnderThis = "/*";
74      private String SelectedNode = "$";
75  
76      private final List<String> subPaths;
77      private final String sourceWorkspaceName;
78      private final String targetWorkspaceName;
79      private final boolean updatePath;
80  
81      /**
82       * @param sourceWorkspaceName source workspace name like 'dms'
83       * @param targetWorkspaceName target workspace name like 'dam'
84       * @param subPaths List of sub path set during migration process.
85       */
86      public MoveAclPermissionsBetweenWorkspaces(String name, String description, String sourceWorkspaceName, String targetWorkspaceName, List<String> subPaths, boolean updatePath) {
87          super(name, description);
88          this.sourceWorkspaceName = sourceWorkspaceName;
89          this.targetWorkspaceName = targetWorkspaceName;
90          this.updatePath = updatePath;
91          this.subPaths = subPaths == null ? new ArrayList<String>() : subPaths;
92      }
93  
94  
95      @Override
96      protected void doExecute(InstallContext installContext) throws RepositoryException, TaskExecutionException {
97          if (StringUtils.isBlank(this.sourceWorkspaceName) || StringUtils.isBlank(this.targetWorkspaceName)) {
98              log.warn("sourceWorkspaceName:'{}' and targetWorkspaceName:'{}' have to be defined ", this.sourceWorkspaceName, this.targetWorkspaceName);
99              return;
100         }
101         // Initialize
102         final Session targetSession = installContext.getJCRSession(targetWorkspaceName);
103         final Session session = installContext.getJCRSession(RepositoryConstants.USER_ROLES);
104         final Node rootNode = session.getRootNode();
105 
106         String nodeName = "acl_" + sourceWorkspaceName;
107         String newAclNodeName = "acl_" + targetWorkspaceName;
108         Iterator<Node> iterator = NodeUtil.collectAllChildren(rootNode, new NodeNamePredicate(nodeName)).iterator();
109         while (iterator.hasNext()) {
110             Node aclNode = iterator.next();
111             handleAclNode(targetSession, aclNode, newAclNodeName, installContext);
112         }
113     }
114 
115     /**
116      * Rename the ACL node and check the related permission path.
117      */
118     private void handleAclNode(Session targetSession, Node aclNode, String newAclNodeName, InstallContext installContext) throws RepositoryException {
119         // If node already exist, return
120         final Node parent = aclNode.getParent();
121         final String newAclPath = NodeUtil.combinePathAndName(parent.getPath(), newAclNodeName);
122         if (aclNode.getSession().nodeExists(newAclPath)) {
123             log.warn("{} already exist. No migration will be performed.", newAclPath);
124             return;
125         }
126 
127         // Rename node
128         String oldAclNodePath = aclNode.getPath();
129         NodeUtil.renameNode(aclNode, newAclNodeName);
130         log.info("The following ACL node '{}' was succesfully renamed to '{}'", oldAclNodePath, aclNode.getPath());
131         // Check the permission path
132         Iterator<Node> childNodeIterator = NodeUtil.collectAllChildren(aclNode, new NodePropertyNamePredicate("path")).iterator();
133         // Iterate child and check if the path is still valid
134         while (childNodeIterator.hasNext()) {
135             Property pathProperty = childNodeIterator.next().getProperty("path");
136             String originalPath = pathProperty.getString();
137             String extraParameter = getExtraAclParameter(originalPath);
138             String pathToHandle = StringUtils.removeEnd(originalPath, extraParameter);
139             // If the path is blank, handle the next one.
140             if (StringUtils.isBlank(pathToHandle)) {
141                 continue;
142             }
143 
144             // Check if the path is well-formed
145             if (!isPathWellFormed(targetSession, pathToHandle)) {
146                 log.info("Following ACL link is not a well-formed path '{}'. ", originalPath);
147             } else if (!targetSession.itemExists(pathToHandle)) {
148                 // Path is well-formed but not valid
149                 handleNoNoValidACLPath(targetSession, pathToHandle, extraParameter, pathProperty, installContext);
150             } else {
151                 log.info("Following ACL link is valid '{}'. ", originalPath);
152             }
153         }
154     }
155 
156     /**
157      * @return true if the path is well formed, false otherwise.
158      */
159     private boolean isPathWellFormed(Session targetSession, String path) {
160         try {
161             targetSession.itemExists(path);
162             return true;
163         } catch (RepositoryException e) {
164             return false;
165         }
166     }
167 
168     /**
169      * Try to found a valid path based on the defined subpaths.
170      */
171     private void handleNoNoValidACLPath(Session targetSession, String originalPath, String extraParameter, Property pathProperty, InstallContext installContext) throws RepositoryException {
172         // Check the path prefixed with the subpath
173         String validPath = getValidPathWithSubPath(targetSession, originalPath);
174         if (StringUtils.isNotBlank(validPath)) {
175             if (this.updatePath) {
176                 // re-add the /* removed in the previous step.
177                 validPath = validPath + extraParameter;
178                 pathProperty.setValue(validPath);
179                 log.info("The original path was incorect '{}' and is replaced by '{}'", originalPath, validPath);
180             } else {
181                 installContext.info("The path '" + originalPath + "' defined for the following ACL '" + pathProperty.getParent().getPath() + "' is no more valid. The following is Valid '" + validPath + "'. Please use Security App to correct this.");
182             }
183         } else {
184             installContext.warn("The path '" + originalPath + "' defined for the following ACL '" + pathProperty.getParent().getPath() + "' is no more valid. Please use Security App to correct this.");
185             log.warn("The path '{}' defined for the following ACL '{}' is no more valid. Please use Security App to correct this.", originalPath, pathProperty.getParent().getPath());
186 
187         }
188     }
189 
190     /**
191      * Iterate the subPaths list and try to found a valid one.
192      *
193      * @return the first valid path found or null otherwise.
194      */
195     private String getValidPathWithSubPath(Session targetSession, String originalPath) throws RepositoryException {
196         for (String subPath : subPaths) {
197             String migratedPath = PathUtil.createPath(StringUtils.removeEnd(subPath, "/"), StringUtils.removeStart(originalPath, "/"));
198             log.debug("Check if the following migrated path exist {}", migratedPath);
199             if (targetSession.itemExists(migratedPath)) {
200                 return migratedPath;
201             }
202         }
203         return null;
204     }
205 
206     private String getExtraAclParameter(String acl) {
207         if (StringUtils.endsWith(acl, everythingUnderThis)) {
208             return everythingUnderThis;
209         }
210         if (StringUtils.endsWith(acl, SelectedNode)) {
211             return SelectedNode;
212         }
213         return StringUtils.EMPTY;
214     }
215 
216 }