/*
* 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.authorize.ws.rs;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.map.JsonMappingException;
import org.gluu.jsf2.service.FacesService;
import org.slf4j.Logger;
import org.xdi.model.custom.script.conf.CustomScriptConfiguration;
import org.xdi.oxauth.i18n.LanguageBean;
import org.xdi.oxauth.model.common.AuthorizationGrant;
import org.xdi.oxauth.model.common.AuthorizationGrantList;
import org.xdi.oxauth.model.common.SessionState;
import org.xdi.oxauth.model.configuration.AppConfiguration;
import org.xdi.oxauth.model.session.EndSessionRequestParam;
import org.xdi.oxauth.model.util.Base64Util;
import org.xdi.oxauth.model.util.Util;
import org.xdi.oxauth.service.SessionStateService;
import org.xdi.oxauth.service.external.ExternalAuthenticationService;
import org.xdi.service.JsonService;
import org.xdi.util.StringHelper;
import javax.enterprise.context.RequestScoped;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import java.io.IOException;
import java.util.Map;
/**
* @author Javier Rojas Blum Date: 03.13.2012
* @author Yuriy Movchan Date: 09/01/2016
*/
@RequestScoped
@Named
public class LogoutAction {
private static final String EXTERNAL_LOGOUT = "external_logout";
private static final String EXTERNAL_LOGOUT_DATA = "external_logout_data";
@Inject
private Logger log;
@Inject
private AuthorizationGrantList authorizationGrantList;
@Inject
private SessionStateService sessionStateService;
@Inject
private ExternalAuthenticationService externalAuthenticationService;
@Inject
private JsonService jsonService;
@Inject
private AppConfiguration appConfiguration;
@Inject
private FacesService facesService;
@Inject
private FacesContext facesContext;
@Inject
private LanguageBean languageBean;
private String idTokenHint;
private String postLogoutRedirectUri;
private SessionState sessionState;
public String getIdTokenHint() {
return idTokenHint;
}
public void setIdTokenHint(String idTokenHint) {
this.idTokenHint = idTokenHint;
}
public String getPostLogoutRedirectUri() {
return postLogoutRedirectUri;
}
public void setPostLogoutRedirectUri(String postLogoutRedirectUri) {
this.postLogoutRedirectUri = postLogoutRedirectUri;
}
public void redirect() {
SessionState sessionState = sessionStateService.getSessionState();
boolean validationResult = validateParameters();
if (!validationResult) {
try {
restoreLogoutParametersFromSession(sessionState);
} catch (IOException ex) {
logoutFailed();
log.debug("Failed to restore logout parameters from session", ex);
}
validationResult = validateParameters();
if (!validationResult) {
missingLogoutParameters();
return;
}
}
ExternalLogoutResult externalLogoutResult = processExternalAuthenticatorLogOut(sessionState);
if (ExternalLogoutResult.FAILURE == externalLogoutResult) {
logoutFailed();
return;
} else if (ExternalLogoutResult.REDIRECT == externalLogoutResult) {
return;
}
StringBuilder sb = new StringBuilder();
// Required parameters
if(idTokenHint!=null && !idTokenHint.isEmpty()){
sb.append(EndSessionRequestParam.ID_TOKEN_HINT + "=").append(idTokenHint);
}
if (sessionState != null && !postLogoutRedirectUri.isEmpty()) {
sb.append("&"+EndSessionRequestParam.SESSION_STATE+"=").append(sessionState.getId());
}
if (postLogoutRedirectUri != null && !postLogoutRedirectUri.isEmpty()) {
sb.append("&"+EndSessionRequestParam.POST_LOGOUT_REDIRECT_URI+"=").append(postLogoutRedirectUri);
}
facesService.redirectToExternalURL("seam/resource/restv1/oxauth/end_session?" + sb.toString());
}
private boolean validateParameters() {
return (StringHelper.isNotEmpty(idTokenHint) || (sessionState != null)) && StringHelper.isNotEmpty(postLogoutRedirectUri);
}
private ExternalLogoutResult processExternalAuthenticatorLogOut(SessionState sessionState) {
if ((sessionState != null) && sessionState.getSessionAttributes().containsKey(EXTERNAL_LOGOUT)) {
log.debug("Detected callback from external system. Resuming logout.");
return ExternalLogoutResult.SUCCESS;
}
AuthorizationGrant authorizationGrant = authorizationGrantList.getAuthorizationGrantByIdToken(idTokenHint);
if (authorizationGrant == null) {
Boolean endSessionWithAccessToken = appConfiguration.getEndSessionWithAccessToken();
if ((endSessionWithAccessToken != null) && endSessionWithAccessToken) {
authorizationGrant = authorizationGrantList.getAuthorizationGrantByAccessToken(idTokenHint);
}
}
if ((authorizationGrant == null) && (sessionState == null)) {
return ExternalLogoutResult.FAILURE;
}
String acrValues;
if (authorizationGrant == null) {
acrValues = sessionStateService.getAcr(sessionState);
} else {
acrValues = authorizationGrant.getAcrValues();
}
boolean isExternalAuthenticatorLogoutPresent = StringHelper.isNotEmpty(acrValues);
if (isExternalAuthenticatorLogoutPresent) {
log.debug("Attemptinmg to execute logout method of '{}' external authenticator.", acrValues);
CustomScriptConfiguration customScriptConfiguration = externalAuthenticationService.getCustomScriptConfigurationByName(acrValues);
if (customScriptConfiguration == null) {
log.error("Failed to get ExternalAuthenticatorConfiguration. acr_values: {}", acrValues);
return ExternalLogoutResult.FAILURE;
} else {
boolean scriptExternalLogoutResult = externalAuthenticationService.executeExternalLogout(customScriptConfiguration, null);
ExternalLogoutResult externalLogoutResult = scriptExternalLogoutResult ? ExternalLogoutResult.SUCCESS : ExternalLogoutResult.FAILURE;
log.debug("Logout result is '{}' for session '{}', userDn: '{}'", externalLogoutResult, sessionState.getId(), sessionState.getUserDn());
int apiVersion = externalAuthenticationService.executeExternalGetApiVersion(customScriptConfiguration);
if (apiVersion < 3) {
// Not support redirect to external system at logout
return externalLogoutResult;
}
log.trace("According to API version script supports logout redirects");
String logoutExternalUrl = externalAuthenticationService.getLogoutExternalUrl(customScriptConfiguration, null);
log.debug("External logout result is '{}' for user '{}'", logoutExternalUrl, sessionState.getUserDn());
if (StringHelper.isEmpty(logoutExternalUrl)) {
return externalLogoutResult;
}
// Store in session parameters needed to call end_session
try {
storeLogoutParametersInSession(sessionState);
} catch (IOException ex) {
log.debug("Failed to persist logout parameters in session", ex);
return ExternalLogoutResult.FAILURE;
}
// Redirect to external URL
facesService.redirectToExternalURL(logoutExternalUrl);
return ExternalLogoutResult.REDIRECT;
}
} else {
return ExternalLogoutResult.SUCCESS;
}
}
private void storeLogoutParametersInSession(SessionState sessionState) throws JsonGenerationException, JsonMappingException, IOException {
Map<String, String> sessionAttributes = sessionState.getSessionAttributes();
LogoutParameters logoutParameters = new LogoutParameters(idTokenHint, postLogoutRedirectUri);
String logoutParametersJson = jsonService.objectToJson(logoutParameters);
String logoutParametersBase64 = Base64Util.base64urlencode(logoutParametersJson.getBytes(Util.UTF8_STRING_ENCODING));
sessionAttributes.put(EXTERNAL_LOGOUT, Boolean.toString(true));
sessionAttributes.put(EXTERNAL_LOGOUT_DATA, logoutParametersBase64);
sessionStateService.updateSessionState(sessionState);
}
private boolean restoreLogoutParametersFromSession(SessionState sessionState) throws IllegalArgumentException, JsonParseException, JsonMappingException, IOException {
if (sessionState == null) {
return false;
}
this.sessionState = sessionState;
Map<String, String> sessionAttributes = sessionState.getSessionAttributes();
boolean restoreParameters = sessionAttributes.containsKey(EXTERNAL_LOGOUT);
if (!restoreParameters) {
return false;
}
String logoutParametersBase64 = sessionAttributes.get(EXTERNAL_LOGOUT_DATA);
String logoutParametersJson = new String(Base64Util.base64urldecode(logoutParametersBase64), Util.UTF8_STRING_ENCODING);
LogoutParameters logoutParameters = jsonService.jsonToObject(logoutParametersJson, LogoutParameters.class);
this.idTokenHint = logoutParameters.getIdTokenHint();
this.postLogoutRedirectUri = logoutParameters.getPostLogoutRedirectUri();
return true;
}
public void missingLogoutParameters() {
String message = languageBean.getMessage("logout.missingParameters");
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message));
facesService.redirect("/error.xhtml");
}
public void logoutFailed() {
String message = languageBean.getMessage("logout.failedToProceed");
facesContext.addMessage(null, new FacesMessage(FacesMessage.SEVERITY_ERROR, message, message));
facesService.redirect("/error.xhtml");
}
public static class LogoutParameters {
private String idTokenHint;
private String postLogoutRedirectUri;
public LogoutParameters() {}
public LogoutParameters(String idTokenHint, String postLogoutRedirectUri) {
this.idTokenHint = idTokenHint;
this.postLogoutRedirectUri = postLogoutRedirectUri;
}
public String getIdTokenHint() {
return idTokenHint;
}
public void setIdTokenHint(String idTokenHint) {
this.idTokenHint = idTokenHint;
}
public String getPostLogoutRedirectUri() {
return postLogoutRedirectUri;
}
public void setPostLogoutRedirectUri(String postLogoutRedirectUri) {
this.postLogoutRedirectUri = postLogoutRedirectUri;
}
}
private enum ExternalLogoutResult {
SUCCESS,
FAILURE,
REDIRECT
}
}