blob: 59832c78bfefe831aa0e267cdc28e1f878825365 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.apache.sling.feature.cpconverter.handlers;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.io.IOUtils;
import org.apache.jackrabbit.vault.fs.io.Archive;
import org.apache.jackrabbit.vault.fs.io.Archive.Entry;
import org.apache.jackrabbit.vault.util.PlatformNameFormat;
import org.apache.sling.feature.cpconverter.ContentPackage2FeatureModelConverter;
import org.apache.sling.feature.cpconverter.acl.Acl;
import org.apache.sling.feature.cpconverter.acl.AclManager;
import org.apache.sling.feature.cpconverter.shared.AbstractJcrNodeParser;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
public final class RepPolicyEntryHandler extends AbstractRegexEntryHandler {
private final SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) TransformerFactory.newInstance();
public RepPolicyEntryHandler() {
super("/jcr_root(.*/)_rep_policy.xml");
}
@Override
public void handle(String path, Archive archive, Entry entry, ContentPackage2FeatureModelConverter converter)
throws Exception {
String resourcePath;
Matcher matcher = getPattern().matcher(path);
// we are pretty sure it matches, here
if (matcher.matches()) {
resourcePath = matcher.group(1);
} else {
throw new IllegalStateException("Something went terribly wrong: pattern '"
+ getPattern().pattern()
+ "' should have matched already with path '"
+ path
+ "' but it does not, currently");
}
TransformerHandler handler = saxTransformerFactory.newTransformerHandler();
handler.getTransformer().setOutputProperty(OutputKeys.INDENT, "yes");
handler.getTransformer().setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
handler.getTransformer().setOutputProperty(OutputKeys.ENCODING, "UTF-8");
StringWriter stringWriter = new StringWriter();
handler.setResult(new StreamResult(stringWriter));
RepPolicyParser systemUserParser = new RepPolicyParser(Paths.get(resourcePath),
Paths.get(PlatformNameFormat.getRepositoryPath(resourcePath)),
converter.getAclManager(),
handler);
boolean hasRejectedAcls;
try (InputStream input = archive.openInputStream(entry)) {
hasRejectedAcls = systemUserParser.parse(input);
}
if (hasRejectedAcls) {
try (Reader reader = new StringReader(stringWriter.toString());
OutputStreamWriter writer = new OutputStreamWriter(converter.getMainPackageAssembler().createEntry(path))) {
IOUtils.copy(reader, writer);
}
}
}
private static final class RepPolicyParser extends AbstractJcrNodeParser<Boolean> {
private static final String REP_ACL = "rep:ACL";
private static final String REP_GRANT_ACE = "rep:GrantACE";
private static final String REP_DENY_ACE = "rep:DenyACE";
private static final String REP_RESTRICTIONS = "rep:Restrictions";
private static final String REP_PRINCIPAL_NAME = "rep:principalName";
private static final String REP_PRIVILEGES = "rep:privileges";
private static final Map<String, String> operations = new HashMap<>();
static {
operations.put(REP_GRANT_ACE, "allow");
operations.put(REP_DENY_ACE, "deny");
}
private static final String[] RESTRICTIONS = new String[] { "rep:glob", "rep:ntNames", "rep:prefixes", "rep:itemNames" };
private static final Pattern typeIndicatorPattern = Pattern.compile("\\{[^\\}]+\\}\\[(.+)\\]");
private final Stack<Acl> acls = new Stack<>();
private final Path path;
private final Path repositoryPath;
private final AclManager aclManager;
private final TransformerHandler handler;
private boolean onRepAclNode = false;
// ACL processing result
private boolean hasRejectedNodes = false;
// just internal pointer for every iteration
private boolean processCurrentAcl = false;
public RepPolicyParser(Path path, Path repositoryPath, AclManager aclManager, TransformerHandler handler) {
super(REP_ACL);
this.path = path;
this.repositoryPath = repositoryPath;
this.aclManager = aclManager;
this.handler = handler;
}
@Override
public void startDocument() throws SAXException {
handler.startDocument();
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes)
throws SAXException {
if (onRepAclNode) {
String primaryType = attributes.getValue(JCR_PRIMARYTYPE);
if (REP_GRANT_ACE.equals(primaryType) || REP_DENY_ACE.equals(primaryType)) {
String principalName = attributes.getValue(REP_PRINCIPAL_NAME);
String operation = operations.get(primaryType);
String privileges = extractValue(attributes.getValue(REP_PRIVILEGES));
Acl acl = new Acl(operation, privileges, path, repositoryPath);
processCurrentAcl = aclManager.addAcl(principalName, acl);
if (processCurrentAcl) {
acls.add(acl);
} else {
hasRejectedNodes = true;
}
} else if (REP_RESTRICTIONS.equals(primaryType) && !acls.isEmpty()) {
if (processCurrentAcl) {
acls.add(acls.peek());
for (String restriction : RESTRICTIONS) {
String path = extractValue(attributes.getValue(restriction));
if (path != null && !path.isEmpty()) {
acls.peek().addRestriction(restriction + ',' + path);
}
}
}
}
} else {
super.startElement(uri, localName, qName, attributes);
}
if (!onRepAclNode || !processCurrentAcl) {
handler.startElement(uri, localName, qName, attributes);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (onRepAclNode && processCurrentAcl && !acls.isEmpty()) {
acls.pop();
} else {
processCurrentAcl = false;
handler.endElement(uri, localName, qName);
}
}
@Override
public void endDocument() throws SAXException {
handler.endDocument();
}
@Override
protected void onJcrRootElement(String uri, String localName, String qName, Attributes attributes) {
onRepAclNode = true;
}
@Override
protected Boolean getParsingResult() {
return hasRejectedNodes;
}
private static String extractValue(String expression) {
if (expression == null || expression.isEmpty()) {
return expression;
}
Matcher matcher = typeIndicatorPattern.matcher(expression);
if (matcher.matches()) {
return matcher.group(1);
}
return expression;
}
}
}