/*
* (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.configreader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.query.InvalidQueryException;
import org.apache.commons.lang.StringUtils;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import biz.netcentric.cq.tools.actool.configmodel.AceBean;
import biz.netcentric.cq.tools.actool.configmodel.AcesConfig;
import biz.netcentric.cq.tools.actool.configmodel.AuthorizableConfigBean;
import biz.netcentric.cq.tools.actool.configmodel.AuthorizablesConfig;
import biz.netcentric.cq.tools.actool.configmodel.GlobalConfiguration;
import biz.netcentric.cq.tools.actool.helper.Constants;
import biz.netcentric.cq.tools.actool.helper.QueryHelper;
import biz.netcentric.cq.tools.actool.validators.AceBeanValidator;
import biz.netcentric.cq.tools.actool.validators.AuthorizableValidator;
import biz.netcentric.cq.tools.actool.validators.exceptions.AcConfigBeanValidationException;
@Service
@Component(label = "AC Yaml Config Reader", description = "Service that installs groups & ACEs according to textual configuration files")
public class YamlConfigReader implements ConfigReader {
private static final Logger LOG = LoggerFactory.getLogger(YamlConfigReader.class);
protected static final String ACE_CONFIG_PROPERTY_GLOB = "repGlob";
protected static final String ACE_CONFIG_PROPERTY_RESTRICTIONS = "restrictions";
protected static final String ACE_CONFIG_PROPERTY_PERMISSION = "permission";
protected static final String ACE_CONFIG_PROPERTY_PRIVILEGES = "privileges";
protected static final String ACE_CONFIG_PROPERTY_ACTIONS = "actions";
protected static final String ACE_CONFIG_PROPERTY_PATH = "path";
protected static final String ACE_CONFIG_PROPERTY_KEEP_ORDER = "keepOrder";
protected static final String ACE_CONFIG_INITIAL_CONTENT = "initialContent";
private static final String GROUP_CONFIG_PROPERTY_MEMBER_OF = "isMemberOf";
private static final String GROUP_CONFIG_PROPERTY_MEMBER_OF_LEGACY = "memberOf";
private static final String GROUP_CONFIG_PROPERTY_MEMBERS = "members";
private static final String GROUP_CONFIG_PROPERTY_PATH = "path";
private static final String GROUP_CONFIG_PROPERTY_PASSWORD = "password";
protected static final String GROUP_CONFIG_PROPERTY_NAME = "name";
private static final String GROUP_CONFIG_PROPERTY_DESCRIPTION = "description";
private static final String GROUP_CONFIG_PROPERTY_EXTERNAL_ID = "externalId";
private static final String GROUP_CONFIG_PROPERTY_MIGRATE_FROM = "migrateFrom";
private static final String USER_CONFIG_PROPERTY_IS_SYSTEM_USER = "isSystemUser";
private static final String USER_CONFIG_PROFILE_CONTENT = "profileContent";
private static final String USER_CONFIG_PREFERENCES_CONTENT = "preferencesContent";
@Reference
private SlingRepository repository;
private final Pattern forLoopPattern = Pattern.compile("for (\\w+) in \\[([,/\\s\\w\\-]+)\\]", Pattern.CASE_INSENSITIVE);
@Override
@SuppressWarnings("rawtypes")
public AcesConfig getAceConfigurationBeans(final Collection<?> aceConfigData,
final AceBeanValidator aceBeanValidator, Session session) throws RepositoryException, AcConfigBeanValidationException {
final List<LinkedHashMap> aclList = (List<LinkedHashMap>) getConfigSection(Constants.ACE_CONFIGURATION_KEY, aceConfigData);
if (aclList == null) {
LOG.debug("ACL configuration not found in this YAML configuration file");
return null;
}
// group based Map from config file
AcesConfig aceMapFromConfig = getPreservedOrderdAceSet(aclList, aceBeanValidator, session);
return aceMapFromConfig;
}
@Override
public AuthorizablesConfig getGroupConfigurationBeans(final Collection yamlList,
final AuthorizableValidator authorizableValidator) throws AcConfigBeanValidationException {
final List<LinkedHashMap> authorizableList = (List<LinkedHashMap>) getConfigSection(Constants.GROUP_CONFIGURATION_KEY, yamlList);
if (authorizableList == null) {
LOG.debug("Group configuration not found in this YAML configuration file");
return null;
}
AuthorizablesConfig authorizableBeans = getAuthorizableBeans(authorizableList, authorizableValidator, true);
return authorizableBeans;
}
@Override
public AuthorizablesConfig getUserConfigurationBeans(final Collection yamlList,
final AuthorizableValidator authorizableValidator) throws AcConfigBeanValidationException {
List<LinkedHashMap> authorizableList = (List<LinkedHashMap>) getConfigSection(Constants.USER_CONFIGURATION_KEY, yamlList);
AuthorizablesConfig authorizableBeans = getAuthorizableBeans(authorizableList, authorizableValidator, false);
return authorizableBeans;
}
@Override
public GlobalConfiguration getGlobalConfiguration(final Collection yamlList) {
Map globalConfigMap = (Map) getConfigSection(Constants.GLOBAL_CONFIGURATION_KEY, yamlList);
GlobalConfiguration globalConfiguration = new GlobalConfiguration(globalConfigMap);
return globalConfiguration;
}
@Override
public Set<String> getObsoluteAuthorizables(final Collection yamlList) {
List obsoleteAuthorizablesList = (List) getConfigSection(Constants.OBSOLETE_AUTHORIZABLES_KEY, yamlList);
Set<String> obsoleteAuthorizables = new HashSet<String>();
if (obsoleteAuthorizablesList != null) {
for (Object obsoleteAuthorizable : obsoleteAuthorizablesList) {
if (obsoleteAuthorizable instanceof String) {
obsoleteAuthorizables.add((String) obsoleteAuthorizable);
} else if (obsoleteAuthorizable instanceof Map) {
// besides plain string also allow map
// (that way it is possible to copy sections one-to-one from group_config to obsolete_authorizables)
Map map = (Map) obsoleteAuthorizable;
obsoleteAuthorizables.add((String) map.keySet().iterator().next());
}
}
}
return obsoleteAuthorizables;
}
private Object getConfigSection(final String sectionName, final Collection yamlList) {
final List<LinkedHashMap<?, ?>> yamList = new ArrayList<LinkedHashMap<?, ?>>(yamlList);
for (final LinkedHashMap<?, ?> currMap : yamList) {
Iterator<?> keyIt = currMap.keySet().iterator();
if (keyIt.hasNext() && sectionName.equals(keyIt.next())) {
return currMap.get(sectionName);
}
}
return null;
}
private AuthorizablesConfig getAuthorizableBeans(
List<LinkedHashMap> yamlMap, final AuthorizableValidator authorizableValidator, boolean isGroupSection)
throws AcConfigBeanValidationException {
final Set<String> alreadyProcessedGroups = new HashSet<String>();
final AuthorizablesConfig authorizableBeans = new AuthorizablesConfig();
if (yamlMap == null) {
return authorizableBeans;
}
for (final LinkedHashMap currentMap : yamlMap) {
final String currentAuthorizableIdFromYaml = (String) currentMap.keySet().iterator().next();
if (!alreadyProcessedGroups.add(currentAuthorizableIdFromYaml)) {
throw new IllegalArgumentException("There is more than one group definition for group: " + currentAuthorizableIdFromYaml);
}
LOG.debug("Found principal: {} in config", currentAuthorizableIdFromYaml);
final List<Map<String, String>> currentAuthorizableData = (List<Map<String, String>>) currentMap.get(currentAuthorizableIdFromYaml);
if ((currentAuthorizableData != null) && !currentAuthorizableData.isEmpty()) {
for (final Map<String, String> currentPrincipalDataMap : currentAuthorizableData) {
final AuthorizableConfigBean tmpPrincipalConfigBean = getNewAuthorizableConfigBean();
setupAuthorizableBean(tmpPrincipalConfigBean, currentPrincipalDataMap, currentAuthorizableIdFromYaml, isGroupSection);
if (authorizableValidator != null) {
authorizableValidator.validate(tmpPrincipalConfigBean);
}
authorizableBeans.add(tmpPrincipalConfigBean);
}
}
}
return authorizableBeans;
}
private AcesConfig getPreservedOrderdAceSet(
List<LinkedHashMap> aceYamlList,
final AceBeanValidator aceBeanValidator, Session session) throws RepositoryException,
AcConfigBeanValidationException {
final AcesConfig aceSet = new AcesConfig();
if (aceYamlList == null) {
return aceSet;
}
for (final Map<String, List<Map<String, ?>>> currentPrincipalAceMap : aceYamlList) {
final String principalName = currentPrincipalAceMap.keySet().iterator().next();
final List<Map<String, ?>> aceDefinitions = currentPrincipalAceMap.get(principalName);
LOG.debug("start reading ACE configuration of authorizable: {}", principalName);
if ((aceDefinitions == null) || aceDefinitions.isEmpty()) {
LOG.warn("No ACE definition(s) found for autorizable: {}",
principalName);
continue;
}
// create ACE bean and populate it according to the properties
// in the config
for (final Map<String, ?> currentAceDefinition : aceDefinitions) {
AceBean newAceBean = getNewAceBean();
setupAceBean(principalName, currentAceDefinition, newAceBean);
if (aceBeanValidator != null) {
aceBeanValidator.validate(newAceBean, session.getAccessControlManager());
}
// --- handle wildcards ---
if ((newAceBean.getJcrPath() != null)
&& newAceBean.getJcrPath().contains("*")
&& (null != session)) {
handleWildcards(session, aceSet, principalName, newAceBean);
} else {
aceSet.add(newAceBean);
}
}
}
return aceSet;
}
protected void handleWildcards(final Session session,
final Set<AceBean> aceSet, final String principal,
final AceBean tmpAclBean) throws InvalidQueryException,
RepositoryException {
// perform query using the path containing wildcards
final String query = "/jcr:root" + tmpAclBean.getJcrPath();
final Set<Node> result = QueryHelper.getNodes(session, query);
if (result.isEmpty()) {
return;
}
for (final Node node : result) {
// ignore rep:policy nodes
if (!node.getPath().contains("/rep:policy")) {
final AceBean replacementBean = tmpAclBean.clone();
replacementBean.setJcrPath(node.getPath());
if (aceSet.add(replacementBean)) {
LOG.info("Wildcard replacement: Cloned " + tmpAclBean + " to " + replacementBean);
} else {
LOG.warn("Wildcard replacement failed: Cloned "
+ tmpAclBean + " to " + replacementBean
+ " but bean was already in set");
}
}
}
}
protected AceBean getNewAceBean() {
return new AceBean();
}
protected AuthorizableConfigBean getNewAuthorizableConfigBean() {
return new AuthorizableConfigBean();
}
protected void setupAceBean(final String principal,
final Map<String, ?> currentAceDefinition, final AceBean tmpAclBean) {
tmpAclBean.setPrincipal(principal);
tmpAclBean.setJcrPath(getMapValueAsString(currentAceDefinition,
ACE_CONFIG_PROPERTY_PATH));
tmpAclBean.setActionsStringFromConfig(getMapValueAsString(
currentAceDefinition, ACE_CONFIG_PROPERTY_ACTIONS));
tmpAclBean.setPrivilegesString(getMapValueAsString(
currentAceDefinition, ACE_CONFIG_PROPERTY_PRIVILEGES));
tmpAclBean.setPermission(getMapValueAsString(
currentAceDefinition, ACE_CONFIG_PROPERTY_PERMISSION));
tmpAclBean.setRestrictions(currentAceDefinition.get(ACE_CONFIG_PROPERTY_RESTRICTIONS),
(String) currentAceDefinition.get(ACE_CONFIG_PROPERTY_GLOB));
tmpAclBean.setActions(parseActionsString(getMapValueAsString(currentAceDefinition,
ACE_CONFIG_PROPERTY_ACTIONS)));
tmpAclBean.setKeepOrder(Boolean.valueOf(getMapValueAsString(currentAceDefinition,
ACE_CONFIG_PROPERTY_KEEP_ORDER)));
String initialContent = getMapValueAsString(currentAceDefinition,
ACE_CONFIG_INITIAL_CONTENT);
tmpAclBean.setInitialContent(initialContent);
}
public static String[] parseActionsString(final String actionsStringFromConfig) {
final String[] empty = {};
return StringUtils.isNotBlank(actionsStringFromConfig) ? actionsStringFromConfig.split(",") : empty;
}
protected void setupAuthorizableBean(
final AuthorizableConfigBean authorizableConfigBean,
final Map<String, String> currentPrincipalDataMap,
final String authorizableId,
boolean isGroupSection) {
authorizableConfigBean.setAuthorizableId(authorizableId);
authorizableConfigBean.setName(getMapValueAsString(currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_NAME));
authorizableConfigBean.setDescription(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_DESCRIPTION));
String externalIdVal = getMapValueAsString(currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_EXTERNAL_ID);
if (StringUtils.isNotBlank(externalIdVal)) {
authorizableConfigBean.setExternalId(externalIdVal);
// if an externalId is used, the principalName differs from authorizableId
String principalName = StringUtils.substringBeforeLast(externalIdVal, ";");
authorizableConfigBean.setPrincipalName(principalName);
} else {
// default: rep:authorizableId and rep:principalName are equal
authorizableConfigBean.setPrincipalName(authorizableId);
}
authorizableConfigBean.setMemberOfString(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_MEMBER_OF));
// read also memberOf property from legacy scripts
if (!StringUtils.isEmpty(getMapValueAsString(currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_MEMBER_OF_LEGACY))) {
authorizableConfigBean.setMemberOfString(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_MEMBER_OF_LEGACY));
}
authorizableConfigBean.setMembersString(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_MEMBERS));
authorizableConfigBean.setPath(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_PATH));
authorizableConfigBean.setMigrateFrom(getMapValueAsString(currentPrincipalDataMap,
GROUP_CONFIG_PROPERTY_MIGRATE_FROM));
authorizableConfigBean.setIsGroup(isGroupSection);
authorizableConfigBean.setIsSystemUser(Boolean.valueOf(getMapValueAsString(currentPrincipalDataMap,
USER_CONFIG_PROPERTY_IS_SYSTEM_USER)));
authorizableConfigBean.setPassword(getMapValueAsString(
currentPrincipalDataMap, GROUP_CONFIG_PROPERTY_PASSWORD));
authorizableConfigBean.setProfileContent(getMapValueAsString(
currentPrincipalDataMap, USER_CONFIG_PROFILE_CONTENT));
authorizableConfigBean.setPreferencesContent(getMapValueAsString(
currentPrincipalDataMap, USER_CONFIG_PREFERENCES_CONTENT));
}
protected String getMapValueAsString(
final Map<String, ?> currentAceDefinition,
final String propertyName) {
if (currentAceDefinition.get(propertyName) != null) {
return currentAceDefinition.get(propertyName).toString();
}
return "";
}
}