
net.smartcosmos.extension.stormpath.service.UserDetailsServiceStormpath Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of smartcosmos-user-details-stormpath Show documentation
Show all versions of smartcosmos-user-details-stormpath Show documentation
Retrieves User Detail Information for use in Authentication from Stormpath
The newest version!
package net.smartcosmos.extension.stormpath.service;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import com.stormpath.sdk.account.Account;
import com.stormpath.sdk.account.AccountCriteria;
import com.stormpath.sdk.account.AccountList;
import com.stormpath.sdk.account.AccountStatus;
import com.stormpath.sdk.account.Accounts;
import com.stormpath.sdk.application.Application;
import com.stormpath.sdk.authc.AuthenticationRequest;
import com.stormpath.sdk.authc.AuthenticationResult;
import com.stormpath.sdk.authc.UsernamePasswordRequests;
import com.stormpath.sdk.client.Client;
import com.stormpath.sdk.directory.DirectoryStatus;
import com.stormpath.sdk.error.Error;
import com.stormpath.sdk.lang.Assert;
import com.stormpath.sdk.resource.ResourceException;
import com.stormpath.sdk.tenant.Tenant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.convert.ConversionService;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import net.smartcosmos.extension.stormpath.config.StormpathProperties;
import net.smartcosmos.userdetails.domain.UserDetails;
import net.smartcosmos.userdetails.service.UserDetailsService;
import static com.stormpath.sdk.account.Accounts.where;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ACCOUNT_DISABLED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ACCOUNT_LOCKED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ACCOUNT_NONEXISTENT;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ACCOUNT_STORE_DISABLED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ACCOUNT_UNVERIFIED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_DIRECTORY_DISABLED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_GROUP_DISABLED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_ORGANIZATION_DISABLED;
import static net.smartcosmos.extension.stormpath.service.StormpathErrorCodeConstants.ERR_PASSWORD_INCORRECT;
import static net.smartcosmos.extension.stormpath.util.StormpathInitializer.getClientFromProperties;
import static net.smartcosmos.extension.stormpath.util.StormpathInitializer.getSingleApplication;
/**
* Stormpath-specific implementation of the {@link UserDetailsService} interface.
*/
@Slf4j
@Service
public class UserDetailsServiceStormpath implements UserDetailsService {
private Application application = null;
private final ConversionService conversionService;
private final PasswordEncoder passwordEncoder;
private final StormpathProperties stormpathProperties;
private final Validator validator;
@Autowired
public UserDetailsServiceStormpath(
StormpathProperties stormpathProperties,
ConversionService conversionService,
PasswordEncoder passwordEncoder,
Validator validator) {
this.conversionService = conversionService;
this.passwordEncoder = passwordEncoder;
this.stormpathProperties = stormpathProperties;
this.validator = validator;
initialize();
}
@Override
public UserDetails getUserDetails(String username, String password) throws IllegalArgumentException, AuthenticationException {
Assert.isTrue(StringUtils.isNotBlank(username), "username may not be blank");
Assert.isTrue(StringUtils.isNotBlank(password), "password may not be blank");
if (isInitialized()) {
log.debug("Stormpath is attempting to authenticate user {}...", username);
AuthenticationRequest request = UsernamePasswordRequests.builder()
.setUsernameOrEmail(username)
.setPassword(password)
.build();
try {
AuthenticationResult result = application.authenticateAccount(request);
Account account = result.getAccount();
log.info("Stormpath has successfully authenticated user {}", username);
log.debug("Details for account {}: {}", username, account);
UserDetails userResponse = conversionService.convert(account, UserDetails.class);
userResponse.setPasswordHash(passwordEncoder.encode(password));
return userResponse;
} catch (ResourceException e) {
String message = String.format("Stormpath authentication request or user %s failed: %s",
username,
e.toString());
log.info(message);
log.debug(message, e);
throwAuthenticationExceptionForError(message, e);
}
}
log.error("Stormpath application is not initialized");
throw new InternalAuthenticationServiceException("Stormpath application could not be initialized");
}
@Override
public UserDetails getUserDetails(String username) throws IllegalArgumentException, AuthenticationException {
Assert.isTrue(StringUtils.isNotBlank(username), "username may not be blank");
if (isInitialized()) {
AccountCriteria accountCriteria = // @formatter:off
where(Accounts.email().eqIgnoreCase(username));
// @formatter:on
AccountList accountList = application.getAccounts(accountCriteria);
if (accountList == null || accountList.getSize() == 0) {
String msg = String.format("Unable to locate username '%s'", username);
log.info(msg);
throw new UsernameNotFoundException(msg);
}
if (accountList.getSize() > 1) {
String msg = String.format("No unique result for username '%s'", username);
log.info(msg);
log.debug("Stormpath returned account list {}", accountList);
throw new AuthenticationServiceException(msg);
}
Account account = accountList.single();
if (!AccountStatus.ENABLED.equals(account.getStatus()) ||
!DirectoryStatus.ENABLED.equals(account.getDirectory()
.getStatus())) {
String msg = String.format("Account or directory for username '%s' is disabled", username);
log.info(msg);
throw new DisabledException(msg);
}
log.info("Stormpath has successfully returned details for user {}", username);
log.debug("Details for account {}: {}", username, account);
return conversionService.convert(account, UserDetails.class);
}
log.error("Stormpath application is not initialized");
throw new InternalAuthenticationServiceException("Stormpath application could not be initialized");
}
@Override
public boolean isValid(UserDetails userDetails) {
log.debug("Entity: {}", userDetails);
Set> violations = validator.validate(userDetails);
log.debug("Constraint violations: {}", violations.toString());
return violations.isEmpty();
}
public boolean initialize() {
try {
String applicationName = stormpathProperties.getApplicationName();
Client stormpathClient = getClientFromProperties(stormpathProperties.getApiKey());
Tenant tenant = stormpathClient.getCurrentTenant();
application = getSingleApplication(tenant, applicationName);
log.info("Stormpath has successfully initialized application {}", applicationName);
log.debug("Details for application {}: {}", applicationName, application);
} catch (NullPointerException e) {
log.warn(
"Initialization of Stormpath service failed. This generally indicates that the Stormpath API key and/or applicationName is missing "
+ "in the configuration: {}",
e);
}
return application != null;
}
private boolean isInitialized() {
if (application == null) {
log.info("Application is not initialized. Attempting again...");
if (!initialize()) {
log.error("Initializing failed again. Authentication attempt is aborted.");
}
}
return application != null;
}
private void throwAuthenticationExceptionForError(String message, ResourceException e) throws AuthenticationException {
Error error = e.getStormpathError();
log.info("Stormpath returned error: {}", error);
switch (error.getCode()) {
case ERR_ACCOUNT_NONEXISTENT:
throw new UsernameNotFoundException(message, e);
case ERR_PASSWORD_INCORRECT:
throw new BadCredentialsException(message, e);
case ERR_ACCOUNT_LOCKED:
throw new LockedException(message, e);
case ERR_ACCOUNT_DISABLED:
case ERR_ACCOUNT_UNVERIFIED:
case ERR_GROUP_DISABLED:
case ERR_DIRECTORY_DISABLED:
case ERR_ORGANIZATION_DISABLED:
case ERR_ACCOUNT_STORE_DISABLED:
throw new DisabledException(message, e);
default:
// If none of the above error codes applied, this is likely to be a more general service problem
throw new AuthenticationServiceException(message, e);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy