Skip to content

Commit

Permalink
feat: add context information data to logs (#589)
Browse files Browse the repository at this point in the history
* feat: add client.id and client.state into MDC context when possible

* fix: set correct client.state into MDC

* feat: introduce MDCHandler class

* feat: add custom annotation to handle MDC context before controller method execution

* feat: centralize interceptors in ControllerCustomInterceptor

* feat: set default behaviour to context.proceed for handleControllerRequest method

* feat: reduce CurrentAuthDTO parameters
  • Loading branch information
BenitoVisone authored Jan 10, 2025
1 parent 9659175 commit 5974ccb
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 121 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import io.quarkus.runtime.Startup;
import it.pagopa.oneid.common.model.Client;
import it.pagopa.oneid.common.model.IDP;
import it.pagopa.oneid.common.model.enums.GrantType;
import it.pagopa.oneid.common.model.exception.AuthorizationErrorException;
import it.pagopa.oneid.common.model.exception.OneIdentityException;
import it.pagopa.oneid.common.model.exception.enums.ErrorCode;
Expand All @@ -15,10 +14,8 @@
import it.pagopa.oneid.exception.IDPNotFoundException;
import it.pagopa.oneid.exception.IDPSSOEndpointNotFoundException;
import it.pagopa.oneid.exception.InvalidGrantException;
import it.pagopa.oneid.exception.InvalidRequestMalformedHeaderAuthorizationException;
import it.pagopa.oneid.exception.InvalidScopeException;
import it.pagopa.oneid.exception.SessionException;
import it.pagopa.oneid.exception.UnsupportedGrantTypeException;
import it.pagopa.oneid.exception.UnsupportedResponseTypeException;
import it.pagopa.oneid.model.session.AccessTokenSession;
import it.pagopa.oneid.model.session.SAMLSession;
Expand All @@ -27,6 +24,8 @@
import it.pagopa.oneid.service.OIDCServiceImpl;
import it.pagopa.oneid.service.SAMLServiceImpl;
import it.pagopa.oneid.service.SessionServiceImpl;
import it.pagopa.oneid.web.controller.interceptors.ControllerCustomInterceptor;
import it.pagopa.oneid.web.controller.interceptors.CurrentAuthDTO;
import it.pagopa.oneid.web.dto.AuthorizationRequestDTOExtended;
import it.pagopa.oneid.web.dto.AuthorizationRequestDTOExtendedGet;
import it.pagopa.oneid.web.dto.AuthorizationRequestDTOExtendedPost;
Expand All @@ -41,8 +40,6 @@
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Base64;
Expand Down Expand Up @@ -75,6 +72,9 @@ public class OIDCController {
@Inject
Map<String, Client> clientsMap;

@Inject
CurrentAuthDTO currentAuthDTO;

private <T> AuthorizationRequestDTOExtended getObject(T object) throws OneIdentityException {
switch (object) {
case AuthorizationRequestDTOExtendedGet authorizationRequestDTOExtendedGet -> {
Expand All @@ -86,8 +86,7 @@ private <T> AuthorizationRequestDTOExtended getObject(T object) throws OneIdenti
.nonce(authorizationRequestDTOExtendedGet.getNonce())
.scope(authorizationRequestDTOExtendedGet.getScope())
.state(authorizationRequestDTOExtendedGet.getState())
.ipAddress(authorizationRequestDTOExtendedGet.getIpAddress())
.build();
.ipAddress(authorizationRequestDTOExtendedGet.getIpAddress()).build();
}
case AuthorizationRequestDTOExtendedPost authorizationRequestDTOExtendedPost -> {
return AuthorizationRequestDTOExtended.builder()
Expand All @@ -98,8 +97,7 @@ private <T> AuthorizationRequestDTOExtended getObject(T object) throws OneIdenti
.nonce(authorizationRequestDTOExtendedPost.getNonce())
.scope(authorizationRequestDTOExtendedPost.getScope())
.state(authorizationRequestDTOExtendedPost.getState())
.ipAddress(authorizationRequestDTOExtendedPost.getIpAddress())
.build();
.ipAddress(authorizationRequestDTOExtendedPost.getIpAddress()).build();
}
default -> {
throw new OneIdentityException("Invalid object for /oidc/authorize route");
Expand Down Expand Up @@ -160,8 +158,7 @@ private Response handleAuthorize(
}

// 2. Check if idp exists
idp = samlServiceImpl.getIDPFromEntityID(
authorizationRequestDTOExtended.getIdp());
idp = samlServiceImpl.getIDPFromEntityID(authorizationRequestDTOExtended.getIdp());
if (idp.isEmpty()) {
Log.debug("selected IDP not found");
throw new IDPNotFoundException(authorizationRequestDTOExtended.getRedirectUri(),
Expand All @@ -172,17 +169,15 @@ private Response handleAuthorize(
// 4. Check if scope is "openid"
if (StringUtils.isNotBlank(authorizationRequestDTOExtended.getScope())
&& !authorizationRequestDTOExtended.getScope().equalsIgnoreCase("openid")) {
Log.error(
"scope not supported");
Log.error("scope not supported");
throw new InvalidScopeException(authorizationRequestDTOExtended.getRedirectUri(),
authorizationRequestDTOExtended.getState(),
authorizationRequestDTOExtended.getClientId());
}

// 5. Check if response type is "code"
if (!authorizationRequestDTOExtended.getResponseType().equals(ResponseType.CODE)) {
Log.error(
"response type not supported");
Log.error("response type not supported");
throw new UnsupportedResponseTypeException(authorizationRequestDTOExtended.getRedirectUri(),
authorizationRequestDTOExtended.getState(),
authorizationRequestDTOExtended.getClientId());
Expand All @@ -197,21 +192,17 @@ private Response handleAuthorize(

AuthnRequest authnRequest = null;
try {
authnRequest = samlServiceImpl.buildAuthnRequest(
idpSSOEndpoint, client.getAcsIndex(),
client.getAttributeIndex(),
client.getAuthLevel().getValue());
authnRequest = samlServiceImpl.buildAuthnRequest(idpSSOEndpoint, client.getAcsIndex(),
client.getAttributeIndex(), client.getAuthLevel().getValue());
} catch (GenericAuthnRequestCreationException | IDPSSOEndpointNotFoundException |
OneIdentityException e) {
Log.error("error building authorization request: "
+ e.getMessage());
Log.error("error building authorization request: " + e.getMessage());
throw new AuthorizationErrorException(authorizationRequestDTOExtended.getRedirectUri(),
authorizationRequestDTOExtended.getState());
}

String encodedAuthnRequest = Base64.getEncoder().encodeToString(
oidcServiceImpl.getStringValue(
oidcServiceImpl.getElementValueFromAuthnRequest(authnRequest)).getBytes());
String encodedAuthnRequest = Base64.getEncoder().encodeToString(oidcServiceImpl.getStringValue(
oidcServiceImpl.getElementValueFromAuthnRequest(authnRequest)).getBytes());
String encodedRelayStateString = "";

// 7. Persist SAMLSession
Expand All @@ -228,65 +219,33 @@ private Response handleAuthorize(
try {
samlSessionServiceImpl.saveSession(samlSession);
} catch (SessionException e) {
Log.error("error during session management "
+ e.getMessage());
Log.error("error during session management " + e.getMessage());
throw new AuthorizationErrorException(authorizationRequestDTOExtended.getRedirectUri(),
authorizationRequestDTOExtended.getState());
}

String redirectAutoSubmitPOSTForm = "<form method='post' action=" + idpSSOEndpoint
+ " id='SAMLRequestForm'>" +
"<input type='hidden' name='SAMLRequest' value=" + encodedAuthnRequest + " />" +
"<input type='hidden' name='RelayState' value=" + encodedRelayStateString + " />" +
"<input id='SAMLSubmitButton' type='submit' value='Submit' />" +
"</form>" +
"<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden'; " +
"document.getElementById('SAMLRequestForm').submit();</script>";

return Response
.ok(redirectAutoSubmitPOSTForm)
.type(MediaType.TEXT_HTML)
.build();
String redirectAutoSubmitPOSTForm =
"<form method='post' action=" + idpSSOEndpoint + " id='SAMLRequestForm'>"
+ "<input type='hidden' name='SAMLRequest' value=" + encodedAuthnRequest + " />"
+ "<input type='hidden' name='RelayState' value=" + encodedRelayStateString + " />"
+ "<input id='SAMLSubmitButton' type='submit' value='Submit' />" + "</form>"
+ "<script>document.getElementById('SAMLSubmitButton').style.visibility='hidden'; "
+ "document.getElementById('SAMLRequestForm').submit();</script>";

return Response.ok(redirectAutoSubmitPOSTForm).type(MediaType.TEXT_HTML).build();
}

@POST
@Path("/token")
@Produces(MediaType.APPLICATION_JSON)
@ControllerCustomInterceptor
public TokenDataDTO token(@BeanParam @Valid TokenRequestDTOExtended tokenRequestDTOExtended)
throws OneIdentityException {
Log.info("start");

String authorization = tokenRequestDTOExtended.getAuthorization().replaceAll("Basic ", "");
byte[] decodedBytes;
String decodedString;
String clientId;
String clientSecret;
try {
decodedBytes = Base64.getDecoder().decode(authorization);
decodedString = new String(decodedBytes);
clientId = URLDecoder.decode(decodedString.split(":")[0], StandardCharsets.UTF_8);
clientSecret = URLDecoder.decode(decodedString.split(":")[1],
StandardCharsets.UTF_8);
} catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) {
// TODO: consider collecting this as Client Error metric
throw new InvalidRequestMalformedHeaderAuthorizationException();
}

// TODO check if possible 5xx are returned as json or html which is an error
if (!tokenRequestDTOExtended.getGrantType().equals(GrantType.AUTHORIZATION_CODE)) {
Log.error("unsupported grant type: " + tokenRequestDTOExtended.getGrantType());
throw new UnsupportedGrantTypeException(clientId);
}

oidcServiceImpl.authorizeClient(clientId, clientSecret);

SAMLSession session;
try {
session = samlSessionServiceImpl.getSAMLSessionByCode(
tokenRequestDTOExtended.getCode());
} catch (SessionException e) {
throw new InvalidGrantException(clientId);
}
// 1. Get CurrentAuthDTO parameters
SAMLSession session = currentAuthDTO.getSamlSession();
String clientId = session.getAuthorizationRequestDTOExtended().getClientId();

// check if redirect uri corresponds to session's redirect uri, needs to be mapped as InvalidGrantException
if (!tokenRequestDTOExtended.getRedirectUri()
Expand All @@ -296,8 +255,7 @@ public TokenDataDTO token(@BeanParam @Valid TokenRequestDTOExtended tokenRequest
}

Assertion assertion = samlServiceImpl.getSAMLResponseFromString(session.getSAMLResponse())
.getAssertions()
.getFirst();
.getAssertions().getFirst();

TokenDataDTO tokenDataDTO = oidcServiceImpl.getOIDCTokens(session.getSamlRequestID(), clientId,
samlServiceImpl.getAttributesFromSAMLAssertion(assertion),
Expand All @@ -307,9 +265,8 @@ public TokenDataDTO token(@BeanParam @Valid TokenRequestDTOExtended tokenRequest
long ttl = Instant.now().plus(2, ChronoUnit.DAYS).getEpochSecond();

AccessTokenSession accessTokenSession = new AccessTokenSession(session.getSamlRequestID(),
RecordType.ACCESS_TOKEN,
creationTime,
ttl, tokenDataDTO.getAccessToken(), tokenDataDTO.getIdToken());
RecordType.ACCESS_TOKEN, creationTime, ttl, tokenDataDTO.getAccessToken(),
tokenDataDTO.getIdToken());

accessTokenSessionServiceImpl.saveSession(accessTokenSession);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import it.pagopa.oneid.service.OIDCServiceImpl;
import it.pagopa.oneid.service.SAMLServiceImpl;
import it.pagopa.oneid.service.SessionServiceImpl;
import it.pagopa.oneid.web.controller.interceptors.ControllerCustomInterceptor;
import it.pagopa.oneid.web.controller.interceptors.CurrentAuthDTO;
import it.pagopa.oneid.web.dto.AccessTokenDTO;
import it.pagopa.oneid.web.dto.SAMLResponseDTO;
import jakarta.inject.Inject;
Expand Down Expand Up @@ -62,42 +64,23 @@ public class SAMLController {
@Inject
Map<String, Client> clientsMap;

@Inject
CurrentAuthDTO currentAuthDTO;

@POST
@Path("/acs")
@ControllerCustomInterceptor
public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {
Log.info("start");

org.opensaml.saml.saml2.core.Response response = null;
try {
response = samlServiceImpl.getSAMLResponseFromString(
samlResponseDTO.getSAMLResponse());
} catch (OneIdentityException e) {
Log.error("error getting SAML Response");
throw new GenericHTMLException(ErrorCode.GENERIC_HTML_ERROR);
}

// 1a. if in ResponseTo does not match with a pending AuthnRequest, raise an exception
SAMLSession samlSession = null;
String inResponseTo = response.getInResponseTo();
// 1a. Get CurrentAuthDTO parameters

if (inResponseTo == null || inResponseTo.isBlank()) {
Log.error("inResponseTo parameter must not be null or blank");
// TODO: consider collecting this as IDP Error metric
throw new GenericHTMLException(ErrorCode.GENERIC_HTML_ERROR);
}
try {
samlSession = samlSessionService.getSession(inResponseTo,
RecordType.SAML);
} catch (SessionException e) {
Log.error("error during session management: " + e.getMessage());
// TODO: consider collecting this as IDP Error metric
throw new GenericHTMLException(ErrorCode.SESSION_ERROR);
}
org.opensaml.saml.saml2.core.Response response = currentAuthDTO.getResponse();
SAMLSession samlSession = currentAuthDTO.getSamlSession();

// 1b. Update SAMLSession with SAMLResponse attribute
try {
samlSessionService.setSAMLResponse(inResponseTo,
samlSessionService.setSAMLResponse(response.getInResponseTo(),
samlResponseDTO.getSAMLResponse());
} catch (SessionException e) {
Log.error("error during session management: " + e.getMessage());
Expand All @@ -109,8 +92,7 @@ public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {
try {
samlServiceImpl.checkSAMLStatus(response);
} catch (OneIdentityException e) {
Log.error(
"error during SAMLResponse status check: " + e.getMessage());
Log.error("error during SAMLResponse status check: " + e.getMessage());
cloudWatchConnectorImpl.sendIDPErrorMetricData(
samlSession.getAuthorizationRequestDTOExtended().getIdp(),
ErrorCode.SAML_RESPONSE_STATUS_ERROR);
Expand All @@ -120,9 +102,8 @@ public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {
// 2. Check if Signatures are valid (Response and Assertion) and if SAML Response is formally correct
Client client = clientsMap.get(samlSession.getAuthorizationRequestDTOExtended().getClientId());
samlServiceImpl.validateSAMLResponse(response,
samlSession.getAuthorizationRequestDTOExtended().getIdp(),
client.getRequestedParameters(), Instant.ofEpochSecond(samlSession.getCreationTime()),
client.getAuthLevel());
samlSession.getAuthorizationRequestDTOExtended().getIdp(), client.getRequestedParameters(),
Instant.ofEpochSecond(samlSession.getCreationTime()), client.getAuthLevel());

// 3. Get Authorization Response
AuthorizationRequest authorizationRequest = oidcServiceImpl.buildAuthorizationRequest(
Expand All @@ -137,9 +118,9 @@ public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {
AuthorizationCode authorizationCode = authorizationResponse.toSuccessResponse()
.getAuthorizationCode();

OIDCSession oidcSession = new OIDCSession(inResponseTo, RecordType.OIDC,
creationTime,
ttl, authorizationCode.getValue());
OIDCSession oidcSession = new OIDCSession(response.getInResponseTo(), RecordType.OIDC,
creationTime, ttl,
authorizationCode.getValue());

// 4. Save OIDC session
try {
Expand All @@ -153,12 +134,11 @@ public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {

URI redirectStringResponse;
try {
redirectStringResponse = new URI(
clientCallbackUri + "?code=" + authorizationCode + "&state="
+ authorizationResponse.getState());
redirectStringResponse = new URI(clientCallbackUri + "?code=" + authorizationCode + "&state="
+ authorizationResponse.getState());
} catch (URISyntaxException e) {
Log.error("error during setting of Callback URI: "
+ clientCallbackUri + "error: " + e.getMessage());
Log.error("error during setting of Callback URI: " + clientCallbackUri + "error: "
+ e.getMessage());
Log.error("error during creation of Callback URI");
throw new GenericHTMLException(ErrorCode.GENERIC_HTML_ERROR);
}
Expand All @@ -169,10 +149,7 @@ public Response samlACS(@BeanParam @Valid SAMLResponseDTO samlResponseDTO) {
Log.info("end");

// 5. Redirect to client callback URI
return jakarta.ws.rs.core.Response
.status(302)
.location(redirectStringResponse)
.build();
return jakarta.ws.rs.core.Response.status(302).location(redirectStringResponse).build();
}

@GET
Expand All @@ -182,8 +159,7 @@ public Response assertion(@BeanParam @Valid AccessTokenDTO accessToken) {
Log.info("start");
String samlResponse = null;
try {
samlResponse = accessTokenSessionService.getSAMLResponseByCode(
accessToken.getAccessToken());
samlResponse = accessTokenSessionService.getSAMLResponseByCode(accessToken.getAccessToken());
} catch (SessionException e) {
Log.debug("error during session management: " + e.getMessage());
throw new AssertionNotFoundException(e);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package it.pagopa.oneid.web.controller.interceptors;

import jakarta.interceptor.InterceptorBinding;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface ControllerCustomInterceptor {

}
Loading

0 comments on commit 5974ccb

Please sign in to comment.