Skip to content

Commit

Permalink
Fixing the REST authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
semancik committed Mar 24, 2016
1 parent 59bd62e commit e48c2e6
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 47 deletions.
Expand Up @@ -87,13 +87,18 @@ public UsernamePasswordAuthenticationToken authenticateUserPassword(ConnectionEn
throws BadCredentialsException, AuthenticationCredentialsNotFoundException, DisabledException, LockedException,
CredentialsExpiredException, AuthenticationServiceException, AccessDeniedException, UsernameNotFoundException {
if (StringUtils.isBlank(enteredPassword)) {
recordAuthenticationFailure(enteredUsername, connEnv, "empty password provided");
throw new BadCredentialsException("web.security.provider.password.encoding");
}

MidPointPrincipal principal = getAndCheckPrincipal(connEnv, enteredUsername);

UserType userType = principal.getUser();
CredentialsType credentials = userType.getCredentials();
if (credentials == null) {
recordAuthenticationFailure(enteredUsername, connEnv, "no credentials in user");
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid");
}
PasswordType passwordType = credentials.getPassword();
SecurityPolicyType securityPolicy = principal.getApplicableSecurityPolicy();
PasswordCredentialsPolicyType passwordCredentialsPolicy = null;
Expand Down Expand Up @@ -147,6 +152,10 @@ public String getAndCheckUserPassword(ConnectionEnvironment connEnv, String ente

UserType userType = principal.getUser();
CredentialsType credentials = userType.getCredentials();
if (credentials == null) {
recordAuthenticationFailure(enteredUsername, connEnv, "no credentials in user");
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid");
}
PasswordType passwordType = credentials.getPassword();
SecurityPolicyType securityPolicy = principal.getApplicableSecurityPolicy();
PasswordCredentialsPolicyType passwordCredentialsPolicy = null;
Expand Down Expand Up @@ -204,15 +213,17 @@ private MidPointPrincipal getAndCheckPrincipal(ConnectionEnvironment connEnv, St
try {
principal = userProfileService.getPrincipal(enteredUsername);
} catch (ObjectNotFoundException e) {
recordAuthenticationFailure(null, connEnv, "no user");
recordAuthenticationFailure(enteredUsername, connEnv, "no user");
throw new UsernameNotFoundException("web.security.provider.invalid");
}

if (principal == null || principal.getUser() == null || principal.getUser().getCredentials() == null) {
throw new AuthenticationCredentialsNotFoundException("web.security.provider.invalid");
if (principal == null || principal.getUser() == null) {
recordAuthenticationFailure(enteredUsername, connEnv, "no user");
throw new UsernameNotFoundException("web.security.provider.invalid");
}

if (!principal.isEnabled()) {
recordAuthenticationFailure(enteredUsername, connEnv, "user disabled");
throw new DisabledException("web.security.provider.disabled");
}
return principal;
Expand Down Expand Up @@ -344,15 +355,17 @@ private void recordAuthenticationSuccess(MidPointPrincipal principal, Connection
private void recordPasswordAuthenticationFailure(MidPointPrincipal principal, ConnectionEnvironment connEnv,
PasswordType passwordType, PasswordCredentialsPolicyType passwordCredentialsPolicy, String reason) {
Integer failedLogins = passwordType.getFailedLogins();
Duration lockoutFailedAttemptsDuration = passwordCredentialsPolicy.getLockoutFailedAttemptsDuration();
if (lockoutFailedAttemptsDuration != null) {
LoginEventType lastFailedLogin = passwordType.getLastFailedLogin();
if (lastFailedLogin != null) {
XMLGregorianCalendar lastFailedLoginTs = lastFailedLogin.getTimestamp();
if (lastFailedLoginTs != null) {
XMLGregorianCalendar failedLoginsExpirationTs = XmlTypeConverter.addDuration(lastFailedLoginTs, lockoutFailedAttemptsDuration);
if (clock.isPast(failedLoginsExpirationTs)) {
failedLogins = 0;
if (passwordCredentialsPolicy != null) {
Duration lockoutFailedAttemptsDuration = passwordCredentialsPolicy.getLockoutFailedAttemptsDuration();
if (lockoutFailedAttemptsDuration != null) {
LoginEventType lastFailedLogin = passwordType.getLastFailedLogin();
if (lastFailedLogin != null) {
XMLGregorianCalendar lastFailedLoginTs = lastFailedLogin.getTimestamp();
if (lastFailedLoginTs != null) {
XMLGregorianCalendar failedLoginsExpirationTs = XmlTypeConverter.addDuration(lastFailedLoginTs, lockoutFailedAttemptsDuration);
if (clock.isPast(failedLoginsExpirationTs)) {
failedLogins = 0;
}
}
}
}
Expand All @@ -374,7 +387,11 @@ private void recordPasswordAuthenticationFailure(MidPointPrincipal principal, Co
}

private void recordAuthenticationFailure(MidPointPrincipal principal, ConnectionEnvironment connEnv, String reason) {
securityHelper.auditLoginFailure(principal.getUsername(), connEnv, reason);
securityHelper.auditLoginFailure(principal==null?null:principal.getUsername(), connEnv, reason);
}

private void recordAuthenticationFailure(String username, ConnectionEnvironment connEnv, String reason) {
securityHelper.auditLoginFailure(username, connEnv, reason);
}

}
Expand Up @@ -28,6 +28,7 @@
import org.apache.cxf.jaxrs.utils.JAXRSUtils;
import org.apache.cxf.message.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
Expand All @@ -46,13 +47,17 @@
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;

/**
* @author Katka Valalikova
* @author Radovan Semancik
*/
public class MidpointRestAuthenticationHandler implements ContainerRequestFilter, ContainerResponseFilter {

private static final Trace LOGGER = TraceManager.getTrace(MidpointRestAuthenticationHandler.class);

@Autowired(required=true)
private AuthenticationEvaluator authenticationEvaluator;
Expand All @@ -78,16 +83,20 @@ public void handleRequest(Message m, ContainerRequestContext requestCtx) {
return;
}

LOGGER.trace("Authenticating username '{}' to REST service", enteredUsername);

ConnectionEnvironment connEnv = createConnectionEnvironment();
String enteredPassword = policy.getPassword();
UsernamePasswordAuthenticationToken token;
try {
token = authenticationEvaluator.authenticateUserPassword(connEnv, enteredUsername, enteredPassword);
} catch (UsernameNotFoundException | BadCredentialsException e) {
LOGGER.trace("Exception while authenticating username '{}' to REST service: {}", enteredUsername, e.getMessage(), e);
requestCtx.abortWith(Response.status(401).header("WWW-Authenticate", "Basic authentication failed. Cannot authenticate user.").build());
return;
} catch (DisabledException | LockedException | CredentialsExpiredException
} catch (DisabledException | LockedException | CredentialsExpiredException | AccessDeniedException
| AuthenticationCredentialsNotFoundException | AuthenticationServiceException e) {
LOGGER.trace("Exception while authenticating username '{}' to REST service: {}", enteredUsername, e.getMessage(), e);
requestCtx.abortWith(Response.status(403).build());
return;
}
Expand All @@ -96,6 +105,8 @@ public void handleRequest(Message m, ContainerRequestContext requestCtx) {

m.put("authenticatedUser", user);
securityEnforcer.setupPreAuthenticatedSecurityContext(user.asPrismObject());

LOGGER.trace("Authenticated to REST service as {}", user);

OperationResult authorizeResult = new OperationResult("Rest authentication/authorization operation.");

Expand All @@ -112,6 +123,8 @@ public void handleRequest(Message m, ContainerRequestContext requestCtx) {
return;
}

LOGGER.trace("Authorized to use REST service ({})", user);

}

@Override
Expand Down
Expand Up @@ -32,6 +32,7 @@
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.testng.AssertJUnit;
Expand Down Expand Up @@ -254,13 +255,97 @@ public void test103PasswordLoginEmptyPasswordJack() throws Exception {
assertFailedLogins(userAfter, 1);
}

@Test
public void test105PasswordLoginNullUsernameNullPassword() throws Exception {
final String TEST_NAME = "test105PasswordLoginNullUsernameNullPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
ConnectionEnvironment connEnv = createConnectionEnvironment();

try {

// WHEN
TestUtil.displayWhen(TEST_NAME);

authenticationEvaluator.authenticateUserPassword(connEnv, null, null);

AssertJUnit.fail("Unexpected success");

} catch (BadCredentialsException e) {
// This is expected

// THEN
TestUtil.displayThen(TEST_NAME);
display("expected exception", e);
assertPasswordEncodingException(e, null);
}

}

@Test
public void test106PasswordLoginEmptyUsernameBadPassword() throws Exception {
final String TEST_NAME = "test106PasswordLoginEmptyUsernameEmptyPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
ConnectionEnvironment connEnv = createConnectionEnvironment();

try {

// WHEN
TestUtil.displayWhen(TEST_NAME);

authenticationEvaluator.authenticateUserPassword(connEnv, "", "bad Bad BAD");

AssertJUnit.fail("Unexpected success");

} catch (UsernameNotFoundException e) {
// This is expected

// THEN
TestUtil.displayThen(TEST_NAME);
display("expected exception", e);
assertNoUserException(e, null);
}

}

@Test
public void test107PasswordLoginBadUsernameBadPassword() throws Exception {
final String TEST_NAME = "test107PasswordLoginBadUsernameBadPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
ConnectionEnvironment connEnv = createConnectionEnvironment();

try {

// WHEN
TestUtil.displayWhen(TEST_NAME);

authenticationEvaluator.authenticateUserPassword(connEnv, "NoSuchUser", "bad Bad BAD");

AssertJUnit.fail("Unexpected success");

} catch (UsernameNotFoundException e) {
// This is expected

// THEN
TestUtil.displayThen(TEST_NAME);
display("expected exception", e);
assertNoUserException(e, null);
}

}

/**
* Wait for 5 minutes. The failed login count should reset after 3 minutes. Therefore bad login
* count should be one after we try to make a bad login.
*/
@Test
public void test105PasswordLoginBadPasswordJackAfterLockoutFailedAttemptsDuration() throws Exception {
final String TEST_NAME = "test105PasswordLoginBadPasswordJackAfterLockoutFailedAttemptsDuration";
public void test125PasswordLoginBadPasswordJackAfterLockoutFailedAttemptsDuration() throws Exception {
final String TEST_NAME = "test125PasswordLoginBadPasswordJackAfterLockoutFailedAttemptsDuration";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -296,8 +381,8 @@ public void test105PasswordLoginBadPasswordJackAfterLockoutFailedAttemptsDuratio


@Test
public void test110PasswordLoginLockout() throws Exception {
final String TEST_NAME = "test110PasswordLoginLockout";
public void test130PasswordLoginLockout() throws Exception {
final String TEST_NAME = "test130PasswordLoginLockout";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -348,8 +433,8 @@ public void test110PasswordLoginLockout() throws Exception {
}

@Test
public void test112PasswordLoginLockedoutGoodPassword() throws Exception {
final String TEST_NAME = "test112PasswordLoginLockedoutGoodPassword";
public void test132PasswordLoginLockedoutGoodPassword() throws Exception {
final String TEST_NAME = "test132PasswordLoginLockedoutGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -377,8 +462,8 @@ public void test112PasswordLoginLockedoutGoodPassword() throws Exception {
}

@Test
public void test113PasswordLoginLockedoutBadPassword() throws Exception {
final String TEST_NAME = "test113PasswordLoginLockedoutBadPassword";
public void test133PasswordLoginLockedoutBadPassword() throws Exception {
final String TEST_NAME = "test133PasswordLoginLockedoutBadPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -410,8 +495,8 @@ public void test113PasswordLoginLockedoutBadPassword() throws Exception {


@Test
public void test118PasswordLoginLockedoutLockExpires() throws Exception {
final String TEST_NAME = "test118PasswordLoginLockedoutLockExpires";
public void test138PasswordLoginLockedoutLockExpires() throws Exception {
final String TEST_NAME = "test138PasswordLoginLockedoutLockExpires";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand All @@ -436,8 +521,8 @@ public void test118PasswordLoginLockedoutLockExpires() throws Exception {
}

@Test
public void test120PasswordLoginDisabledGoodPassword() throws Exception {
final String TEST_NAME = "test120PasswordLoginDisabledGoodPassword";
public void test150PasswordLoginDisabledGoodPassword() throws Exception {
final String TEST_NAME = "test150PasswordLoginDisabledGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -473,8 +558,8 @@ public void test120PasswordLoginDisabledGoodPassword() throws Exception {
}

@Test
public void test122PasswordLoginEnabledGoodPassword() throws Exception {
final String TEST_NAME = "test122PasswordLoginEnabledGoodPassword";
public void test152PasswordLoginEnabledGoodPassword() throws Exception {
final String TEST_NAME = "test152PasswordLoginEnabledGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand All @@ -501,8 +586,8 @@ public void test122PasswordLoginEnabledGoodPassword() throws Exception {
}

@Test
public void test124PasswordLoginNotValidYetGoodPassword() throws Exception {
final String TEST_NAME = "test124PasswordLoginNotValidYetGoodPassword";
public void test154PasswordLoginNotValidYetGoodPassword() throws Exception {
final String TEST_NAME = "test154PasswordLoginNotValidYetGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -544,8 +629,8 @@ public void test124PasswordLoginNotValidYetGoodPassword() throws Exception {
}

@Test
public void test125PasswordLoginValidGoodPassword() throws Exception {
final String TEST_NAME = "test125PasswordLoginValidGoodPassword";
public void test155PasswordLoginValidGoodPassword() throws Exception {
final String TEST_NAME = "test155PasswordLoginValidGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -574,8 +659,8 @@ public void test125PasswordLoginValidGoodPassword() throws Exception {
}

@Test
public void test126PasswordLoginNotValidAnyLongerGoodPassword() throws Exception {
final String TEST_NAME = "test126PasswordLoginNotValidAnyLongerGoodPassword";
public void test156PasswordLoginNotValidAnyLongerGoodPassword() throws Exception {
final String TEST_NAME = "test156PasswordLoginNotValidAnyLongerGoodPassword";
TestUtil.displayTestTile(TEST_NAME);

// GIVEN
Expand Down Expand Up @@ -794,6 +879,10 @@ private void assertExpiredException(CredentialsExpiredException e, String princi
assertEquals("Wrong exception meessage (key)", "web.security.provider.password.bad", e.getMessage());
}

private void assertNoUserException(UsernameNotFoundException e, String principal) {
assertEquals("Wrong exception meessage (key)", "web.security.provider.invalid", e.getMessage());
}

private ConnectionEnvironment createConnectionEnvironment() {
ConnectionEnvironment connEnv = new ConnectionEnvironment();
connEnv.setRemoteHost("remote.example.com");
Expand Down Expand Up @@ -824,6 +913,9 @@ private void assertLastFailedLogin(PrismObject<UserType> user, XMLGregorianCalen
}

private void addFakeAuthorization(MidPointPrincipal principal) {
if (principal == null) {
return;
}
if (principal.getAuthorities().isEmpty()) {
AuthorizationType authorizationType = new AuthorizationType();
authorizationType.getAction().add("FAKE");
Expand Down

0 comments on commit e48c2e6

Please sign in to comment.