/*
* 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.jdbc;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.JdbcUtils;
import org.apache.shiro.util.ThreadContext;
import org.hsqldb.jdbc.jdbcDataSource;
import org.junit.*;
import org.junit.rules.TestName;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
/**
* Test case for JDBCRealm.
*/
public class JDBCRealmTest {
protected DefaultSecurityManager securityManager = null;
protected AuthorizingRealm realm;
protected final String username = "testUser";
protected final String plainTextPassword = "testPassword";
protected final String salt = username; //Default impl of getSaltForUser returns username
protected final String testRole = "testRole";
protected final String testPermissionString = "testDomain:testTarget:testAction";
// Maps keyed on test method name so setup/teardown can manage per test resources
protected HashMap<String, JdbcRealm> realmMap = new HashMap<String, JdbcRealm>();
protected HashMap<String, DataSource> dsMap = new HashMap<String, DataSource>();
@Rule
public TestName name = new TestName();
@Before
public void setup() {
ThreadContext.remove();
Ini config = new Ini();
config.setSectionProperty("main", "myRealm", "org.apache.shiro.realm.jdbc.JdbcRealm");
config.setSectionProperty("main", "myRealmCredentialsMatcher", "org.apache.shiro.authc.credential.Sha256CredentialsMatcher");
config.setSectionProperty("main", "myRealm.credentialsMatcher", "$myRealmCredentialsMatcher");
config.setSectionProperty("main", "securityManager.sessionManager.sessionValidationSchedulerEnabled", "false");
IniSecurityManagerFactory factory = new IniSecurityManagerFactory(config);
securityManager = (DefaultSecurityManager) factory.createInstance();
SecurityUtils.setSecurityManager(securityManager);
// Create a database and realm for the test
createRealm(name.getMethodName());
}
@After
public void tearDown() {
final String testName = name.getMethodName();
shutDown(testName);
SecurityUtils.setSecurityManager(null);
securityManager.destroy();
ThreadContext.remove();
}
@Test
public void testUnSaltedSuccess() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
currentUser.logout();
}
@Test
public void testUnSaltedWrongPassword() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ex) {
// Expected
}
}
@Test
public void testUnSaltedMultipleRows() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
Connection conn = dsMap.get(testMethodName).getConnection();
Statement sql = conn.createStatement();
sql.executeUpdate("insert into users values ('" + username + "', 'dupe')");
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
try {
currentUser.login(token);
} catch (AuthenticationException ex) {
// Expected
}
}
@Test
public void testSaltColumnSuccess() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createSaltColumnSchema(testMethodName);
realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
currentUser.logout();
}
@Test
public void testSaltColumnWrongPassword() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createSaltColumnSchema(testMethodName);
realm.setSaltStyle(JdbcRealm.SaltStyle.COLUMN);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ex) {
// Expected
}
}
@Test
public void testExternalSuccess() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, true);
realm.setSaltStyle(JdbcRealm.SaltStyle.EXTERNAL);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
currentUser.logout();
}
@Test
public void testExternalWrongPassword() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, true);
realm.setSaltStyle(JdbcRealm.SaltStyle.EXTERNAL);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, "passwrd");
try {
currentUser.login(token);
} catch (IncorrectCredentialsException ex) {
// Expected
}
}
@Test
public void testRolePresent() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
Assert.assertTrue(currentUser.hasRole(testRole));
}
@Test
public void testRoleNotPresent() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
Assert.assertFalse(currentUser.hasRole("Game Overall Director"));
}
@Test
public void testPermissionPresent() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
realm.setPermissionsLookupEnabled(true);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
Assert.assertTrue(currentUser.isPermitted(testPermissionString));
}
@Test
public void testPermissionNotPresent() throws Exception {
String testMethodName = name.getMethodName();
JdbcRealm realm = realmMap.get(testMethodName);
createDefaultSchema(testMethodName, false);
realm.setSaltStyle(JdbcRealm.SaltStyle.NO_SALT);
realm.setPermissionsLookupEnabled(true);
Subject.Builder builder = new Subject.Builder(securityManager);
Subject currentUser = builder.buildSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, plainTextPassword);
currentUser.login(token);
Assert.assertFalse(currentUser.isPermitted("testDomain:testTarget:specialAction"));
}
/**
* Creates a realm for a test method and puts it in the realMap.
*/
protected void createRealm(String testMethodName) {
JdbcRealm realm = (JdbcRealm) securityManager.getRealms().iterator().next();
realmMap.put(testMethodName, realm);
}
/**
* Shuts down the database and removes the realm from the realm map.
*/
protected void shutDown(String testName) {
Connection conn = null;
Statement sql = null;
DataSource ds = dsMap.get(testName);
try {
Connection c = ds.getConnection();
Statement s = c.createStatement();
s.executeUpdate("SHUTDOWN");
} catch (SQLException ex) {
// ignore
} finally {
JdbcUtils.closeStatement(sql);
JdbcUtils.closeConnection(conn);
dsMap.remove(testName);
realmMap.remove(testName);
}
}
/**
* Creates a test database with the default (no separate salt column) schema, salting with
* username if salted is true. Sets the DataSource of the realm associated with the test
* to a DataSource connected to the database. (To prevent concurrency problems when tests
* are executed in multithreaded mode, each test method gets its own database.)
*/
protected void createDefaultSchema(String testName, boolean salted) {
jdbcDataSource ds = new jdbcDataSource();
ds.setDatabase("jdbc:hsqldb:mem:" + name);
ds.setUser("SA");
ds.setPassword("");
Connection conn = null;
Statement sql = null;
try {
conn = ds.getConnection();
sql = conn.createStatement();
sql.executeUpdate("create table users (username varchar(20), password varchar(20))");
Sha256Hash sha256Hash = salted ? new Sha256Hash(plainTextPassword, salt) :
new Sha256Hash(plainTextPassword);
String password = sha256Hash.toHex();
sql.executeUpdate("insert into users values ('" + username + "', '" + password + "')");
} catch (SQLException ex) {
Assert.fail("Exception creating test database");
} finally {
JdbcUtils.closeStatement(sql);
JdbcUtils.closeConnection(conn);
}
createRolesAndPermissions(ds);
realmMap.get(testName).setDataSource(ds);
dsMap.put(testName, ds);
}
/**
* Creates a test database with a separate salt column in the users table. Sets the
* DataSource of the realm associated with the test to a DataSource connected to the database.
*/
protected void createSaltColumnSchema(String testName) {
jdbcDataSource ds = new jdbcDataSource();
ds.setDatabase("jdbc:hsqldb:mem:" + name);
ds.setUser("SA");
ds.setPassword("");
Connection conn = null;
Statement sql = null;
try {
conn = ds.getConnection();
sql = conn.createStatement();
sql.executeUpdate(
"create table users (username varchar(20), password varchar(20), password_salt varchar(20))");
Sha256Hash sha256Hash = new Sha256Hash(plainTextPassword, salt);
String password = sha256Hash.toHex();
sql.executeUpdate("insert into users values ('" + username + "', '" + password + "', '" + salt + "')");
} catch (SQLException ex) {
Assert.fail("Exception creating test database");
} finally {
JdbcUtils.closeStatement(sql);
JdbcUtils.closeConnection(conn);
}
createRolesAndPermissions(ds);
realmMap.get(testName).setDataSource(ds);
dsMap.put(testName, ds);
}
/**
* Creates and adds test data to user_role and roles_permissions tables.
*/
protected void createRolesAndPermissions(DataSource ds) {
Connection conn = null;;
Statement sql = null;
try {
conn = ds.getConnection();
sql = conn.createStatement();
sql.executeUpdate("create table user_roles (username varchar(20), role_name varchar(20))");
sql.executeUpdate("insert into user_roles values ('" + username + "', '" + testRole + "')");
sql.executeUpdate("create table roles_permissions (role_name varchar(20), permission varchar(40))");
sql.executeUpdate(
"insert into roles_permissions values ('" + testRole + "', '" + testPermissionString + "')");
} catch (SQLException ex) {
Assert.fail("Exception adding test role and permission");
} finally {
JdbcUtils.closeStatement(sql);
JdbcUtils.closeConnection(conn);
}
}
}