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 javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.ParseException; import org.apache.http.StatusLine; import org.apache.http.client.ResponseHandler; import org.apache.http.client.utils.URIBuilder; import org.apache.http.util.EntityUtils; 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.databind.JsonNode; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import lombok.Data; import lombok.Getter; public class SL20HttpBindingUtils { private static final Logger log = LoggerFactory.getLogger(SL20HttpBindingUtils.class); private static JsonMapper mapper = new JsonMapper(); @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 ResponseHandler sl20ResponseHandler() { return response -> { try { final int httpStatusCode = response.getStatusLine().getStatusCode(); if (httpStatusCode == HttpStatus.OK.value()) { if (response.getEntity().getContentType() == null) { throw new SlCommandoParserException("SL20 response contains NO ContentType"); } if (!response.getEntity().getContentType().getValue().startsWith("application/json")) { throw new SlCommandoParserException( "SL20 response with a wrong ContentType: " + response.getEntity().getContentType().getValue()); } //parse OK response from body return new Sl20ResponseHolder(parseSL20ResultFromResponse(response.getEntity()), response.getStatusLine()); } 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(mapper.getMapper().readTree(Base64Url.decode(sl20RespString)), response.getStatusLine()); } 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: {0} and errorMsg: {1}", httpStatusCode, bodyMsg); Sl20ResponseHolder holder = new Sl20ResponseHolder( mapper.getMapper().readTree(bodyMsg), response.getStatusLine()); return holder; } catch (final IOException | ParseException e) { log.warn("SL20 response contains no valid JSON", e); throw new SlCommandoParserException(MessageFormat.format( "SL20 response with http-code: {0} with body: {1} and generic response-processing error: {2}", httpStatusCode, bodyMsg, e.getMessage())); } } else { //all other HTTP StatusCodes throw new SlCommandoParserException(MessageFormat.format( "SL20 response with http-code: {0} and errorMsg: {1}", httpStatusCode, EntityUtils.toString(response.getEntity()))); } } catch (SlCommandoParserException e) { Sl20ResponseHolder holder = new Sl20ResponseHolder(null, response.getStatusLine()); holder.setError(e); return holder; } catch (final Exception e) { Sl20ResponseHolder holder = new Sl20ResponseHolder(null, response.getStatusLine()); 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); final JsonNode sl20Resp = mapper.getMapper().readTree(rawSL20Resp); // TODO: check sl20Resp type like && sl20Resp.isJsonObject() if (sl20Resp != null) { return sl20Resp; } else { throw new SlCommandoParserException("SL2.0 can NOT parse to a JSON object"); } } else { throw new SlCommandoParserException("Can NOT find content in http response"); } } }