| /* |
| * 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.jackrabbit.oak.security.authorization.accesscontrol; |
| |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.jcr.PropertyType; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.Value; |
| import javax.jcr.nodetype.ConstraintViolationException; |
| import javax.jcr.security.AccessControlEntry; |
| import javax.jcr.security.AccessControlException; |
| import javax.jcr.security.AccessControlManager; |
| import javax.jcr.security.AccessControlPolicy; |
| import javax.jcr.security.Privilege; |
| |
| import org.apache.jackrabbit.api.JackrabbitSession; |
| import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; |
| import org.apache.jackrabbit.api.security.principal.PrincipalManager; |
| import org.apache.jackrabbit.oak.api.Root; |
| import org.apache.jackrabbit.oak.api.Tree; |
| import org.apache.jackrabbit.oak.namepath.NamePathMapper; |
| import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager; |
| import org.apache.jackrabbit.oak.spi.security.SecurityProvider; |
| import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfiguration; |
| import org.apache.jackrabbit.oak.spi.security.authorization.accesscontrol.AccessControlConstants; |
| import org.apache.jackrabbit.oak.spi.security.principal.PrincipalConfiguration; |
| import org.apache.jackrabbit.oak.spi.security.principal.PrincipalImpl; |
| import org.apache.jackrabbit.oak.spi.xml.ImportBehavior; |
| import org.apache.jackrabbit.oak.spi.xml.NodeInfo; |
| import org.apache.jackrabbit.oak.spi.xml.PropInfo; |
| import org.apache.jackrabbit.oak.spi.xml.ProtectedNodeImporter; |
| import org.apache.jackrabbit.oak.spi.xml.ReferenceChangeTracker; |
| import org.apache.jackrabbit.oak.spi.xml.TextValue; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| |
| /** |
| * {@link ProtectedNodeImporter} implementation that handles access control lists, |
| * entries and restrictions. |
| */ |
| public class AccessControlImporter implements ProtectedNodeImporter, AccessControlConstants { |
| |
| private static final Logger log = LoggerFactory.getLogger(AccessControlImporter.class); |
| |
| private static final int CHILD_STATUS_UNDEFINED = 0; |
| private static final int CHILD_STATUS_ACE = 1; |
| private static final int CHILD_STATUS_RESTRICTION = 2; |
| |
| private AccessControlManager acMgr; |
| private PrincipalManager principalManager; |
| private ReadOnlyNodeTypeManager ntMgr; |
| |
| private boolean initialized = false; |
| private int childStatus; |
| |
| private JackrabbitAccessControlList acl; |
| private MutableEntry entry; |
| |
| private int importBehavior; |
| |
| //----------------------------------------------< ProtectedItemImporter >--- |
| |
| @Override |
| public boolean init(@NotNull Session session, @NotNull Root root, @NotNull NamePathMapper namePathMapper, |
| boolean isWorkspaceImport, int uuidBehavior, |
| @NotNull ReferenceChangeTracker referenceTracker, @NotNull SecurityProvider securityProvider) { |
| if (initialized) { |
| throw new IllegalStateException("Already initialized"); |
| } |
| if (!(session instanceof JackrabbitSession)) { |
| return false; |
| } |
| try { |
| AuthorizationConfiguration config = securityProvider.getConfiguration(AuthorizationConfiguration.class); |
| importBehavior = Util.getImportBehavior(config); |
| |
| if (isWorkspaceImport) { |
| acMgr = config.getAccessControlManager(root, namePathMapper); |
| PrincipalConfiguration pConfig = securityProvider.getConfiguration(PrincipalConfiguration.class); |
| principalManager = pConfig.getPrincipalManager(root, namePathMapper); |
| } else { |
| acMgr = session.getAccessControlManager(); |
| principalManager = ((JackrabbitSession) session).getPrincipalManager(); |
| } |
| ntMgr = ReadOnlyNodeTypeManager.getInstance(root, namePathMapper); |
| initialized = true; |
| } catch (RepositoryException e) { |
| log.warn("Error while initializing access control importer", e); |
| } |
| return initialized; |
| } |
| |
| @Override |
| public void processReferences() throws RepositoryException { |
| // nothing to do. |
| } |
| |
| //----------------------------------------------< ProtectedNodeImporter >--- |
| |
| @Override |
| public boolean start(@NotNull Tree protectedParent) throws RepositoryException { |
| checkInitialized(); |
| |
| // the acl node must have been added during the regular import before |
| // this importer is only successfully started if an valid ACL was created. |
| acl = getACL(protectedParent); |
| return acl != null; |
| } |
| |
| @Override |
| public void end(@NotNull Tree protectedParent) throws RepositoryException { |
| if (acl != null) { |
| acMgr.setPolicy(acl.getPath(), acl); |
| acl = null; |
| } else { |
| throw new IllegalStateException("End reached without ACL to write back."); |
| } |
| } |
| |
| @Override |
| public void startChildInfo(@NotNull NodeInfo childInfo, @NotNull List<PropInfo> propInfos) throws RepositoryException { |
| checkInitialized(); |
| String ntName = childInfo.getPrimaryTypeName(); |
| if (NT_REP_GRANT_ACE.equals(ntName) || NT_REP_DENY_ACE.equals(ntName)) { |
| if (entry != null) { |
| throw new ConstraintViolationException("Invalid child node sequence: ACEs may not be nested."); |
| } |
| entry = new MutableEntry(NT_REP_GRANT_ACE.equals(ntName)); |
| for (PropInfo prop : propInfos) { |
| String name = prop.getName(); |
| if (REP_PRINCIPAL_NAME.equals(name)) { |
| entry.setPrincipal(prop.getTextValue()); |
| } else if (REP_PRIVILEGES.equals(name)) { |
| entry.setPrivilegeNames(prop.getTextValues()); |
| } else { |
| entry.addRestriction(prop); |
| } |
| } |
| childStatus = CHILD_STATUS_ACE; |
| } else if (NT_REP_RESTRICTIONS.equals(ntName)) { |
| if (entry == null) { |
| throw new ConstraintViolationException("Invalid child node sequence: Restriction must be associated with an ACE"); |
| } |
| entry.addRestrictions(propInfos); |
| childStatus = CHILD_STATUS_RESTRICTION; |
| } else { |
| throw new ConstraintViolationException("Invalid child node with type " + ntName); |
| } |
| } |
| |
| @Override |
| public void endChildInfo() throws RepositoryException { |
| checkInitialized(); |
| switch (childStatus) { |
| case CHILD_STATUS_ACE: |
| // write the ace to the policy |
| entry.applyTo(acl); |
| entry = null; |
| childStatus = CHILD_STATUS_UNDEFINED; |
| break; |
| case CHILD_STATUS_RESTRICTION: |
| // back to ace status |
| childStatus = CHILD_STATUS_ACE; |
| break; |
| default: |
| throw new ConstraintViolationException("Invalid child node sequence."); |
| } |
| } |
| |
| //------------------------------------------------------------< private >--- |
| private void checkInitialized() { |
| if (!initialized) { |
| throw new IllegalStateException("Not initialized"); |
| } |
| } |
| |
| @Nullable |
| private JackrabbitAccessControlList getACL(Tree tree) throws RepositoryException { |
| String nodeName = tree.getName(); |
| |
| JackrabbitAccessControlList acList = null; |
| if (!tree.isRoot()) { |
| Tree parent = tree.getParent(); |
| if (AccessControlConstants.REP_POLICY.equals(nodeName) |
| && ntMgr.isNodeType(tree, AccessControlConstants.NT_REP_ACL)) { |
| String path = parent.getPath(); |
| acList = getACL(path); |
| } else if (AccessControlConstants.REP_REPO_POLICY.equals(nodeName) |
| && ntMgr.isNodeType(tree, AccessControlConstants.NT_REP_ACL) |
| && parent.isRoot()) { |
| acList = getACL((String) null); |
| } |
| } |
| |
| if (acList != null) { |
| // clear all existing entries |
| for (AccessControlEntry ace: acList.getAccessControlEntries()) { |
| acList.removeAccessControlEntry(ace); |
| } |
| } |
| |
| return acList; |
| } |
| |
| @Nullable |
| private JackrabbitAccessControlList getACL(@Nullable String path) throws RepositoryException { |
| JackrabbitAccessControlList acList = null; |
| for (AccessControlPolicy p : acMgr.getPolicies(path)) { |
| if (p instanceof JackrabbitAccessControlList) { |
| acList = (JackrabbitAccessControlList) p; |
| break; |
| } |
| } |
| return acList; |
| } |
| |
| //-------------------------------------------------------------------------- |
| private final class MutableEntry { |
| |
| private final boolean isAllow; |
| |
| private Principal principal; |
| private List<Privilege> privileges = new ArrayList(); |
| private Map<String, Value> restrictions = new HashMap(); |
| private Map<String, Value[]> mvRestrictions = new HashMap(); |
| |
| private boolean ignore; |
| |
| private MutableEntry(boolean isAllow) { |
| this.isAllow = isAllow; |
| } |
| |
| private void setPrincipal(TextValue txtValue) throws AccessControlException { |
| String principalName = txtValue.getString(); |
| principal = principalManager.getPrincipal(principalName); |
| if (principal == null) { |
| switch (importBehavior) { |
| case ImportBehavior.IGNORE: |
| log.debug("Unknown principal " + principalName + " -> Ignoring this ACE."); |
| ignore = true; |
| break; |
| case ImportBehavior.ABORT: |
| throw new AccessControlException("Unknown principal " + principalName); |
| case ImportBehavior.BESTEFFORT: |
| principal = new PrincipalImpl(principalName); |
| } |
| } |
| } |
| |
| private void setPrivilegeNames(List<? extends TextValue> txtValues) throws RepositoryException { |
| for (TextValue value : txtValues) { |
| Value privilegeName = value.getValue(PropertyType.NAME); |
| privileges.add(acMgr.privilegeFromName(privilegeName.getString())); |
| } |
| } |
| |
| private void addRestriction(PropInfo propInfo) throws RepositoryException { |
| String restrictionName = propInfo.getName(); |
| int targetType = acl.getRestrictionType(restrictionName); |
| List<Value> values = propInfo.getValues(targetType); |
| if (values.size() == 1) { |
| restrictions.put(propInfo.getName(), values.get(0)); |
| } else { |
| mvRestrictions.put(propInfo.getName(), values.toArray(new Value[values.size()])); |
| } |
| } |
| |
| private void addRestrictions(List<PropInfo> propInfos) throws RepositoryException { |
| for (PropInfo prop : propInfos) { |
| addRestriction(prop); |
| } |
| } |
| |
| private void applyTo(JackrabbitAccessControlList acl) throws RepositoryException { |
| checkNotNull(acl); |
| if (!ignore) { |
| acl.addEntry(principal, privileges.toArray(new Privilege[privileges.size()]), isAllow, restrictions, mvRestrictions); |
| } else { |
| log.debug("Unknown principal: Ignore ACE based on ImportBehavior.IGNORE configuration."); |
| } |
| } |
| } |
| } |