/*******************************************************************************
* Cloud Foundry
* Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product includes a number of subcomponents with
* separate copyright notices and license terms. Your use of these
* subcomponents is subject to the terms and conditions of the
* subcomponent's license, as noted in the LICENSE file.
*******************************************************************************/
package org.cloudfoundry.identity.uaa.audit;
import org.cloudfoundry.identity.uaa.test.JdbcTestBase;
import org.cloudfoundry.identity.uaa.util.TimeService;
import org.cloudfoundry.identity.uaa.zone.IdentityZone;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.util.ReflectionTestUtils;
import java.sql.Timestamp;
import java.util.List;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.ClientAuthenticationFailure;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.ClientAuthenticationSuccess;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.PasswordChangeSuccess;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.SecretChangeSuccess;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationFailure;
import static org.cloudfoundry.identity.uaa.audit.AuditEventType.UserAuthenticationSuccess;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
public class JdbcFailedLoginCountingAuditServiceTests extends JdbcTestBase {
private JdbcFailedLoginCountingAuditService auditService;
private String authDetails;
private JdbcTemplate template;
@Before
public void createService() throws Exception {
template = spy(jdbcTemplate);
auditService = new JdbcFailedLoginCountingAuditService(template);
jdbcTemplate.execute("DELETE FROM sec_audit WHERE principal_id='1' or principal_id='clientA' or principal_id='clientB'");
authDetails = "1.1.1.1";
}
@Test
public void userAuthenticationFailureAuditSucceeds() throws Exception {
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
Thread.sleep(100);
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
List<AuditEvent> events = auditService.find("1", 0);
assertEquals(2, events.size());
assertEquals("1", events.get(0).getPrincipalId());
assertEquals("joe", events.get(0).getData());
assertEquals("1.1.1.1", events.get(0).getOrigin());
}
@Test
public void userAuthenticationFailureDeletesOldData() throws Exception {
long now = System.currentTimeMillis();
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1));
ReflectionTestUtils.invokeMethod(ReflectionTestUtils.getField(auditService, "lastDelete"), "set", 0l);
// Set the created column to 25 hours past
jdbcTemplate.update("update sec_audit set created=?", new Timestamp(now - 25 * 3600 * 1000));
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1));
}
@Test
public void delete_happens_single_thread_on_intervals() throws Exception {
long now = System.currentTimeMillis();
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1));
// Set the created column to 25 hours past
jdbcTemplate.update("update sec_audit set created=?", new Timestamp(now - 25 * 3600 * 1000));
int count = 5;
for (int i = 0; i< count; i++) {
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
}
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(count+1));
ArgumentCaptor<String> queries = ArgumentCaptor.forClass(String.class);
verify(template, times(1)).update(queries.capture(), any(Timestamp.class));
}
@Test
public void periodic_delete_works() throws Exception {
for (int i=0; i<5; i++) {
auditService.periodicDelete();
}
verify(template, times(1)).update(anyString(), any(Timestamp.class));
// 30 seconds has passed
auditService.setTimeService(new TimeService() {
@Override
public long getCurrentTimeMillis() {
return System.currentTimeMillis() + (31 * 1000);
}
});
reset(template);
for (int i=0; i<5; i++) {
auditService.periodicDelete();
}
verify(template, times(1)).update(anyString(), any(Timestamp.class));
}
@Test
public void userAuthenticationSuccessResetsData() throws Exception {
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1));
auditService.log(getAuditEvent(UserAuthenticationSuccess, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(0));
}
@Test
public void userPasswordChangeSuccessResetsData() throws Exception {
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(1));
auditService.log(getAuditEvent(PasswordChangeSuccess, "1", "joe"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='1'", Integer.class), is(0));
}
@Test
public void findMethodOnlyReturnsEventsWithinRequestedPeriod() {
long now = System.currentTimeMillis();
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
// Set the created column to 2 hour past
jdbcTemplate.update("update sec_audit set created=?", new Timestamp(now - 3600 * 1000));
auditService.log(getAuditEvent(UserAuthenticationFailure, "1", "joe"));
auditService.log(getAuditEvent(UserAuthenticationFailure, "2", "joe"));
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
auditService.log(getAuditEvent(ClientAuthenticationFailure, "otherclient", "testman"));
// Find events within last 2 mins
List<AuditEvent> userEvents = auditService.find("1", now - 120 * 1000);
List<AuditEvent> clientEvents = auditService.find("client", now - 120 * 1000);
assertEquals(1, userEvents.size());
assertEquals(0, clientEvents.size());
}
@Test
public void clientAuthenticationFailureAuditSucceeds() throws Exception {
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
Thread.sleep(100);
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
verifyZeroInteractions(template);
}
@Test
public void clientAuthenticationFailureDeletesOldData() throws Exception {
long now = System.currentTimeMillis();
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
// Set the created column to 25 hours past
jdbcTemplate.update("update sec_audit set created=?", new Timestamp(now - 25 * 3600 * 1000));
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
verifyZeroInteractions(template);
}
@Test
public void clientAuthenticationSuccessResetsData() throws Exception {
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
auditService.log(getAuditEvent(ClientAuthenticationSuccess, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
verifyZeroInteractions(template);
}
@Test
public void clientSecretChangeSuccessResetsData() throws Exception {
auditService.log(getAuditEvent(ClientAuthenticationFailure, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
auditService.log(getAuditEvent(SecretChangeSuccess, "client", "testman"));
assertThat(jdbcTemplate.queryForObject("select count(*) from sec_audit where principal_id='client'", Integer.class), is(0));
verifyZeroInteractions(template);
}
private AuditEvent getAuditEvent(AuditEventType type, String principal, String data) {
return new AuditEvent(type, principal, authDetails, data, System.currentTimeMillis(), IdentityZone.getUaa().getId());
}
private AuditEvent getAuditEventForAltZone(AuditEventType type, String principal, String data) {
return new AuditEvent(type, principal, authDetails, data, System.currentTimeMillis(), "test-zone");
}
}