Index: oak-core/src/main/java/org/apache/jackrabbit/oak/management/NamedOperation.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/management/NamedOperation.java (revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/management/NamedOperation.java (revision )
@@ -0,0 +1,36 @@
+/*
+ * 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.management;
+
+/**
+ * Interface implemented by management operations that
+ * can be executed by name.
+ *
+ * @see RepositoryManager#execute(Class, String, com.google.common.base.Function)
+ */
+interface NamedOperation {
+
+ /**
+ * The name of this operation
+ *
+ * @return the name.
+ */
+ String getName();
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/management/ConsistencyMBean.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/management/ConsistencyMBean.java (revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/management/ConsistencyMBean.java (revision )
@@ -0,0 +1,55 @@
+/*
+ * 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.management;
+
+import javax.annotation.Nonnull;
+import javax.management.openmbean.CompositeData;
+
+/**
+ * MBean for verifying and restoring consistency of a {@code NodeStore}.
+ */
+public interface ConsistencyMBean extends NamedOperation {
+
+ String TYPE = "Consistency";
+
+ /**
+ * Initiate the consistency analysis operation.
+ *
+ * @return the status of the operation right after it was initiated
+ */
+ @Nonnull
+ CompositeData analyseConsistency();
+
+ /**
+ * Initiate the consistency fix operation.
+ *
+ * @return the status of the operation right after it was initiated
+ */
+ @Nonnull
+ CompositeData fixConsistency();
+
+ /**
+ * The status of the ongoing consistency check operation.
+ *
+ * @return the status of the ongoing operation or if none the terminal
+ * status of the last operation or Status not available if none.
+ */
+ @Nonnull
+ CompositeData getConsistencyStatus();
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistency.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistency.java (revision )
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistency.java (revision )
@@ -0,0 +1,247 @@
+/*
+ * 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.permission;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nonnull;
+import javax.management.openmbean.CompositeData;
+
+import com.google.common.collect.Lists;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.core.ImmutableRoot;
+import org.apache.jackrabbit.oak.management.ConsistencyMBean;
+import org.apache.jackrabbit.oak.management.ManagementOperation;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate;
+import org.apache.jackrabbit.oak.plugins.tree.ImmutableTree;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+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.authorization.restriction.RestrictionProvider;
+import org.apache.jackrabbit.oak.spi.security.privilege.PrivilegeBitsProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.util.TreeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * MBean to find and fix inconsistencies between access control content and the
+ * permission store.
+ */
+public class PermissionConsistency implements ConsistencyMBean, AccessControlConstants {
+
+ private static final Logger log = LoggerFactory.getLogger(PermissionConsistency.class);
+
+ private static final long DEFAULT_LIFETIME = TimeUnit.HOURS.toMillis(1);
+
+ private static final String NAME = "PermissionConsistency";
+
+ private final NodeStore store;
+ private final String workspaceName;
+ private final AuthorizationConfiguration config;
+ private final Executor executor;
+
+ private ManagementOperation consistencyOp = ManagementOperation.done(NAME, 0);
+
+ private PrivilegeBitsProvider bitsProvider;
+ private RestrictionProvider restrictionProvider;
+
+ private NodeBuilder permissionRoot;
+ private PermissionStore permissionStore;
+
+ private TypePredicate isACE;
+ private TypePredicate isGrantACE;
+
+ private List extraEntries;
+ private List missingEntries;
+
+ /**
+ * @param store store to back up from or restore to
+ * @param executor executor for running the back up or restore operation
+ */
+ public PermissionConsistency(@Nonnull NodeStore store,
+ @Nonnull String workspaceName,
+ @Nonnull AuthorizationConfiguration config,
+ @Nonnull Executor executor) {
+ this.store = store;
+ this.workspaceName = workspaceName;
+ this.config = config;
+ this.executor = executor;
+ }
+
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData analyseConsistency() {
+ if (consistencyOp.isDone()) {
+ consistencyOp = new ManagementOperation("PermissionConsistencyAnalysis", new Callable() {
+ @Override
+ public Long call() throws Exception {
+ long t0 = System.nanoTime();
+ checkConsistency(false);
+ return System.nanoTime() - t0;
+ }
+ });
+ executor.execute(consistencyOp);
+ }
+ return getConsistencyStatus();
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData fixConsistency() {
+ if (consistencyOp.isDone()) {
+ consistencyOp = new ManagementOperation("PermissionConsistencyFix", new Callable() {
+ @Override
+ public Long call() throws Exception {
+ long t0 = System.nanoTime();
+ checkConsistency(true);
+ return System.nanoTime() - t0;
+ }
+ });
+ executor.execute(consistencyOp);
+ }
+ return getConsistencyStatus();
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData getConsistencyStatus() {
+ // TODO include inconsistency information
+ return consistencyOp.getStatus().toCompositeData();
+ }
+
+ //------------------------------------------------------------< private >---
+
+ public void checkConsistency(boolean doFix) throws CommitFailedException {
+ long s = System.currentTimeMillis();
+
+ // create a new checkpoint with the current state
+ String checkpoint = store.checkpoint(DEFAULT_LIFETIME);
+ NodeState rootState = store.retrieve(checkpoint);
+ if (rootState == null) {
+ // unable to retrieve the checkpoint; use root state instead
+ rootState = store.getRoot();
+ }
+
+ ImmutableRoot root = new ImmutableRoot(rootState);
+
+ bitsProvider = new PrivilegeBitsProvider(root);
+ restrictionProvider = config.getRestrictionProvider();
+
+ permissionRoot = ((ImmutableTree) PermissionUtil.getPermissionsRoot(root, workspaceName)).getNodeState().builder();
+ permissionStore = new PermissionStoreImpl(root, workspaceName, restrictionProvider);
+
+ isACE = new TypePredicate(rootState, NT_REP_ACE);
+ isGrantACE = new TypePredicate(rootState, NT_REP_GRANT_ACE);
+
+ extraEntries = Lists.newArrayList();
+ missingEntries = Lists.newArrayList();
+
+ traverse(root.getTree("/"), permissionStore, restrictionProvider, doFix);
+ if (doFix && !extraEntries.isEmpty()) {
+ store.merge(permissionRoot, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ }
+
+ log.debug("Permission consistency check finished in {} ms.", System.currentTimeMillis() - s);
+ }
+
+ private void traverse(@Nonnull ImmutableTree tree, @Nonnull PermissionStore permissionStore,
+ @Nonnull RestrictionProvider restrictionProvider, boolean doFix) {
+ if (entering(tree, permissionStore, restrictionProvider, doFix)) {
+ for (Tree child : tree.getChildren()) {
+ traverse((ImmutableTree) child, permissionStore, restrictionProvider, doFix);
+ }
+ }
+ }
+
+ private boolean entering(@Nonnull ImmutableTree tree, @Nonnull PermissionStore permissionStore,
+ @Nonnull RestrictionProvider restrictionProvider, boolean doFix) {
+ if (config.getContext().definesContextRoot(tree)) {
+ return false;
+ }
+
+ if (tree.isRoot() && tree.hasChild(AccessControlConstants.REP_REPO_POLICY)) {
+ ImmutableTree policy = tree.getChild(AccessControlConstants.REP_REPO_POLICY);
+ verify("", policy, doFix);
+ }
+ if (tree.hasChild(AccessControlConstants.REP_POLICY)) {
+ ImmutableTree policy = tree.getChild(AccessControlConstants.REP_POLICY);
+ verify(tree.getPath(), policy, doFix);
+
+ }
+ return true;
+ }
+
+ private void verify(@Nonnull String accessControlledPath, @Nonnull ImmutableTree policyTree, boolean doFix) {
+ PermissionStoreEditor editor = new PermissionStoreEditor(accessControlledPath, policyTree.getName(), policyTree.getNodeState(), permissionRoot, isACE, isGrantACE, bitsProvider, restrictionProvider);
+
+ for (Map.Entry> mapEntry : editor.entries.entrySet()) {
+ String principalName = mapEntry.getKey();
+ Collection storeEntries = permissionStore.load(null, principalName, accessControlledPath);
+ Iterator acEntries = mapEntry.getValue().iterator();
+
+ while (acEntries.hasNext()) {
+ PermissionStoreEditor.AcEntry acEntry = acEntries.next();
+ if (!storeEntries.remove(acEntry.asPermissionEntry())) {
+ log.warn("Missing permission entry detected for ACE " + acEntry + ".");
+ missingEntries.add(acEntry.toString());
+ } else {
+ acEntries.remove();
+ }
+ }
+
+ // remove all remaining permission entries that have been obtained
+ // from the permission store as they have no correspondence in the ac content
+ for (PermissionEntry pe : storeEntries) {
+ log.warn("Inconsistent or superfluous permission entry detected in the permission store " + pe + ".");
+ if (doFix) {
+ editor.removePermissionEntry(principalName, pe);
+ }
+ // TODO add proper string representation of the pe
+ extraEntries.add(pe.toString());
+ }
+ }
+
+ // finally add all ac-entries for the various principals that are not
+ // contained in the permission store
+ if (doFix) {
+ editor.updatePermissionEntries();
+ }
+ }
+
+ private static boolean isAccessControlEntry(@Nonnull Tree tree) {
+ String ntName = TreeUtil.getPrimaryTypeName(tree);
+ return AccessControlConstants.NT_REP_GRANT_ACE.equals(ntName) || AccessControlConstants.NT_REP_DENY_ACE.equals(ntName);
+ }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java (revision d9af020286cb8db1804551f10ac18576bda0d01b)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/api/jmx/RepositoryManagementMBean.java (revision )
@@ -73,6 +73,7 @@
* status to invocation. {@code -1} is returned when not available.
*/
public interface RepositoryManagementMBean {
+
String TYPE = "RepositoryManagement";
/**
@@ -174,6 +175,39 @@
*/
@CheckForNull
String checkpoint(long lifetime);
+
+ /**
+ * Start the consistency analysis for the {@link org.apache.jackrabbit.oak.management.ConsistencyMBean}
+ * implementation with the specified {@code name}.
+ *
+ * @param name The name of the {@link org.apache.jackrabbit.oak.management.ConsistencyMBean}.
+ * @return the status of the operation right after it was initiated
+ * @see {@link org.apache.jackrabbit.oak.management.ConsistencyMBean#getName()}
+ */
+ @Nonnull
+ CompositeData analyseConsistency(@Nonnull String name);
+
+ /**
+ * Start the consistency fix for the {@link org.apache.jackrabbit.oak.management.ConsistencyMBean}
+ * implementation with the specified {@code name}.
+ *
+ * @param name The name of the {@link org.apache.jackrabbit.oak.management.ConsistencyMBean}.
+ * @return the status of the operation right after it was initiated
+ * @see {@link org.apache.jackrabbit.oak.management.ConsistencyMBean#getName()}
+ */
+ @Nonnull
+ CompositeData fixConsistency(@Nonnull String name);
+
+ /**
+ * Status of the consistency analisys.
+ *
+ * @param name The name of the {@link org.apache.jackrabbit.oak.management.ConsistencyMBean}.
+ * @return the status of the ongoing operation or if none the terminal
+ * status of the last operation or Status not available if none.
+ * @see {@link org.apache.jackrabbit.oak.management.ConsistencyMBean#getName()}
+ */
+ @Nonnull
+ CompositeData getConsistencyStatus(@Nonnull String name);
/**
* Initiate a reindex operation for the property indexes marked for
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistencyTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistencyTest.java (revision )
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/PermissionConsistencyTest.java (revision )
@@ -0,0 +1,51 @@
+/*
+ * 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.permission;
+
+import org.apache.jackrabbit.oak.AbstractSecurityTest;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * PermissionConsistencyTest... TODO
+ */
+public class PermissionConsistencyTest extends AbstractSecurityTest {
+
+ /**
+ * logger instance
+ */
+ private static final Logger log = LoggerFactory.getLogger(PermissionConsistencyTest.class);
+
+
+ public void testMissingPermissionEntry() {
+ // TODO
+ }
+
+ public void testInconsistentPermissionEntry() {
+ // TODO
+ }
+
+ public void testExtraPermissionEntry() {
+ // TODO
+ }
+
+ public void testConsistentPermissionEntries() {
+ // TODO
+ }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java (revision d9af020286cb8db1804551f10ac18576bda0d01b)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/management/RepositoryManager.java (revision )
@@ -59,9 +59,20 @@
}
private Status execute(Class serviceType, Function operation) {
+ return execute(serviceType, null, operation);
+ }
+
+ private Status execute(Class serviceType, String name, Function operation) {
Tracker tracker = whiteboard.track(serviceType);
try {
List services = tracker.getServices();
+ for (T service : tracker.getServices()) {
+ if (name == null
+ || (service instanceof NamedOperation) && name.equals(((NamedOperation) service).getName())) {
+ services.add(service);
+ }
+ }
+
if (services.size() == 1) {
return operation.apply(services.get(0));
} else if (services.isEmpty()) {
@@ -204,8 +215,44 @@
public Status apply(PropertyIndexAsyncReindexMBean reindexer) {
return fromCompositeData(reindexer
.getPropertyIndexAsyncReindexStatus());
+ }
+ }).toCompositeData();
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData analyseConsistency(@Nonnull String name) {
+ return execute(ConsistencyMBean.class, name, new Function() {
+ @Nonnull
+ @Override
+ public Status apply(ConsistencyMBean consistencyMBean) {
+ return fromCompositeData(consistencyMBean.analyseConsistency());
+ }
+ }).toCompositeData();
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData fixConsistency(@Nonnull String name) {
+ return execute(ConsistencyMBean.class, name, new Function() {
+ @Nonnull
+ @Override
+ public Status apply(ConsistencyMBean consistencyMBean) {
+ return fromCompositeData(consistencyMBean.fixConsistency());
+ }
+ }).toCompositeData();
+ }
+
+ @Nonnull
+ @Override
+ public CompositeData getConsistencyStatus(@Nonnull String name) {
+ return execute(ConsistencyMBean.class, name, new Function() {
+ @Nonnull
+ @Override
+ public Status apply(ConsistencyMBean consistencyMBean) {
+ return fromCompositeData(consistencyMBean.getConsistencyStatus());
- }
- }).toCompositeData();
+ }
+ }).toCompositeData();
}
}