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/04/13 10:42:36 UTC

svn commit: r1325655 - in /jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr: ./ security/principal/ security/user/ util/

Author: angela
Date: Fri Apr 13 08:42:36 2012
New Revision: 1325655

URL: http://svn.apache.org/viewvc?rev=1325655&view=rev
Log:
OAK-50 - Implement User Management  (WIP)

- re-adding ValueConverter#toScalar(String, int)
- making NodeImpl#getEditor public

Added:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/PrincipalIteratorAdapter.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableNodeCreator.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ImpersonationImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ItemBasedPrincipalImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/PasswordUtility.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerConfig.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/diff.txt
Modified:
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
    jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java?rev=1325655&r1=1325654&r2=1325655&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/NodeImpl.java Fri Apr 13 08:42:36 2012
@@ -582,7 +582,19 @@ public class NodeImpl extends ItemImpl i
     public boolean isNodeType(String nodeTypeName) throws RepositoryException {
         checkStatus();
 
-        // TODO
+        if (getProperty(JcrConstants.JCR_PRIMARYTYPE).getString().equals(nodeTypeName)) {
+            return true;
+        }
+        if (hasProperty(JcrConstants.JCR_MIXINTYPES)) {
+            Value[] mixins = getProperty(JcrConstants.JCR_MIXINTYPES).getValues();
+            for (Value mixin : mixins) {
+                if (mixin.getString().equals(nodeTypeName)) {
+                    return true;
+                }
+            }
+        }
+
+        // TODO evaluate effective node type
         return false;
     }
 
@@ -815,6 +827,17 @@ public class NodeImpl extends ItemImpl i
 
     }
 
