diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountUtils.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountUtils.java index 9c65bd80eb..98b921ad5e 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountUtils.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authorization/permission/MountUtils.java @@ -24,11 +24,11 @@ import org.apache.jackrabbit.oak.spi.security.authorization.AuthorizationConfigu import org.jetbrains.annotations.NotNull; import org.junit.Assert; -final class MountUtils { +public final class MountUtils { private MountUtils() {} - static AuthorizationConfiguration bindMountInfoProvider(@NotNull SecurityProvider securityProvider, @NotNull MountInfoProvider mountInfoProvider) { + public static AuthorizationConfiguration bindMountInfoProvider(@NotNull SecurityProvider securityProvider, @NotNull MountInfoProvider mountInfoProvider) { AuthorizationConfiguration acConfig = securityProvider.getConfiguration(AuthorizationConfiguration.class); Assert.assertTrue(acConfig instanceof CompositeAuthorizationConfiguration); ((AuthorizationConfigurationImpl) ((CompositeAuthorizationConfiguration) acConfig).getDefaultConfig()) diff --git a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/ImmutableServiceUserTest.java b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/ImmutableServiceUserTest.java new file mode 100644 index 0000000000..da5471914d --- /dev/null +++ b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/security/user/ImmutableServiceUserTest.java @@ -0,0 +1,180 @@ +/* + * 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.jcr.security.user; + +import com.google.common.collect.Lists; +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.oak.Oak; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.composite.CompositeNodeStore; +import org.apache.jackrabbit.oak.composite.CrossMountReferenceValidatorProvider; +import org.apache.jackrabbit.oak.composite.InitialContentMigrator; +import org.apache.jackrabbit.oak.composite.checks.MountedNodeStoreChecker; +import org.apache.jackrabbit.oak.composite.checks.NamespacePrefixNodestoreChecker; +import org.apache.jackrabbit.oak.composite.checks.NodeStoreChecksService; +import org.apache.jackrabbit.oak.composite.checks.NodeTypeDefinitionNodeStoreChecker; +import org.apache.jackrabbit.oak.composite.checks.NodeTypeMountedNodeStoreChecker; +import org.apache.jackrabbit.oak.composite.checks.UniqueIndexNodeStoreChecker; +import org.apache.jackrabbit.oak.jcr.Jcr; +import org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl; +import org.apache.jackrabbit.oak.plugins.document.bundlor.BundlingConfigInitializer; +import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore; +import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.security.authorization.permission.MountUtils; +import org.apache.jackrabbit.oak.security.internal.SecurityProviderBuilder; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; +import org.apache.jackrabbit.oak.spi.security.SecurityProvider; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.jackrabbit.oak.spi.state.NodeStore; +import org.junit.Test; + +import javax.jcr.ReferentialIntegrityException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.SimpleCredentials; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class ImmutableServiceUserTest { + + private static final String USER_ID = "my-immutable-system-user"; + + private static final MountInfoProvider MOUNT_INFO_PROVIDER = Mounts.newBuilder().mount("libs", + true, Lists.newArrayList("/oak:index/*$"), + Lists.newArrayList("/libs", + "/rep:security/rep:authorizables/rep:users/system/immutable", + "/jcr:system/rep:permissionStore/oak:mount-libs-crx.default") + ).build(); + + @Test + public void testServiceUserIsPartOfTheMount() throws Exception { + runInCompositeNodeStore((session, um) -> { + Authorizable auth = um.getAuthorizable(USER_ID); + assertEquals("libs", MOUNT_INFO_PROVIDER.getMountByPath(auth.getPath()).getName()); + }); + } + + @Test (expected = UnsupportedOperationException.class) + public void testServiceUserCantBeDisabled() throws Exception { + runInCompositeNodeStore((session, um) -> { + User user = (User) um.getAuthorizable(USER_ID); + user.disable("gone"); + }); + } + + @Test (expected = ReferentialIntegrityException.class) + public void testServiceUserCantBeAddedToGroupInCompositeSetup() throws Exception { + runInCompositeNodeStore((session, um) -> { + User user = (User) um.getAuthorizable(USER_ID); + Group group = um.createGroup("my-group"); + group.addMember(user); + session.save(); + }); + } + + @Test (expected = ReferentialIntegrityException.class) + public void testServiceUserCantBeAddedToGroup() throws Exception { + runInNodeStore(createImmutableNodeStore(), (session, um) -> { + User user = (User) um.getAuthorizable(USER_ID); + Group group = (Group) um.getAuthorizable("my-group"); + group.addMember(user); + session.save(); + }); + } + + private static void runInCompositeNodeStore(UserTest test) throws RepositoryException, IOException, CommitFailedException { + runInNodeStore(createCompositeNodeStore(), test); + } + + private static void runInNodeStore(NodeStore nodeStore, UserTest test) throws RepositoryException { + RepositoryImpl repo = createRepository(nodeStore); + try { + JackrabbitSession session = getAdminSession(repo); + try { + test.doTest(session, session.getUserManager()); + } finally { + session.logout(); + } + } finally { + repo.shutdown(); + } + } + + private interface UserTest { + void doTest(JackrabbitSession session, UserManager um) throws RepositoryException; + } + + private static NodeStore createImmutableNodeStore() throws RepositoryException { + MemoryNodeStore seed = new MemoryNodeStore(); + RepositoryImpl repo = createRepository(seed); + JackrabbitSession session = (JackrabbitSession) repo.login(new SimpleCredentials(UserConstants.DEFAULT_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID.toCharArray())); + try { + UserManager um = session.getUserManager(); + um.createSystemUser("my-system-user", "system/immutable"); + session.save(); + } finally { + session.logout(); + } + repo.shutdown(); + return seed; + } + + private static NodeStore createCompositeNodeStore() throws RepositoryException, IOException, CommitFailedException { + NodeStore global = new MemoryNodeStore(); + NodeStore seed = createImmutableNodeStore(); + + new InitialContentMigrator(global, seed, MOUNT_INFO_PROVIDER.getMountByName("libs")).migrate(); + + List> checkerList = new ArrayList<>(); + checkerList.add(new NamespacePrefixNodestoreChecker()); + checkerList.add(new NodeTypeDefinitionNodeStoreChecker()); + checkerList.add(new NodeTypeMountedNodeStoreChecker()); + checkerList.add(new UniqueIndexNodeStoreChecker()); + + return new CompositeNodeStore.Builder(MOUNT_INFO_PROVIDER, global) + .addMount("libs", seed) + .with(new NodeStoreChecksService(MOUNT_INFO_PROVIDER, checkerList)) + .build(); + } + + private static RepositoryImpl createRepository(NodeStore nodeStore) { + Oak oak = new Oak(nodeStore); + Jcr jcr = new Jcr(oak); + QueryEngineSettings qs = new QueryEngineSettings(); + qs.setFullTextComparisonWithoutIndex(true); + jcr.with(BundlingConfigInitializer.INSTANCE); + jcr.withAsyncIndexing().with(qs); + + SecurityProvider sp = SecurityProviderBuilder.newBuilder().build(); + MountUtils.bindMountInfoProvider(sp, MOUNT_INFO_PROVIDER); + jcr.with(sp); + jcr.with(new CrossMountReferenceValidatorProvider(MOUNT_INFO_PROVIDER, true)); + return (RepositoryImpl) jcr.createRepository(); + } + + private static JackrabbitSession getAdminSession(Repository repository) throws RepositoryException { + return (JackrabbitSession) repository.login(new SimpleCredentials(UserConstants.DEFAULT_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID.toCharArray())); + } +}