/*
* (C) Copyright 2015 Netcentric AG.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package biz.netcentric.cq.tools.actool.validators.impl;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.RepositoryException;
import javax.jcr.security.AccessControlManager;
import org.apache.commons.lang.StringUtils;
import org.apache.jackrabbit.api.security.JackrabbitAccessControlList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.day.cq.security.util.CqActions;
import biz.netcentric.cq.tools.actool.configmodel.AceBean;
import biz.netcentric.cq.tools.actool.configmodel.Restriction;
import biz.netcentric.cq.tools.actool.helper.AccessControlUtils;
import biz.netcentric.cq.tools.actool.validators.AceBeanValidator;
import biz.netcentric.cq.tools.actool.validators.Validators;
import biz.netcentric.cq.tools.actool.validators.exceptions.AcConfigBeanValidationException;
import biz.netcentric.cq.tools.actool.validators.exceptions.DoubledDefinedActionException;
import biz.netcentric.cq.tools.actool.validators.exceptions.DoubledDefinedJcrPrivilegeException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidActionException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidGroupNameException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidJcrPrivilegeException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidPathException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidPermissionException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidRepGlobException;
import biz.netcentric.cq.tools.actool.validators.exceptions.InvalidRestrictionsException;
import biz.netcentric.cq.tools.actool.validators.exceptions.NoActionOrPrivilegeDefinedException;
import biz.netcentric.cq.tools.actool.validators.exceptions.NoGroupDefinedException;
import biz.netcentric.cq.tools.actool.validators.exceptions.TooManyActionsException;
public class AceBeanValidatorImpl implements AceBeanValidator {
private static final Logger LOG = LoggerFactory.getLogger(AceBeanValidatorImpl.class);
private long currentBeanCounter = 0;
private AceBean aceBean;
private Set<String> authorizableIdsFromCurrentConfig;
private String previousPrincipal;
public AceBeanValidatorImpl(Set<String> authorizableIdsFromCurrentConfig) {
this.authorizableIdsFromCurrentConfig = authorizableIdsFromCurrentConfig;
}
public AceBeanValidatorImpl() {
}
@Override
public boolean validate(final AceBean aceBean, AccessControlManager aclManager)
throws AcConfigBeanValidationException {
this.aceBean = aceBean;
return validate(aclManager);
}
private boolean validate(AccessControlManager aclManager) throws AcConfigBeanValidationException {
if (this.aceBean.isInitialContentOnlyConfig()) {
return true;
}
maintainBeanCounter();
// mandatory properties per AceBean
boolean isActionDefined = false;
boolean isPrivilegeDefined = false;
validateAuthorizableId();
validateAcePath();
isActionDefined = validateActions();
isPrivilegeDefined = validatePrivileges(aclManager);
validatePermission(this.aceBean);
// either action(s) or permission(s) or both have to be defined!
final boolean isActionOrPrivilegeDefined = isActionDefined || isPrivilegeDefined;
final boolean hasInitialContent = StringUtils.isNotBlank(this.aceBean.getInitialContent());
if (!isActionOrPrivilegeDefined && !hasInitialContent) {
final String errorMessage = getBeanDescription(this.currentBeanCounter,
this.aceBean.getPrincipalName())
+ ", no actions or privileges defined"
+ "! Installation aborted!";
LOG.error(errorMessage);
throw new NoActionOrPrivilegeDefinedException(errorMessage);
}
validateRestrictions(this.aceBean, aclManager);
return true;
}
private void maintainBeanCounter() {
if (StringUtils.equals(aceBean.getPrincipalName(), previousPrincipal)) {
this.currentBeanCounter++;
} else {
this.currentBeanCounter = 1;
}
previousPrincipal = aceBean.getPrincipalName();
}
private boolean validateRestrictions(final AceBean tmpAceBean, final AccessControlManager aclManager)
throws InvalidRepGlobException, InvalidRestrictionsException {
boolean valid = true;
final List<Restriction> restrictions = tmpAceBean.getRestrictions();
if (restrictions.isEmpty()) {
return true;
}
final String principal = tmpAceBean.getPrincipalName();
final Set<String> restrictionNamesFromAceBean = new HashSet<String>();
for (Restriction restriction : restrictions) {
restrictionNamesFromAceBean.add(restriction.getName());
}
final Set<String> allowedRestrictionNames = getSupportedRestrictions(aclManager);
if (!allowedRestrictionNames.containsAll(restrictionNamesFromAceBean)) {
restrictionNamesFromAceBean.removeAll(allowedRestrictionNames);
valid = false;
final String errorMessage = getBeanDescription(this.currentBeanCounter,
principal)
+ ", this repository doesn't support following restriction(s): "
+ restrictionNamesFromAceBean;
throw new InvalidRestrictionsException(errorMessage);
}
return valid;
}
private Set<String> getSupportedRestrictions(final AccessControlManager aclManager)
throws InvalidRepGlobException {
Set<String> allowedRestrictions = new HashSet<>();
try {
final JackrabbitAccessControlList jacl = getJackrabbitAccessControlList(aclManager);
allowedRestrictions = new HashSet<>(Arrays.asList(jacl.getRestrictionNames()));
} catch (final RepositoryException e) {
throw new InvalidRepGlobException("Could not get restriction names from ACL of path: " + this.aceBean.getJcrPath());
}
return allowedRestrictions;
}
private JackrabbitAccessControlList getJackrabbitAccessControlList(final AccessControlManager aclManager) throws RepositoryException, AccessDeniedException {
JackrabbitAccessControlList jacl = null;
// don't check paths containing wildcards
if(!this.aceBean.getJcrPath().contains("*")){
jacl = AccessControlUtils.getModifiableAcl(aclManager, this.aceBean.getJcrPath());
}
if(jacl == null){
// root as fallback
jacl = AccessControlUtils.getModifiableAcl(aclManager, "/");
}
return jacl;
}
private boolean validatePermission(final AceBean tmpAclBean) throws InvalidPermissionException {
final String permission = tmpAclBean.getPermission();
if (StringUtils.isNotBlank(this.aceBean.getInitialContent()) && StringUtils.isBlank(permission)) {
return true;
}
if (Validators.isValidPermission(permission)) {
tmpAclBean.setPermission(permission);
} else {
final String principal = tmpAclBean.getPrincipalName();
final String errorMessage = getBeanDescription(this.currentBeanCounter,
principal) + ", invalid permission: '" + permission + "'";
LOG.error(errorMessage);
throw new InvalidPermissionException(errorMessage);
}
return true;
}
private boolean validateActions() throws InvalidActionException, TooManyActionsException, DoubledDefinedActionException {
final String principal = aceBean.getPrincipalName();
if (!StringUtils.isNotBlank(aceBean.getActionsStringFromConfig())) {
return false;
}
final String[] actions = aceBean.getActionsStringFromConfig().split(",");
if (actions.length > CqActions.ACTIONS.length) {
final String errorMessage = getBeanDescription(this.currentBeanCounter,
principal) + " too many actions defined!";
LOG.error(errorMessage);
throw new TooManyActionsException(errorMessage);
}
final Set<String> actionsSet = new HashSet<String>();
for (int i = 0; i < actions.length; i++) {
// remove leading and trailing blanks from action name
actions[i] = StringUtils.strip(actions[i]);
if (!Validators.isValidAction(actions[i])) {
final String errorMessage = getBeanDescription(
this.currentBeanCounter, principal)
+ ", invalid action: " + actions[i];
LOG.error(errorMessage);
throw new InvalidActionException(errorMessage);
}
if (!actionsSet.add(actions[i])) {
final String errorMessage = getBeanDescription(
this.currentBeanCounter, principal)
+ ", doubled defined action: " + actions[i];
LOG.error(errorMessage);
throw new DoubledDefinedActionException(errorMessage);
}
}
aceBean.setActions(actions);
return true;
}
public boolean validatePrivileges(AccessControlManager aclManager)
throws InvalidJcrPrivilegeException, DoubledDefinedJcrPrivilegeException {
final String currentEntryValue = aceBean.getPrivilegesString();
final String principal = aceBean.getPrincipalName();
if (!StringUtils.isNotBlank(currentEntryValue)) {
return false;
}
final String[] privileges = currentEntryValue.split(",");
final Set<String> privilegesSet = new HashSet<String>();
for (int i = 0; i < privileges.length; i++) {
// remove leading and trailing blanks from privilege name
privileges[i] = StringUtils.strip(privileges[i]);
if (!Validators.isValidJcrPrivilege(privileges[i], aclManager)) {
final String errorMessage = getBeanDescription(
this.currentBeanCounter, principal)
+ ", invalid jcr privilege: " + privileges[i];
LOG.error(errorMessage);
throw new InvalidJcrPrivilegeException(errorMessage);
}
if (!privilegesSet.add(privileges[i])) {
final String errorMessage = getBeanDescription(
this.currentBeanCounter, principal)
+ ", doubled defined jcr privilege: " + privileges[i];
LOG.error(errorMessage);
throw new DoubledDefinedJcrPrivilegeException(errorMessage);
}
}
aceBean.setPrivilegesString(currentEntryValue);
return true;
}
private boolean validateAcePath() throws InvalidPathException {
boolean isPathDefined = false;
final String currentEntryValue = aceBean.getJcrPath();
final String principal = aceBean.getPrincipalName();
if (Validators.isValidNodePath(currentEntryValue)) {
aceBean.setJcrPath(currentEntryValue);
isPathDefined = true;
} else {
final String errorMessage = getBeanDescription(this.currentBeanCounter,
principal) + ", invalid path: " + currentEntryValue;
LOG.error(errorMessage);
throw new InvalidPathException(errorMessage);
}
return isPathDefined;
}
private boolean validateAuthorizableId() throws NoGroupDefinedException, InvalidGroupNameException {
boolean valid = true;
final String principal = aceBean.getPrincipalName();
// validate authorizable name format
if (Validators.isValidAuthorizableId(principal)) {
// validate if authorizable is contained in config
if (!authorizableIdsFromCurrentConfig.contains(principal)) {
final String message = getBeanDescription(this.currentBeanCounter,
principal) + " is not defined in group configuration";
throw new NoGroupDefinedException(message);
}
aceBean.setPrincipal(principal);
} else {
valid = false;
final String errorMessage = getBeanDescription(this.currentBeanCounter,
principal)
+ principal
+ ", invalid authorizable name: "
+ principal;
LOG.error(errorMessage);
throw new InvalidGroupNameException(errorMessage);
}
return valid;
}
private String getBeanDescription(final long beanCounter,
final String principalName) {
return "Validation error while reading ACE definition nr." + beanCounter + " of authorizable " + principalName;
}
}