package at.gv.egiz.eaaf.modules.auth.sl20.utils; import java.io.IOException; import java.io.StringWriter; import java.net.URISyntaxException; import java.text.MessageFormat; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.HttpClientResponseHandler; import org.apache.hc.core5.http.io.entity.EntityUtils; import org.apache.hc.core5.http.message.StatusLine; import org.apache.hc.core5.net.URIBuilder; import org.jose4j.base64url.Base64Url; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import com.fasterxml.jackson.core.JacksonException; import com.fasterxml.jackson.databind.JsonNode; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.Data; import lombok.Getter; public class SL20HttpBindingUtils { private static final Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); @Data @Getter public static class Sl20ResponseHolder { private final JsonNode responseBody; private final StatusLine responseStatus; private SlCommandoParserException error; } /** * Security-Layer 2.0 specific response-handler for Apache HTTP client. * * @return {@link Sl20ResponseHolder} */ public static HttpClientResponseHandler sl20ResponseHandler() { return response -> { try { final int httpStatusCode = response.getCode(); if (httpStatusCode == HttpStatus.OK.value()) { if (response.getEntity().getContentType() == null) { throw new SlCommandoParserException("SL20 response contains NO ContentType"); } final ContentType contentType = ContentType.parse(response.getEntity().getContentType()); if (!ContentType.APPLICATION_JSON.getMimeType().equals(contentType.getMimeType())) { log.error("SL20 response with statuscode: {} has wrong http ContentType: {}", response.getCode(), contentType); throw new SlCommandoParserException( "SL20 response with a wrong http ContentType: " + contentType); } //parse OK response from body return new Sl20ResponseHolder(parseSL20ResultFromResponse(response.getEntity()), new StatusLine(response)); } else if (httpStatusCode == HttpStatus.SEE_OTHER.value() || httpStatusCode == HttpStatus.TEMPORARY_REDIRECT.value()) { final Header[] locationHeader = response.getHeaders("Location"); if (locationHeader == null) { throw new SlCommandoParserException("Find Redirect statuscode but not Location header"); } final String sl20RespString = new URIBuilder(locationHeader[0].getValue()).getQueryParams().get(0).getValue(); return new Sl20ResponseHolder(JsonMapper.getMapper().readTree(Base64Url.decode(sl20RespString)), new StatusLine(response)); } else if ( httpStatusCode == HttpStatus.INTERNAL_SERVER_ERROR.value() || httpStatusCode == HttpStatus.UNAUTHORIZED.value() || httpStatusCode == HttpStatus.BAD_REQUEST.value()) { log.info("SL20 response with http-code: {}. Search for error message", httpStatusCode); String bodyMsg = "_EMPTY_"; try { //extract JSON body from defined http error-codes bodyMsg = EntityUtils.toString(response.getEntity()); log.info("SL20 response with http-code: {} and errorMsg: {}", httpStatusCode, bodyMsg); Sl20ResponseHolder holder = new Sl20ResponseHolder( JsonMapper.getMapper().readTree(bodyMsg), new StatusLine(response)); return holder; } catch (final JacksonException e) { log.warn("SL20 response contains no valid JSON. Body msg: {}", bodyMsg, e); throw new SlCommandoParserException(MessageFormat.format( "SL20 response with http-code: {} and generic response-processing error: {}", httpStatusCode, e.getMessage())); } } else { //all other HTTP StatusCodes throw new SlCommandoParserException(MessageFormat.format( "SL20 response with http-code: {} and errorMsg: {}", httpStatusCode, EntityUtils.toString(response.getEntity()))); } } catch (SlCommandoParserException e) { Sl20ResponseHolder holder = new Sl20ResponseHolder(null, new StatusLine(response)); holder.setError(e); return holder; } catch (final Exception e) { Sl20ResponseHolder holder = new Sl20ResponseHolder(null, new StatusLine(response)); holder.setError( new SlCommandoParserException("SL20 response parsing FAILED! Reason: " + e.getMessage(), e)); return holder; } }; } /** * Write SL2.0 response into http-response object * * @param httpReq Current http request * @param httpResp Current http response * @param sl20Forward SL2.0 command that should be written to response * @param redirectUrl SL2.0 redirect URL in case of SL2.0 redirect command * and no native client (see SL2.0 specification) * @param httpCodeRedirect http redirect-code in case of SL2.0 redirect command * and no native client (see SL2.0 specification) * @throws IOException In case of an IO error * @throws URISyntaxException In case of a wrong URL */ public static void writeIntoResponse(@Nonnull final HttpServletRequest httpReq, @Nonnull final HttpServletResponse httpResp, @Nonnull final JsonNode sl20Forward, @Nullable final String redirectUrl, @Nonnull final int httpCodeRedirect) throws IOException, URISyntaxException { // forward SL2.0 command httpResp.addIntHeader(SL20Constants.HTTP_HEADER_SL20_RESP, SL20Constants.CURRENT_SL20_VERSION); if (httpReq.getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE) != null && httpReq .getHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE).equals(SL20Constants.HTTP_HEADER_VALUE_NATIVE)) { log.debug("Client request containts 'native client' header ... "); final StringWriter writer = new StringWriter(); writer.write(sl20Forward.toString()); final byte[] content = writer.toString().getBytes("UTF-8"); httpResp.setStatus(HttpServletResponse.SC_OK); httpResp.setContentLength(content.length); httpResp.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); httpResp.getOutputStream().write(content); } else { log.debug("Client request containts is no native client ... "); final URIBuilder clientRedirectUri = new URIBuilder(redirectUrl); clientRedirectUri.addParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, Base64Url.encode(sl20Forward.toString().getBytes("UTF-8"))); httpResp.setStatus(httpCodeRedirect); httpResp.setHeader("Location", clientRedirectUri.build().toString()); } } private static JsonNode parseSL20ResultFromResponse(final HttpEntity resp) throws Exception { if (resp != null && resp.getContent() != null) { final String rawSL20Resp = EntityUtils.toString(resp); try { final JsonNode sl20Resp = JsonMapper.getMapper().readTree(rawSL20Resp); if (sl20Resp != null) { return sl20Resp; } else { log.error("SL2.0 can NOT parse to a JSON object from msg: {}", rawSL20Resp); throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); } } catch (SlCommandoParserException e) { throw e; } catch (Exception e) { log.error("SL2.0 can NOT parse to a JSON object from msg: {}", rawSL20Resp); throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); } } else { throw new SlCommandoParserException("Can NOT find any content in http response"); } } }