diff options
Diffstat (limited to 'eaaf_modules/eaaf_module_auth_sl20/src/main')
6 files changed, 277 insertions, 161 deletions
diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java index 5be5a61f..63bf7897 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractCreateQualEidRequestTask.java @@ -2,6 +2,7 @@ package at.gv.egiz.eaaf.modules.auth.sl20.tasks; import java.io.Serializable; import java.net.ConnectException; +import java.nio.charset.StandardCharsets; import java.security.cert.CertificateEncodingException; import java.util.ArrayList; import java.util.List; @@ -11,6 +12,19 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.NameValuePair; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.utils.URIBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.jose4j.base64url.Base64Url; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.i18n.LocaleContextHolder; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + import at.gv.egiz.eaaf.core.api.idp.IConfigurationWithSP; import at.gv.egiz.eaaf.core.api.idp.ISpConfiguration; import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; @@ -29,22 +43,9 @@ import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20Constants.VdaAuthMethod; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20HttpBindingUtils.Sl20ResponseHolder; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonBuilderUtils; import at.gv.egiz.eaaf.modules.auth.sl20.utils.SL20JsonExtractorUtils; - -import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.message.BasicNameValuePair; -import org.jose4j.base64url.Base64Url; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.i18n.LocaleContextHolder; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; @Slf4j @@ -91,38 +92,42 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl // build request container final String qualEidReqId = Random.nextProcessReferenceValue(); - final ObjectNode sl20Req = SL20JsonBuilderUtils.createGenericRequest(qualEidReqId, null, null, - signedQualEidCommand); + final ObjectNode sl20Req = SL20JsonBuilderUtils.createGenericRequest(qualEidReqId, + pendingReq.getUniqueTransactionIdentifier(), null, signedQualEidCommand); // build http POST request final HttpPost httpReq = new HttpPost(new URIBuilder(vdaQualEidDUrl).build()); final List<NameValuePair> parameters = new ArrayList<>(); parameters.add(new BasicNameValuePair(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, - Base64Url.encode(sl20Req.toString().getBytes("UTF-8")))); + Base64Url.encode(sl20Req.toString().getBytes(StandardCharsets.UTF_8)))); //inject additional request parameters injectAdditionalSL20RequestParams(parameters, executionContext, request); httpReq.setEntity(new UrlEncodedFormEntity(parameters)); - // build http GET request - // URIBuilder sl20ReqUri = new URIBuilder(vdaQualeIDUrl); - // sl20ReqUri.addParameter(SL20Constants.PARAM_SL20_REQ_COMMAND_PARAM, - // Base64Url.encode(sl20Req.toString().getBytes())); - // HttpGet httpReq = new HttpGet(sl20ReqUri.build()); - // set native client header httpReq.addHeader(SL20Constants.HTTP_HEADER_SL20_CLIENT_TYPE, SL20Constants.HTTP_HEADER_VALUE_NATIVE); - log.trace("Request VDA via SL20 with: " + Base64Url.encode(sl20Req.toString().getBytes("UTF-8"))); + log.trace("Request VDA via SL20 with: {}", + Base64Url.encode(sl20Req.toString().getBytes(StandardCharsets.UTF_8))); // request VDA - final HttpResponse httpResp = httpClientFactory.getHttpClient(false).execute(httpReq); - + final Sl20ResponseHolder httpResp = httpClientFactory.getHttpClient(false).execute( + httpReq, SL20HttpBindingUtils.sl20ResponseHandler()); + + //check on error on http channel + if (httpResp.getError() != null) { + log.info("Basic SL2.0 response processing has an error. HTTP-StatusCode: {} ErrorMsg: {}", + httpResp.getResponseStatus().getStatusCode(), httpResp.getError().getMessage()); + throw httpResp.getError(); + + } + // parse response log.info("Receive response from VDA ... "); - final JsonNode sl20Resp = SL20JsonExtractorUtils.getSL20ContainerFromResponse(httpResp); - final VerificationResult respPayloadContainer = SL20JsonExtractorUtils.extractSL20PayLoad(sl20Resp, null, false); + final VerificationResult respPayloadContainer = + SL20JsonExtractorUtils.extractSL20PayLoad(httpResp.getResponseBody(), null, false); if (respPayloadContainer.isValidSigned() == null) { log.debug("Receive unsigned payLoad from VDA"); @@ -143,7 +148,7 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl SL20Constants.SL20_COMMAND_PARAM_GENERAL_REDIRECT_SIGNEDCOMMAND, false); // create forward SL2.0 command - final ObjectNode sl20Forward = sl20Resp.deepCopy(); + final ObjectNode sl20Forward = httpResp.getResponseBody().deepCopy(); SL20JsonBuilderUtils.addOnlyOnceOfTwo(sl20Forward, SL20Constants.SL20_PAYLOAD, SL20Constants.SL20_SIGNEDPAYLOAD, command.deepCopy(), signedCommand); @@ -227,7 +232,7 @@ public abstract class AbstractCreateQualEidRequestTask extends AbstractAuthServl final Locale locale = LocaleContextHolder.getLocale(); final String language = locale.getLanguage(); if (StringUtils.isNotEmpty(language)) { - log.trace("Find i18n context. Inject locale: {} into VDA request", locale.getLanguage()); + log.trace("Find i18n context). Inject locale: {} into VDA request", locale.getLanguage()); parameters.add(new BasicNameValuePair( SL20Constants.PARAM_SL20_REQ_AUTH_VDA_LOCALE, language.toUpperCase(locale))); diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java index 4786ff39..7591b3bd 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/tasks/AbstractReceiveQualEidTask.java @@ -6,15 +6,6 @@ import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.apache.commons.lang3.StringUtils; -import org.jose4j.base64url.Base64Url; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; - -import com.fasterxml.jackson.core.JsonParseException; -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; @@ -36,6 +27,15 @@ 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 org.apache.commons.lang3.StringUtils; +import org.jose4j.base64url.Base64Url; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonNode; + public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask { private static final Logger log = LoggerFactory.getLogger(AbstractReceiveQualEidTask.class); @@ -117,7 +117,7 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask log.debug("VDA provides an optional sessionId. Inject it to internal error-holder "); ex.setVdaSessionId(vdaSessionId); - } + } throw ex; } else { @@ -161,7 +161,6 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask } } catch (final EaafAuthenticationException e) { - log.warn("SL2.0 processing error:", e); if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } @@ -170,8 +169,8 @@ public abstract class AbstractReceiveQualEidTask extends AbstractAuthServletTask new TaskExecutionException(pendingReq, "SL2.0 Authentication FAILED. Msg: " + e.getMessage(), e)); } catch (final Exception e) { - log.warn("ERROR:", e); - log.warn("SL2.0 Authentication FAILED with a generic error.", e); + + if (sl20Result != null) { log.debug("Received SL2.0 result: " + sl20Result); } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java index d8c39931..5b221bbe 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JoseUtils.java @@ -2,12 +2,19 @@ package at.gv.egiz.eaaf.modules.auth.sl20.utils; import java.io.IOException; import java.security.Key; +import java.security.KeyFactory; import java.security.KeyStore; +import java.security.NoSuchAlgorithmException; import java.security.Provider; +import java.security.PublicKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; import java.security.interfaces.RSAPrivateKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.util.Collections; import java.util.List; import java.util.Map; @@ -15,13 +22,8 @@ import java.util.Map.Entry; import javax.annotation.Nonnull; -import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; -import at.gv.egiz.eaaf.core.impl.data.Pair; -import at.gv.egiz.eaaf.core.impl.utils.X509Utils; - import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jose4j.jca.ProviderContext; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jws.AlgorithmIdentifiers; @@ -32,6 +34,11 @@ import org.jose4j.keys.resolvers.X509VerificationKeyResolver; import org.jose4j.lang.JoseException; import org.springframework.util.Base64Utils; +import at.gv.egiz.eaaf.core.exception.EaafKeyUsageException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -45,6 +52,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class JoseUtils { + private static final Provider provider = new BouncyCastleProvider(); + /** * Create a JWS signature. * @@ -161,7 +170,10 @@ public class JoseUtils { // set signing information final Pair<Key, X509Certificate[]> signingCred = EaafKeyStoreUtils.getPrivateKeyAndCertificates( keyStore.getFirst(), keyAlias, keyPassword, true, friendlyNameForLogging); - jws.setKey(signingCred.getFirst()); + + // set verification key + jws.setKey(convertToBcKeyIfRequired(signingCred.getFirst())); + jws.setAlgorithmHeaderValue(getKeyOperationAlgorithmFromCredential( jws.getKey(), rsaAlgToUse, eccAlgToUse, friendlyNameForLogging)); @@ -169,11 +181,16 @@ public class JoseUtils { if (keyStore.getSecond() != null) { log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); final ProviderContext providerCtx = new ProviderContext(); - providerCtx.getSuppliedKeyProviderContext().setSignatureProvider( - keyStore.getSecond().getName()); + providerCtx.getSuppliedKeyProviderContext().setSignatureProvider(keyStore.getSecond().getName()); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); jws.setProviderContext(providerCtx); - } + } else { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + jws.setProviderContext(providerCtx); + + } if (addFullCertChain) { jws.setCertificateChainHeaderValue(signingCred.getSecond()); @@ -216,6 +233,8 @@ public class JoseUtils { log.trace("Sorting received X509 certificates ... "); final List<X509Certificate> sortedX5cCerts = X509Utils.sortCertificates(x5cCerts); + + if (trustedCerts.contains(sortedX5cCerts.get(0))) { selectedKey = sortedX5cCerts.get(0).getPublicKey(); @@ -247,10 +266,15 @@ public class JoseUtils { throw new JoseException("Can NOT select verification key for JWS. Signature verification FAILED"); } - + + //set BouncyCastleProvider as default provider + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + jws.setProviderContext(providerCtx); + // set verification key - jws.setKey(selectedKey); - + jws.setKey(convertToBcKeyIfRequired(selectedKey)); + // load payLoad return new JwsResult( jws.verifySignature(), @@ -260,6 +284,48 @@ public class JoseUtils { } + + /** + * Convert an ECC public-key into BouncyCastle implementation. + * + * <p> IAIK JCE / Eccelerate ECC Keys are not compatible to JWS impl.</p> + * @param input Key + * @return input Key, or BC ECC-Key in case of a ECC Key + */ + public static Key convertToBcKeyIfRequired(Key input) { + try { + if (input instanceof ECPublicKey + && "iaik.security.ec.common.ECPublicKey".equals(input.getClass().getName())) { + + //convert Key to BouncyCastle KeyImplemenation because there is an + //incompatibility with IAIK EC Keys and JWS signature-verfification implementation + PublicKey publicKey = KeyFactory.getInstance( + input.getAlgorithm(), provider).generatePublic( + new X509EncodedKeySpec(input.getEncoded())); + return publicKey; + + } else if (input instanceof ECPrivateKey + && "iaik.security.ec.common.ECPrivateKey".equals(input.getClass().getName())) { + //convert Key to BouncyCastle KeyImplemenation because there is an + //incompatibility with IAIK EC Keys and JWS signature-creation implementation + Key privateKey = KeyFactory.getInstance( + input.getAlgorithm(), provider).generatePrivate( + new PKCS8EncodedKeySpec(input.getEncoded())); + + return privateKey; + + } + + } catch (InvalidKeySpecException | NoSuchAlgorithmException e) { + log.warn("Can NOT convert {} to {}. The verification may FAIL.", + input.getClass().getName(), PublicKey.class.getName(), e); + + } + + return input; + + } + /** * Select signature algorithm for a given credential. * diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java index 10cfeafa..58e3e41c 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/JsonSecurityUtils.java @@ -13,25 +13,8 @@ import java.util.List; import javax.annotation.Nonnull; import javax.annotation.PostConstruct; -import at.gv.egiz.eaaf.core.api.idp.IConfiguration; -import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; -import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; -import at.gv.egiz.eaaf.core.exceptions.EaafException; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; -import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; -import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; -import at.gv.egiz.eaaf.core.impl.data.Pair; -import at.gv.egiz.eaaf.core.impl.utils.X509Utils; -import at.gv.egiz.eaaf.modules.auth.sl20.Constants; -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.SlCommandoBuildException; -import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; -import at.gv.egiz.eaaf.modules.auth.sl20.utils.JoseUtils.JwsResult; - import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.jose4j.jca.ProviderContext; import org.jose4j.jwa.AlgorithmConstraints; import org.jose4j.jwa.AlgorithmConstraints.ConstraintType; @@ -50,6 +33,24 @@ import org.springframework.util.Base64Utils; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonNode; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.exception.EaafKeyAccessException; +import at.gv.egiz.eaaf.core.exceptions.EaafConfigurationException; +import at.gv.egiz.eaaf.core.exceptions.EaafException; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreFactory; +import at.gv.egiz.eaaf.core.impl.credential.EaafKeyStoreUtils; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration; +import at.gv.egiz.eaaf.core.impl.credential.KeyStoreConfiguration.KeyStoreType; +import at.gv.egiz.eaaf.core.impl.data.Pair; +import at.gv.egiz.eaaf.core.impl.utils.X509Utils; +import at.gv.egiz.eaaf.modules.auth.sl20.Constants; +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.SlCommandoBuildException; +import at.gv.egiz.eaaf.modules.auth.sl20.exceptions.SlCommandoParserException; +import at.gv.egiz.eaaf.modules.auth.sl20.utils.JoseUtils.JwsResult; + @Service public class JsonSecurityUtils implements IJoseTools { private static final Logger log = LoggerFactory.getLogger(JsonSecurityUtils.class); @@ -223,10 +224,15 @@ public class JsonSecurityUtils implements IJoseTools { if (keyStore.getSecond() != null) { log.trace("Injecting special Java Security Provider: {}", keyStore.getSecond().getName()); final ProviderContext providerCtx = new ProviderContext(); - providerCtx.getSuppliedKeyProviderContext().setGeneralProvider( - keyStore.getSecond().getName()); + providerCtx.getSuppliedKeyProviderContext().setGeneralProvider(keyStore.getSecond().getName()); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); receiverJwe.setProviderContext(providerCtx); + } else { + final ProviderContext providerCtx = new ProviderContext(); + providerCtx.getGeneralProviderContext().setGeneralProvider(BouncyCastleProvider.PROVIDER_NAME); + receiverJwe.setProviderContext(providerCtx); + } // validate key from header against key from config @@ -269,7 +275,7 @@ public class JsonSecurityUtils implements IJoseTools { } // set key - receiverJwe.setKey(encryptionCred.getFirst()); + receiverJwe.setKey(JoseUtils.convertToBcKeyIfRequired(encryptionCred.getFirst())); // decrypt payload return mapper.getMapper().readTree(receiverJwe.getPlaintextString()); diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java index 1d7c9646..d07c0e66 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20HttpBindingUtils.java @@ -3,23 +3,129 @@ 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<Sl20ResponseHolder> 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 * @@ -59,6 +165,24 @@ public class SL20HttpBindingUtils { 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"); + } } } diff --git a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java index 40ea0430..bed25c0c 100644 --- a/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java +++ b/eaaf_modules/eaaf_module_auth_sl20/src/main/java/at/gv/egiz/eaaf/modules/auth/sl20/utils/SL20JsonExtractorUtils.java @@ -8,12 +8,6 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -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; @@ -299,84 +293,6 @@ public class SL20JsonExtractorUtils { } - /** - * Extract generic transport container from httpResponse. - * - * @param httpResp Http response object - * @return JSON with SL2.0 response - * @throws SlCommandoParserException In case of an error - */ - public static JsonNode getSL20ContainerFromResponse(final HttpResponse httpResp) throws SlCommandoParserException { - try { - JsonNode sl20Resp = null; - if (httpResp.getStatusLine().getStatusCode() == 303 || httpResp.getStatusLine().getStatusCode() == 307) { - final Header[] locationHeader = httpResp.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(); - sl20Resp = mapper.getMapper().readTree(Base64Url.decode(sl20RespString)); - - } else if (httpResp.getStatusLine().getStatusCode() == 200) { - if (httpResp.getEntity().getContentType() == null) { - throw new SlCommandoParserException("SL20 response contains NO ContentType"); - } - - if (!httpResp.getEntity().getContentType().getValue().startsWith("application/json")) { - throw new SlCommandoParserException( - "SL20 response with a wrong ContentType: " + httpResp.getEntity().getContentType().getValue()); - } - sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); - - } else if (httpResp.getStatusLine().getStatusCode() == 500 || httpResp.getStatusLine().getStatusCode() == 401 - || httpResp.getStatusLine().getStatusCode() == 400) { - log.info( - "SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode() + ". Search for error message"); - - try { - sl20Resp = parseSL20ResultFromResponse(httpResp.getEntity()); - - } catch (final Exception e) { - log.warn("SL20 response contains no valid JSON", e); - throw new SlCommandoParserException("SL20 response with http-code: " - + httpResp.getStatusLine().getStatusCode() + " AND NO valid JSON errormsg", e); - - } - - } else { - throw new SlCommandoParserException( - "SL20 response with http-code: " + httpResp.getStatusLine().getStatusCode()); - } - - log.info("Find JSON object in http response"); - return sl20Resp; - - } catch (final Exception e) { - throw new SlCommandoParserException("SL20 response parsing FAILED! Reason: " + e.getMessage(), e); - - } - } - - 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"); - } - - } - private static JsonNode getAndCheck(final JsonNode input, final String keyID, final boolean isRequired) throws SlCommandoParserException { final JsonNode internal = input.get(keyID); |