Author: angela Date: Wed Oct 31 13:40:15 2012 New Revision: 1404136 URL: http://svn.apache.org/viewvc?rev=1404136&view=rev Log: OAK-50 : Implement User Management (WIP)
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableBaseProvider.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtility.java Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableBaseProvider.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableBaseProvider.java?rev=1404136&r1=1404135&r2=1404136&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableBaseProvider.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/AuthorizableBaseProvider.java Wed Oct 31 13:40:15 2012 @@ -57,11 +57,11 @@ abstract class AuthorizableBaseProvider } } - String getContentID(String authorizableId) { - return IdentifierManager.generateUUID(authorizableId.toLowerCase()); - } - String getContentID(Tree authorizableTree) { return identifierManager.getIdentifier(authorizableTree); } + + static String getContentID(String authorizableId) { + return IdentifierManager.generateUUID(authorizableId.toLowerCase()); + } } \ No newline at end of file Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java?rev=1404136&r1=1404135&r2=1404136&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserProvider.java Wed Oct 31 13:40:15 2012 @@ -160,29 +160,28 @@ class UserProvider extends AuthorizableB userPath = config.getConfigValue(PARAM_USER_PATH, DEFAULT_USER_PATH); } - //-------------------------------------------------------< UserProvider >--- @Nonnull - public Tree createUser(String userID, String intermediateJcrPath) throws RepositoryException { + Tree createUser(String userID, String intermediateJcrPath) throws RepositoryException { return createAuthorizableNode(userID, false, intermediateJcrPath); } @Nonnull - public Tree createGroup(String groupID, String intermediateJcrPath) throws RepositoryException { + Tree createGroup(String groupID, String intermediateJcrPath) throws RepositoryException { return createAuthorizableNode(groupID, true, intermediateJcrPath); } @CheckForNull - public Tree getAuthorizable(String authorizableId) { + Tree getAuthorizable(String authorizableId) { return getByID(authorizableId, AuthorizableType.AUTHORIZABLE); } @CheckForNull - public Tree getAuthorizableByPath(String authorizableOakPath) { + Tree getAuthorizableByPath(String authorizableOakPath) { return getByPath(authorizableOakPath); } @CheckForNull - public Tree getAuthorizableByPrincipal(Principal principal) { + Tree getAuthorizableByPrincipal(Principal principal) { if (principal instanceof TreeBasedPrincipal) { return root.getTree(((TreeBasedPrincipal) principal).getOakPath()); } @@ -213,7 +212,7 @@ class UserProvider extends AuthorizableB } @CheckForNull - public String getAuthorizableId(Tree authorizableTree) { + static String getAuthorizableId(Tree authorizableTree) { checkNotNull(authorizableTree); if (UserUtility.isType(authorizableTree, AuthorizableType.AUTHORIZABLE)) { PropertyState idProp = authorizableTree.getProperty(UserConstants.REP_AUTHORIZABLE_ID); Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java?rev=1404136&r1=1404135&r2=1404136&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserValidator.java Wed Oct 31 13:40:15 2012 @@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.securi import javax.jcr.nodetype.ConstraintViolationException; +import org.apache.jackrabbit.JcrConstants; import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; @@ -32,7 +33,10 @@ import org.apache.jackrabbit.oak.util.No import org.apache.jackrabbit.util.Text; /** - * UserValidator... TODO + * Validator that enforces user management specific constraints. Please note that + * is this validator is making implementation specific assumptions; if the + * user management implementation is replace it is most probably necessary to + * provide a custom validator as well. */ class UserValidator extends DefaultValidator implements UserConstants { @@ -52,24 +56,38 @@ class UserValidator extends DefaultValid @Override public void propertyAdded(PropertyState after) throws CommitFailedException { + if (!isAuthorizable(parentAfter)) { + return; + } + String name = after.getName(); if (REP_DISABLED.equals(name) && isAdminUser(parentAfter)) { String msg = "Admin user cannot be disabled."; fail(msg); } + + if (JcrConstants.JCR_UUID.equals(name) && !isValidUUID(after.getValue(Type.STRING))) { + String msg = "Invalid jcr:uuid for authorizable " + parentAfter.getName(); + fail(msg); + } } @Override public void propertyChanged(PropertyState before, PropertyState after) throws CommitFailedException { + if (!isAuthorizable(parentAfter)) { + return; + } + String name = before.getName(); - if (UserUtility.isAuthorizableTree(parentBefore.getTree()) && (REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name))) { + if (REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name)) { String msg = "Authorizable property " + name + " may not be altered after user/group creation."; fail(msg); + } else if (JcrConstants.JCR_UUID.equals(name) && !isValidUUID(after.getValue(Type.STRING))) { + String msg = "Invalid jcr:uuid for authorizable " + parentAfter.getName(); + fail(msg); } - if (UserUtility.isType(parentBefore.getTree(), AuthorizableType.USER) - && REP_PASSWORD.equals(name) - && PasswordUtility.isPlainTextPassword(after.getValue(Type.STRING))) { + if (isUser(parentBefore) && REP_PASSWORD.equals(name) && PasswordUtility.isPlainTextPassword(after.getValue(Type.STRING))) { String msg = "Password may not be plain text."; fail(msg); } @@ -78,9 +96,12 @@ class UserValidator extends DefaultValid @Override public void propertyDeleted(PropertyState before) throws CommitFailedException { + if (!isAuthorizable(parentAfter)) { + return; + } + String name = before.getName(); - if (UserUtility.isAuthorizableTree(parentBefore.getTree()) - && (REP_PASSWORD.equals(name) || REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name))) { + if (REP_PASSWORD.equals(name) || REP_PRINCIPAL_NAME.equals(name) || REP_AUTHORIZABLE_ID.equals(name)) { String msg = "Authorizable property " + name + " may not be removed."; fail(msg); } @@ -148,13 +169,28 @@ class UserValidator extends DefaultValid } } + // FIXME: copied from UserProvider#isAdminUser private boolean isAdminUser(NodeUtil userNode) { String id = (userNode.getString(REP_AUTHORIZABLE_ID, Text.unescapeIllegalJcrChars(userNode.getName()))); - return UserUtility.isType(userNode.getTree(), AuthorizableType.USER) && - UserUtility.getAdminId(provider.getConfig()).equals(id); + return isUser(userNode) && UserUtility.getAdminId(provider.getConfig()).equals(id); + } + + private boolean isValidUUID(String uuid) { + String id = UserProvider.getAuthorizableId(parentAfter.getTree()); + return uuid.equals(UserProvider.getContentID(id)); + } + + private static boolean isAuthorizable(NodeUtil node) { + return UserUtility.isType(node.getTree(), AuthorizableType.AUTHORIZABLE); } + private static boolean isUser(NodeUtil node) { + return UserUtility.isType(node.getTree(), AuthorizableType.USER); + } + + + private static void fail(String msg) throws CommitFailedException { Exception e = new ConstraintViolationException(msg); throw new CommitFailedException(e); Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtility.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtility.java?rev=1404136&r1=1404135&r2=1404136&view=diff ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtility.java (original) +++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/user/util/UserUtility.java Wed Oct 31 13:40:15 2012 @@ -41,10 +41,6 @@ public final class UserUtility implement return parameters.getConfigValue(PARAM_ANONYMOUS_ID, DEFAULT_ANONYMOUS_ID); } - public static boolean isAuthorizableTree(Tree authorizableTree) { - return isType(authorizableTree, AuthorizableType.AUTHORIZABLE); - } - public static boolean isType(Tree authorizableTree, AuthorizableType type) { // FIXME: check for node type according to the specified type constraint if (authorizableTree != null && authorizableTree.hasProperty(JcrConstants.JCR_PRIMARYTYPE)) { Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java?rev=1404136&view=auto ============================================================================== --- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java (added) +++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserValidatorTest.java Wed Oct 31 13:40:15 2012 @@ -0,0 +1,286 @@ +/* + * 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.user; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.oak.api.CommitFailedException; +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.security.AbstractSecurityTest; +import org.apache.jackrabbit.oak.spi.security.user.UserConstants; +import org.apache.jackrabbit.util.Text; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.fail; + +/** + * UserValidatorTest + */ +public class UserValidatorTest extends AbstractSecurityTest { + + private Root root; + private UserManagerImpl userMgr; + private User user; + + @Before + public void before() throws Exception { + super.before(); + + root = admin.getLatestRoot(); + userMgr = new UserManagerImpl(null, root, NamePathMapper.DEFAULT, getSecurityProvider()); + user = userMgr.createUser("test", "pw"); + root.commit(); + } + + @After + public void after() throws Exception { + try { + Authorizable a = userMgr.getAuthorizable("test"); + if (a != null) { + a.remove(); + root.commit(); + } + } finally { + super.after(); + } + } + + @Test + public void removePassword() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.removeProperty(UserConstants.REP_PASSWORD); + root.commit(); + fail("removing password should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void removePrincipalName() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.removeProperty(UserConstants.REP_PRINCIPAL_NAME); + root.commit(); + fail("removing principal name should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void removeAuthorizableId() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.removeProperty(UserConstants.REP_AUTHORIZABLE_ID); + root.commit(); + fail("removing authorizable id should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void createWithoutPrincipalName() throws Exception { + try { + User user = userMgr.createUser("withoutPrincipalName", "pw"); + // TODO: use user.getPath instead (blocked by OAK-343) + Tree tree = root.getTree("/rep:security/rep:authorizables/rep:users/t/te/test"); + tree.removeProperty(UserConstants.REP_PRINCIPAL_NAME); + root.commit(); + + fail("creating user with invalid jcr:uuid should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void createWithInvalidUUID() throws Exception { + try { + User user = userMgr.createUser("withInvalidUUID", "pw"); + // TODO: use user.getPath instead (blocked by OAK-343) + Tree tree = root.getTree("/rep:security/rep:authorizables/rep:users/t/te/test"); + tree.setProperty(JcrConstants.JCR_UUID, UUID.randomUUID().toString()); + root.commit(); + + fail("creating user with invalid jcr:uuid should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void changeUUID() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.setProperty(JcrConstants.JCR_UUID, UUID.randomUUID().toString()); + root.commit(); + fail("changing jcr:uuid should fail if it the uuid valid is invalid"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void changePrincipalName() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.setProperty(UserConstants.REP_PRINCIPAL_NAME, "another"); + root.commit(); + fail("changing the principal name should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void changeAuthorizableId() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.setProperty(UserConstants.REP_AUTHORIZABLE_ID, "modified"); + root.commit(); + fail("changing the authorizable id should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void changePasswordToPlainText() throws Exception { + try { + Tree userTree = root.getTree(user.getPath()); + userTree.setProperty(UserConstants.REP_PASSWORD, "plaintext"); + root.commit(); + fail("storing a plaintext password should fail"); + } catch (CommitFailedException e) { + // expected + } finally { + root.refresh(); + } + } + + @Test + public void testRemoveAdminUser() throws Exception { + try { + String adminId = userMgr.getConfig().getConfigValue(UserConstants.PARAM_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID); + Authorizable admin = userMgr.getAuthorizable(adminId); + if (admin == null) { + admin = userMgr.createUser(adminId, adminId); + root.commit(); + } + + root.getTree(admin.getPath()).remove(); + root.commit(); + fail("Admin user cannot be removed"); + } catch (CommitFailedException e) { + // success + } finally { + root.refresh(); + } + } + + @Test + public void testDisableAdminUser() throws Exception { + try { + String adminId = userMgr.getConfig().getConfigValue(UserConstants.PARAM_ADMIN_ID, UserConstants.DEFAULT_ADMIN_ID); + Authorizable admin = userMgr.getAuthorizable(adminId); + if (admin == null) { + admin = userMgr.createUser(adminId, adminId); + root.commit(); + } + + root.getTree(admin.getPath()).setProperty(UserConstants.REP_DISABLED, "disabled"); + root.commit(); + fail("Admin user cannot be disabled"); + } catch (CommitFailedException e) { + // success + } finally { + root.refresh(); + } + } + + @Test + public void testEnforceHierarchy() throws RepositoryException, CommitFailedException { + List<String> invalid = new ArrayList<String>(); + invalid.add("/"); + invalid.add("/jcr:system"); + String groupPath = userMgr.getConfig().getConfigValue(UserConstants.PARAM_GROUP_PATH, UserConstants.DEFAULT_GROUP_PATH); + invalid.add(groupPath); + String userPath = userMgr.getConfig().getConfigValue(UserConstants.PARAM_USER_PATH, UserConstants.DEFAULT_USER_PATH); + invalid.add(Text.getRelativeParent(userPath, 1)); + invalid.add(user.getPath()); + invalid.add(user.getPath() + "/folder"); + + for (String path : invalid) { + try { + Tree parent = root.getTree(path); + if (parent == null) { + String[] segments = Text.explode(path, '/', false); + parent = root.getTree("/"); + for (String segment : segments) { + Tree next = parent.getChild(segment); + if (next == null) { + next = parent.addChild(segment); + next.setProperty(JcrConstants.JCR_PRIMARYTYPE, UserConstants.NT_REP_AUTHORIZABLE_FOLDER); + parent = next; + } + } + } + Tree userTree = parent.addChild("testUser"); + userTree.setProperty(JcrConstants.JCR_PRIMARYTYPE, UserConstants.NT_REP_USER); + userTree.setProperty(JcrConstants.JCR_UUID, UserProvider.getContentID("testUser")); + userTree.setProperty(UserConstants.REP_PRINCIPAL_NAME, "testUser"); + root.commit(); + fail("Invalid hierarchy should be detected"); + + } catch (CommitFailedException e) { + // success + } finally { + root.refresh(); + } + } + } + +} \ No newline at end of file