/*
* 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.core.security.authentication.token;
import org.apache.jackrabbit.api.JackrabbitSession;
import org.apache.jackrabbit.api.security.authentication.token.TokenCredentials;
import org.apache.jackrabbit.api.security.principal.ItemBasedPrincipal;
import org.apache.jackrabbit.api.security.user.User;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.SessionImpl;
import org.apache.jackrabbit.test.AbstractJCRTest;
import org.apache.jackrabbit.test.NotExecutableException;
import org.apache.jackrabbit.test.RepositoryStub;
import javax.jcr.LoginException;
import javax.jcr.Node;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
/**
* <code>TokenBasedLoginTest</code>...
*/
public class TokenBasedLoginTest extends AbstractJCRTest {
private static final String TOKENS_NAME = ".tokens";
private static final String TOKEN_ATTRIBUTE = TokenBasedAuthentication.TOKEN_ATTRIBUTE;
private User testuser;
private String testuserPath;
private SimpleCredentials creds;
private boolean doSave;
@Override
protected void setUp() throws Exception {
super.setUp();
if (superuser instanceof JackrabbitSession) {
UserManager umgr = ((JackrabbitSession) superuser).getUserManager();
String uid = "test";
while (umgr.getAuthorizable(uid) != null) {
uid += "_";
}
testuser = umgr.createUser(uid, uid);
Principal p = testuser.getPrincipal();
if (p instanceof ItemBasedPrincipal) {
testuserPath = ((ItemBasedPrincipal) p).getPath();
} else {
throw new NotExecutableException();
}
creds = new SimpleCredentials(uid, uid.toCharArray());
if (!umgr.isAutoSave()) {
doSave = true;
superuser.save();
}
} else {
throw new NotExecutableException();
}
}
@Override
protected void tearDown() throws Exception {
if (testuser != null) {
testuser.remove();
if (doSave) {
superuser.save();
}
}
super.tearDown();
}
public void testLogin() throws RepositoryException {
Repository repo = getHelper().getRepository();
// make sure regular simple login works.
Session s = repo.login(creds);
s.logout();
// test if token creation works.
creds.setAttribute(TOKEN_ATTRIBUTE, "");
// an additional attribute that must match
creds.setAttribute(TOKEN_ATTRIBUTE + ".any", "any");
// an attribute just for info purposes
creds.setAttribute("attr", "attr");
String token = null;
s = repo.login(creds);
try {
// token credentials must be created
Set<TokenCredentials> tokenCreds = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
assertFalse(tokenCreds.isEmpty());
assertEquals(1, tokenCreds.size());
TokenCredentials tc = tokenCreds.iterator().next();
token = tc.getToken();
// original simple credentials: token attribute should be updated
assertNotNull(creds.getAttribute(TOKEN_ATTRIBUTE));
assertFalse("".equals(creds.getAttribute(TOKEN_ATTRIBUTE)));
// simple credentials must also be present on the subject
Set<SimpleCredentials> scs = ((SessionImpl) s).getSubject().getPublicCredentials(SimpleCredentials.class);
assertFalse(scs.isEmpty());
assertEquals(1, scs.size());
SimpleCredentials sc = scs.iterator().next();
assertNotNull(sc.getAttribute(TOKEN_ATTRIBUTE));
assertFalse("".equals(sc.getAttribute(TOKEN_ATTRIBUTE)));
// test if session attributes only exposed non-mandatory attributes
assertNull(s.getAttribute(TOKEN_ATTRIBUTE));
for (String attrName : tc.getAttributeNames()) {
if (TokenBasedAuthentication.isMandatoryAttribute(attrName)) {
assertNull(s.getAttribute(attrName));
} else {
assertEquals(tc.getAttribute(attrName), s.getAttribute(attrName));
}
}
// only test node characteristics if user-node resided within the same
// workspace as 'superuser' has been created for.
if (superuser.nodeExists(testuserPath)) {
Node userNode = superuser.getNode(testuserPath);
assertTrue(userNode.hasNode(TOKENS_NAME));
Node tNode = userNode.getNode(TOKENS_NAME);
assertTrue(tNode.hasNodes());
Node ttNode = tNode.getNodes().nextNode();
assertTrue(ttNode.hasProperty("attr"));
assertEquals("attr", ttNode.getProperty("attr").getString());
assertTrue(ttNode.hasProperty(TOKEN_ATTRIBUTE + ".any"));
assertEquals("any", ttNode.getProperty(TOKEN_ATTRIBUTE + ".any").getString());
String id = ttNode.getIdentifier();
assertTrue(token.startsWith(id));
}
} finally {
s.logout();
}
// login with token only must succeed as well.
TokenCredentials tokenOnly = new TokenCredentials(token);
tokenOnly.setAttribute(TOKEN_ATTRIBUTE + ".any", "any");
s = repo.login(tokenOnly);
try {
assertEquals(creds.getUserID(), s.getUserID());
Set<TokenCredentials> tokenCreds = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
assertFalse(tokenCreds.isEmpty());
assertEquals(1, tokenCreds.size());
TokenCredentials tc = tokenCreds.iterator().next();
String tk = tc.getToken();
assertEquals(token, tk);
assertNull(s.getAttribute(TOKEN_ATTRIBUTE));
for (String attrName : tc.getAttributeNames()) {
if (TokenBasedAuthentication.isMandatoryAttribute(attrName)) {
assertNull(s.getAttribute(attrName));
} else {
assertEquals(tc.getAttribute(attrName), s.getAttribute(attrName));
}
}
} finally {
s.logout();
}
// the non-mandatory attribute may have any value if present with the creds.
tokenOnly.setAttribute("attr", "another");
s = repo.login(tokenOnly);
try {
assertEquals(creds.getUserID(), s.getUserID());
} finally {
s.logout();
tokenOnly.removeAttribute("attr");
}
// login with token but wrong mandatory attribute
tokenOnly.setAttribute(TOKEN_ATTRIBUTE + ".any", "another");
try {
s = repo.login(tokenOnly);
s.logout();
fail("The additional mandatory attr doesn't match. login must fail.");
} catch (LoginException e) {
// success
}
// login with token but missing the mandatory attribute
tokenOnly.removeAttribute(TOKEN_ATTRIBUTE + ".any");
try {
s = repo.login(tokenOnly);
s.logout();
fail("The additional mandatory attr is missing. login must fail.");
} catch (LoginException e) {
// success
}
}
/**
* Tests concurrent login on the Repository including token creation.
* Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
*/
public void testConcurrentLogin() throws RepositoryException, NotExecutableException {
final Exception[] exception = new Exception[1];
List<Thread> testRunner = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
testRunner.add(new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
try {
SimpleCredentials sc = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
Session s = getHelper().getRepository().login(sc);
try {
Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
assertFalse(tcs.isEmpty());
} finally {
s.logout();
}
} catch (Exception e) {
exception[0] = e;
break;
}
}
}
}));
}
// start threads
for (Object aTestRunner : testRunner) {
((Thread) aTestRunner).start();
}
// join threads
for (Object aTestRunner : testRunner) {
try {
((Thread) aTestRunner).join();
} catch (InterruptedException e) {
fail(e.toString());
}
}
if (exception[0] != null) {
fail(exception[0].toString());
}
}
/**
* Tests concurrent login of 3 different users on the Repository including
* token creation.
* Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
*/
public void testConcurrentLoginOfDifferentUsers() throws RepositoryException, NotExecutableException {
final Exception[] exception = new Exception[1];
List<Thread> testRunner = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
testRunner.add(new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
try {
SimpleCredentials c;
double rand = 3 * Math.random();
int index = (int) Math.floor(rand);
switch (index) {
case 0:
c = new SimpleCredentials(testuser.getID(), testuser.getID().toCharArray());
break;
case 1:
c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_SUPERUSER_PWD).toCharArray());
break;
default:
c = new SimpleCredentials(getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_NAME), getHelper().getProperty(RepositoryStub.PROP_PREFIX + "." + RepositoryStub.PROP_READONLY_PWD).toCharArray());
break;
}
c.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
Session s = getHelper().getRepository().login(c);
try {
Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
assertFalse(tcs.isEmpty());
} finally {
s.logout();
}
} catch (Exception e) {
exception[0] = e;
break;
}
}
}
}));
}
// start threads
for (Object aTestRunner : testRunner) {
((Thread) aTestRunner).start();
}
// join threads
for (Object aTestRunner : testRunner) {
try {
((Thread) aTestRunner).join();
} catch (InterruptedException e) {
fail(e.toString());
}
}
if (exception[0] != null) {
fail(exception[0].toString());
}
}
/**
* Tests concurrent login on the Repository including token creation.
* Test copied and slightly adjusted from org.apache.jackrabbit.core.ConcurrentLoginTest
*/
public void testConcurrentLoginDifferentWorkspaces() throws RepositoryException, NotExecutableException {
final String testID = testuser.getID();
// check if test is executable
// - multiple workspaces must be present
final List<String> wspNames = Arrays.asList(superuser.getWorkspace().getAccessibleWorkspaceNames());
if (wspNames.size() <= 1) {
throw new NotExecutableException();
}
// - testuser must be present for all workspaces
for (String wspName : wspNames) {
JackrabbitSession s = null;
try {
s = (JackrabbitSession) getHelper().getSuperuserSession(wspName);
if (s.getUserManager().getAuthorizable(testID) == null) {
throw new NotExecutableException();
}
} finally {
if (s != null) {
s.logout();
}
}
}
final Exception[] exception = new Exception[1];
List<Thread> testRunner = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
testRunner.add(new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++) {
try {
double rand = wspNames.size() * Math.random();
int index = (int) Math.floor(rand);
String wspName = wspNames.get(index);
SimpleCredentials sc = new SimpleCredentials(testID, testID.toCharArray());
sc.setAttribute(TokenBasedAuthentication.TOKEN_ATTRIBUTE, "");
Session s = getHelper().getRepository().login(sc, wspName);
try {
Set<TokenCredentials> tcs = ((SessionImpl) s).getSubject().getPublicCredentials(TokenCredentials.class);
assertFalse(tcs.isEmpty());
} finally {
s.logout();
}
} catch (Exception e) {
exception[0] = e;
break;
}
}
}
}));
}
// start threads
for (Object aTestRunner : testRunner) {
((Thread) aTestRunner).start();
}
// join threads
for (Object aTestRunner : testRunner) {
try {
((Thread) aTestRunner).join();
} catch (InterruptedException e) {
fail(e.toString());
}
}
if (exception[0] != null) {
fail(exception[0].toString());
}
}
}