+    //--------------------------------------------------------------------------
+    /**
+     * Access to KernelNodeStateEditor to allow code in other packages to
+     * access item states.
+     *
+     * @return The node state editor.
+     */
+    public NodeStateEditor getEditor() {
+        return getNodeState().getEditor();
+    }
+
     //------------------------------------------------------------< private >---
     /**
      * Shortcut to retrieve the version manager from the workspace associated
@@ -846,10 +869,6 @@ public class NodeImpl extends ItemImpl i
         return nodeState = getItemStateProvider().getNodeState(nodeState.getPath());
     }
 
-    private NodeStateEditor getEditor() {
-        return getNodeState().getEditor();
-    }
-
     private String path() {
         return getNodeState().getPath();
     }

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/PrincipalIteratorAdapter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/PrincipalIteratorAdapter.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/PrincipalIteratorAdapter.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/principal/PrincipalIteratorAdapter.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,70 @@
+/*
+ * 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.principal;
+
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
+import org.apache.jackrabbit.commons.iterator.RangeIteratorDecorator;
+
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * PrincipalIteratorAdapter...
+ *
+ * TODO: move to jackrabbit-jcr-commons
+ */
+public class PrincipalIteratorAdapter extends RangeIteratorDecorator
+        implements PrincipalIterator {
+
+    /**
+     * Static instance of an empty {@link PrincipalIterator}.
+     */
+    public static final PrincipalIteratorAdapter EMPTY = new PrincipalIteratorAdapter(RangeIteratorAdapter.EMPTY);
+
+
+    /**
+     * Creates an adapter for the given {@link java.util.Iterator} of principals.
+     *
+     * @param iterator iterator of {@link java.security.Principal}s
+     */
+    public PrincipalIteratorAdapter(Iterator<? extends Principal> iterator) {
+        super(new RangeIteratorAdapter(iterator));
+    }
+
+    /**
+     * Creates an iterator for the given collection of <code>Principal</code>s.
+     *
+     * @param collection collection of {@link Principal} objects.
+     */
+    public PrincipalIteratorAdapter(Collection<? extends Principal> collection) {
+        super(new RangeIteratorAdapter(collection));
+    }
+
+    //----------------------------------------< AccessControlPolicyIterator >---
+    /**
+     * Returns the next policy.
+     *
+     * @return next policy.
+     * @throws java.util.NoSuchElementException if there is no next policy.
+     */
+    public Principal nextPrincipal() throws NoSuchElementException {
+        return (Principal) next();
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,315 @@
+/*
+ * 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 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.commons.JcrUtils;
+import org.apache.jackrabbit.oak.jcr.NodeImpl;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.nodetype.PropertyDefinition;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * AuthorizableImpl...
+ */
+abstract class AuthorizableImpl implements Authorizable {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(AuthorizableImpl.class);
+
+    static final String NT_REP_AUTHORIZABLE = "rep:Authorizable";
+    static final String NT_REP_USER = "rep:User";
+    static final String NT_REP_GROUP = "rep:Group";
+    static final String REP_PRINCIPAL_NAME = "rep:principalName";
+    static final String REP_PASSWORD = "rep:password";
+    static final String REP_DISABLED = "rep:disabled";
+    static final String REP_MEMBERS = "rep:members";
+    static final String REP_IMPERSONATORS = "rep:impersonators";
+
+    private final NodeImpl node;
+    private final UserManagerImpl userManager;
+
+    private int hashCode;
+
+    AuthorizableImpl(NodeImpl node, UserManagerImpl userManager) {
+        this.node = node;
+        this.userManager = userManager;
+    }
+
+    //-------------------------------------------------------< Authorizable >---
+    @Override
+    public String getID() throws RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public Iterator<Group> declaredMemberOf() throws RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public Iterator<Group> memberOf() throws RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public void remove() throws RepositoryException {
+        // don't allow for removal of the administrator even if the executing
+        // session has all permissions.
+        if (!isGroup() && ((User) this).isAdmin()) {
+            throw new RepositoryException("The administrator cannot be removed.");
+        }
+        Session s = node.getSession();
+        userManager.onRemove(this);
+        node.remove();
+    }
+
+    @Override
+    public Iterator<String> getPropertyNames() throws RepositoryException {
+        return getPropertyNames(".");
+    }
+
+    @Override
+    public Iterator<String> getPropertyNames(String relPath) throws RepositoryException {
+        Node n = node.getNode(relPath);
+        if (Text.isDescendantOrEqual(node.getPath(), n.getPath())) {
+            List<String> l = new ArrayList<String>();
+            for (PropertyIterator it = n.getProperties(); it.hasNext();) {
+                Property prop = it.nextProperty();
+                if (isAuthorizableProperty(prop, false)) {
+                    l.add(prop.getName());
+                }
+            }
+            return l.iterator();
+        } else {
+            throw new IllegalArgumentException("Relative path " + relPath + " refers to items outside of scope of authorizable " + getID());
+        }
+    }
+
+    @Override
+    public boolean hasProperty(String relPath) throws RepositoryException {
+        return node.hasProperty(relPath) && isAuthorizableProperty(node.getProperty(relPath), true);
+    }
+
+    @Override
+    public Value[] getProperty(String relPath) throws RepositoryException {
+        Value[] values = null;
+        if (node.hasProperty(relPath)) {
+            Property prop = node.getProperty(relPath);
+            if (isAuthorizableProperty(prop, true)) {
+                if (prop.isMultiple()) {
+                    values = prop.getValues();
+                } else {
+                    values = new Value[]{prop.getValue()};
+                }
+            }
+        }
+        return values;
+    }
+
+    @Override
+    public void setProperty(String relPath, Value value) throws RepositoryException {
+        String name = Text.getName(relPath);
+        String intermediate = (relPath.equals(name)) ? null : Text.getRelativeParent(relPath, 1);
+
+        Node n = getOrCreateTargetNode(intermediate);
+        // check if the property has already been created as multi valued
+        // property before -> in this case remove in order to avoid
+        // ValueFormatException.
+        if (n.hasProperty(name)) {
+            Property p = n.getProperty(name);
+            if (p.isMultiple()) {
+                p.remove();
+            }
+        }
+        n.setProperty(name, value);
+    }
+
+    @Override
+    public void setProperty(String relPath, Value[] values) throws RepositoryException {
+        String name = Text.getName(relPath);
+        String intermediate = (relPath.equals(name)) ? null : Text.getRelativeParent(relPath, 1);
+
+        Node n = getOrCreateTargetNode(intermediate);
+        // check if the property has already been created as single valued
+        // property before -> in this case remove in order to avoid
+        // ValueFormatException.
+        if (n.hasProperty(name)) {
+            Property p = n.getProperty(name);
+            if (!p.isMultiple()) {
+                p.remove();
+            }
+        }
+        n.setProperty(name, values);
+    }
+
+    @Override
+    public boolean removeProperty(String relPath) throws RepositoryException {
+        String name = Text.getName(relPath);
+
+        if (node.hasProperty(relPath)) {
+            Property p = node.getProperty(relPath);
+            if (isAuthorizableProperty(p, true)) {
+                p.remove();
+                return true;
+            }
+        }
+        // no such property or wasn't a property of this authorizable.
+        return false;
+    }
+
+    @Override
+    public String getPath() throws UnsupportedRepositoryOperationException, RepositoryException {
+        return node.getPath();
+    }
+
+    //-------------------------------------------------------------< Object >---
+    @Override
+    public int hashCode() {
+        if (hashCode == 0) {
+            try {
+                StringBuilder sb = new StringBuilder();
+                sb.append(isGroup() ? "group:" : "user:");
+                sb.append(node.getSession().getWorkspace().getName());
+                sb.append(':');
+                sb.append(node.getIdentifier());
+                hashCode = sb.toString().hashCode();
+            } catch (RepositoryException e) {
+            }
+        }
+        return hashCode;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof AuthorizableImpl) {
+            AuthorizableImpl otherAuth = (AuthorizableImpl) obj;
+            try {
+                return isGroup() == otherAuth.isGroup() && node.isSame(otherAuth.node);
+            } catch (RepositoryException e) {
+                // should not occur -> return false in this case.
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        try {
+            String typeStr = (isGroup()) ? "Group '" : "User '";
+            return new StringBuilder().append(typeStr).append(getID()).append('\'').toString();
+        } catch (RepositoryException e) {
+            return super.toString();
+        }
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * @return node The node associated with this authorizable instance.
+     */
+    NodeImpl getNode() {
+        return node;
+    }
+
+    UserManagerImpl getUserManager() {
+        return userManager;
+    }
+
+    String getPrincipalName() throws RepositoryException {
+        String principalName;
+        if (node.hasProperty(REP_PRINCIPAL_NAME)) {
+            principalName = node.getProperty(REP_PRINCIPAL_NAME).getString();
+        } else {
+            log.debug("Authorizable without principal name -> using ID as fallback.");
+            principalName = getID();
+        }
+        return principalName;
+    }
+
+    /**
+     * Returns true if the given property of the authorizable node is one of the
+     * non-protected properties defined by the rep:Authorizable node type or a
+     * some other descendant of the authorizable node.
+     *
+     * @param prop Property to be tested.
+     * @param verifyAncestor If true the property is tested to be a descendant
+     * of the node of this authorizable; otherwise it is expected that this
+     * test has been executed by the caller.
+     * @return {@code true} if the given property is defined
+     * by the rep:authorizable node type or one of it's sub-node types;
+     * {@code false} otherwise.
+     * @throws RepositoryException If the property definition cannot be retrieved.
+     */
+    private boolean isAuthorizableProperty(Property prop, boolean verifyAncestor) throws RepositoryException {
+        if (verifyAncestor && !Text.isDescendant(node.getPath(), prop.getPath())) {
+            log.debug("Attempt to access property outside of authorizable scope.");
+            return false;
+        }
+
+        PropertyDefinition def = prop.getDefinition();
+        if (def.isProtected()) {
+            return false;
+        } else if (node.isSame(prop.getParent())) {
+            NodeType declaringNt = prop.getDefinition().getDeclaringNodeType();
+            return declaringNt.isNodeType(NT_REP_AUTHORIZABLE);
+        } else {
+            // another non-protected property somewhere in the subtree of this
+            // authorizable node -> is a property that can be set using #setProperty.
+            return true;
+        }
+    }
+
+    /**
+     *
+     * @param relPath A relative path.
+     * @return The corresponding node.
+     * @throws RepositoryException If an error occurs.
+     */
+    private Node getOrCreateTargetNode(String relPath) throws RepositoryException {
+        Node n;
+        if (relPath != null) {
+            n = JcrUtils.getOrCreateByPath(node, relPath, false, null, null, false);
+            if (!Text.isDescendantOrEqual(node.getPath(), n.getPath())) {
+                node.refresh(false);
+                throw new RepositoryException("Relative path " + relPath + " outside of scope of " + this);
+            }
+        } else {
+            n = node;
+        }
+        return n;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableNodeCreator.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableNodeCreator.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableNodeCreator.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/AuthorizableNodeCreator.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,47 @@
+/*
+ * 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 org.apache.jackrabbit.oak.jcr.NodeImpl;
+import org.apache.jackrabbit.oak.jcr.SessionContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * AuthorizableNodeCreator...
+ */
+class AuthorizableNodeCreator {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(AuthorizableNodeCreator.class);
+
+    AuthorizableNodeCreator(SessionContext sessionContext) {
+        // TODO
+    }
+
+    NodeImpl createUserNode(String userID, String intermediatePath) {
+        // TODO
+        return null;
+    }
+
+    NodeImpl createGroupNode(String groupID, String intermediatePath) {
+        // TODO
+        return null;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/GroupImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,125 @@
+/*
+ * 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 org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.oak.jcr.NodeImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+/**
+ * GroupImpl...
+ */
+public class GroupImpl extends AuthorizableImpl implements Group {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(GroupImpl.class);
+
+    public GroupImpl(NodeImpl node, UserManagerImpl userManager) {
+        super(node, userManager);
+    }
+
+    //-------------------------------------------------------< Authorizable >---
+    @Override
+    public boolean isGroup() {
+        return true;
+    }
+
+    @Override
+    public Principal getPrincipal() throws RepositoryException {
+        return new GroupPrincipal(getPrincipalName(), getNode().getPath());
+    }
+
+    //--------------------------------------------------------------< Group >---
+
+    @Override
+    public Iterator<Authorizable> getDeclaredMembers() throws RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public Iterator<Authorizable> getMembers() throws RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public boolean isDeclaredMember(Authorizable authorizable) throws RepositoryException {
+        // TODO
+        return false;
+    }
+
+    @Override
+    public boolean isMember(Authorizable authorizable) throws RepositoryException {
+        // TODO
+        return false;
+    }
+
+    @Override
+    public boolean addMember(Authorizable authorizable) throws RepositoryException {
+        // TODO
+        return false;
+    }
+
+    @Override
+    public boolean removeMember(Authorizable authorizable) throws RepositoryException {
+        // TODO
+        return false;
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     *
+     */
+    private class GroupPrincipal extends ItemBasedPrincipalImpl implements java.security.acl.Group {
+
+        GroupPrincipal(String principalName, String nodePath) {
+            super(principalName, getNode());
+        }
+
+        @Override
+        public boolean addMember(Principal principal) {
+            return false;
+        }
+
+        @Override
+        public boolean removeMember(Principal principal) {
+            return false;
+        }
+
+        @Override
+        public boolean isMember(Principal principal) {
+            // TODO
+            return false;
+        }
+
+        @Override
+        public Enumeration<? extends Principal> members() {
+            // TODO
+            return null;
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ImpersonationImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ImpersonationImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ImpersonationImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ImpersonationImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,212 @@
+/*
+ * 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 org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.principal.PrincipalIterator;
+import org.apache.jackrabbit.api.security.principal.PrincipalManager;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.Impersonation;
+import org.apache.jackrabbit.oak.jcr.security.principal.PrincipalIteratorAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.Value;
+import javax.security.auth.Subject;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * ImpersonationImpl...
+ */
+public class ImpersonationImpl implements Impersonation {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(ImpersonationImpl.class);
+
+    private static final String P_IMPERSONATORS = "rep:impersonators";
+
+    private final UserImpl user;
+
+    ImpersonationImpl(UserImpl user) throws RepositoryException {
+        this.user = user;
+    }
+
+    //------------------------------------------------------< Impersonation >---
+    /**
+     * @see org.apache.jackrabbit.api.security.user.Impersonation#getImpersonators()
+     */
+    @Override
+    public PrincipalIterator getImpersonators() throws RepositoryException {
+        Set<String> impersonators = getImpersonatorNames();
+        if (impersonators.isEmpty()) {
+            return PrincipalIteratorAdapter.EMPTY;
+        } else {
+            final PrincipalManager pMgr = getPrincipalManager();
+            Set<Principal> s = new HashSet<Principal>();
+            for (final String pName : impersonators) {
+                Principal p = pMgr.getPrincipal(pName);
+                if (p == null) {
+                    log.debug("Impersonator " + pName + " does not correspond to a known Principal.");
+                    p = new Principal() {
+                        @Override
+                        public String getName() {
+                            return pName;
+                        }
+                    };
+                }
+                s.add(p);
+
+            }
+            return new PrincipalIteratorAdapter(s);
+        }
+    }
+
+    /**
+     * @see org.apache.jackrabbit.api.security.user.Impersonation#grantImpersonation(Principal)
+     */
+    @Override
+    public synchronized boolean grantImpersonation(Principal principal) throws RepositoryException {
+        String principalName = principal.getName();
+        PrincipalManager pMgr = getPrincipalManager();
+        if (!pMgr.hasPrincipal(principalName)) {
+            log.debug("Cannot grant impersonation to an unknown principal.");
+            return false;
+        }
+
+        Principal p = pMgr.getPrincipal(principalName);
+        if (p instanceof Group) {
+            log.debug("Cannot grant impersonation to a principal that is a Group.");
+            return false;
+        }
+
+        // make sure user does not impersonate himself
+        if (user.getPrincipal().getName().equals(principalName)) {
+            log.warn("Cannot grant impersonation to oneself.");
+            return false;
+        }
+
+        // make sure the given principal doesn't refer to the admin user.
+        Authorizable a = user.getUserManager().getAuthorizable(p);
+        if (a != null && user.getUserManager().isAdminId(a.getID())) {
+            log.debug("Admin principal is already granted impersonation.");
+            return false;
+        }
+
+        boolean granted = false;
+        Set<String> impersonators = getImpersonatorNames();
+        if (impersonators.add(principalName)) {
+            updateImpersonatorNames(impersonators);
+            granted = true;
+        }
+        return granted;
+    }
+
+    /**
+     * @see Impersonation#revokeImpersonation(java.security.Principal)
+     */
+    @Override
+    public synchronized boolean revokeImpersonation(Principal principal) throws RepositoryException {
+        boolean revoked = false;
+        String pName = principal.getName();
+
+        Set<String> impersonators = getImpersonatorNames();
+        if (impersonators.remove(pName)) {
+            updateImpersonatorNames(impersonators);
+            revoked = true;
+        }
+        return revoked;
+    }
+
+    /**
+     * @see Impersonation#allows(javax.security.auth.Subject)
+     */
+    @Override
+    public boolean allows(Subject subject) throws RepositoryException {
+        if (subject == null) {
+            return false;
+        }
+
+        Set<String> principalNames = new HashSet<String>();
+        for (Principal p : subject.getPrincipals()) {
+            principalNames.add(p.getName());
+        }
+
+        boolean allows;
+        Set<String> impersonators = getImpersonatorNames();
+        allows = impersonators.removeAll(principalNames);
+
+        if (!allows) {
+            // check if subject belongs to administrator user
+            for (Principal p : subject.getPrincipals()) {
+                if (p instanceof Group) {
+                    continue;
+                }
+                UserManagerImpl userManager = getUserManagerImpl();
+                Authorizable a = userManager.getAuthorizable(p);
+                if (a != null && userManager.isAdminId(a.getID())) {
+                    allows = true;
+                    break;
+                }
+            }
+        }
+        return allows;
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private Set<String> getImpersonatorNames() throws RepositoryException {
+        Set<String> princNames = new HashSet<String>();
+        if (user.getNode().hasProperty(P_IMPERSONATORS)) {
+            Value[] vs = user.getNode().getProperty(P_IMPERSONATORS).getValues();
+            for (Value v : vs) {
+                princNames.add(v.getString());
+            }
+        }
+        return princNames;
+    }
+
+    private void updateImpersonatorNames(Set<String> principalNames) throws RepositoryException {
+        String[] pNames = principalNames.toArray(new String[principalNames.size()]);
+        if (pNames.length == 0) {
+            user.getUserManager().removeInternalProperty(user.getNode(), P_IMPERSONATORS);
+        } else {
+            user.getUserManager().setInternalProperty(user.getNode(), P_IMPERSONATORS, pNames, PropertyType.STRING);
+        }
+    }
+
+    private PrincipalManager getPrincipalManager() throws RepositoryException {
+        Session s = user.getNode().getSession();
+        if (s instanceof JackrabbitSession) {
+            return ((JackrabbitSession) s).getPrincipalManager();
+        } else {
+            throw new UnsupportedRepositoryOperationException("Principal management not supported.");
+        }
+    }
+
+    private UserManagerImpl getUserManagerImpl() {
+        return user.getUserManager();
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ItemBasedPrincipalImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ItemBasedPrincipalImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ItemBasedPrincipalImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/ItemBasedPrincipalImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,92 @@
+/*
+ * 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 org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.principal.JackrabbitPrincipal;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Item;
+import javax.jcr.RepositoryException;
+import java.security.Principal;
+
+/**
+ * ItemBasedPrincipalImpl...
+ */
+class ItemBasedPrincipalImpl implements ItemBasedPrincipal {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(ItemBasedPrincipalImpl.class);
+
+    private final String principalName;
+    private final Item item;
+
+    ItemBasedPrincipalImpl(String principalName, Item item) {
+        this.principalName = principalName;
+        this.item = item;
+    }
+
+    //-------------------------------------------------< ItemBasedPrincipal >---
+    @Override
+    public String getPath() throws RepositoryException {
+        return item.getPath();
+    }
+
+    //----------------------------------------------------------< Principal >---
+    @Override
+    public String getName() {
+        return principalName;
+    }
+
+    //-------------------------------------------------------------< Object >---
+    /**
+     * Two principals are equal, if their names are.
+     * @see Object#equals(Object)
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof JackrabbitPrincipal) {
+            return principalName.equals(((Principal) obj).getName());
+        }
+        return false;
+    }
+
+    /**
+     * @return the hash code of the principals name.
+     * @see Object#hashCode()
+     */
+    @Override
+    public int hashCode() {
+        return principalName.hashCode();
+    }
+
+    /**
+     * @see Object#toString()
+     */
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getName()).append(':').append(principalName);
+        return sb.toString();
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/PasswordUtility.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/PasswordUtility.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/PasswordUtility.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/PasswordUtility.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,60 @@
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * PasswordUtility...
+ */
+public class PasswordUtility {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(PasswordUtility.class);
+
+    public static final String DEFAULT_ALGORITHM = "SHA-256";
+    public static final int DEFAULT_SALT_SIZE = 8;
+    public static final int DEFAULT_ITERATIONS = 10000;
+
+    /**
+     * Avoid instanciation
+     */
+    private PasswordUtility() {};
+
+    public static boolean isSame(String passwordHash, String toTest) {
+        // TODO
+        return false;
+    }
+
+    public static String buildPasswordHash(String password, String algorithm,
+                                           int defaultSaltSize, int iterations)
+            throws NoSuchAlgorithmException, UnsupportedEncodingException {
+        // TODO
+        return null;
+    }
+
+    public static boolean isPlainTextPassword(String password) {
+        // TODO
+        return false;
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,127 @@
+/*
+ * 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 org.apache.jackrabbit.api.security.user.Impersonation;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.oak.jcr.NodeImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Credentials;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import java.security.Principal;
+
+/**
+ * UserImpl...
+ */
+public class UserImpl extends AuthorizableImpl implements User {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(UserImpl.class);
+
+    UserImpl(NodeImpl node, UserManagerImpl userManager) {
+        super(node, userManager);
+    }
+
+    //-------------------------------------------------------< Authorizable >---
+    @Override
+    public boolean isGroup() {
+        return false;
+    }
+
+    @Override
+    public Principal getPrincipal() throws RepositoryException {
+        String principalName = getPrincipalName();
+        return new ItemBasedPrincipalImpl(principalName, getNode());
+
+    }
+
+    //---------------------------------------------------------------< User >---
+    @Override
+    public boolean isAdmin() {
+        try {
+            return getUserManager().isAdminId(getID());
+        } catch (RepositoryException e) {
+            // should never get here
+            log.error("Internal error while retrieving UserID.", e);
+            return false;
+        }
+    }
+
+    @Override
+    public Credentials getCredentials() throws RepositoryException {
+        throw new UnsupportedOperationException("Not implemented.");
+    }
+
+    @Override
+    public Impersonation getImpersonation() throws RepositoryException {
+       return new ImpersonationImpl(this);
+    }
+
+    @Override
+    public void changePassword(String password) throws RepositoryException {
+        UserManagerImpl userManager = getUserManager();
+        userManager.onPasswordChange(this, password);
+        userManager.setPassword(getNode(), password, true);
+    }
+
+    @Override
+    public void changePassword(String password, String oldPassword) throws RepositoryException {
+        // make sure the old password matches.
+        String pwHash = null;
+        if (getNode().hasProperty(REP_PASSWORD)) {
+            pwHash = getNode().getProperty(REP_PASSWORD).getString();
+        }
+        if (!PasswordUtility.isSame(pwHash, oldPassword)) {
+            throw new RepositoryException("Failed to change password: Old password does not match.");
+        }
+        changePassword(password);
+    }
+
+    @Override
+    public void disable(String reason) throws RepositoryException {
+        if (isAdmin()) {
+            throw new RepositoryException("The administrator user cannot be disabled.");
+        }
+        if (reason == null) {
+            if (isDisabled()) {
+                // enable the user again.
+                getUserManager().removeInternalProperty(getNode(), REP_DISABLED);
+            } // else: nothing to do.
+        } else {
+            getUserManager().setInternalProperty(getNode(), REP_DISABLED, reason, PropertyType.STRING);
+        }
+    }
+
+    @Override
+    public boolean isDisabled() throws RepositoryException {
+        return getNode().hasProperty(REP_DISABLED);
+    }
+
+    @Override
+    public String getDisabledReason() throws RepositoryException {
+        if (isDisabled()) {
+            return getNode().getProperty(REP_DISABLED).getString();
+        } else {
+            return null;
+        }
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerConfig.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerConfig.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerConfig.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerConfig.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,117 @@
+/*
+ * 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 org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Value;
+
+/**
+ * UserManagerConfig...
+ */
+public class UserManagerConfig {
+
+    private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
+
+    /**
+     * Configuration parameter to change the default algorithm used to generate
+     * password hashes. The default value is {@link PasswordUtility#DEFAULT_ALGORITHM}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ALGORITHM = "passwordHashAlgorithm";
+
+    /**
+     * Configuration parameter to change the number of iterations used for
+     * password hash generation. The default value is {@link PasswordUtility#DEFAULT_ITERATIONS}.
+     */
+    public static final String PARAM_PASSWORD_HASH_ITERATIONS = "passwordHashIterations";
+
+    /**
+     * Configuration parameter to change the number of iterations used for
+     * password hash generation. The default value is {@link PasswordUtility#DEFAULT_ITERATIONS}.
+     */
+    public static final String PARAM_PASSWORD_SALT_SIZE = "passwordSaltSize";
+
+    // TODO: check if that can really be node, who would retrieve it and what kind of access rights needed to be enforced on it
+    private final Node configNode;
+    private final String adminId;
+    //private final AuthorizableAction[] actions;
+
+    UserManagerConfig(Node configNode, String adminId) {
+        this.configNode = configNode;
+        this.adminId = adminId;
+        // this.actions = (actions == null) ? new AuthorizableAction[0] : actions;
+    }
+
+    public <T> T getConfigValue(String key, T defaultValue) {
+        try {
+            if (configNode.hasProperty(key)) {
+                return convert(configNode.getProperty(key), defaultValue);
+            }
+        } catch (RepositoryException e) {
+            // unexpected error -> return default value
+            log.debug(e.getMessage());
+        }
+        return defaultValue;
+    }
+
+    public String getAdminId() {
+        return adminId;
+    }
+
+//    public AuthorizableAction[] getAuthorizableActions() {
+//        return actions;
+//    }
+
+    //--------------------------------------------------------< private >---
+    private static <T> T convert(Property configProperty, T defaultValue) throws RepositoryException {
+        T value;
+        String str;
+        // TODO properly deal with multi-value properties and array-default-values.
+        if (configProperty.isMultiple()) {
+            Value[] vls = configProperty.getValues();
+            str = (vls.length == 0) ? "" : vls[0].getString();
+        } else {
+            str = configProperty.getString();
+        }
+        Class targetClass = (defaultValue == null) ? String.class : defaultValue.getClass();
+        try {
+            if (targetClass == String.class) {
+                value = (T) str;
+            } else if (targetClass == Integer.class) {
+                value = (T) Integer.valueOf(str);
+            } else if (targetClass == Long.class) {
+                value = (T) Long.valueOf(str);
+            } else if (targetClass == Double.class) {
+                value = (T) Double.valueOf(str);
+            } else if (targetClass == Boolean.class) {
+                value = (T) Boolean.valueOf(str);
+            } else {
+                // unsupported target type
+                log.warn("Unsupported target type {} for config entry {}", targetClass.getName(), configProperty.getName());
+                throw new IllegalArgumentException("Cannot convert config entry " + configProperty.getName() + " to " + targetClass.getName());
+            }
+        } catch (NumberFormatException e) {
+            log.warn("Invalid value of config entry {}; cannot be parsed into {}", configProperty.getName(), targetClass.getName());
+            value = defaultValue;
+        }
+        return value;
+    }
+}

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/UserManagerImpl.java Fri Apr 13 08:42:36 2012
@@ -0,0 +1,365 @@
+/*
+ * 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 org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
+import org.apache.jackrabbit.api.security.user.Authorizable;
+import org.apache.jackrabbit.api.security.user.AuthorizableExistsException;
+import org.apache.jackrabbit.api.security.user.Group;
+import org.apache.jackrabbit.api.security.user.Query;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.jackrabbit.oak.api.Scalar;
+import org.apache.jackrabbit.oak.jcr.NodeImpl;
+import org.apache.jackrabbit.oak.jcr.SessionContext;
+import org.apache.jackrabbit.oak.jcr.util.ValueConverter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * UserManagerImpl...
+ */
+public class UserManagerImpl implements UserManager {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(UserManagerImpl.class);
+
+    private final SessionContext sessionContext;
+    private final UserManagerConfig config;
+    private final AuthorizableNodeCreator nodeCreator;
+
+    public UserManagerImpl(SessionContext sessionContext, UserManagerConfig config) {
+        this.sessionContext = sessionContext;
+        this.config = config;
+        nodeCreator = new AuthorizableNodeCreator(sessionContext);
+    }
+
+    //--------------------------------------------------------< UserManager >---
+    /**
+     * @see UserManager#getAuthorizable(String)
+     */
+    @Override
+    public Authorizable getAuthorizable(String id) throws RepositoryException {
+        Authorizable authorizable = null;
+        try {
+            NodeImpl node = (NodeImpl) sessionContext.getSession().getNodeByIdentifier(buildIdentifier(id));
+            authorizable = getAuthorizable(node);
+        } catch (ItemNotFoundException e) {
+            log.debug("No authorizable with ID " + id);
+        }
+        return authorizable;
+    }
+
+    /**
+     * @see UserManager#getAuthorizable(Principal)
+     */
+    @Override
+    public Authorizable getAuthorizable(Principal principal) throws RepositoryException {
+        Session session = sessionContext.getSession();
+        Authorizable authorizable = null;
+        if (principal instanceof ItemBasedPrincipal) {
+            String authPath = ((ItemBasedPrincipal) principal).getPath();
+            if (session.nodeExists(authPath)) {
+                NodeImpl n = (NodeImpl) session.getNode(authPath);
+                authorizable = getAuthorizable(n);
+            }
+        } else {
+            // another Principal implementation.
+            String name = principal.getName();
+            Authorizable a = getAuthorizable(name);
+            if (a != null && name.equals(a.getPrincipal().getName())) {
+                authorizable = a;
+            } else {
+                Iterator<Authorizable> result = findAuthorizables(AuthorizableImpl.REP_PRINCIPAL_NAME, name, SEARCH_TYPE_AUTHORIZABLE);
+                if (result.hasNext()) {
+                    authorizable = result.next();
+                }
+            }
+        }
+        // build the corresponding authorizable object
+        return authorizable;
+    }
+
+    @Override
+    public Authorizable getAuthorizableByPath(String path) throws UnsupportedRepositoryOperationException, RepositoryException {
+        // TODO
+        return null;
+    }
+
+    @Override
+    public Iterator<Authorizable> findAuthorizables(String relPath, String value) throws RepositoryException {
+        // TODO : create and execute a query
+        return null;
+    }
+
+    @Override
+    public Iterator<Authorizable> findAuthorizables(String relPath, String value, int searchType) throws RepositoryException {
+        // TODO : create and execute a query
+        return null;
+    }
+
+    @Override
+    public Iterator<Authorizable> findAuthorizables(Query query) throws RepositoryException {
+        // TODO : execute the specified query
+        return null;
+    }
+
+    @Override
+    public User createUser(final String userID, String password) throws AuthorizableExistsException, RepositoryException {
+        Principal principal = new Principal() {
+            @Override
+            public String getName() {
+                return userID;
+            }
+        };
+        return createUser(userID, password, principal, null);
+    }
+
+    @Override
+    public User createUser(String userID, String password, Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        checkValidID(userID);
+
+        NodeImpl userNode = (NodeImpl) nodeCreator.createUserNode(userID, intermediatePath);
+        setPrincipal(userNode, principal);
+        setPassword(userNode, password, true);
+
+        User user = new UserImpl(userNode, this);
+        onCreate(user, password);
+
+        log.debug("User created: " + userID + "; " + userNode.getPath());
+        return user;
+    }
+
+    @Override
+    public Group createGroup(final String groupID) throws AuthorizableExistsException, RepositoryException {
+        Principal principal = new Principal() {
+            @Override
+            public String getName() {
+                return groupID;
+            }
+        };
+        return createGroup(groupID, principal, null);
+    }
+
+    @Override
+    public Group createGroup(Principal principal) throws AuthorizableExistsException, RepositoryException {
+        return createGroup(principal, null);
+    }
+
+    @Override
+    public Group createGroup(Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        String groupID = buildGroupID(principal.getName());
+        return createGroup(groupID, principal, intermediatePath);
+    }
+
+    @Override
+    public Group createGroup(String groupID, Principal principal, String intermediatePath) throws AuthorizableExistsException, RepositoryException {
+        checkValidID(groupID);
+
+        NodeImpl groupNode = (NodeImpl) nodeCreator.createGroupNode(groupID, intermediatePath);
+        setPrincipal(groupNode, principal);
+
+        Group group = new GroupImpl(groupNode, this);
+        onCreate(group);
+
+        log.debug("Group created: " + groupID + "; " + groupNode.getPath());
+        return group;
+    }
+
+    /**
+     * Always returns {@code false}. Any modifications made to this user
+     * manager instance require a subsequent call to {@link javax.jcr.Session#save()}
+     * in order to have the changes persisted.
+     *
+     * @see org.apache.jackrabbit.api.security.user.UserManager#isAutoSave()
+     */
+    @Override
+    public boolean isAutoSave() {
+        return false;
+    }
+
+    /**
+     * Changing the auto-save behavior is not supported by this implementation
+     * and this method always throws {@code UnsupportedRepositoryOperationException}
+     *
+     * @see UserManager#autoSave(boolean)
+     */
+    @Override
+    public void autoSave(boolean enable) throws UnsupportedRepositoryOperationException, RepositoryException {
+        throw new UnsupportedRepositoryOperationException("Session#save() is always required.");
+    }
+
+
+    //--------------------------------------------------------------------------
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform additional
+     * tasks associated with the creation of the new user before the
+     * corresponding new node is persisted.
+     *
+     * @param user The new user.
+     * @param pw The password.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onCreate(User user, String pw) throws RepositoryException {
+        // TODO
+    }
+
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform additional
+     * tasks associated with the creation of the new group before the
+     * corresponding new node is persisted.
+     *
+     * @param group The new group.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onCreate(Group group) throws RepositoryException {
+        // TODO
+    }
+
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform any clean
+     * up tasks related to the authorizable removal (before the corresponding
+     * node gets removed).
+     *
+     * @param authorizable The authorizable to be removed.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onRemove(Authorizable authorizable) throws RepositoryException {
+        // TODO
+    }
+
+    /**
+     * Let the configured <code>AuthorizableAction</code>s perform additional
+     * tasks associated with password changing of a given user before the
+     * corresponding property is being changed.
+     *
+     * @param user The target user.
+     * @param password The new password.
+     * @throws RepositoryException If an exception occurs.
+     */
+    void onPasswordChange(User user, String password) throws RepositoryException {
+        // TODO
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     * @param userID A userID.
+     * @return true if the given userID belongs to the administrator user.
+     */
+    boolean isAdminId(String userID) {
+        return config.getAdminId().equals(userID);
+    }
+
+    /**
+     *
+     *
+     * @param userNode The node representing the user.
+     * @param password The plaintext password to set.
+     * @param forceHash If true the specified password will always be hashed.
+     * @throws javax.jcr.RepositoryException If an error occurs
+     */
+    void setPassword(NodeImpl userNode, String password, boolean forceHash) throws RepositoryException {
+        if (password != null) {
+            log.debug("Password is null.");
+            return;
+        }
+        String pwHash;
+        if (forceHash || PasswordUtility.isPlainTextPassword(password)) {
+            try {
+                String algorithm = config.getConfigValue(UserManagerConfig.PARAM_PASSWORD_HASH_ALGORITHM, PasswordUtility.DEFAULT_ALGORITHM);
+                int iterations = config.getConfigValue(UserManagerConfig.PARAM_PASSWORD_HASH_ITERATIONS, PasswordUtility.DEFAULT_ITERATIONS);
+                int saltSize = config.getConfigValue(UserManagerConfig.PARAM_PASSWORD_SALT_SIZE, PasswordUtility.DEFAULT_SALT_SIZE);
+                pwHash = PasswordUtility.buildPasswordHash(password, algorithm, saltSize, iterations);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RepositoryException(e);
+            } catch (UnsupportedEncodingException e) {
+                throw new RepositoryException(e);
+            }
+        } else {
+            pwHash = password;
+        }
+        setInternalProperty(userNode, AuthorizableImpl.REP_PASSWORD, pwHash, PropertyType.STRING);
+
+    }
+
+    void setPrincipal(NodeImpl userNode, Principal principal) throws RepositoryException {
+        // TODO: validate the principal
+
+        if (!userNode.isNew() || userNode.hasProperty(AuthorizableImpl.REP_PRINCIPAL_NAME)) {
+            throw new RepositoryException("rep:principalName can only be set once on a new node.");
+        }
+        setInternalProperty(userNode, AuthorizableImpl.REP_PRINCIPAL_NAME, principal.getName(), PropertyType.STRING);
+    }
+
+    void setInternalProperty(NodeImpl userNode, String name, String value, int type) throws RepositoryException {
+        // TODO: check again if this really makes a transient modification with marking the property modified/new
+        Scalar scalar = ValueConverter.toScalar(value, type);
+        userNode.getEditor().setProperty(name, scalar);
+    }
+
+    void setInternalProperty(NodeImpl userNode, String name, String[] values, int type) throws RepositoryException {
+        // TODO: check again if this really makes a transient modification with marking the property modified/new
+        List<Scalar> scalarList = new ArrayList<Scalar>(values.length);
+        for (int i = 0; i < values.length; i++) {
+            scalarList.add(ValueConverter.toScalar(values[i], PropertyType.STRING));
+        }
+        userNode.getEditor().setProperty(name, scalarList);
+    }
+
+    void removeInternalProperty(NodeImpl userNode, String name) {
+        // TODO: check again if this really makes a transient modification with marking the property modified
+        userNode.getEditor().removeProperty(name);
+    }
+
+    private Authorizable getAuthorizable(NodeImpl node) throws RepositoryException {
+        if (node.isNodeType(AuthorizableImpl.NT_REP_USER)) {
+            return new UserImpl(node, this);
+        } else if (node.isNodeType(AuthorizableImpl.NT_REP_GROUP)) {
+            return new GroupImpl(node, this);
+        } else {
+            throw new RepositoryException("Unexpected node type " + node.getPrimaryNodeType().getName() + ". Expected rep:User or rep:Group.");
+        }
+    }
+
+    private String buildIdentifier(String authorizableID) {
+        // TODO
+        return null;
+    }
+
+    private String buildGroupID(String principalName) {
+        // TODO
+        return principalName;
+    }
+
+    private void checkValidID(String authorizableID) {
+        // TODO
+    }
+}
\ No newline at end of file

Added: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/diff.txt
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/diff.txt?rev=1325655&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/diff.txt (added)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/security/user/diff.txt Fri Apr 13 08:42:36 2012
@@ -0,0 +1,40 @@
+differences regarding jackrabbit 2.x implementation
+================================================================================
+
+General:
+- user management always associated with editing Session
+- changes made to user-mgt API are always transient + require Session#save()
+- in case of failure Session#refresh is not called anymore
+
+Unsupported Operations:
+- UserManager#autoSave(boolean)
+- User#getCredentials()
+
+UserManager:
+- UserManager#isAutoSave() always returns false
+- password not mandatory upon creation (TODO: check again)
+
+Authorizable:
+- setProperty, removeProperty executes no extra shortcut check for protected properties
+- not relying on rep:principalName to exist -> using ID as fallback
+-
+
+User:
+
+Group:
+
+
+TODO:
+================================================================================
+
+- Namespace handling on session level
+
+- Path handling that rely on oak-core implementation
+
+- Configuration based on oak-core implementation
+
+
+
+
+
+

Modified: jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java?rev=1325655&r1=1325654&r2=1325655&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java (original)
+++ jackrabbit/oak/trunk/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/util/ValueConverter.java Fri Apr 13 08:42:36 2012
@@ -38,6 +38,37 @@ import java.util.List;
 public final class ValueConverter {
     private ValueConverter() {}
 
+    public static Scalar toScalar(String value, int propertyType) throws RepositoryException {
+        switch (propertyType) {
+            case PropertyType.STRING: {
+                return ScalarImpl.stringScalar(value);
+            }
+            case PropertyType.DOUBLE: {
+                return ScalarImpl.doubleScalar(Double.parseDouble(value));
+            }
+            case PropertyType.LONG: {
+                return ScalarImpl.longScalar(Long.parseLong(value));
+            }
+            case PropertyType.BOOLEAN: {
+                return ScalarImpl.booleanScalar(Boolean.parseBoolean(value));
+            }
+            case PropertyType.BINARY: {
+                return ScalarImpl.binaryScalar(value);
+            }
+            case PropertyType.DECIMAL:
+            case PropertyType.DATE:
+            case PropertyType.NAME:
+            case PropertyType.PATH:
+            case PropertyType.REFERENCE:
+            case PropertyType.WEAKREFERENCE:
+            case PropertyType.URI:
+            default: {
+                // todo implement toScalar
+                throw new UnsupportedRepositoryOperationException("toScalar");
+            }
+        }
+    }
+
     public static Scalar toScalar(Value value) throws RepositoryException {
         switch (value.getType()) {
             case PropertyType.STRING: {