Skip to content
Snippets Groups Projects
Verified Commit f1d459c8 authored by Dominik Frantisek Bucik's avatar Dominik Frantisek Bucik
Browse files

refactor: :bulb: Refactor endsession endpoint

parent a3a06386
No related branches found
No related tags found
1 merge request!395refactor: 💡 Refactor endsession endpoint
Pipeline #432521 passed
...@@ -12,8 +12,8 @@ import java.io.UnsupportedEncodingException; ...@@ -12,8 +12,8 @@ import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_POST_LOGOUT_REDIRECT_URI;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_STATE; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_STATE;
import static cz.muni.ics.openid.connect.web.endpoint.EndSessionEndpoint.PARAM_POST_LOGOUT_REDIRECT_URI;
@Slf4j @Slf4j
public class PerunOidcLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { public class PerunOidcLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
......
...@@ -23,7 +23,6 @@ public interface AuthProcFilterConstants { ...@@ -23,7 +23,6 @@ public interface AuthProcFilterConstants {
String PARAM_ACCEPTED = "accepted"; String PARAM_ACCEPTED = "accepted";
String PARAM_ACR_VALUES = "acr_values"; String PARAM_ACR_VALUES = "acr_values";
String PARAM_MAX_AGE = "max_age"; String PARAM_MAX_AGE = "max_age";
String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri";
String PARAM_STATE = "state"; String PARAM_STATE = "state";
String CLIENT_ID_PREFIX = "urn:cesnet:proxyidp:client_id:"; String CLIENT_ID_PREFIX = "urn:cesnet:proxyidp:client_id:";
String AARC_IDP_HINT = "aarc_idp_hint"; String AARC_IDP_HINT = "aarc_idp_hint";
......
...@@ -31,11 +31,12 @@ import lombok.extern.slf4j.Slf4j; ...@@ -31,11 +31,12 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException; import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.saml.SAMLLogoutFilter; import org.springframework.security.saml.SAMLLogoutFilter;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
...@@ -45,8 +46,6 @@ import javax.servlet.http.HttpSession; ...@@ -45,8 +46,6 @@ import javax.servlet.http.HttpSession;
import java.text.ParseException; import java.text.ParseException;
import java.util.Map; import java.util.Map;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_POST_LOGOUT_REDIRECT_URI;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_STATE;
import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_TARGET; import static cz.muni.ics.oidc.server.filters.AuthProcFilterConstants.PARAM_TARGET;
/** /**
...@@ -69,9 +68,23 @@ public class EndSessionEndpoint { ...@@ -69,9 +68,23 @@ public class EndSessionEndpoint {
public static final String URL = "endsession"; public static final String URL = "endsession";
private static final String CLIENT_KEY = "client"; public static final String PARAM_POST_LOGOUT_REDIRECT_URI = "post_logout_redirect_uri";
private static final String STATE_KEY = "state";
private static final String REDIRECT_URI_KEY = "redirectUri"; public static final String PARAM_STATE = "state";
public static final String PARAM_CLIENT_ID = "client_id";
public static final String PARAM_ID_TOKEN_HINT = "id_token_hint";
private static final String SESSION_KEY_CLIENT = "client";
private static final String SESSION_KEY_STATE = "state";
private static final String SESSION_KEY_REDIRECT_URI = "redirect_uri";
private static final String MODEL_CLIENT_KEY = "client";
private static final String PREFIX_REDIRECT = "redirect:";
private final SelfAssertionValidator validator; private final SelfAssertionValidator validator;
private final PerunOidcConfig perunOidcConfig; private final PerunOidcConfig perunOidcConfig;
...@@ -90,73 +103,67 @@ public class EndSessionEndpoint { ...@@ -90,73 +103,67 @@ public class EndSessionEndpoint {
this.htmlClasses = htmlClasses; this.htmlClasses = htmlClasses;
} }
@RequestMapping(value = "/" + URL, method = RequestMethod.GET) @GetMapping(value = "/" + URL)
public String endSession(@RequestParam(value = "id_token_hint", required = false) String idTokenHint, public String endSession(@RequestParam(value = PARAM_ID_TOKEN_HINT, required = false) String idTokenHint,
@RequestParam(value = PARAM_POST_LOGOUT_REDIRECT_URI, required = false) String postLogoutRedirectUri, @RequestParam(value = PARAM_POST_LOGOUT_REDIRECT_URI, required = false) String postLogoutRedirectUri,
@RequestParam(value = STATE_KEY, required = false) String state, @RequestParam(value = PARAM_STATE, required = false) String state,
@RequestParam(value = PARAM_CLIENT_ID, required = false) String clientId,
HttpServletRequest request, HttpServletRequest request,
HttpSession session, HttpSession session,
Authentication auth, Map<String, Object> model) Authentication auth,
Map<String, Object> model)
{ {
JWTClaimsSet idTokenClaims = null; // pulled from the parsed and validated ID token
ClientDetailsEntity client = null; // pulled from ID token's audience field ClientDetailsEntity client = null; // pulled from ID token's audience field
if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) { if (!Strings.isNullOrEmpty(postLogoutRedirectUri)) {
session.setAttribute(REDIRECT_URI_KEY, postLogoutRedirectUri); session.setAttribute(SESSION_KEY_REDIRECT_URI, postLogoutRedirectUri);
} }
if (!Strings.isNullOrEmpty(state)) { if (!Strings.isNullOrEmpty(state)) {
session.setAttribute(STATE_KEY, state); session.setAttribute(SESSION_KEY_STATE, state);
} }
// parse the ID token hint to see if it's valid // parse the ID token hint to see if it's valid
if (!Strings.isNullOrEmpty(idTokenHint)) { if (!Strings.isNullOrEmpty(idTokenHint)) {
try { clientId = resolveClientIdFromTokenAndParameter(idTokenHint, clientId);
JWT idToken = JWTParser.parse(idTokenHint); }
if (validator.isValid(idToken)) {
// we issued this ID token, figure out who it's for
idTokenClaims = idToken.getJWTClaimsSet();
String clientId = Iterables.getOnlyElement(idTokenClaims.getAudience());
client = clientService.loadClientByClientId(clientId);
// save a reference in the session for us to pick up later if (StringUtils.hasText(clientId)) {
//session.setAttribute("endSession_idTokenHint_claims", idTokenClaims); try {
session.setAttribute(CLIENT_KEY, client); client = clientService.loadClientByClientId(clientId);
} session.setAttribute(SESSION_KEY_CLIENT, client);
} catch (ParseException e) { } catch (InvalidClientException e) {
// it's not a valid ID token, ignore it
log.debug("Invalid id token hint", e);
} catch (InvalidClientException e) {
// couldn't find the client, ignore it // couldn't find the client, ignore it
log.debug("Invalid client", e); throw new InvalidRequestException(
"Client requesting the logout cannot be found. Is someone doing something nasty?"
);
} }
} }
// are we logged in or not? // are we logged in or not?
if (auth == null || !request.isUserInRole("ROLE_USER")) { if (auth == null || !request.isUserInRole("ROLE_USER")) {
// we're not logged in anyway, process the final redirect bits if needed // We're not logged in, anyway. Process the final redirect bits if needed.
return processLogout(null, null, session); return processLogout(null, null, request, session, model);
} else { } else {
log.info("Logout confirmating for user {} from client {}", auth.getName(), client != null ? client.getClientName() : "unknown"); log.info("Display logout confirm prompt for user {} from client {}",
auth.getName(), client != null ? client.getClientName() : "unknown"
);
// we are logged in, need to prompt the user before we log out // we are logged in, need to prompt the user before we log out
model.put("client", client); model.put(MODEL_CLIENT_KEY, client);
model.put("idToken", idTokenClaims);
ControllerUtils.setPageOptions(model, request, htmlClasses, perunOidcConfig); ControllerUtils.setPageOptions(model, request, htmlClasses, perunOidcConfig);
return "logout"; return "logout";
} }
} }
@RequestMapping(value = "/" + URL, method = RequestMethod.POST) @PostMapping(value = "/" + URL)
public String processLogout(@RequestParam(value = "approve", required = false) String approved, public String processLogout(@RequestParam(value = "approve", required = false) String approved,
@RequestParam(value = "deny", required = false) String deny, @RequestParam(value = "deny", required = false) String deny,
HttpSession session) HttpServletRequest request,
HttpSession session,
Map<String, Object> model)
{ {
String redirectUri = (String) session.getAttribute(REDIRECT_URI_KEY); String redirectUri = (String) session.getAttribute(SESSION_KEY_REDIRECT_URI);
String state = (String) session.getAttribute(STATE_KEY); String state = (String) session.getAttribute(SESSION_KEY_STATE);
ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(CLIENT_KEY); ClientDetailsEntity client = (ClientDetailsEntity) session.getAttribute(SESSION_KEY_CLIENT);
String redirectURL = null; String redirectURL = null;
// if we have a client AND the client has post-logout redirect URIs // if we have a client AND the client has post-logout redirect URIs
...@@ -164,10 +171,9 @@ public class EndSessionEndpoint { ...@@ -164,10 +171,9 @@ public class EndSessionEndpoint {
if (isUriValid(redirectUri, client)) { if (isUriValid(redirectUri, client)) {
UriComponentsBuilder uri = UriComponentsBuilder.fromHttpUrl(redirectUri); UriComponentsBuilder uri = UriComponentsBuilder.fromHttpUrl(redirectUri);
if (StringUtils.hasText(state)) { if (StringUtils.hasText(state)) {
uri = uri.queryParam("state", state); uri = uri.queryParam(PARAM_STATE, state);
} }
UriComponents uriComponents = uri.build(); UriComponents uriComponents = uri.build();
log.trace("redirect URL: {}", uriComponents);
redirectURL = uriComponents.toString(); redirectURL = uriComponents.toString();
} }
...@@ -175,22 +181,52 @@ public class EndSessionEndpoint { ...@@ -175,22 +181,52 @@ public class EndSessionEndpoint {
String target = getRedirectUrl(redirectUri, state); String target = getRedirectUrl(redirectUri, state);
if (StringUtils.hasText(approved)) { if (StringUtils.hasText(approved)) {
target = getLogoutUrl(target); target = getLogoutUrl(target);
log.trace("redirecting to logout SAML and then {}", target); log.trace("Endsession - redirecting to SAML logout, then to {}", target);
return "redirect:" + target; return PREFIX_REDIRECT + target;
} else { } else {
log.trace("redirecting to {}", target); log.trace("Endsession - redirecting to {}", target);
return "redirect:" + redirectURL; return PREFIX_REDIRECT + redirectURL;
} }
} else { } else {
if (StringUtils.hasText(approved)) { if (StringUtils.hasText(approved)) {
log.trace("redirecting to logout SAML only"); log.trace("Endsession - redirecting to SAML logout only");
return "redirect:" + getLogoutUrl(null); return PREFIX_REDIRECT + getLogoutUrl(null);
} else { } else {
ControllerUtils.setPageOptions(model, request, htmlClasses, perunOidcConfig);
log.trace("Endsession - user denied the logout and we have no redirect, display logout denied page");
return "logout_denied"; return "logout_denied";
} }
} }
} }
private String resolveClientIdFromTokenAndParameter(String idTokenHint, String clientId) {
JWTClaimsSet idTokenClaims = null;
try {
JWT idToken = JWTParser.parse(idTokenHint);
if (validator.isValid(idToken)) {
idTokenClaims = idToken.getJWTClaimsSet();
}
} catch (ParseException e) {
// it's not a valid ID token, ignore it
log.debug("Invalid id token hint", e);
}
if (idTokenClaims != null) {
String clientIdFromToken = Iterables.getOnlyElement(idTokenClaims.getAudience());
if (StringUtils.hasText(clientId)) {
if (StringUtils.hasText(clientIdFromToken) && !clientIdFromToken.equals(clientId)) {
throw new InvalidRequestException(
"Client ID and client for which the ID token has been issued do not match. Is someone doing something nasty?"
);
}
} else {
clientId = clientIdFromToken;
}
}
return clientId;
}
private boolean isUriValid(String redirectUri, ClientDetailsEntity client) { private boolean isUriValid(String redirectUri, ClientDetailsEntity client) {
return StringUtils.hasText(redirectUri) return StringUtils.hasText(redirectUri)
&& client != null && client != null
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment