/*
* oxAuth is available under the MIT License (2008). See http://opensource.org/licenses/MIT for full text.
*
* Copyright (c) 2014, Gluu
*/
package org.xdi.oxauth.auth;
import org.apache.commons.lang.StringUtils;
import org.gluu.jsf2.service.FacesService;
import org.slf4j.Logger;
import org.xdi.model.AuthenticationScriptUsageType;
import org.xdi.model.custom.script.conf.CustomScriptConfiguration;
import org.xdi.model.security.Credentials;
import org.xdi.model.security.SimplePrincipal;
import org.xdi.oxauth.i18n.LanguageBean;
import org.xdi.oxauth.model.common.SessionIdState;
import org.xdi.oxauth.model.common.SessionState;
import org.xdi.oxauth.model.common.User;
import org.xdi.oxauth.model.config.Constants;
import org.xdi.oxauth.model.configuration.AppConfiguration;
import org.xdi.oxauth.model.jwt.JwtClaimName;
import org.xdi.oxauth.model.registration.Client;
import org.xdi.oxauth.security.Identity;
import org.xdi.oxauth.service.AuthenticationService;
import org.xdi.oxauth.service.ClientService;
import org.xdi.oxauth.service.SessionStateService;
import org.xdi.oxauth.service.external.ExternalAuthenticationService;
import org.xdi.util.StringHelper;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import java.security.Principal;
import java.util.List;
import java.util.Map;
/**
* Authenticator component
*
* @author Javier Rojas Blum
* @author Yuriy Movchan
* @version December 15, 2015
*/
@RequestScoped
@Named
public class Authenticator {
@Inject
private Logger log;
@Inject
private Identity identity;
@Inject
private Credentials credentials;
@Inject
private ClientService clientService;
@Inject
private SessionStateService sessionStateService;
@Inject
private AuthenticationService authenticationService;
@Inject
private ExternalAuthenticationService externalAuthenticationService;
@Inject
private AppConfiguration appConfiguration;
@Inject
private FacesContext facesContext;
@Inject
private ExternalContext externalContext;
@Inject
private FacesService facesService;
@Inject
private LanguageBean languageBean;
private String authAcr;
private Integer authStep;
private boolean addedErrorMessage;
/**
* Tries to authenticate an user, returns <code>true</code> if the
* authentication succeed
*
* @return Returns <code>true</code> if the authentication succeed
*/
public boolean authenticate() {
if (!authenticateImpl(true, false)) {
return authenticationFailed();
} else {
return true;
}
}
public String authenticateWithOutcome() {
boolean result = authenticateImpl(true, false);
if (result) {
return Constants.RESULT_SUCCESS;
} else {
addMessage(FacesMessage.SEVERITY_ERROR, "login.failedToAuthenticate");
return Constants.RESULT_FAILURE;
}
}
public boolean authenticateWebService(boolean skipPassword) {
return authenticateImpl(false, skipPassword);
}
public boolean authenticateWebService() {
return authenticateImpl(false, false);
}
public boolean authenticateImpl(boolean interactive, boolean skipPassword) {
boolean authenticated = false;
try {
log.trace("Authenticating ... (interactive: " + interactive + ", skipPassword: " + skipPassword
+ ", credentials.username: " + credentials.getUsername() + ")");
if (StringHelper.isNotEmpty(credentials.getUsername())
&& (skipPassword || StringHelper.isNotEmpty(credentials.getPassword()))
&& credentials.getUsername().startsWith("@!")) {
authenticated = clientAuthentication(credentials, interactive, skipPassword);
} else {
if (interactive) {
authenticated = userAuthenticationInteractive();
} else {
authenticated = userAuthenticationService();
}
}
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
}
if (authenticated) {
log.trace("Authentication successfully for '{}'", credentials.getUsername());
return true;
}
log.info("Authentication failed for '{}'", credentials.getUsername());
return false;
}
private boolean clientAuthentication(Credentials credentials, boolean interactive, boolean skipPassword) {
boolean isServiceUsesExternalAuthenticator = !interactive
&& externalAuthenticationService.isEnabled(AuthenticationScriptUsageType.SERVICE);
if (isServiceUsesExternalAuthenticator) {
CustomScriptConfiguration customScriptConfiguration = externalAuthenticationService
.determineCustomScriptConfiguration(AuthenticationScriptUsageType.SERVICE, 1, this.authAcr);
if (customScriptConfiguration == null) {
log.error("Failed to get CustomScriptConfiguration. acr: '{}'", this.authAcr);
} else {
this.authAcr = customScriptConfiguration.getCustomScript().getName();
boolean result = externalAuthenticationService.executeExternalAuthenticate(customScriptConfiguration,
null, 1);
log.info("Authentication result for user '{}', result: '{}'", credentials.getUsername(), result);
if (result) {
authenticationService.configureSessionClient();
log.info("Authentication success for client: '{}'", credentials.getUsername());
return true;
}
}
}
boolean loggedIn = skipPassword;
if (!loggedIn) {
loggedIn = clientService.authenticate(credentials.getUsername(), credentials.getPassword());
}
if (loggedIn) {
authenticationService.configureSessionClient();
log.info("Authentication success for Client: '{}'", credentials.getUsername());
return true;
}
return false;
}
private boolean userAuthenticationInteractive() {
SessionState sessionState = sessionStateService.getSessionState();
Map<String, String> sessionIdAttributes = sessionStateService.getSessionAttributes(sessionState);
if (sessionIdAttributes == null) {
log.error("Failed to get session attributes");
authenticationFailedSessionInvalid();
return false;
}
// Set current state into identity to allow use in login form and
// authentication scripts
identity.setSessionState(sessionState);
initCustomAuthenticatorVariables(sessionIdAttributes);
boolean useExternalAuthenticator = externalAuthenticationService
.isEnabled(AuthenticationScriptUsageType.INTERACTIVE);
if (useExternalAuthenticator && !StringHelper.isEmpty(this.authAcr)) {
initCustomAuthenticatorVariables(sessionIdAttributes);
if ((this.authStep == null) || StringHelper.isEmpty(this.authAcr)) {
log.error("Failed to determine authentication mode");
authenticationFailedSessionInvalid();
return false;
}
CustomScriptConfiguration customScriptConfiguration = externalAuthenticationService
.getCustomScriptConfiguration(AuthenticationScriptUsageType.INTERACTIVE, this.authAcr);
if (customScriptConfiguration == null) {
log.error("Failed to get CustomScriptConfiguration for acr: '{}', auth_step: '{}'", this.authAcr,
this.authStep);
return false;
}
// Check if all previous steps had passed
boolean passedPreviousSteps = isPassedPreviousAuthSteps(sessionIdAttributes, this.authStep);
if (!passedPreviousSteps) {
log.error("There are authentication steps not marked as passed. acr: '{}', auth_step: '{}'",
this.authAcr, this.authStep);
return false;
}
boolean result = externalAuthenticationService.executeExternalAuthenticate(customScriptConfiguration,
externalContext.getRequestParameterValuesMap(), this.authStep);
log.debug("Authentication result for user '{}'. auth_step: '{}', result: '{}', credentials: '{}'",
credentials.getUsername(), this.authStep, result, System.identityHashCode(credentials));
int overridenNextStep = -1;
int apiVersion = externalAuthenticationService.executeExternalGetApiVersion(customScriptConfiguration);
if (apiVersion > 1) {
log.trace("According to API version script supports steps overriding");
overridenNextStep = externalAuthenticationService.getNextStep(customScriptConfiguration,
externalContext.getRequestParameterValuesMap(), this.authStep);
log.debug("Get next step from script: '{}'", apiVersion);
}
if (!result && (overridenNextStep == -1)) {
return false;
}
boolean overrideCurrentStep = false;
if (overridenNextStep > -1) {
overrideCurrentStep = true;
// Reload session state
sessionState = sessionStateService.getSessionState();
// Reset to pecified step
sessionStateService.resetToStep(sessionState, overridenNextStep);
this.authStep = overridenNextStep;
log.info("Authentication reset to step : '{}'", this.authStep);
}
// Update parameters map to allow access it from count
// authentication steps method
updateExtraParameters(customScriptConfiguration, this.authStep + 1, sessionIdAttributes);
// Determine count authentication methods
int countAuthenticationSteps = externalAuthenticationService
.executeExternalGetCountAuthenticationSteps(customScriptConfiguration);
// Reload from LDAP to make sure that we are updating latest session
// attributes
sessionState = sessionStateService.getSessionState();
sessionIdAttributes = sessionStateService.getSessionAttributes(sessionState);
// Prepare for next step
if ((this.authStep < countAuthenticationSteps) || overrideCurrentStep) {
int nextStep;
if (overrideCurrentStep) {
nextStep = overridenNextStep;
} else {
nextStep = this.authStep + 1;
}
String redirectTo = externalAuthenticationService
.executeExternalGetPageForStep(customScriptConfiguration, nextStep);
if (StringHelper.isEmpty(redirectTo)) {
redirectTo = "/login.xhtml";
}
// Store/Update extra parameters in session attributes map
updateExtraParameters(customScriptConfiguration, nextStep, sessionIdAttributes);
if (!overrideCurrentStep) {
// Update auth_step
sessionIdAttributes.put("auth_step", Integer.toString(nextStep));
// Mark step as passed
markAuthStepAsPassed(sessionIdAttributes, this.authStep);
}
if (sessionState != null) {
boolean updateResult = updateSession(sessionState, sessionIdAttributes);
if (!updateResult) {
return false;
}
}
log.trace("Redirect to page: '{}'", redirectTo);
facesService.redirect(redirectTo);
return true;
}
if (this.authStep == countAuthenticationSteps) {
SessionState eventSessionState = authenticationService.configureSessionUser(sessionState,
sessionIdAttributes);
Principal principal = new SimplePrincipal(credentials.getUsername());
identity.acceptExternallyAuthenticatedPrincipal(principal);
identity.quietLogin();
// Redirect to authorization workflow
log.debug("Sending event to trigger user redirection: '{}'", credentials.getUsername());
authenticationService.onSuccessfulLogin(eventSessionState);
log.info("Authentication success for User: '{}'", credentials.getUsername());
return true;
}
} else {
if (StringHelper.isNotEmpty(credentials.getUsername())) {
boolean authenticated = authenticationService.authenticate(credentials.getUsername(),
credentials.getPassword());
if (authenticated) {
SessionState eventSessionState = authenticationService.configureSessionUser(sessionState,
sessionIdAttributes);
// Redirect to authorization workflow
log.debug("Sending event to trigger user redirection: '{}'", credentials.getUsername());
authenticationService.onSuccessfulLogin(eventSessionState);
}
log.info("Authentication success for User: '{}'", credentials.getUsername());
return true;
}
}
return false;
}
private boolean updateSession(SessionState sessionState, Map<String, String> sessionIdAttributes) {
sessionState.setSessionAttributes(sessionIdAttributes);
boolean updateResult = sessionStateService.updateSessionState(sessionState, true, true, true);
if (!updateResult) {
log.debug("Failed to update session entry: '{}'", sessionState.getId());
return false;
}
return true;
}
private boolean userAuthenticationService() {
if (externalAuthenticationService.isEnabled(AuthenticationScriptUsageType.SERVICE)) {
CustomScriptConfiguration customScriptConfiguration = externalAuthenticationService
.determineCustomScriptConfiguration(AuthenticationScriptUsageType.SERVICE, 1, this.authAcr);
if (customScriptConfiguration == null) {
log.error("Failed to get CustomScriptConfiguration. auth_step: '{}', acr: '{}'", this.authStep,
this.authAcr);
} else {
this.authAcr = customScriptConfiguration.getName();
boolean result = externalAuthenticationService.executeExternalAuthenticate(customScriptConfiguration,
null, 1);
log.info("Authentication result for '{}'. auth_step: '{}', result: '{}'", credentials.getUsername(),
this.authStep, result);
if (result) {
authenticationService.configureEventUser();
log.info("Authentication success for User: '{}'", credentials.getUsername());
return true;
}
log.info("Authentication failed for User: '{}'", credentials.getUsername());
}
}
if (StringHelper.isNotEmpty(credentials.getUsername())) {
boolean authenticated = authenticationService.authenticate(credentials.getUsername(),
credentials.getPassword());
if (authenticated) {
authenticationService.configureEventUser();
log.info("Authentication success for User: '{}'", credentials.getUsername());
return true;
}
log.info("Authentication failed for User: '{}'", credentials.getUsername());
}
return false;
}
private void updateExtraParameters(CustomScriptConfiguration customScriptConfiguration, final int step,
Map<String, String> sessionIdAttributes) {
List<String> extraParameters = externalAuthenticationService
.executeExternalGetExtraParametersForStep(customScriptConfiguration, step);
if (extraParameters != null) {
for (String extraParameter : extraParameters) {
if (authenticationService.isParameterExists(extraParameter)) {
String extraParameterValue = authenticationService.getParameterValue(extraParameter);
sessionIdAttributes.put(extraParameter, extraParameterValue);
}
}
}
}
public String prepareAuthenticationForStep() {
String result = prepareAuthenticationForStepImpl();
if (Constants.RESULT_SUCCESS.equals(result)) {
} else if (Constants.RESULT_FAILURE.equals(result)) {
addMessage(FacesMessage.SEVERITY_ERROR, "login.failedToAuthenticate");
} else if (Constants.RESULT_NO_PERMISSIONS.equals(result)) {
addMessage(FacesMessage.SEVERITY_ERROR, "login.youDontHavePermission");
} else if (Constants.RESULT_EXPIRED.equals(result)) {
addMessage(FacesMessage.SEVERITY_ERROR, "login.errorSessionInvalidMessage");
}
return result;
}
private String prepareAuthenticationForStepImpl() {
SessionState sessionState = sessionStateService.getSessionState();
Map<String, String> sessionIdAttributes = sessionStateService.getSessionAttributes(sessionState);
if (sessionIdAttributes == null) {
log.error("Failed to get attributes from session");
return Constants.RESULT_EXPIRED;
}
// Set current state into identity to allow use in login form and
// authentication scripts
identity.setSessionState(sessionState);
if (!externalAuthenticationService.isEnabled(AuthenticationScriptUsageType.INTERACTIVE)) {
return Constants.RESULT_SUCCESS;
}
initCustomAuthenticatorVariables(sessionIdAttributes);
if (StringHelper.isEmpty(this.authAcr)) {
return Constants.RESULT_SUCCESS;
}
if ((this.authStep == null) || (this.authStep < 1)) {
return Constants.RESULT_NO_PERMISSIONS;
}
CustomScriptConfiguration customScriptConfiguration = externalAuthenticationService
.getCustomScriptConfiguration(AuthenticationScriptUsageType.INTERACTIVE, this.authAcr);
if (customScriptConfiguration == null) {
log.error("Failed to get CustomScriptConfiguration. auth_step: '{}', acr: '{}'", this.authStep,
this.authAcr);
return Constants.RESULT_FAILURE;
}
String currentauthAcr = customScriptConfiguration.getName();
customScriptConfiguration = externalAuthenticationService.determineExternalAuthenticatorForWorkflow(
AuthenticationScriptUsageType.INTERACTIVE, customScriptConfiguration);
if (customScriptConfiguration == null) {
return Constants.RESULT_FAILURE;
} else {
String determinedauthAcr = customScriptConfiguration.getName();
if (!StringHelper.equalsIgnoreCase(currentauthAcr, determinedauthAcr)) {
// Redirect user to alternative login workflow
String redirectTo = externalAuthenticationService
.executeExternalGetPageForStep(customScriptConfiguration, this.authStep);
if (StringHelper.isEmpty(redirectTo)) {
redirectTo = "/login.xhtml";
}
CustomScriptConfiguration determinedCustomScriptConfiguration = externalAuthenticationService
.getCustomScriptConfiguration(AuthenticationScriptUsageType.INTERACTIVE, determinedauthAcr);
if (determinedCustomScriptConfiguration == null) {
log.error("Failed to get determined CustomScriptConfiguration. auth_step: '{}', acr: '{}'",
this.authStep, this.authAcr);
return Constants.RESULT_FAILURE;
}
log.debug("Redirect to page: '{}'. Force to use acr: '{}'", redirectTo, determinedauthAcr);
determinedauthAcr = determinedCustomScriptConfiguration.getName();
String determinedAuthLevel = Integer.toString(determinedCustomScriptConfiguration.getLevel());
sessionIdAttributes.put("acr", determinedauthAcr);
sessionIdAttributes.put("auth_level", determinedAuthLevel);
sessionIdAttributes.put("auth_step", Integer.toString(1));
if (sessionState != null) {
boolean updateResult = updateSession(sessionState, sessionIdAttributes);
if (!updateResult) {
return Constants.RESULT_EXPIRED;
}
}
facesService.redirect(redirectTo);
return Constants.RESULT_SUCCESS;
}
}
// Check if all previous steps had passed
boolean passedPreviousSteps = isPassedPreviousAuthSteps(sessionIdAttributes, this.authStep);
if (!passedPreviousSteps) {
log.error("There are authentication steps not marked as passed. acr: '{}', auth_step: '{}'", this.authAcr,
this.authStep);
return Constants.RESULT_FAILURE;
}
Boolean result = externalAuthenticationService.executeExternalPrepareForStep(customScriptConfiguration,
externalContext.getRequestParameterValuesMap(), this.authStep);
if ((result != null) && result) {
// Store/Update extra parameters in session attributes map
updateExtraParameters(customScriptConfiguration, this.authStep, sessionIdAttributes);
if (sessionState != null) {
boolean updateResult = updateSession(sessionState, sessionIdAttributes);
if (!updateResult) {
return Constants.RESULT_FAILURE;
}
}
return Constants.RESULT_SUCCESS;
} else {
return Constants.RESULT_FAILURE;
}
}
public boolean authenticateBySessionState(String p_sessionState) {
if (StringUtils.isNotBlank(p_sessionState) && appConfiguration.getSessionIdEnabled()) {
try {
SessionState sessionState = sessionStateService.getSessionState(p_sessionState);
return authenticateBySessionState(sessionState);
} catch (Exception e) {
log.trace(e.getMessage(), e);
}
}
return false;
}
public boolean authenticateBySessionState(SessionState sessionState) {
if (sessionState == null) {
return false;
}
String p_sessionState = sessionState.getId();
log.trace("authenticateBySessionState, sessionState = '{}', session = '{}', state= '{}'", p_sessionState,
sessionState, sessionState.getState());
// IMPORTANT : authenticate by session state only if state of session is
// authenticated!
if (SessionIdState.AUTHENTICATED == sessionState.getState()) {
final User user = authenticationService.getUserOrRemoveSession(sessionState);
if (user != null) {
try {
authenticationService.configureEventUser(sessionState);
} catch (Exception e) {
log.trace(e.getMessage(), e);
}
return true;
}
}
return false;
}
private void initCustomAuthenticatorVariables(Map<String, String> sessionIdAttributes) {
if (sessionIdAttributes == null) {
log.error("Failed to restore attributes from session attributes");
return;
}
this.authStep = StringHelper.toInteger(sessionIdAttributes.get("auth_step"), null);
this.authAcr = sessionIdAttributes.get(JwtClaimName.AUTHENTICATION_CONTEXT_CLASS_REFERENCE);
}
private boolean authenticationFailed() {
if (!this.addedErrorMessage) {
addMessage(FacesMessage.SEVERITY_ERROR, "login.errorMessage");
}
return false;
}
private void authenticationFailedSessionInvalid() {
this.addedErrorMessage = true;
addMessage(FacesMessage.SEVERITY_ERROR, "login.errorSessionInvalidMessage");
facesService.redirect("/error.xhtml");
}
private void markAuthStepAsPassed(Map<String, String> sessionIdAttributes, Integer authStep) {
String key = String.format("auth_step_passed_%d", authStep);
sessionIdAttributes.put(key, Boolean.TRUE.toString());
}
private boolean isAuthStepPassed(Map<String, String> sessionIdAttributes, Integer authStep) {
String key = String.format("auth_step_passed_%d", authStep);
if (sessionIdAttributes.containsKey(key) && Boolean.parseBoolean(sessionIdAttributes.get(key))) {
return true;
}
return false;
}
private boolean isPassedPreviousAuthSteps(Map<String, String> sessionIdAttributes, Integer authStep) {
for (int i = 1; i < authStep; i++) {
boolean isAuthStepPassed = isAuthStepPassed(sessionIdAttributes, i);
if (!isAuthStepPassed) {
return false;
}
}
return true;
}
public void configureSessionClient(Client client) {
authenticationService.configureSessionClient(client);
}
public void addMessage(Severity severity, String summary) {
String msg = languageBean.getMessage(summary);
FacesMessage message = new FacesMessage(severity, msg, null);
facesContext.addMessage(null, message);
}
}