package org.apereo.cas.adaptors.jdbc; import org.apereo.cas.authentication.CoreAuthenticationTestUtils; import org.apereo.cas.authentication.HandlerResult; import org.apereo.cas.authentication.PreventedException; import org.apereo.cas.authentication.exceptions.AccountDisabledException; import org.apereo.cas.authentication.exceptions.AccountPasswordMustChangeException; import org.apereo.cas.configuration.support.Beans; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.autoconfigure.RefreshAutoConfiguration; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.security.auth.login.AccountNotFoundException; import javax.security.auth.login.FailedLoginException; import javax.sql.DataSource; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.sql.Connection; import java.sql.Statement; import java.util.Arrays; import java.util.Collections; import static org.junit.Assert.*; /** * This is tests for {@link QueryDatabaseAuthenticationHandler}. * * @author Misagh Moayyed mmoayyed@unicon.net * @since 4.0.0 */ @RunWith(SpringRunner.class) @SpringBootTest(classes = {RefreshAutoConfiguration.class}) @ContextConfiguration(locations = {"classpath:/jpaTestApplicationContext.xml"}) public class QueryDatabaseAuthenticationHandlerTests { private static final String SQL = "SELECT * FROM casusers where username=?"; private static final String PASSWORD_FIELD = "password"; @Rule public ExpectedException thrown = ExpectedException.none(); @Autowired @Qualifier("dataSource") private DataSource dataSource; @Before public void setUp() throws Exception { final Connection c = this.dataSource.getConnection(); final Statement s = c.createStatement(); c.setAutoCommit(true); s.execute(getSqlInsertStatementToCreateUserAccount(0, Boolean.FALSE.toString(), Boolean.FALSE.toString())); for (int i = 0; i < 10; i++) { s.execute(getSqlInsertStatementToCreateUserAccount(i, Boolean.FALSE.toString(), Boolean.FALSE.toString())); } s.execute(getSqlInsertStatementToCreateUserAccount(20, Boolean.TRUE.toString(), Boolean.FALSE.toString())); s.execute(getSqlInsertStatementToCreateUserAccount(21, Boolean.FALSE.toString(), Boolean.TRUE.toString())); c.close(); } @After public void tearDown() throws Exception { final Connection c = this.dataSource.getConnection(); final Statement s = c.createStatement(); c.setAutoCommit(true); for (int i = 0; i < 5; i++) { s.execute("delete from casusers;"); } c.close(); } private static String getSqlInsertStatementToCreateUserAccount(final int i, final String expired, final String disabled) { return String.format("insert into casusers (username, password, expired, disabled, phone) values('%s', '%s', '%s', '%s', '%s');", "user" + i, "psw" + i, expired, disabled, "123456789"); } @Entity(name = "casusers") public static class UsersTable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String username; @Column private String password; @Column private String expired; @Column private String disabled; @Column private String phone; } @Test public void verifyAuthenticationFailsToFindUser() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, null, null, Collections.emptyMap()); this.thrown.expect(AccountNotFoundException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("usernotfound", "psw1")); } @Test public void verifyPasswordInvalid() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, null, null, Collections.emptyMap()); this.thrown.expect(FailedLoginException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user1", "psw11")); } @Test public void verifyMultipleRecords() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, null, null, Collections.emptyMap()); this.thrown.expect(FailedLoginException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "psw0")); } @Test public void verifyBadQuery() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL.replace("*", "error"), PASSWORD_FIELD, null, null, Collections.emptyMap()); this.thrown.expect(PreventedException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "psw0")); } @Test public void verifySuccess() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, null, null, Beans.transformPrincipalAttributesListIntoMap(Arrays.asList("phone:phoneNumber"))); final HandlerResult result = q.authenticate( CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user3", "psw3")); assertNotNull(result); assertNotNull(result.getPrincipal()); assertTrue(result.getPrincipal().getAttributes().containsKey("phoneNumber")); } @Test public void verifyFindUserAndExpired() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, "expired", null, Collections.emptyMap()); this.thrown.expect(AccountPasswordMustChangeException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user20", "psw20")); fail("Shouldn't get here"); } @Test public void verifyFindUserAndDisabled() throws Exception { final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, SQL, PASSWORD_FIELD, null, "disabled", Collections.emptyMap()); this.thrown.expect(AccountDisabledException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user21", "psw21")); fail("Shouldn't get here"); } /** * This test proves that in case BCRYPT is used authentication using encoded password always fail * with FailedLoginException * * @throws Exception in case encoding fails */ @Test public void verifyBCryptFail() throws Exception { final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(8, new SecureRandom("secret".getBytes(StandardCharsets.UTF_8))); final String sql = SQL.replace("*", "'" + encoder.encode("pswbc1") + "' password"); final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, sql, PASSWORD_FIELD, null, null, Collections.emptyMap()); q.setPasswordEncoder(encoder); this.thrown.expect(FailedLoginException.class); q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user0", "pswbc1")); } /** * This test proves that in case BCRYPT and * using raw password test can authenticate */ @Test public void verifyBCryptSuccess() throws Exception { final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(6, new SecureRandom("secret2".getBytes(StandardCharsets.UTF_8))); final String sql = SQL.replace("*", "'" + encoder.encode("pswbc2") + "' password"); final QueryDatabaseAuthenticationHandler q = new QueryDatabaseAuthenticationHandler("", null, null, null, this.dataSource, sql, PASSWORD_FIELD, null, null, Collections.emptyMap()); q.setPasswordEncoder(encoder); assertNotNull(q.authenticate(CoreAuthenticationTestUtils.getCredentialsWithDifferentUsernameAndPassword("user3", "pswbc2"))); } }