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 2017/03/07 16:35:35 UTC

svn commit: r1785855 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/security/authentication/token/ main/java/org/apache/jackrabbit/oak/security/authentication/user/ main/java/org/apache/jackrabbit/oak/security/user/ main/j...

Author: angela
Date: Tue Mar  7 16:35:35 2017
New Revision: 1785855

URL: http://svn.apache.org/viewvc?rev=1785855&view=rev
Log:
OAK-5903 : Authentication: add extension to retrieve user principal
OAK-4462 : LoginModuleImpl: option to have AuthInfo populated with userId instead of loginName

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/Authentication.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthenticationTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImplTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModuleTest.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryTest.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthentication.java Tue Mar  7 16:35:35 2017
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.oak.security.authentication.token;
 
+import java.security.Principal;
 import java.util.Date;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import javax.jcr.Credentials;
@@ -63,6 +65,28 @@ class TokenAuthentication implements Aut
         return false;
     }
 
+    @CheckForNull
+    @Override
+    public String getUserId() {
+        if (tokenInfo == null) {
+            throw new IllegalStateException("UserId can only be retrieved after successful authentication.");
+        }
+        return tokenInfo.getUserId();
+    }
+
+    @CheckForNull
+    @Override
+    public Principal getUserPrincipal() {
+        if (tokenInfo == null) {
+            throw new IllegalStateException("Token info can only be retrieved after successful authentication.");
+        }
+        if (tokenInfo instanceof TokenProviderImpl.TokenInfoImpl) {
+            return ((TokenProviderImpl.TokenInfoImpl) tokenInfo).getPrincipal();
+        } else {
+            return null;
+        }
+    }
+
     //-----------------------------------------------------------< internal >---
     @Nonnull
     TokenInfo getTokenInfo() {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java Tue Mar  7 16:35:35 2017
@@ -120,6 +120,7 @@ public final class TokenLoginModule exte
     private TokenCredentials tokenCredentials;
     private TokenInfo tokenInfo;
     private String userId;
+    private Principal principal;
 
     //--------------------------------------------------------< LoginModule >---
     @Override
@@ -136,7 +137,8 @@ public final class TokenLoginModule exte
             if (authentication.authenticate(tc)) {
                 tokenCredentials = tc;
                 tokenInfo = authentication.getTokenInfo();
-                userId = tokenInfo.getUserId();
+                userId = authentication.getUserId();
+                principal = authentication.getUserPrincipal();
 
                 log.debug("Login: adding login name to shared state.");
                 sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
@@ -150,7 +152,7 @@ public final class TokenLoginModule exte
     @Override
     public boolean commit() throws LoginException {
         if (tokenCredentials != null && userId != null) {
-            Set<? extends Principal> principals = getPrincipals(userId);
+            Set<? extends Principal> principals = (principal != null) ? getPrincipals(principal) : getPrincipals(userId);
             updateSubject(tokenCredentials, getAuthInfo(tokenInfo, principals), principals);
             return true;
         }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenProviderImpl.java Tue Mar  7 16:35:35 2017
@@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.securi
 
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
 import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -30,6 +31,7 @@ import java.util.Map;
 import java.util.UUID;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.jcr.AccessDeniedException;
 import javax.jcr.Credentials;
 import javax.jcr.RepositoryException;
@@ -224,17 +226,12 @@ class TokenProviderImpl implements Token
                     root.commit(CommitMarker.asCommitAttributes());
                 }
                 return tokenInfo;
-            } catch (NoSuchAlgorithmException e) {
+            } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
                 // error while generating login token
                 log.error(error, e.getMessage());
-            } catch (UnsupportedEncodingException e) {
-                // error while generating login token
-                log.error(error, e.getMessage());
-            } catch (CommitFailedException e) {
+            } catch (CommitFailedException | RepositoryException e) {
                 // conflict while committing changes
                 log.warn(error, e.getMessage());
-            } catch (RepositoryException e) {
-                log.warn(error, e.getMessage());
             }
         } else {
             log.warn("Unable to get/create token store for user " + userId);
@@ -258,12 +255,16 @@ class TokenProviderImpl implements Token
         String nodeId = (pos == -1) ? token : token.substring(0, pos);
         Tree tokenTree = identifierManager.getTree(nodeId);
         if (isValidTokenTree(tokenTree)) {
-            String userId = getUserId(tokenTree);
-            if (userId != null) {
-                return new TokenInfoImpl(new NodeUtil(tokenTree), token, userId);
+            try {
+                User user = getUser(tokenTree);
+                if (user != null) {
+                    return new TokenInfoImpl(new NodeUtil(tokenTree), token, user.getID(), user.getPrincipal());
+                }
+            } catch (RepositoryException e) {
+                log.debug("Cannot determine userID/principal from token: {}", e.getMessage());
             }
         }
-        // not a valid token tree or failed to extract userID
+        // invalid token tree or failed to extract user or it's id/principal
         return null;
     }
 
@@ -331,17 +332,14 @@ class TokenProviderImpl implements Token
     }
 
     @CheckForNull
