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.app.dialog.field.validator;
35  
36  import static info.magnolia.security.app.util.AccessControlPropertyUtil.*;
37  
38  import info.magnolia.cms.security.Permission;
39  import info.magnolia.cms.security.PrincipalUtil;
40  import info.magnolia.cms.security.auth.ACL;
41  import info.magnolia.cms.security.operations.AccessDefinition;
42  import info.magnolia.context.MgnlContext;
43  import info.magnolia.security.app.dialog.field.AccessControlList;
44  
45  import java.security.AccessControlException;
46  import java.text.MessageFormat;
47  import java.util.Set;
48  
49  import javax.jcr.RepositoryException;
50  
51  import org.apache.commons.lang3.StringUtils;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  import com.vaadin.v7.data.validator.AbstractValidator;
56  
57  /**
58   * Typed validator for ACL {@linkplain AccessControlList.Entry entries}.
59   * It examines whether the current user creating/editing a role has himself the required permissions
60   * to the URIs he's specifying in the ACLs. See MGNLUI-2357.
61   *
62   * <p>The validation strategy is as follows:</p>
63   * <ul>
64   * <li>Current user must have access to the uri "workspace"
65   * <li>Current user needs to have a matching permission to the path he's granting permission for
66   * <li>The user's best matching permission needs to grant equal or greater rights than the ones being granted
67   * <li>In order to deny permission, user requires read/get permission to that path
68   * <li>Assigning a recursive permission (ending with '*' wildcard) requires user to also have such recursive permission
69   * </ul>
70   */
71  public class WebAccessControlValidator extends AbstractValidator<AccessControlList.Entry> {
72      private static final Logger log = LoggerFactory.getLogger(WebAccessControlValidator.class);
73      private final String originalErrorMessage;
74  
75      public WebAccessControlValidator(String errorMessage) {
76          super(errorMessage);
77          this.originalErrorMessage = errorMessage;
78      }
79  
80      @Override
81      public Class<AccessControlList.Entry> getType() {
82          return AccessControlList.Entry.class;
83      }
84  
85      @Override
86      protected boolean isValidValue(AccessControlList.Entry entry) {
87          boolean isValid = true;
88  
89          if (MgnlContext.getUser().hasRole(AccessDefinition.DEFAULT_SUPERUSER_ROLE)) {
90              return true;
91          }
92  
93          String path = entry.getPath();
94          long permissions = entry.getPermissions();
95  
96          try {
97              if (!isCurrentUserEntitledToGrantUriRights(path, permissions)) {
98                  isValid = !isValid;
99              }
100         } catch (AccessControlException e) {
101             isValid = !isValid;
102         } catch (RepositoryException e) {
103             log.error("Could not validate current user permissions: ", e);
104             isValid = !isValid;
105         }
106 
107         if (!isValid) {
108             setErrorMessage(MessageFormat.format(originalErrorMessage, permissions, path));
109         }
110 
111         return isValid;
112     }
113 
114     /**
115      * @return false if current user has no matching permission in the uri "workspace", or if the matching permission does not grant sufficient rights.
116      */
117     private boolean isCurrentUserEntitledToGrantUriRights(String path, long permissions) throws RepositoryException {
118         // Granting DENY access is only allowed if the user has READ access to the path
119         if (permissions == Permission.NONE) {
120             permissions = Permission.READ;
121         }
122 
123         ACL acl = PrincipalUtil.findAccessControlList(MgnlContext.getSubject(), "uri");
124         if (acl == null) {
125             return false;
126         }
127 
128         // validate base path permission
129         String basePath = stripWildcardsFromPath(path);
130         Permission pathPerm = findBestMatchingPermissions(acl.getList(), basePath);
131         if (pathPerm == null || !granted(pathPerm, permissions)) {
132             return false;
133         }
134 
135         // validate sub-path permission
136         if (path.endsWith("*")) {
137 
138             // find sub-path restrictions: any permission to a sub-path of the selected path, with lower permission
139             Set<Permission> violatedPerms = findViolatedPermissions(acl.getList(), path, permissions);
140             if (!violatedPerms.isEmpty()) {
141                 return false;
142             }
143 
144             Permission childPerm = findBestMatchingPermissions(acl.getList(), path);
145             // The child permission must end with *
146             if (childPerm == null || !granted(childPerm, permissions) || !StringUtils.endsWith(childPerm.getPattern().getPatternString(), "*")) {
147                 return false;
148             }
149         }
150 
151         return true;
152     }
153 
154     private String stripWildcardsFromPath(String path) {
155         path = StringUtils.stripEnd(path, "/*");
156         if (StringUtils.isBlank(path)) {
157             path = "/";
158         }
159         return path;
160     }
161 
162     private boolean granted(Permission permissionsGranted, long permissionsNeeded) {
163         return (permissionsGranted.getPermissions() & permissionsNeeded) == permissionsNeeded;
164     }
165 }