/*
* 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.shiro.realm.text;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.junit.Test;
import java.text.ParseException;
import java.util.Arrays;
import static org.junit.Assert.*;
public class TextConfigurationRealmTest {
private TestRealm realm;
private void setRoles() {
StringBuilder roleDefinitions = new StringBuilder()
.append("role1 = role1_permission1\n")
.append("role2 = role2_persission1, role2_permission2\n");
realm.setRoleDefinitions(roleDefinitions.toString());
}
private void setUsers() {
StringBuilder userDefinitions = new StringBuilder();
for (int i = 1; i < 3; i++) {
userDefinitions.append(String.format("user%1$d = user%1$d_password, role1, role2%n", i));
}
realm.setUserDefinitions(userDefinitions.toString());
}
private void setUpForReadConfigurationTest() {
realm = new TestRealm() {
/*
* Demonstrates that a lock can't be obtained on the realm by a read thread until after
* the lock is released.
*/
public void test(Thread runnable) throws InterruptedException {
// Obtain the realm's locks
USERS_LOCK.writeLock().lock();
try {
ROLES_LOCK.writeLock().lock();
try {
// Any read threads attempting to obtain the realms lock will block
runnable.start();
Thread.sleep(500);
// Process role and user definitions
realm.onInit();
} finally {
ROLES_LOCK.writeLock().unlock();
}
} finally {
USERS_LOCK.writeLock().unlock();
}
}
};
setRoles();
setUsers();
}
/*
* Executes a test that attempts to read to read from a realm before it is loaded.
*/
private void executeTest(Runnable runnable) throws InterruptedException {
TestThread testThread = new TestThread(runnable);
Thread testTask = new Thread(testThread);
realm.test(testTask);
testTask.join(500);
// Check whether any assertion error was thrown by the read thread
testThread.test();
}
/*
* Tests that roles and account can't be tested while the realm is being loaded.
*/
@Test
public void testRoleAndUserAccount() throws InterruptedException {
setUpForReadConfigurationTest();
executeTest(new Runnable() {
public void run() {
assertTrue("role not found when it was expected", realm.roleExists("role1"));
assertTrue("user not found when it was expected", realm.accountExists("user1"));
}
});
}
/*
* Tests that roles can't be read while the realm is being loaded.
*/
@Test
public void testHasRole() throws InterruptedException {
setUpForReadConfigurationTest();
executeTest(new Runnable() {
public void run() {
PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
assertTrue("principal doesn't have role when it should",
realm.hasRole(principalCollection, "role2"));
assertTrue("principal doesn't have all roles when it should",
realm.hasAllRoles(principalCollection, Arrays.asList(new String[]{"role1", "role2"})));
}
});
}
/*
* Tests that roles can't be checked while the realm is being loaded.
*/
@Test
public void testCheckRole() throws InterruptedException {
setUpForReadConfigurationTest();
executeTest(new Runnable() {
public void run() {
PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
try {
realm.checkRoles(principalCollection, new String[]{"role1", "role2"});
} catch (AuthorizationException ae) {
fail("principal doesn't have all roles when it should");
}
}
});
}
/*
* Tests that a principal's permissions can't be checked while the realm is being loaded.
*/
@Test
public void testCheckPermission() throws InterruptedException {
setUpForReadConfigurationTest();
executeTest(new Runnable() {
public void run() {
PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
try {
realm.checkPermission(principalCollection, "role1_permission1");
realm.checkPermissions(principalCollection, new String[]{"role1_permission1", "role2_permission2"});
} catch (AuthorizationException ae) {
fail("principal doesn't have permission when it should");
}
}
});
}
/*
* Tests that a principal's permissions can't be checked while the realm is being loaded.
*/
@Test
public void testIsPermitted() throws InterruptedException {
setUpForReadConfigurationTest();
executeTest(new Runnable() {
public void run() {
PrincipalCollection principalCollection = new SimplePrincipalCollection("user1", "realm1");
assertTrue("permission not permitted when it should be", realm.isPermitted(principalCollection, "role1_permission1"));
assertTrue("permission not permitted when it should be",
realm.isPermittedAll(principalCollection, new String[]{"role1_permission1", "role2_permission2"}));
}
});
}
/*
* Test that role definitions cannot be updated when a read thread holds the realm's lock.
*/
@Test
public void testProcessRoleDefinitions() throws InterruptedException {
realm = new TestRealm() {
public void test(Thread runnable) throws InterruptedException {
// While the realm's lock is held by this thread role definitions cannot be processed
// Obtain the realm's locks
ROLES_LOCK.writeLock().lock();
try {
runnable.start();
Thread.sleep(500);
// No role until lock is released and role definitions are processed
assertFalse("role exists when it shouldn't", realm.roleExists("role1"));
} finally {
ROLES_LOCK.writeLock().unlock();
}
}
};
// A thread to process new role definitions
TestThread testThread = new TestThread(new Runnable() {
public void run() {
try {
realm.processRoleDefinitions();
} catch (ParseException e) {
fail("Unable to parse role definitions");
}
}
});
setRoles();
Thread testTask = new Thread(testThread);
realm.test(testTask);
testTask.join(500);
assertTrue("role doesn't exist when it should", realm.roleExists("role1"));
testThread.test();
}
/*
* Test that user definitions cannot be updated when a read thread holds the realm's lock.
*/
@Test
public void testProcessUserDefinitions() throws InterruptedException {
realm = new TestRealm() {
public void test(Thread runnable) throws InterruptedException {
// While the realm's lock is held by this thread user definitions cannot be processed
// Obtain the realm's locks
USERS_LOCK.writeLock().lock();
try {
runnable.start();
Thread.sleep(500);
// No account until lock is released and user definitions are processed
assertFalse("account exists when it shouldn't", realm.accountExists("user1"));
} finally {
USERS_LOCK.writeLock().unlock();
}
}
};
TestThread testThread = new TestThread(new Runnable() {
public void run() {
try {
realm.processUserDefinitions();
} catch (ParseException e) {
fail("Unable to parse user definitions");
}
}
});
setUsers();
Thread testTask = new Thread(testThread);
realm.test(testTask);
testTask.join(500);
assertTrue("account doesn't exist when it should", realm.accountExists("user1"));
testThread.test();
}
/*
* A Class that captures a thread's assertion error.
*/
private class TestThread implements Runnable {
private Runnable test;
private volatile AssertionError ae;
public TestThread(Runnable test) {
this.test = test;
}
public void run() {
try {
test.run();
} catch (AssertionError ae) {
this.ae = ae;
}
}
public void test() {
if (ae != null)
throw ae;
}
}
/*
* Provides an additional method that has access to the realm's lock for mutual exclusion.
*/
private abstract class TestRealm extends TextConfigurationRealm {
abstract public void test(Thread runnable) throws InterruptedException;
}
}