You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by an...@apache.org on 2012/10/31 14:40:15 UTC

svn commit: r1404136 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/security/user/ main/java/org/apache/jackrabbit/oak/spi/security/user/util/ test/java/org/apache/jackrabbit/oak/security/user/

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