/*
* 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.session.ws.rs;
import java.util.Set;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.xdi.model.security.Identity;
import org.xdi.oxauth.audit.ApplicationAuditLogger;
import org.xdi.oxauth.model.audit.Action;
import org.xdi.oxauth.model.audit.OAuth2AuditLog;
import org.xdi.oxauth.model.authorize.AuthorizeRequestParam;
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.config.Constants;
import org.xdi.oxauth.model.configuration.AppConfiguration;
import org.xdi.oxauth.model.error.ErrorResponseFactory;
import org.xdi.oxauth.model.registration.Client;
import org.xdi.oxauth.model.session.EndSessionErrorResponseType;
import org.xdi.oxauth.model.session.EndSessionParamsValidator;
import org.xdi.oxauth.model.util.Util;
import org.xdi.oxauth.service.ClientService;
import org.xdi.oxauth.service.GrantService;
import org.xdi.oxauth.service.RedirectionUriService;
import org.xdi.oxauth.service.SessionStateService;
import org.xdi.oxauth.service.external.ExternalApplicationSessionService;
import org.xdi.oxauth.util.ServerUtil;
import org.xdi.util.Pair;
import org.xdi.util.StringHelper;
import com.google.common.collect.Sets;
/**
* @author Javier Rojas Blum
* @author Yuriy Movchan
* @author Yuriy Zabrovarnyy
* @version December 15, 2015
*/
@Path("/oxauth")
public class EndSessionRestWebServiceImpl implements EndSessionRestWebService {
@Inject
private Logger log;
@Inject
private ErrorResponseFactory errorResponseFactory;
@Inject
private RedirectionUriService redirectionUriService;
@Inject
private AuthorizationGrantList authorizationGrantList;
@Inject
private ExternalApplicationSessionService externalApplicationSessionService;
@Inject
private SessionStateService sessionStateService;
@Inject
private ClientService clientService;
@Inject
private GrantService grantService;
@Inject
private Identity identity;
@Inject
private ApplicationAuditLogger applicationAuditLogger;
@Inject
private AppConfiguration appConfiguration;
@Override
public Response requestEndSession(String idTokenHint, String postLogoutRedirectUri, String state, String sessionState,
HttpServletRequest httpRequest, HttpServletResponse httpResponse, SecurityContext sec) {
log.debug("Attempting to end session, idTokenHint: {}, postLogoutRedirectUri: {}, sessionState: {}, Is Secure = {}",
idTokenHint, postLogoutRedirectUri, sessionState, sec.isSecure());
EndSessionParamsValidator.validateParams(idTokenHint, sessionState, errorResponseFactory);
final Pair<SessionState, AuthorizationGrant> pair = endSession(idTokenHint, sessionState, httpRequest, httpResponse, sec);
auditLogging(httpRequest, pair);
return httpBased(postLogoutRedirectUri, state, pair);
}
public Response httpBased(String postLogoutRedirectUri, String state, Pair<SessionState, AuthorizationGrant> pair) {
SessionState sessionState = pair.getFirst();
AuthorizationGrant authorizationGrant = pair.getSecond();
// Validate redirectUri
String redirectUri;
if (authorizationGrant == null) {
redirectUri = redirectionUriService.validatePostLogoutRedirectUri(sessionState, postLogoutRedirectUri);
} else {
redirectUri = redirectionUriService.validatePostLogoutRedirectUri(authorizationGrant.getClient().getClientId(), postLogoutRedirectUri);
}
final Set<String> frontchannelLogoutUris = getRpFrontchannelLogoutUris(pair);
final String html = constructPage(frontchannelLogoutUris, redirectUri, state);
log.debug("Constructed http logout page: " + html);
return Response.ok().
cacheControl(ServerUtil.cacheControl(true, true)).
header("Pragma", "no-cache").
type(MediaType.TEXT_HTML_TYPE).entity(html).
build();
}
private Pair<SessionState, AuthorizationGrant> endSession(String idTokenHint, String sessionState,
HttpServletRequest httpRequest, HttpServletResponse httpResponse, SecurityContext sec) {
AuthorizationGrant authorizationGrant = authorizationGrantList.getAuthorizationGrantByIdToken(idTokenHint);
if (authorizationGrant == null) {
Boolean endSessionWithAccessToken = appConfiguration.getEndSessionWithAccessToken();
if ((endSessionWithAccessToken != null) && endSessionWithAccessToken) {
authorizationGrant = authorizationGrantList.getAuthorizationGrantByAccessToken(idTokenHint);
}
}
SessionState ldapSessionState = removeSessionState(sessionState, httpRequest, httpResponse);
if ((authorizationGrant == null) && (ldapSessionState == null)) {
log.info("Failed to find out authorization grant for id_token_hint '{}' and session_state '{}'", idTokenHint, sessionState);
errorResponseFactory.throwUnauthorizedException(EndSessionErrorResponseType.INVALID_GRANT);
}
boolean isExternalLogoutPresent;
boolean externalLogoutResult = false;
isExternalLogoutPresent = externalApplicationSessionService.isEnabled();
if (isExternalLogoutPresent && (ldapSessionState != null)) {
String userName = ldapSessionState.getSessionAttributes().get(Constants.AUTHENTICATED_USER);
externalLogoutResult = externalApplicationSessionService.executeExternalEndSessionMethods(httpRequest, ldapSessionState);
log.info("End session result for '{}': '{}'", userName, "logout", externalLogoutResult);
}
boolean isGrantAndExternalLogoutSuccessful = isExternalLogoutPresent && externalLogoutResult;
if (isExternalLogoutPresent && !isGrantAndExternalLogoutSuccessful) {
errorResponseFactory.throwUnauthorizedException(EndSessionErrorResponseType.INVALID_GRANT);
}
if (ldapSessionState != null) {
grantService.removeAllTokensBySession(ldapSessionState.getDn());
}
if (identity != null) {
identity.logout();
}
return new Pair<SessionState, AuthorizationGrant>(ldapSessionState, authorizationGrant);
}
private Set<String> getRpFrontchannelLogoutUris(Pair<SessionState, AuthorizationGrant> pair) {
final Set<String> result = Sets.newHashSet();
SessionState sessionState = pair.getFirst();
AuthorizationGrant authorizationGrant = pair.getSecond();
if (sessionState == null) {
log.error("session_state is not passed to endpoint (as cookie or manually). Therefore unable to match clients for session_state." +
"Http based html will contain no iframes.");
return result;
}
final Set<Client> clientsByDns = sessionState.getPermissionGrantedMap() != null ?
clientService.getClient(sessionState.getPermissionGrantedMap().getClientIds(true), true) :
Sets.<Client>newHashSet();
if (authorizationGrant != null) {
clientsByDns.add(authorizationGrant.getClient());
}
for (Client client : clientsByDns) {
String[] logoutUris = client.getFrontChannelLogoutUri();
if (logoutUris == null) {
continue;
}
for (String logoutUri : logoutUris) {
if (Util.isNullOrEmpty(logoutUri)) {
continue; // skip client if logout_uri is blank
}
if (client.getFrontChannelLogoutSessionRequired() != null && client.getFrontChannelLogoutSessionRequired()) {
if (logoutUri.contains("?")) {
logoutUri = logoutUri + "&sid=" + sessionState.getId();
} else {
logoutUri = logoutUri + "?sid=" + sessionState.getId();
}
}
result.add(logoutUri);
}
}
return result;
}
private SessionState removeSessionState(String sessionState, HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
SessionState ldapSessionState = null;
try {
String id = sessionState;
if (StringHelper.isEmpty(id)) {
id = sessionStateService.getSessionStateFromCookie(httpRequest);
}
if (StringHelper.isNotEmpty(id)) {
ldapSessionState = sessionStateService.getSessionState(id);
if (ldapSessionState != null) {
boolean result = sessionStateService.remove(ldapSessionState);
if (!result) {
log.error("Failed to remove session_state '{}' from LDAP", id);
}
} else {
log.error("Failed to load session from LDAP by session_state: '{}'", id);
}
}
} catch (Exception e) {
log.error(e.getMessage(), e);
} finally {
sessionStateService.removeSessionStateCookie(httpResponse);
}
return ldapSessionState;
}
private String constructPage(Set<String> logoutUris, String postLogoutUrl, String state) {
String iframes = "";
for (String logoutUri : logoutUris) {
iframes = iframes + String.format("<iframe height=\"0\" width=\"0\" src=\"%s\"></iframe>", logoutUri);
}
String html = "<!DOCTYPE html>" +
"<html>" +
"<head>";
if (!Util.isNullOrEmpty(postLogoutUrl)) {
if (!Util.isNullOrEmpty(state)) {
if (postLogoutUrl.contains("?")) {
postLogoutUrl += "&state=" + state;
} else {
postLogoutUrl += "?state=" + state;
}
}
html += "<script>" +
"window.onload=function() {" +
"window.location='" + postLogoutUrl + "'" +
"}" +
"</script>";
}
html += "<title>Gluu Generated logout page</title>" +
"</head>" +
"<body>" +
"Logout requests sent.<br/>" +
iframes +
"</body>" +
"</html>";
return html;
}
private void auditLogging(HttpServletRequest request, Pair<SessionState, AuthorizationGrant> pair){
SessionState sessionState = pair.getFirst();
AuthorizationGrant authorizationGrant = pair.getSecond();
OAuth2AuditLog oAuth2AuditLog = new OAuth2AuditLog(ServerUtil.getIpAddress(request), Action.SESSION_DESTROYED);
oAuth2AuditLog.setSuccess(true);
if (authorizationGrant != null) {
oAuth2AuditLog.setClientId(authorizationGrant.getClientId());
oAuth2AuditLog.setScope(StringUtils.join(authorizationGrant.getScopes(), " "));
oAuth2AuditLog.setUsername(authorizationGrant.getUserId());
} else {
oAuth2AuditLog.setClientId(sessionState.getPermissionGrantedMap().getClientIds(true).toString());
oAuth2AuditLog.setScope(sessionState.getSessionAttributes().get(AuthorizeRequestParam.SCOPE));
oAuth2AuditLog.setUsername(sessionState.getUserDn());
}
applicationAuditLogger.sendMessage(oAuth2AuditLog);
}
}