-    private String getUserId(@Nonnull Tree tokenTree) {
-        try {
-            String userPath = Text.getRelativeParent(tokenTree.getPath(), 2);
-            Authorizable authorizable = userManager.getAuthorizableByPath(userPath);
-            if (authorizable != null && !authorizable.isGroup() && !((User) authorizable).isDisabled()) {
-                return authorizable.getID();
-            }
-        } catch (RepositoryException e) {
-            log.debug("Cannot determine userID from token: {}", e.getMessage());
+    private User getUser(@Nonnull Tree tokenTree) throws RepositoryException {
+        String userPath = Text.getRelativeParent(tokenTree.getPath(), 2);
+        Authorizable authorizable = userManager.getAuthorizableByPath(userPath);
+        if (authorizable != null && !authorizable.isGroup() && !((User) authorizable).isDisabled()) {
+            return (User) authorizable;
+        } else {
+            return null;
         }
-        return null;
     }
 
     @CheckForNull
@@ -425,7 +423,7 @@ class TokenProviderImpl implements Token
                 tokenNode.setString(name, attr);
             }
         }
-        return new TokenInfoImpl(tokenNode, token, id);
+        return new TokenInfoImpl(tokenNode, token, id, null);
     }
 
     //--------------------------------------------------------------------------
@@ -433,11 +431,12 @@ class TokenProviderImpl implements Token
     /**
      * TokenInfo
      */
