package at.gv.egiz.eaaf.modules.auth.sl20.tasks; import java.io.IOException; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.lang3.StringUtils; import org.jose4j.base64url.Base64Url; import org.springframework.beans.factory.annotation.Autowired; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; import at.gv.egiz.eaaf.core.exceptions.EaafAuthenticationException; import at.gv.egiz.eaaf.core.exceptions.EaafStorageException; import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; import at.gv.egiz.eaaf.core.impl.data.ExceptionContainer; import at.gv.egiz.eaaf.core.impl.idp.auth.modules.AbstractAuthServletTask; import at.gv.egiz.eaaf.core.impl.utils.DataUrlBuilder; import at.gv.egiz.eaaf.core.impl.utils.StreamUtils; import at.gv.egiz.eaaf.modules.auth.sl20.Constants; import at.gv.egiz.eaaf.modules.auth.sl20.EventCodes; import at.gv.egiz.eaaf.modules.auth.sl20.data.VerificationResult; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20Exception; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20SecurityException; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SL20VdaResponseException; import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import at.gv.egiz.eaaf.modules.auth.sl20.utils.IJoseTools; import at.gv.egiz.eaaf.modules.auth.sl20.utils.JsonMapper; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20ResponseUtils; import lombok.extern.slf4j.Slf4j; @Slf4j public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask { @Autowired(required = true) private IJoseTools joseTools; @Override public void execute(final ExecutionContext executionContext, final HttpServletRequest request, final HttpServletResponse response) throws TaskExecutionException { String sl20Result = null; try { log.debug("Receiving SL2.0 response process .... "); // get SL2.0 command or result from HTTP request sl20Result = getSl20OperationFromHttp(request); revisionsLogger.logEvent(pendingReq, EventCodes.AUTHPROCESS_SL20_DATAURL_IP, request.getRemoteAddr()); internalExecute(request, response, sl20Result); } catch (final Exception e) { // write internal server errror 500 according to SL2.0 specification, chapter // https transport // binding log.warn("Can NOT build SL2.0 response. Reason: " + e.getMessage(), e); if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } try { response.sendError(500, "Internal Server Error."); } catch (final IOException e1) { log.error("Can NOT send error message. SOMETHING IS REALY WRONG!", e); } } } protected abstract void handleResponsePayLoad(JsonNode payLoad) throws SlCommandoParserException, SL20Exception, EaafStorageException; protected abstract String getResumeEndPoint(); private void internalExecute(HttpServletRequest request, HttpServletResponse response, String sl20Result) throws Exception { JsonNode sl20ReqObj = null; try { // parse SL2.0 command/result into JSON sl20ReqObj = decodeSl20Request(sl20Result); log.info("Receive response from A-Trust. Starting response-message validation ... "); // check on errorMessage final VerificationResult payLoadContainerErrorCheck = SL20JsonExtractorUtils.extractSL20PayLoad(sl20ReqObj, joseTools, false); if (SL20JsonExtractorUtils.getStringValue( payLoadContainerErrorCheck.getPayload(), SL20Constants.SL20_COMMAND_CONTAINER_NAME, true) .equals(SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR)) { handleSl20Error(payLoadContainerErrorCheck); } else { // Receive no error - To request validation handleSl20Operation(sl20ReqObj); } } catch (final EaafAuthenticationException e) { if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } pendingReq.setRawDataToTransaction( Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR, new ExceptionContainer(null, new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e))); } catch (final Exception e) { if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } pendingReq.setRawDataToTransaction( Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR, new ExceptionContainer(null, new TaskExecutionException(pendingReq, e.getMessage(), e))); } finally { // store pending request requestStoreage.storePendingRequest(pendingReq); // write SL2.0 response if (sl20ReqObj != null) { final String resumeEndpoint = new DataUrlBuilder().buildDataUrl(pendingReq.getAuthUrl(), getResumeEndPoint(), pendingReq.getPendingRequestId()); SL20ResponseUtils.buildResponse(request, response, pendingReq, resumeEndpoint, SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_TRANSACTIONID, false), authConfig); } else { SL20ResponseUtils.buildErrorResponse(response, "2000", "General transport Binding error"); } } } private String getSl20OperationFromHttp(HttpServletRequest request) throws SL20Exception, IOException, FileUploadException { // get SL2.0 command or result from HTTP request final Map reqParams = getParameters(request); String sl20Result = reqParams.get(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM); if (StringUtils.isEmpty(sl20Result)) { // Workaround for SIC Handy-Signature, because it sends result in InputStream final String isReqInput = StreamUtils.readStream(request.getInputStream(), "UTF-8"); if (StringUtils.isNotEmpty(isReqInput)) { log.info("Use SIC Handy-Signature work-around!"); sl20Result = isReqInput.substring("slcommand=".length()); } else { log.info("NO SL2.0 commando or result FOUND."); throw new SL20Exception("sl20.04", null); } } log.trace("Received SL2.0 result: {}", sl20Result); return sl20Result; } private JsonNode decodeSl20Request(String sl20Result) throws JsonMappingException, JsonProcessingException, SL20Exception { try { return JsonMapper.getMapper().readTree(Base64Url.decodeToUtf8String(sl20Result)); } catch (final JsonParseException e) { log.error("SL2.0 command or result is NOT valid JSON. Received msg: {}", sl20Result, e); throw new SL20Exception("sl20.02", new Object[] { "SL2.0 command or result is NOT valid JSON." }, e); } } private void handleSl20Error(VerificationResult payLoadContainerErrorCheck) throws SL20Exception { log.debug("Find " + SL20Constants.SL20_COMMAND_IDENTIFIER_ERROR + " result .... "); final JsonNode errorResult = SL20JsonExtractorUtils.extractSL20Result(payLoadContainerErrorCheck .getPayload(), joseTools, false); final String errorCode = SL20JsonExtractorUtils.getStringValue(errorResult, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORCODE, true); final String errorMsg = SL20JsonExtractorUtils.getStringValue(errorResult, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERRORMESSAGE, false); final SL20VdaResponseException ex = new SL20VdaResponseException("sl20.08", new Object[] { errorCode, errorMsg }, errorCode); log.info("Receiving errorcode: {} with msg: {} from VDA! Stopping auth-process ... ", errorCode, errorMsg); final String vdaSessionId = SL20JsonExtractorUtils.getStringValue(errorResult, SL20Constants.SL20_COMMAND_PARAM_GENERAL_RESPONSE_ERROR_VDASESSIONID, false); if (StringUtils.isNotEmpty(vdaSessionId)) { log.debug("VDA provides an optional sessionId. Inject it to internal error-holder "); ex.setVdaSessionId(vdaSessionId); } throw ex; } private void handleSl20Operation(JsonNode sl20ReqObj) throws SlCommandoParserException, SL20Exception, EaafStorageException { // validate reqId with inResponseTo final String sl20ReqId = pendingReq .getRawData(Constants.PENDING_REQ_STORAGE_PREFIX + SL20Constants.SL20_REQID, String.class); final String inRespTo = SL20JsonExtractorUtils.getStringValue(sl20ReqObj, SL20Constants.SL20_INRESPTO, true); if (sl20ReqId == null || !sl20ReqId.equals(inRespTo)) { log.info("SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); throw new SL20SecurityException( "SL20 'reqId': " + sl20ReqId + " does NOT match to 'inResponseTo':" + inRespTo); } // validate signature final VerificationResult payLoadContainer = SL20JsonExtractorUtils.extractSL20PayLoad(sl20ReqObj, joseTools, authConfig.getBasicConfigurationBoolean(Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)); if (payLoadContainer.isValidSigned() == null || !payLoadContainer.isValidSigned()) { if (authConfig.getBasicConfigurationBoolean(Constants.CONFIG_PROP_FORCE_EID_SIGNED_RESULT, true)) { log.info("SL20 result from VDA was not valid signed"); throw new SL20SecurityException(new Object[] { "Signature on SL20 result NOT valid." }); } else { log.warn("SL20 result from VDA is NOT valid signed, but signatures-verification " + "is DISABLED by configuration!"); } } // extract payloaf final JsonNode payLoad = payLoadContainer.getPayload(); // handle SL2.0 response payLoad handleResponsePayLoad(payLoad); } }