diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java index 8b9a1cb..bf60fae 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModule.java @@ -26,6 +26,7 @@ import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.jcr.Credentials; +import javax.jcr.SimpleCredentials; import javax.security.auth.callback.Callback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.auth.login.LoginException; @@ -36,6 +37,7 @@ import org.apache.jackrabbit.oak.api.Root; import org.apache.jackrabbit.oak.spi.security.SecurityProvider; import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule; import org.apache.jackrabbit.oak.spi.security.authentication.AuthInfoImpl; +import org.apache.jackrabbit.oak.spi.security.authentication.ImpersonationCredentials; import org.apache.jackrabbit.oak.spi.security.authentication.callback.TokenProviderCallback; import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenConfiguration; import org.apache.jackrabbit.oak.spi.security.authentication.token.TokenInfo; @@ -163,7 +165,31 @@ public final class TokenLoginModule extends AbstractLoginModule { if (r != null) { r.refresh(); // refresh root, in case the external login module created users } - TokenInfo ti = tokenProvider.createToken(shared); + + TokenInfo ti = null; + + SimpleCredentials sc = extractSimpleCredentials(shared); + if (sc != null) { + String[] attrNames = sc.getAttributeNames(); + Map attributes = new HashMap(attrNames.length); + for (String attrName : sc.getAttributeNames()) { + attributes.put(attrName, sc.getAttribute(attrName).toString()); + } + + // shared login name can override userid from SimpleCredentials + String userId = getSharedLoginName(); + if (userId == null) { + userId = sc.getUserID(); + } + + // create the token + ti = tokenProvider.createToken(userId, attributes); + if (ti != null) { + // also pass back the new token in the simple credentials. + sc.setAttribute(TokenConstants.TOKEN_ATTRIBUTE, ti.getToken()); + } + } + if (ti != null) { TokenCredentials tc = new TokenCredentials(ti.getToken()); Map attributes = ti.getPrivateAttributes(); @@ -270,4 +296,21 @@ public final class TokenLoginModule extends AbstractLoginModule { } } } + + @CheckForNull + private static SimpleCredentials extractSimpleCredentials(Credentials credentials) { + if (credentials instanceof SimpleCredentials) { + return (SimpleCredentials) credentials; + } + + if (credentials instanceof ImpersonationCredentials) { + Credentials base = ((ImpersonationCredentials) credentials).getBaseCredentials(); + if (base instanceof SimpleCredentials) { + return (SimpleCredentials) base; + } + } + + // cannot extract SimpleCredentials + return null; + } } diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModuleTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModuleTest.java index 3c47110..7ac88cc 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModuleTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/security/authentication/token/TokenLoginModuleTest.java @@ -17,22 +17,35 @@ package org.apache.jackrabbit.oak.security.authentication.token; import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.jcr.Credentials; import javax.jcr.GuestCredentials; import javax.jcr.SimpleCredentials; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.AppConfigurationEntry; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials; import org.apache.jackrabbit.oak.AbstractSecurityTest; import org.apache.jackrabbit.oak.api.ContentSession; import org.apache.jackrabbit.oak.api.Root; +import org.apache.jackrabbit.oak.spi.security.authentication.AbstractLoginModule; +import org.apache.jackrabbit.oak.spi.security.authentication.AuthInfoImpl; 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.apache.jackrabbit.oak.spi.security.user.util.UserUtil; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNotSame; import static org.junit.Assert.fail; /** @@ -150,4 +163,105 @@ public class TokenLoginModuleTest extends AbstractSecurityTest { cs.close(); } } + + @Test + public void testSimpleCredentialsDifferentLoginName() throws Exception { + Configuration.setConfiguration(new Configuration() { + @Override + public AppConfigurationEntry[] getAppConfigurationEntry(String s) { + AppConfigurationEntry firstEntry = new AppConfigurationEntry( + TestLoginModule.class.getName(), + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + Collections.emptyMap()); + AppConfigurationEntry secondEntry = new AppConfigurationEntry( + TokenLoginModule.class.getName(), + AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, + Collections.emptyMap()); + + return new AppConfigurationEntry[] {firstEntry, secondEntry}; + } + }); + + // need to use a user that actually exists, thus leverage admin user + final String userId = UserUtil.getAdminId(getUserConfiguration().getParameters()); + + // note the null userId here + SimpleCredentials credentials = new SimpleCredentials(null, new char[0]); + // pass actual user id in a non standard attribute to our TestLoginModule + credentials.setAttribute(TestLoginModule.CUSTOM_USER_ID_ATTR, userId); + credentials.setAttribute(TokenConstants.TOKEN_ATTRIBUTE, ""); + + ContentSession cs = null; + try { + cs = login(credentials); + assertEquals(userId, cs.getAuthInfo().getUserID()); + + // verify token has been created and passed back in credentials object + Object token = credentials.getAttribute(TokenConstants.TOKEN_ATTRIBUTE); + assertNotNull("token was not created", token); + assertNotSame("token was not created", "", token); + } catch (LoginException e) { + fail("token was not created: " + e.getMessage()); + } finally { + if (cs != null) { + cs.close(); + } + } + } + + public static class TestLoginModule extends AbstractLoginModule { + + public static final String CUSTOM_USER_ID_ATTR = "custom.user.id"; + + private Subject subject; + private Map sharedState; + private String userId; + + public TestLoginModule() { + } + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { + super.initialize(subject, callbackHandler, sharedState, options); + this.subject = subject; + this.sharedState = sharedState; + } + + @SuppressWarnings("unchecked") + @Override + public boolean login() throws LoginException { + Credentials credentials = getCredentials(); + SimpleCredentials simpleCredentials = (SimpleCredentials) credentials; + + userId = (String) simpleCredentials.getAttribute(CUSTOM_USER_ID_ATTR); + sharedState.put(SHARED_KEY_CREDENTIALS, credentials); + + // this is the clue, setting a userId that is + sharedState.put(SHARED_KEY_LOGIN_NAME, userId); + return true; + } + + @Override + public boolean commit() throws LoginException { + subject.getPublicCredentials().add(new AuthInfoImpl(userId, null, null)); + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + return true; + } + + @Nonnull + @Override + protected Set getSupportedCredentials() { + Class scClass = SimpleCredentials.class; + return Collections.singleton(scClass); + } + } } \ No newline at end of file