-    private final class TokenInfoImpl implements TokenInfo {
+    final class TokenInfoImpl implements TokenInfo {
 
         private final String token;
         private final String tokenPath;
         private final String userId;
+        private final Principal principal;
 
         private final long expirationTime;
         private final String key;
@@ -445,17 +444,17 @@ class TokenProviderImpl implements Token
         private final Map<String, String> mandatoryAttributes;
         private final Map<String, String> publicAttributes;
 
-
-        private TokenInfoImpl(@Nonnull NodeUtil tokenNode, @Nonnull String token, @Nonnull String userId) {
+        private TokenInfoImpl(@Nonnull NodeUtil tokenNode, @Nonnull String token, @Nonnull String userId, @Nullable Principal principal) {
             this.token = token;
             this.tokenPath = tokenNode.getTree().getPath();
             this.userId = userId;
+            this.principal = principal;
 
             expirationTime = getExpirationTime(tokenNode, Long.MIN_VALUE);
             key = tokenNode.getString(TOKEN_ATTRIBUTE_KEY, null);
 
-            mandatoryAttributes = new HashMap<String, String>();
-            publicAttributes = new HashMap<String, String>();
+            mandatoryAttributes = new HashMap();
+            publicAttributes = new HashMap();
             for (PropertyState propertyState : tokenNode.getTree().getProperties()) {
                 String name = propertyState.getName();
                 String value = propertyState.getValue(STRING);
@@ -471,6 +470,11 @@ class TokenProviderImpl implements Token
             }
         }
 
+        @CheckForNull
+        Principal getPrincipal() {
+            return principal;
+        }
+
         //------------------------------------------------------< TokenInfo >---
 
         @Nonnull

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImpl.java Tue Mar  7 16:35:35 2017
@@ -111,34 +111,41 @@ public final class LoginModuleImpl exten
 
     private Credentials credentials;
     private String userId;
+    private Principal principal;
+    private boolean success;
 
     //--------------------------------------------------------< LoginModule >---
 
     @Override
     public boolean login() throws LoginException {
-        final boolean success;
         credentials = getCredentials();
 
         // check if we have a pre authenticated login from a previous login module
         PreAuthenticatedLogin preAuthLogin = getSharedPreAuthLogin();
-        if (preAuthLogin != null) {
-            userId = preAuthLogin.getUserId();
-            Authentication authentication = getUserAuthentication(userId);
-            success = authentication != null && authentication.authenticate(PreAuthenticatedLogin.PRE_AUTHENTICATED);
-        } else {
-            userId = getUserId();
-            Authentication authentication = getUserAuthentication(userId);
-            success = authentication != null && authentication.authenticate(credentials);
-        }
+        String loginName = getLoginId(preAuthLogin);
+        Authentication authentication = getUserAuthentication(loginName);
+        if (authentication != null) {
+            if (preAuthLogin != null) {
+                success = authentication.authenticate(PreAuthenticatedLogin.PRE_AUTHENTICATED);
+            } else {
+                success = authentication.authenticate(credentials);
+            }
 
-        if (success) {
-            log.debug("Adding Credentials to shared state.");
-            //noinspection unchecked
-            sharedState.put(SHARED_KEY_CREDENTIALS, credentials);
-
-            log.debug("Adding login name to shared state.");
-            //noinspection unchecked
-            sharedState.put(SHARED_KEY_LOGIN_NAME, userId);
+            if (success) {
+                log.debug("Adding Credentials to shared state.");
+                //noinspection unchecked
+                sharedState.put(SHARED_KEY_CREDENTIALS, credentials);
+
+                log.debug("Adding login name to shared state.");
+                //noinspection unchecked
+                sharedState.put(SHARED_KEY_LOGIN_NAME, loginName);
+
+                userId = authentication.getUserId();
+                if (userId == null) {
+                    userId = loginName;
+                }
+                principal = authentication.getUserPrincipal();
+            }
         } else {
             // ensure that we don't commit (OAK-2998, OAK-3032)
             credentials = null;
@@ -149,14 +156,18 @@ public final class LoginModuleImpl exten
 
     @Override
     public boolean commit() {
-        if (credentials == null) {
+        if (!success) {
             // login attempt in this login module was not successful
             clearState();
             return false;
         } else {
             if (!subject.isReadOnly()) {
-                Set<? extends Principal> principals = getPrincipals(userId);
-                subject.getPrincipals().addAll(principals);
+                Set<Principal> principals = subject.getPrincipals();
+                if (principal != null) {
+                    principals.addAll(getPrincipals(principal));
+                } else if (userId != null) {
+                    principals.addAll(getPrincipals(userId));
+                }
                 subject.getPublicCredentials().add(credentials);
                 setAuthInfo(createAuthInfo(principals), subject);
             } else {
@@ -179,11 +190,16 @@ public final class LoginModuleImpl exten
 
         credentials = null;
         userId = null;
+        principal = null;
     }
 
     //--------------------------------------------------------------------------
     @CheckForNull
-    private String getUserId() {
+    private String getLoginId(@CheckForNull PreAuthenticatedLogin preAuthenticatedLogin) {
+        if (preAuthenticatedLogin != null) {
+            return preAuthenticatedLogin.getUserId();
+        }
+
         String uid = null;
         if (credentials != null) {
             if (credentials instanceof SimpleCredentials) {
@@ -225,14 +241,14 @@ public final class LoginModuleImpl exten
     }
 
     @CheckForNull
-    private Authentication getUserAuthentication(@Nullable String userId) {
+    private Authentication getUserAuthentication(@Nullable String loginName) {
         SecurityProvider securityProvider = getSecurityProvider();
         Root root = getRoot();
         if (securityProvider != null && root != null) {
             UserConfiguration uc = securityProvider.getConfiguration(UserConfiguration.class);
             UserAuthenticationFactory factory = uc.getParameters().getConfigValue(UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, null, UserAuthenticationFactory.class);
             if (factory != null) {
-                return factory.getAuthentication(uc, root, userId);
+                return factory.getAuthentication(uc, root, loginName);
             } else {
                 log.error("No user authentication factory configured in user configuration.");
             }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/security/user/UserAuthentication.java Tue Mar  7 16:35:35 2017
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.security.user;
 
+import java.security.Principal;
 import java.util.Collections;
 import java.util.concurrent.TimeUnit;
 import javax.annotation.CheckForNull;
@@ -79,42 +80,45 @@ class UserAuthentication implements Auth
 
     private final UserConfiguration config;
     private final Root root;
-    private final String userId;
+    private final String loginId;
 
-    UserAuthentication(@Nonnull UserConfiguration config, @Nonnull Root root, @Nullable String userId) {
+    private String userId;
+    private Principal principal;
+
+    UserAuthentication(@Nonnull UserConfiguration config, @Nonnull Root root, @Nullable String loginId) {
         this.config = config;
         this.root = root;
-        this.userId = userId;
+        this.loginId = loginId;
     }
 
     //-----------------------------------------------------< Authentication >---
     @Override
     public boolean authenticate(@Nullable Credentials credentials) throws LoginException {
-        if (credentials == null || userId == null) {
+        if (credentials == null || loginId == null) {
             return false;
         }
 
         boolean success = false;
         try {
             UserManager userManager = config.getUserManager(root, NamePathMapper.DEFAULT);
-            Authorizable authorizable = userManager.getAuthorizable(userId);
+            Authorizable authorizable = userManager.getAuthorizable(loginId);
             if (authorizable == null) {
                 return false;
             }
 
             if (authorizable.isGroup()) {
-                throw new AccountNotFoundException("Not a user " + userId);
+                throw new AccountNotFoundException("Not a user " + loginId);
             }
 
             User user = (User) authorizable;
             if (user.isDisabled()) {
-                throw new AccountLockedException("User with ID " + userId + " has been disabled: "+ user.getDisabledReason());
+                throw new AccountLockedException("User with ID " + loginId + " has been disabled: "+ user.getDisabledReason());
             }
 
             if (credentials instanceof SimpleCredentials) {
                 SimpleCredentials creds = (SimpleCredentials) credentials;
                 Credentials userCreds = user.getCredentials();
-                if (userId.equals(creds.getUserID()) && userCreds instanceof CredentialsImpl) {
+                if (loginId.equals(creds.getUserID()) && userCreds instanceof CredentialsImpl) {
                     success = PasswordUtil.isSame(((CredentialsImpl) userCreds).getPasswordHash(), creds.getPassword());
                 }
                 checkSuccess(success, "UserId/Password mismatch.");
@@ -129,18 +133,39 @@ class UserAuthentication implements Auth
             } else if (credentials instanceof ImpersonationCredentials) {
                 ImpersonationCredentials ipCreds = (ImpersonationCredentials) credentials;
                 AuthInfo info = ipCreds.getImpersonatorInfo();
-                success = equalUserId(ipCreds, userId) && impersonate(info, user);
+                success = equalUserId(ipCreds, loginId) && impersonate(info, user);
                 checkSuccess(success, "Impersonation not allowed.");
             } else {
                 // guest login is allowed if an anonymous user exists in the content (see get user above)
                 success = (credentials instanceof GuestCredentials) || credentials == PreAuthenticatedLogin.PRE_AUTHENTICATED;
             }
+            userId = user.getID();
+            principal = user.getPrincipal();
         } catch (RepositoryException e) {
             throw new LoginException(e.getMessage());
         }
         return success;
     }
 
+    @CheckForNull
+    @Override
+    public String getUserId() {
+        if (userId == null) {
+            throw new IllegalStateException("UserId can only be retrieved after successful authentication.");
+        }
+        return userId;
+    }
+
+    @CheckForNull
+    @Override
+    public Principal getUserPrincipal() {
+        if (principal == null) {
+            throw new IllegalStateException("Principal can only be retrieved after successful authentication.");
+        }
+        return principal;
+    }
+
+
     //--------------------------------------------------------------------------
     private static void checkSuccess(boolean success, String msg) throws LoginException {
         if (!success) {
@@ -160,22 +185,22 @@ class UserAuthentication implements Auth
                 if (newPasswordObject instanceof String) {
                     user.changePassword((String) newPasswordObject);
                     root.commit();
-                    log.debug("User " + userId + ": changed user password");
+                    log.debug("User " + loginId + ": changed user password");
                     return true;
                 } else {
-                    log.warn("Aborted password change for user " + userId
+                    log.warn("Aborted password change for user " + loginId
                             + ": provided new password is of incompatible type "
                             + newPasswordObject.getClass().getName());
                 }
             }
         } catch (PasswordHistoryException e) {
             credentials.setAttribute(e.getClass().getSimpleName(), e.getMessage());
-            log.error("Failed to change password for user " + userId, e.getMessage());
+            log.error("Failed to change password for user " + loginId, e.getMessage());
         } catch (RepositoryException e) {
-            log.error("Failed to change password for user " + userId, e.getMessage());
+            log.error("Failed to change password for user " + loginId, e.getMessage());
         } catch (CommitFailedException e) {
             root.refresh();
-            log.error("Failed to change password for user " + userId, e.getMessage());
+            log.error("Failed to change password for user " + loginId, e.getMessage());
         }
         return false;
     }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModule.java Tue Mar  7 16:35:35 2017
@@ -21,6 +21,7 @@ import java.security.Principal;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.CheckForNull;
@@ -396,11 +397,7 @@ public abstract class AbstractLoginModul
                 } else {
                     log.debug("Unable to retrieve the Root via RepositoryCallback; ContentRepository not available.");
                 }
-            } catch (UnsupportedCallbackException e) {
-                log.debug(e.getMessage());
-            } catch (IOException e) {
-                log.debug(e.getMessage());
-            } catch (PrivilegedActionException e){
+            } catch (UnsupportedCallbackException | PrivilegedActionException | IOException e) {
                 log.debug(e.getMessage());
             }
         }
@@ -429,9 +426,7 @@ public abstract class AbstractLoginModul
                 UserManagerCallback userCallBack = new UserManagerCallback();
                 callbackHandler.handle(new Callback[]{userCallBack});
                 userManager = userCallBack.getUserManager();
-            } catch (IOException e) {
-                log.debug(e.getMessage());
-            } catch (UnsupportedCallbackException e) {
+            } catch (IOException | UnsupportedCallbackException e) {
                 log.debug(e.getMessage());
             }
         }
@@ -461,9 +456,7 @@ public abstract class AbstractLoginModul
                 PrincipalProviderCallback principalCallBack = new PrincipalProviderCallback();
                 callbackHandler.handle(new Callback[]{principalCallBack});
                 principalProvider = principalCallBack.getPrincipalProvider();
-            } catch (IOException e) {
-                log.debug(e.getMessage());
-            } catch (UnsupportedCallbackException e) {
+            } catch (IOException | UnsupportedCallbackException e) {
                 log.debug(e.getMessage());
             }
         }
@@ -489,6 +482,20 @@ public abstract class AbstractLoginModul
         }
     }
 
+    @Nonnull
+    protected Set<? extends Principal> getPrincipals(@Nonnull Principal userPrincipal) {
+        PrincipalProvider principalProvider = getPrincipalProvider();
+        if (principalProvider == null) {
+            log.debug("Cannot retrieve principals. No principal provider configured.");
+            return Collections.emptySet();
+        } else {
+            Set<Principal> principals = new HashSet();
+            principals.add(userPrincipal);
+            principals.addAll(principalProvider.getGroupMembership(userPrincipal));
+            return principals;
+        }
+    }
+
     protected static void setAuthInfo(@Nonnull AuthInfo authInfo, @Nonnull Subject subject) {
         Set<AuthInfo> ais = subject.getPublicCredentials(AuthInfo.class);
         if (!ais.isEmpty()) {

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/Authentication.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/Authentication.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/Authentication.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/Authentication.java Tue Mar  7 16:35:35 2017
@@ -16,6 +16,8 @@
  */
 package org.apache.jackrabbit.oak.spi.security.authentication;
 
+import java.security.Principal;
+import javax.annotation.CheckForNull;
 import javax.annotation.Nullable;
 import javax.jcr.Credentials;
 import javax.security.auth.login.LoginException;
@@ -49,4 +51,27 @@ public interface Authentication {
      * @throws LoginException if the authentication failed.
      */
     boolean authenticate(@Nullable Credentials credentials) throws LoginException;
+
+    /**
+     * Optional method that return the userID extracted upon {@link #authenticate(Credentials)}.
+     * It is expected to return {@code null} if the implementation doesn't support this.
+     *
+     * An {@link IllegalStateException} may be thrown if called prior to {@link #authenticate(Credentials)}.
+     *
+     * @return a user identifier or {@code null}
+     */
+    @CheckForNull
+    String getUserId();
+
+    /**
+     * Optional method that return the {@link Principal} of the authenticating user
+     * extracted upon {@link #authenticate(Credentials)}. It is expected to return
+     * {@code null} if the implementation doesn't support this.
+     *
+     * An {@link IllegalStateException} may be thrown if called prior to {@link #authenticate(Credentials)}.
+     *
+     * @return a valid {@code Principal} or {@code null}
+     */
+    @CheckForNull
+    Principal getUserPrincipal();
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/security/authentication/package-info.java Tue Mar  7 16:35:35 2017
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@Version("1.1.0")
+@Version("2.0.0")
 package org.apache.jackrabbit.oak.spi.security.authentication;
 
 import aQute.bnd.annotation.Version;
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthenticationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthenticationTest.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthenticationTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenAuthenticationTest.java Tue Mar  7 16:35:35 2017
@@ -31,7 +31,6 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.oak.AbstractSecurityTest;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
 import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
-import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration;
 import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenInfo;
 import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenProvider;
 import org.junit.Before;
@@ -44,9 +43,6 @@ import static org.junit.Assert.assertNul
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-/**
- * TokenAuthenticationTest...
- */
 public class TokenAuthenticationTest extends AbstractSecurityTest {
 
     TokenAuthentication authentication;
@@ -156,4 +152,28 @@ public class TokenAuthenticationTest ext
             now = waitForSystemTimeIncrement(now);
         }
     }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetUserIdBeforeLogin() {
+        authentication.getUserId();
+    }
+
+    @Test
+    public void testGetUserId() throws LoginException {
+        TokenInfo info = tokenProvider.createToken(userId, Collections.<String, Object>emptyMap());
+        assertTrue(authentication.authenticate(new TokenCredentials(info.getToken())));
+        assertEquals(userId, authentication.getUserId());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetUserPrincipalBeforeLogin() {
+        authentication.getUserPrincipal();
+    }
+
+    @Test
+    public void testGetUserPrincipal() throws Exception {
+        TokenInfo info = tokenProvider.createToken(userId, Collections.<String, Object>emptyMap());
+        assertTrue(authentication.authenticate(new TokenCredentials(info.getToken())));
+        assertEquals(getTestUser().getPrincipal(), authentication.getUserPrincipal());
+    }
 }
\ No newline at end of file

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImplTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImplTest.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImplTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/user/LoginModuleImplTest.java Tue Mar  7 16:35:35 2017
@@ -16,13 +16,26 @@
  */
 package org.apache.jackrabbit.oak.security.authentication.user;
 
+import java.io.IOException;
+import java.security.Principal;
 import java.util.Arrays;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.Credentials;
 import javax.jcr.GuestCredentials;
 import javax.jcr.RepositoryException;
 import javax.jcr.SimpleCredentials;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.Configuration;
 import javax.security.auth.login.LoginException;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import org.apache.jackrabbit.api.security.user.Authorizable;
 import org.apache.jackrabbit.api.security.user.User;
 import org.apache.jackrabbit.api.security.user.UserManager;
@@ -30,13 +43,20 @@ import org.apache.jackrabbit.oak.Abstrac
 import org.apache.jackrabbit.oak.api.AuthInfo;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.ContentSession;
+import org.apache.jackrabbit.oak.api.Root;
+import org.apache.jackrabbit.oak.security.SecurityProviderImpl;
 import org.apache.jackrabbit.oak.spi.security.ConfigurationParameters;
+import org.apache.jackrabbit.oak.spi.security.SecurityProvider;
+import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
 import org.apache.jackrabbit.oak.spi.security.authentication.ConfigurationUtil;
 import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials;
+import org.apache.jackrabbit.oak.spi.security.authentication.callback.RepositoryCallback;
+import org.apache.jackrabbit.oak.spi.security.user.UserAuthenticationFactory;
 import org.apache.jackrabbit.oak.spi.security.user.UserConfiguration;
 import org.apache.jackrabbit.oak.spi.security.user.UserConstants;
 import org.apache.jackrabbit.oak.spi.security.user.util.UserUtil;
 import org.junit.Test;
+import org.mockito.Mockito;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -44,9 +64,6 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-/**
- * LoginTest...
- */
 public class LoginModuleImplTest extends AbstractSecurityTest {
 
     private static final String USER_ID = "test";
@@ -93,13 +110,10 @@ public class LoginModuleImplTest extends
 
     @Test
     public void testGuestLogin() throws Exception {
-        ContentSession cs = login(new GuestCredentials());
-        try {
+        try (ContentSession cs = login(new GuestCredentials())) {
             AuthInfo authInfo = cs.getAuthInfo();
             String anonymousID = UserUtil.getAnonymousId(getUserConfiguration().getParameters());
             assertEquals(anonymousID, authInfo.getUserID());
-        } finally {
-            cs.close();
         }
     }
 
@@ -144,6 +158,22 @@ public class LoginModuleImplTest extends
     }
 
     @Test
+    public void testAuthInfoContainsUserId() throws Exception {
+        ContentSession cs = null;
+        try {
+            createTestUser();
+
+            cs = login(new SimpleCredentials(USER_ID_CASED, USER_PW.toCharArray()));
+            AuthInfo authInfo = cs.getAuthInfo();
+            assertEquals(user.getID(), authInfo.getUserID());
+        } finally {
+            if (cs != null) {
+                cs.close();
+            }
+        }
+    }
+
+    @Test
     public void testUserLoginIsCaseInsensitive() throws Exception {
         ContentSession cs = null;
         try {
@@ -154,6 +184,7 @@ public class LoginModuleImplTest extends
             UserManager userMgr = getUserManager(root);
             Authorizable auth = userMgr.getAuthorizable(authInfo.getUserID());
             assertNotNull(auth);
+            assertTrue(auth.getID().equalsIgnoreCase(USER_ID_CASED));
         } finally {
             if (cs != null) {
                 cs.close();
@@ -168,7 +199,8 @@ public class LoginModuleImplTest extends
             createTestUser();
             cs = login(new SimpleCredentials(USER_ID_CASED, USER_PW.toCharArray()));
             AuthInfo authInfo = cs.getAuthInfo();
-            assertEquals(USER_ID_CASED, authInfo.getUserID());
+            assertEquals(user.getID(), authInfo.getUserID());
+            assertTrue(USER_ID_CASED.equalsIgnoreCase(authInfo.getUserID()));
         } finally {
             if (cs != null) {
                 cs.close();
@@ -300,4 +332,82 @@ public class LoginModuleImplTest extends
             }
         }
     }
+
+    @Test
+    public void testGetNullUserAuthentication() throws Exception {
+        LoginModuleImpl loginModule = new LoginModuleImpl();
+        CallbackHandler cbh = new TestCallbackHandler(Mockito.mock(UserAuthenticationFactory.class));
+        loginModule.initialize(new Subject(), cbh, Maps.<String, Object>newHashMap(), Maps.<String, Object>newHashMap());
+
+        assertFalse(loginModule.login());
+        assertFalse(loginModule.commit());
+    }
+
+    @Test
+    public void testCustomUserAuthentication() throws Exception {
+        LoginModuleImpl loginModule = new LoginModuleImpl();
+
+        UserAuthenticationFactory factory = new UserAuthenticationFactory() {
+            @CheckForNull
+            @Override
+            public Authentication getAuthentication(@Nonnull UserConfiguration configuration, @Nonnull Root root, @Nullable String userId) {
+                return new Authentication() {
+                    @Override
+                    public boolean authenticate(@Nullable Credentials credentials) throws LoginException {
+                        return true;
+                    }
+
+                    @CheckForNull
+                    @Override
+                    public String getUserId() {
+                        return null;
+                    }
+
+                    @CheckForNull
+                    @Override
+                    public Principal getUserPrincipal() {
+                        return null;
+                    }
+                };
+            }
+        };
+
+        CallbackHandler cbh = new TestCallbackHandler(factory);
+        SimpleCredentials creds = new SimpleCredentials("loginId", new char[0]);
+        Subject subject = new Subject(false, Sets.<Principal>newHashSet(), ImmutableSet.of(creds), Sets.newHashSet());
+
+        loginModule.initialize(subject, cbh, Maps.<String, Object>newHashMap(), Maps.<String, Object>newHashMap());
+        assertTrue(loginModule.login());
+        assertTrue(loginModule.commit());
+
+        AuthInfo authInfo = subject.getPublicCredentials(AuthInfo.class).iterator().next();
+        assertEquals("loginId", authInfo.getUserID());
+    }
+
+
+    private class TestCallbackHandler implements CallbackHandler {
+
+        private final SecurityProvider sp;
+
+        private TestCallbackHandler(@Nullable UserAuthenticationFactory authenticationFactory) {
+            ConfigurationParameters params = ConfigurationParameters.of(
+                    UserConfiguration.NAME,
+                    ConfigurationParameters.of(
+                            UserConstants.PARAM_USER_AUTHENTICATION_FACTORY, authenticationFactory));
+            this.sp = new SecurityProviderImpl(params);
+        }
+
+        @Override
+        public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+            for (Callback callback : callbacks) {
+                if (callback instanceof RepositoryCallback) {
+                    ((RepositoryCallback) callback).setSecurityProvider(sp);
+                    ((RepositoryCallback) callback).setContentRepository(getContentRepository());
+                } else {
+                    throw new UnsupportedCallbackException(callback);
+                }
+            }
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/security/user/UserAuthenticationTest.java Tue Mar  7 16:35:35 2017
@@ -39,7 +39,9 @@ import org.apache.jackrabbit.oak.spi.sec
 import org.junit.Before;
 import org.junit.Test;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -96,10 +98,8 @@ public class UserAuthenticationTest exte
             // success
             assertTrue(e instanceof AccountNotFoundException);
         } finally {
-            if (g != null) {
-                g.remove();
-                root.commit();
-            }
+            g.remove();
+            root.commit();
         }
     }
 
@@ -189,6 +189,39 @@ public class UserAuthenticationTest exte
         assertTrue(authentication.authenticate(new ImpersonationCredentials(sc, new TestAuthInfo())));
     }
 
+    @Test(expected = IllegalStateException.class)
+    public void testGetUserIdBeforeLogin() {
+       authentication.getUserId();
+    }
+
+    @Test
+    public void testGetUserId() throws LoginException {
+        authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+        assertEquals(userId, authentication.getUserId());
+    }
+
+    @Test
+    public void testGetUserIdCasedLoginId() throws LoginException {
+        String loginId = userId.toLowerCase();
+
+        UserAuthentication auth = new UserAuthentication(getUserConfiguration(), root, loginId);
+        assertTrue(auth.authenticate(new SimpleCredentials(loginId, userId.toCharArray())));
+
+        assertNotEquals(loginId, auth.getUserId());
+        assertEquals(userId, auth.getUserId());
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testGetUserPrincipalBeforeLogin() {
+        authentication.getUserPrincipal();
+    }
+
+    @Test
+    public void testGetUserPrincipal() throws Exception {
+        authentication.authenticate(new SimpleCredentials(userId, userId.toCharArray()));
+        assertEquals(getTestUser().getPrincipal(), authentication.getUserPrincipal());
+    }
+
     //--------------------------------------------------------------------------
 
     private final class TestAuthInfo implements AuthInfo {

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModuleTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModuleTest.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModuleTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/security/authentication/AbstractLoginModuleTest.java Tue Mar  7 16:35:35 2017
@@ -19,6 +19,7 @@ package org.apache.jackrabbit.oak.spi.se
 import java.security.Principal;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import javax.annotation.Nonnull;
@@ -65,9 +66,6 @@ import static org.junit.Assert.assertNul
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-/**
- * AbstractLoginModuleTest...
- */
 public class AbstractLoginModuleTest {
 
     private static AbstractLoginModule initLoginModule(Class supportedCredentials, Map sharedState) {
@@ -452,6 +450,31 @@ public class AbstractLoginModuleTest {
         assertTrue(principals.isEmpty());
     }
 
+    @Test
+    public void testGetPrincipalsFromPrincipal() {
+        PrincipalProvider principalProvider = new TestPrincipalProvider();
+
+        AbstractLoginModule loginModule = initLoginModule(TestCredentials.class, new TestCallbackHandler(principalProvider));
+
+        Principal principal = principalProvider.findPrincipals(PrincipalManager.SEARCH_TYPE_NOT_GROUP).next();
+        Set<Principal> expected = new HashSet<>();
+        expected.add(principal);
+        expected.addAll(principalProvider.getGroupMembership(principal));
+
+        Set<? extends Principal> principals = loginModule.getPrincipals(principal);
+
+        assertFalse(principals.isEmpty());
+        assertEquals(expected, principals);
+    }
+
+    @Test
+    public void testGetPrincipalsFromPrincipalMissingProvider() {
+        AbstractLoginModule loginModule = initLoginModule(TestCredentials.class, new TestCallbackHandler());
+
+        Set<? extends Principal> principals = loginModule.getPrincipals(new PrincipalImpl("principalName"));
+        assertTrue(principals.isEmpty());
+    }
+
     @Test
     public void testSetAuthInfo() {
         Subject subject = new Subject();

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryTest.java?rev=1785855&r1=1785854&r2=1785855&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardUserAuthenticationFactoryTest.java Tue Mar  7 16:35:35 2017
@@ -20,7 +20,6 @@ import java.util.ArrayList;
 import java.util.List;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
-import javax.jcr.Credentials;
 
 import org.apache.jackrabbit.oak.api.Root;
 import org.apache.jackrabbit.oak.spi.security.authentication.Authentication;
@@ -106,12 +105,7 @@ public class WhiteboardUserAuthenticatio
         @Override
         public Authentication getAuthentication(@Nonnull UserConfiguration configuration, @Nonnull Root root, @Nullable String userId) {
             if (this.userId.equals(userId)) {
-                return new Authentication() {
-                    @Override
-                    public boolean authenticate(@Nullable Credentials credentials) {
-                        return true;
-                    }
-                };
+                return Mockito.mock(Authentication.class);
             } else {
                 return null;
             }