This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push: new 2f3acd52eb7 OAuth2 authentication with client credentials flow (#11628) 2f3acd52eb7 is described below commit 2f3acd52eb79e902280f430aa239557b83da9fbb Author: Gilvan Filho <gilvan.sfi...@gmail.com> AuthorDate: Thu Oct 12 04:31:27 2023 -0300 OAuth2 authentication with client credentials flow (#11628) --- .../catalog/components/atmosphere-websocket.json | 5 +- .../org/apache/camel/catalog/components/http.json | 7 +- .../org/apache/camel/catalog/components/https.json | 7 +- .../websocket/WebsocketEndpointConfigurer.java | 18 +++++ .../websocket/WebsocketEndpointUriFactory.java | 10 ++- .../atmosphere/websocket/atmosphere-websocket.json | 5 +- .../camel/http/common/HttpCommonEndpoint.java | 40 ++++++++++ .../camel/http/common/HttpConfiguration.java | 40 ++++++++++ components/camel-http/pom.xml | 4 + .../component/http/HttpEndpointConfigurer.java | 18 +++++ .../component/http/HttpEndpointUriFactory.java | 9 ++- .../component/http/HttpTimeoutConverterLoader.java | 50 ------------- .../org/apache/camel/component/http/http.json | 7 +- .../org/apache/camel/component/http/https.json | 7 +- .../camel-http/src/main/docs/http-component.adoc | 20 +++++ .../apache/camel/component/http/HttpComponent.java | 15 ++++ .../component/http/HttpCredentialsHelper.java | 10 ++- .../apache/camel/component/http/HttpEndpoint.java | 11 +-- .../component/http/OAuth2ClientConfigurer.java | 79 ++++++++++++++++++++ .../http/HttpOAuth2AuthenticationTest.java | 87 ++++++++++++++++++++++ .../http/handler/OAuth2TokenRequestHandler.java | 68 +++++++++++++++++ 21 files changed, 447 insertions(+), 70 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json index 8e7ef7688f5..2ac5978912b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/atmosphere-websocket.json @@ -70,6 +70,9 @@ "optionsEnabled": { "index": 24, "kind": "parameter", "displayName": "Options Enabled", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off." }, "traceEnabled": { "index": 25, "kind": "parameter", "displayName": "Trace Enabled", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Specifies whether to enable HTTP TRACE for this Servlet consumer. By default TRACE is turned off." }, "bridgeEndpoint": { "index": 26, "kind": "parameter", "displayName": "Bridge Endpoint", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If the option is true, HttpProducer will ignore the Exchange.HTTP_URI header, and use the endpoint's URI for request. You may also set the option throwExceptionOnFailure to be false to let the HttpPro [...] - "lazyStartProducer": { "index": 27, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "lazyStartProducer": { "index": 27, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "oauth2ClientId": { "index": 28, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 29, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 30, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json index f635c08afc2..b76d5c885a3 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/http.json @@ -132,7 +132,10 @@ "authMethodPriority": { "index": 46, "kind": "parameter", "displayName": "Auth Method Priority", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "enum": [ "Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret": false, "description": "Which authentication method to prioritize to use, either as Basic, Digest or NTLM." }, "authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth Password", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication password" }, "authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth Username", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication username" }, - "sslContextParameters": { "index": 49, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] - "x509HostnameVerifier": { "index": 50, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } + "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 51, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" }, + "sslContextParameters": { "index": 52, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] + "x509HostnameVerifier": { "index": 53, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } } } diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json index 949ce8ba489..6f86f429003 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/https.json @@ -132,7 +132,10 @@ "authMethodPriority": { "index": 46, "kind": "parameter", "displayName": "Auth Method Priority", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "enum": [ "Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret": false, "description": "Which authentication method to prioritize to use, either as Basic, Digest or NTLM." }, "authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth Password", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication password" }, "authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth Username", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication username" }, - "sslContextParameters": { "index": 49, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] - "x509HostnameVerifier": { "index": 50, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } + "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 51, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" }, + "sslContextParameters": { "index": 52, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] + "x509HostnameVerifier": { "index": 53, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } } } diff --git a/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointConfigurer.java b/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointConfigurer.java index d817561f479..bd01d94ba11 100644 --- a/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointConfigurer.java +++ b/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointConfigurer.java @@ -25,6 +25,12 @@ public class WebsocketEndpointConfigurer extends ServletEndpointConfigurer imple case "bridgeEndpoint": target.setBridgeEndpoint(property(camelContext, boolean.class, value)); return true; case "lazystartproducer": case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; + case "oauth2clientid": + case "oauth2ClientId": target.setOauth2ClientId(property(camelContext, java.lang.String.class, value)); return true; + case "oauth2clientsecret": + case "oauth2ClientSecret": target.setOauth2ClientSecret(property(camelContext, java.lang.String.class, value)); return true; + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": target.setOauth2TokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; case "sendtoall": case "sendToAll": target.setSendToAll(property(camelContext, boolean.class, value)); return true; case "usestreaming": @@ -40,6 +46,12 @@ public class WebsocketEndpointConfigurer extends ServletEndpointConfigurer imple case "bridgeEndpoint": return boolean.class; case "lazystartproducer": case "lazyStartProducer": return boolean.class; + case "oauth2clientid": + case "oauth2ClientId": return java.lang.String.class; + case "oauth2clientsecret": + case "oauth2ClientSecret": return java.lang.String.class; + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": return java.lang.String.class; case "sendtoall": case "sendToAll": return boolean.class; case "usestreaming": @@ -56,6 +68,12 @@ public class WebsocketEndpointConfigurer extends ServletEndpointConfigurer imple case "bridgeEndpoint": return target.isBridgeEndpoint(); case "lazystartproducer": case "lazyStartProducer": return target.isLazyStartProducer(); + case "oauth2clientid": + case "oauth2ClientId": return target.getOauth2ClientId(); + case "oauth2clientsecret": + case "oauth2ClientSecret": return target.getOauth2ClientSecret(); + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": return target.getOauth2TokenEndpoint(); case "sendtoall": case "sendToAll": return target.isSendToAll(); case "usestreaming": diff --git a/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointUriFactory.java b/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointUriFactory.java index d011c5aa34a..773fda85f09 100644 --- a/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointUriFactory.java +++ b/components/camel-atmosphere-websocket/src/generated/java/org/apache/camel/component/atmosphere/websocket/WebsocketEndpointUriFactory.java @@ -21,7 +21,7 @@ public class WebsocketEndpointUriFactory extends org.apache.camel.support.compon private static final Set<String> SECRET_PROPERTY_NAMES; private static final Set<String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(28); + Set<String> props = new HashSet<>(31); props.add("async"); props.add("attachmentMultipartBinding"); props.add("bridgeEndpoint"); @@ -42,6 +42,9 @@ public class WebsocketEndpointUriFactory extends org.apache.camel.support.compon props.add("mapHttpMessageHeaders"); props.add("matchOnUriPrefix"); props.add("muteException"); + props.add("oauth2ClientId"); + props.add("oauth2ClientSecret"); + props.add("oauth2TokenEndpoint"); props.add("optionsEnabled"); props.add("responseBufferSize"); props.add("sendToAll"); @@ -51,7 +54,10 @@ public class WebsocketEndpointUriFactory extends org.apache.camel.support.compon props.add("transferException"); props.add("useStreaming"); PROPERTY_NAMES = Collections.unmodifiableSet(props); - SECRET_PROPERTY_NAMES = Collections.emptySet(); + Set<String> secretProps = new HashSet<>(2); + secretProps.add("oauth2ClientId"); + secretProps.add("oauth2ClientSecret"); + SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); MULTI_VALUE_PREFIXES = Collections.emptySet(); } diff --git a/components/camel-atmosphere-websocket/src/generated/resources/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json b/components/camel-atmosphere-websocket/src/generated/resources/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json index 8e7ef7688f5..2ac5978912b 100644 --- a/components/camel-atmosphere-websocket/src/generated/resources/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json +++ b/components/camel-atmosphere-websocket/src/generated/resources/org/apache/camel/component/atmosphere/websocket/atmosphere-websocket.json @@ -70,6 +70,9 @@ "optionsEnabled": { "index": 24, "kind": "parameter", "displayName": "Options Enabled", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Specifies whether to enable HTTP OPTIONS for this Servlet consumer. By default OPTIONS is turned off." }, "traceEnabled": { "index": 25, "kind": "parameter", "displayName": "Trace Enabled", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Specifies whether to enable HTTP TRACE for this Servlet consumer. By default TRACE is turned off." }, "bridgeEndpoint": { "index": 26, "kind": "parameter", "displayName": "Bridge Endpoint", "group": "producer", "label": "producer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "If the option is true, HttpProducer will ignore the Exchange.HTTP_URI header, and use the endpoint's URI for request. You may also set the option throwExceptionOnFailure to be false to let the HttpPro [...] - "lazyStartProducer": { "index": 27, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "lazyStartProducer": { "index": 27, "kind": "parameter", "displayName": "Lazy Start Producer", "group": "producer (advanced)", "label": "producer,advanced", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether the producer should be started lazy (on the first message). By starting lazy you can use this to allow CamelContext and routes to startup in situations where a produ [...] + "oauth2ClientId": { "index": 28, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 29, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 30, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" } } } diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java index 6c6a090d693..208c4e55690 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpCommonEndpoint.java @@ -153,6 +153,12 @@ public abstract class HttpCommonEndpoint extends DefaultEndpoint implements Head private String authUsername; @UriParam(label = "producer,security", secret = true, description = "Authentication password") private String authPassword; + @UriParam(label = "producer,security", secret = true, description = "OAuth2 client id") + private String oauth2ClientId; + @UriParam(label = "producer,security", secret = true, description = "OAuth2 client secret") + private String oauth2ClientSecret; + @UriParam(label = "producer,security", description = "OAuth2 Token endpoint") + private String oauth2TokenEndpoint; @UriParam(label = "producer,security", description = "Authentication domain to use with NTML") private String authDomain; @UriParam(label = "producer,security", description = "Authentication host to use with NTML") @@ -774,4 +780,38 @@ public abstract class HttpCommonEndpoint extends DefaultEndpoint implements Head public void setProxyAuthNtHost(String proxyAuthNtHost) { this.proxyAuthNtHost = proxyAuthNtHost; } + + public String getOauth2ClientId() { + return this.oauth2ClientId; + } + + /** + * OAuth2 Client id + */ + public void setOauth2ClientId(String oauth2ClientId) { + this.oauth2ClientId = oauth2ClientId; + } + + public String getOauth2ClientSecret() { + return this.oauth2ClientSecret; + } + + /** + * OAuth2 Client secret + */ + public void setOauth2ClientSecret(String oauth2ClientSecret) { + this.oauth2ClientSecret = oauth2ClientSecret; + } + + public String getOauth2TokenEndpoint() { + return this.oauth2TokenEndpoint; + } + + /** + * OAuth2 token endpoint + */ + public void setOauth2TokenEndpoint(String oauth2TokenEndpoint) { + this.oauth2TokenEndpoint = oauth2TokenEndpoint; + } + } diff --git a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java index cf6a72093f1..4dbc0984414 100644 --- a/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java +++ b/components/camel-http-common/src/main/java/org/apache/camel/http/common/HttpConfiguration.java @@ -33,6 +33,12 @@ public class HttpConfiguration implements Serializable { private String authUsername; @Metadata(label = "producer,security", secret = true, description = "Authentication password") private String authPassword; + @Metadata(label = "producer,security", secret = true, description = "OAuth2 client id") + private String oauth2ClientId; + @Metadata(label = "producer,security", secret = true, description = "OAuth2 client secret") + private String oauth2ClientSecret; + @Metadata(label = "producer,security", description = "OAuth2 token endpoint") + private String oauth2TokenEndpoint; @Metadata(label = "producer,security", description = "Authentication domain to use with NTML") private String authDomain; @Metadata(label = "producer,security", description = "Authentication host to use with NTML") @@ -220,4 +226,38 @@ public class HttpConfiguration implements Serializable { public void setProxyPort(int proxyPort) { this.proxyPort = proxyPort; } + + public String getOauth2ClientId() { + return this.oauth2ClientId; + } + + /** + * OAuth2 Client id + */ + public void setOauth2ClientId(String oauth2ClientId) { + this.oauth2ClientId = oauth2ClientId; + } + + public String getOauth2ClientSecret() { + return this.oauth2ClientSecret; + } + + /** + * OAuth2 Client secret + */ + public void setOauth2ClientSecret(String oauth2ClientSecret) { + this.oauth2ClientSecret = oauth2ClientSecret; + } + + public String getOauth2TokenEndpoint() { + return this.oauth2TokenEndpoint; + } + + /** + * OAuth2 token endpoint + */ + public void setOauth2TokenEndpoint(String oauth2TokenEndpoint) { + this.oauth2TokenEndpoint = oauth2TokenEndpoint; + } + } diff --git a/components/camel-http/pom.xml b/components/camel-http/pom.xml index 6331c4aaf63..88c3c2e3db9 100644 --- a/components/camel-http/pom.xml +++ b/components/camel-http/pom.xml @@ -49,6 +49,10 @@ <groupId>org.apache.camel</groupId> <artifactId>camel-file</artifactId> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-util-json</artifactId> + </dependency> <dependency> <groupId>jakarta.servlet</groupId> <artifactId>jakarta.servlet-api</artifactId> diff --git a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java index 801fc56b1cf..aa5159cddb8 100644 --- a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java +++ b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointConfigurer.java @@ -83,6 +83,12 @@ public class HttpEndpointConfigurer extends PropertyConfigurerSupport implements case "lazyStartProducer": target.setLazyStartProducer(property(camelContext, boolean.class, value)); return true; case "maxtotalconnections": case "maxTotalConnections": target.setMaxTotalConnections(property(camelContext, int.class, value)); return true; + case "oauth2clientid": + case "oauth2ClientId": target.setOauth2ClientId(property(camelContext, java.lang.String.class, value)); return true; + case "oauth2clientsecret": + case "oauth2ClientSecret": target.setOauth2ClientSecret(property(camelContext, java.lang.String.class, value)); return true; + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": target.setOauth2TokenEndpoint(property(camelContext, java.lang.String.class, value)); return true; case "okstatuscoderange": case "okStatusCodeRange": target.setOkStatusCodeRange(property(camelContext, java.lang.String.class, value)); return true; case "preservehostheader": @@ -190,6 +196,12 @@ public class HttpEndpointConfigurer extends PropertyConfigurerSupport implements case "lazyStartProducer": return boolean.class; case "maxtotalconnections": case "maxTotalConnections": return int.class; + case "oauth2clientid": + case "oauth2ClientId": return java.lang.String.class; + case "oauth2clientsecret": + case "oauth2ClientSecret": return java.lang.String.class; + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": return java.lang.String.class; case "okstatuscoderange": case "okStatusCodeRange": return java.lang.String.class; case "preservehostheader": @@ -298,6 +310,12 @@ public class HttpEndpointConfigurer extends PropertyConfigurerSupport implements case "lazyStartProducer": return target.isLazyStartProducer(); case "maxtotalconnections": case "maxTotalConnections": return target.getMaxTotalConnections(); + case "oauth2clientid": + case "oauth2ClientId": return target.getOauth2ClientId(); + case "oauth2clientsecret": + case "oauth2ClientSecret": return target.getOauth2ClientSecret(); + case "oauth2tokenendpoint": + case "oauth2TokenEndpoint": return target.getOauth2TokenEndpoint(); case "okstatuscoderange": case "okStatusCodeRange": return target.getOkStatusCodeRange(); case "preservehostheader": diff --git a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java index ee87c1e8562..f8f1fbdf858 100644 --- a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java +++ b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpEndpointUriFactory.java @@ -22,7 +22,7 @@ public class HttpEndpointUriFactory extends org.apache.camel.support.component.E private static final Set<String> SECRET_PROPERTY_NAMES; private static final Set<String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(51); + Set<String> props = new HashSet<>(54); props.add("authDomain"); props.add("authHost"); props.add("authMethod"); @@ -55,6 +55,9 @@ public class HttpEndpointUriFactory extends org.apache.camel.support.component.E props.add("ignoreResponseBody"); props.add("lazyStartProducer"); props.add("maxTotalConnections"); + props.add("oauth2ClientId"); + props.add("oauth2ClientSecret"); + props.add("oauth2TokenEndpoint"); props.add("okStatusCodeRange"); props.add("preserveHostHeader"); props.add("proxyAuthDomain"); @@ -75,9 +78,11 @@ public class HttpEndpointUriFactory extends org.apache.camel.support.component.E props.add("userAgent"); props.add("x509HostnameVerifier"); PROPERTY_NAMES = Collections.unmodifiableSet(props); - Set<String> secretProps = new HashSet<>(4); + Set<String> secretProps = new HashSet<>(6); secretProps.add("authPassword"); secretProps.add("authUsername"); + secretProps.add("oauth2ClientId"); + secretProps.add("oauth2ClientSecret"); secretProps.add("proxyAuthPassword"); secretProps.add("proxyAuthUsername"); SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps); diff --git a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpTimeoutConverterLoader.java b/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpTimeoutConverterLoader.java deleted file mode 100644 index 2cad4863d98..00000000000 --- a/components/camel-http/src/generated/java/org/apache/camel/component/http/HttpTimeoutConverterLoader.java +++ /dev/null @@ -1,50 +0,0 @@ -/* Generated by camel build tools - do NOT edit this file! */ -package org.apache.camel.component.http; - -import org.apache.camel.CamelContext; -import org.apache.camel.CamelContextAware; -import org.apache.camel.DeferredContextBinding; -import org.apache.camel.TypeConverterLoaderException; -import org.apache.camel.spi.TypeConverterLoader; -import org.apache.camel.spi.TypeConverterRegistry; -import org.apache.camel.support.SimpleTypeConverter; - -/** - * Generated by camel build tools - do NOT edit this file! - */ -@SuppressWarnings("unchecked") -@DeferredContextBinding -public final class HttpTimeoutConverterLoader implements TypeConverterLoader, CamelContextAware { - - private CamelContext camelContext; - - public HttpTimeoutConverterLoader() { - } - - @Override - public void setCamelContext(CamelContext camelContext) { - this.camelContext = camelContext; - } - - @Override - public CamelContext getCamelContext() { - return camelContext; - } - - @Override - public void load(TypeConverterRegistry registry) throws TypeConverterLoaderException { - registerConverters(registry); - } - - private void registerConverters(TypeConverterRegistry registry) { - addTypeConverter(registry, org.apache.hc.core5.util.Timeout.class, java.lang.String.class, false, - (type, exchange, value) -> HttpConverters.toTimeout((java.lang.String) value)); - addTypeConverter(registry, org.apache.hc.core5.util.Timeout.class, long.class, false, - (type, exchange, value) -> HttpConverters.toTimeout((long) value)); - } - - private static void addTypeConverter(TypeConverterRegistry registry, Class<?> toType, Class<?> fromType, boolean allowNull, SimpleTypeConverter.ConversionMethod method) { - registry.addTypeConverter(toType, fromType, new SimpleTypeConverter(allowNull, method)); - } - -} diff --git a/components/camel-http/src/generated/resources/org/apache/camel/component/http/http.json b/components/camel-http/src/generated/resources/org/apache/camel/component/http/http.json index f635c08afc2..b76d5c885a3 100644 --- a/components/camel-http/src/generated/resources/org/apache/camel/component/http/http.json +++ b/components/camel-http/src/generated/resources/org/apache/camel/component/http/http.json @@ -132,7 +132,10 @@ "authMethodPriority": { "index": 46, "kind": "parameter", "displayName": "Auth Method Priority", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "enum": [ "Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret": false, "description": "Which authentication method to prioritize to use, either as Basic, Digest or NTLM." }, "authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth Password", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication password" }, "authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth Username", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication username" }, - "sslContextParameters": { "index": 49, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] - "x509HostnameVerifier": { "index": 50, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } + "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 51, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" }, + "sslContextParameters": { "index": 52, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] + "x509HostnameVerifier": { "index": 53, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } } } diff --git a/components/camel-http/src/generated/resources/org/apache/camel/component/http/https.json b/components/camel-http/src/generated/resources/org/apache/camel/component/http/https.json index 949ce8ba489..6f86f429003 100644 --- a/components/camel-http/src/generated/resources/org/apache/camel/component/http/https.json +++ b/components/camel-http/src/generated/resources/org/apache/camel/component/http/https.json @@ -132,7 +132,10 @@ "authMethodPriority": { "index": 46, "kind": "parameter", "displayName": "Auth Method Priority", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "enum": [ "Basic", "Digest", "NTLM" ], "deprecated": false, "autowired": false, "secret": false, "description": "Which authentication method to prioritize to use, either as Basic, Digest or NTLM." }, "authPassword": { "index": 47, "kind": "parameter", "displayName": "Auth Password", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication password" }, "authUsername": { "index": 48, "kind": "parameter", "displayName": "Auth Username", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "Authentication username" }, - "sslContextParameters": { "index": 49, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] - "x509HostnameVerifier": { "index": 50, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } + "oauth2ClientId": { "index": 49, "kind": "parameter", "displayName": "Oauth2 Client Id", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client id" }, + "oauth2ClientSecret": { "index": 50, "kind": "parameter", "displayName": "Oauth2 Client Secret", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "description": "OAuth2 client secret" }, + "oauth2TokenEndpoint": { "index": 51, "kind": "parameter", "displayName": "Oauth2 Token Endpoint", "group": "security", "label": "producer,security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "OAuth2 Token endpoint" }, + "sslContextParameters": { "index": 52, "kind": "parameter", "displayName": "Ssl Context Parameters", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "org.apache.camel.support.jsse.SSLContextParameters", "deprecated": false, "autowired": false, "secret": false, "description": "To configure security using SSLContextParameters. Important: Only one instance of org.apache.camel.util.jsse.SSLContextParameters is supported per HttpComponent. If you [...] + "x509HostnameVerifier": { "index": 53, "kind": "parameter", "displayName": "X509 Hostname Verifier", "group": "security", "label": "security", "required": false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom X509HostnameVerifier such as DefaultHostnameVerifier or NoopHostnameVerifier" } } } diff --git a/components/camel-http/src/main/docs/http-component.adoc b/components/camel-http/src/main/docs/http-component.adoc index ee2bc8f878f..f47e29be15e 100644 --- a/components/camel-http/src/main/docs/http-component.adoc +++ b/components/camel-http/src/main/docs/http-component.adoc @@ -342,6 +342,26 @@ adding this URI option: `httpClient.cookieSpec=ignoreCookies` In order to avoid the `NonRepeatableRequestException`, you need to do the Preemptive Basic Authentication by adding the option: `authenticationPreemptive=true` +== OAuth2 Support + +In order to get a access token from a Authorization Server and fill that in Authorization header to do requests to protected services, you will need to use `oauth2ClientId`, `oauth2ClientSecret` and `oauth2TokenEndpoint` properties, and those should be defined as specified at RFC 6749 and provided by your Authorization Server. + +In below example camel will do a underlying request to `https://localhost:8080/realms/master/protocol/openid-connect/token` using provided credentials (client id and client secret), then will get `access_token` from response and lastly will fill it at `Authorization` header of request which will be done to `https://localhost:9090`. + +[source,java] +------------------------------------------------------------------------------------ +String clientId = "my-client-id"; +String clientSecret = "my-client-secret"; +String tokenEndpoint = "https://localhost:8080/realms/master/protocol/openid-connect/token"; + +from("direct:start") + .to("https://localhost:9090/?oauth2ClientId=" + clientId + "&oauth2ClientSecret=" + clientSecret + "&oauth2TokenEndpoint=" + tokenEndpoint); +------------------------------------------------------------------------------------ + +NOTE: Camel only provide support for OAuth2 client credentials flow + +Important: Camel does not perform any validation in access token. It's up to the underlying service to validate it. + == Advanced Usage If you need more control over the HTTP producer you should use the diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java index afb481813c2..9c49a4de718 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpComponent.java @@ -225,10 +225,25 @@ public class HttpComponent extends HttpCommonComponent implements RestProducerFa HttpCredentialsHelper credentialsProvider = new HttpCredentialsHelper(); configurer = configureBasicAuthentication(parameters, configurer, credentialsProvider); configurer = configureHttpProxy(parameters, configurer, secure, credentialsProvider); + configurer = configureOAuth2Authentication(parameters, configurer); return configurer; } + private HttpClientConfigurer configureOAuth2Authentication( + Map<String, Object> parameters, HttpClientConfigurer configurer) { + + String clientId = getParameter(parameters, "oauth2ClientId", String.class); + String clientSecret = getParameter(parameters, "oauth2ClientSecret", String.class); + String tokenEndpoint = getParameter(parameters, "oauth2TokenEndpoint", String.class); + + if (clientId != null && clientSecret != null && tokenEndpoint != null) { + return CompositeHttpConfigurer.combineConfigurers(configurer, + new OAuth2ClientConfigurer(clientId, clientSecret, tokenEndpoint)); + } + return configurer; + } + private HttpClientConfigurer configureBasicAuthentication( Map<String, Object> parameters, HttpClientConfigurer configurer, HttpCredentialsHelper credentialsProvider) { diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpCredentialsHelper.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpCredentialsHelper.java index f51163f93b5..4c605149e0e 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpCredentialsHelper.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpCredentialsHelper.java @@ -16,6 +16,7 @@ */ package org.apache.camel.component.http; +import java.nio.charset.StandardCharsets; import java.util.Objects; import org.apache.hc.client5.http.auth.AuthScope; @@ -23,8 +24,9 @@ import org.apache.hc.client5.http.auth.Credentials; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.CredentialsStore; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; +import org.apache.hc.client5.http.utils.Base64; -final class HttpCredentialsHelper { +public final class HttpCredentialsHelper { private final CredentialsStore credentialsProvider; @@ -40,4 +42,10 @@ final class HttpCredentialsHelper { return credentialsProvider; } + public static String generateBasicAuthHeader(String user, String pass) { + final String auth = user + ":" + pass; + final byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8)); + return "Basic " + new String(encodedAuth); + } + } diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java index 309ef7ff39c..64edd3ba52c 100644 --- a/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/HttpEndpoint.java @@ -269,11 +269,6 @@ public class HttpEndpoint extends HttpCommonEndpoint { clientBuilder.setUserAgent(userAgent); } - HttpClientConfigurer configurer = getHttpClientConfigurer(); - if (configurer != null) { - configurer.configureHttpClient(clientBuilder); - } - if (isBridgeEndpoint()) { // need to use noop cookiestore as we do not want to keep cookies in memory clientBuilder.setDefaultCookieStore(new NoopCookieStore()); @@ -283,7 +278,13 @@ public class HttpEndpoint extends HttpCommonEndpoint { clientBuilder.setRedirectStrategy(DefaultRedirectStrategy.INSTANCE); } + HttpClientConfigurer configurer = getHttpClientConfigurer(); + if (configurer != null) { + configurer.configureHttpClient(clientBuilder); + } + LOG.debug("Setup the HttpClientBuilder {}", clientBuilder); + return clientBuilder.build(); } diff --git a/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java b/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java new file mode 100644 index 00000000000..23b633f631c --- /dev/null +++ b/components/camel-http/src/main/java/org/apache/camel/component/http/OAuth2ClientConfigurer.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.http; + +import org.apache.camel.util.json.DeserializationException; +import org.apache.camel.util.json.JsonObject; +import org.apache.camel.util.json.Jsoner; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.protocol.HttpContext; + +public class OAuth2ClientConfigurer implements HttpClientConfigurer { + + private final String clientId; + private final String clientSecret; + private final String tokenEndpoint; + + public OAuth2ClientConfigurer(String clientId, String clientSecret, String tokenEndpoint) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.tokenEndpoint = tokenEndpoint; + } + + @Override + public void configureHttpClient(HttpClientBuilder clientBuilder) { + HttpClient httpClient = clientBuilder.build(); + clientBuilder.addRequestInterceptorFirst((HttpRequest request, EntityDetails entity, HttpContext context) -> { + + final HttpPost httpPost = new HttpPost(tokenEndpoint); + + httpPost.addHeader(HttpHeaders.AUTHORIZATION, + HttpCredentialsHelper.generateBasicAuthHeader(clientId, clientSecret)); + httpPost.setEntity(new StringEntity("grant_type=client_credentials", ContentType.APPLICATION_FORM_URLENCODED)); + + httpClient.execute(httpPost, response -> { + + try { + String responseString = EntityUtils.toString(response.getEntity()); + + if (response.getCode() == 200) { + String accessToken = ((JsonObject) Jsoner.deserialize(responseString)).getString("access_token"); + request.addHeader(HttpHeaders.AUTHORIZATION, accessToken); + } else { + throw new HttpException("Received error response from token request with Status Code: " + response.getCode()); + } + + } catch (DeserializationException e) { + throw new HttpException("Something went wrong when reading token request response", e); + } + + return null; + }); + + }); + } + +} diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2AuthenticationTest.java b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2AuthenticationTest.java new file mode 100644 index 00000000000..593180caf58 --- /dev/null +++ b/components/camel-http/src/test/java/org/apache/camel/component/http/HttpOAuth2AuthenticationTest.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.http; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.Exchange; +import org.apache.camel.component.http.handler.HeaderValidationHandler; +import org.apache.camel.component.http.handler.OAuth2TokenRequestHandler; +import org.apache.hc.core5.http.HttpStatus; +import org.apache.hc.core5.http.impl.bootstrap.HttpServer; +import org.apache.hc.core5.http.impl.bootstrap.ServerBootstrap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class HttpOAuth2AuthenticationTest extends BaseHttpTest { + + private static final String FAKE_TOKEN = "xxx.yyy.zzz"; + private static String clientId = "test-client"; + private static String clientSecret = "test-secret"; + + private HttpServer localServer; + + @BeforeEach + @Override + public void setUp() throws Exception { + Map<String, String> expectedHeaders = new HashMap<>(); + expectedHeaders.put("Authorization", FAKE_TOKEN); + + localServer = ServerBootstrap.bootstrap().setHttpProcessor(getBasicHttpProcessor()) + .setConnectionReuseStrategy(getConnectionReuseStrategy()).setResponseFactory(getHttpResponseFactory()) + .setSslContext(getSSLContext()) + .register("/token", new OAuth2TokenRequestHandler(FAKE_TOKEN, clientId, clientSecret)) + .register("/post", + new HeaderValidationHandler( + "POST", + null, + null, + null, + expectedHeaders)) + .create(); + + localServer.start(); + super.setUp(); + } + + @Test + public void authorizationHeaderIsPresent() throws Exception { + + String tokenEndpoint = "http://localhost:" + localServer.getLocalPort() + "/token"; + + Exchange exchange + = template.request("http://localhost:" + localServer.getLocalPort() + "/post?httpMethod=POST&oauth2ClientId=" + + clientId + "&oauth2ClientSecret=" + clientSecret + "&oauth2TokenEndpoint=" + tokenEndpoint, + exchange1 -> { + }); + + assertExchange(exchange); + + } + + protected void assertHeaders(Map<String, Object> headers) { + assertEquals(HttpStatus.SC_OK, headers.get(Exchange.HTTP_RESPONSE_CODE)); + } + + protected String getExpectedContent() { + return ""; + } + +} diff --git a/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java b/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java new file mode 100644 index 00000000000..9423dd3a3c1 --- /dev/null +++ b/components/camel-http/src/test/java/org/apache/camel/component/http/handler/OAuth2TokenRequestHandler.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.http.handler; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.camel.component.http.HttpCredentialsHelper; +import org.apache.camel.util.json.Jsoner; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.ClassicHttpResponse; +import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpHeaders; +import org.apache.hc.core5.http.io.HttpRequestHandler; +import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.http.io.entity.StringEntity; +import org.apache.hc.core5.http.protocol.HttpContext; +import org.apache.hc.core5.net.WWWFormCodec; + +public class OAuth2TokenRequestHandler implements HttpRequestHandler { + + private String clientId; + private String clientSecret; + private String expectedToken; + + public OAuth2TokenRequestHandler(String expectedToken, String clientId, String clientSecret) { + this.expectedToken = expectedToken; + this.clientId = clientId; + this.clientSecret = clientSecret; + } + + @Override + public void handle(ClassicHttpRequest request, ClassicHttpResponse response, HttpContext context) + throws HttpException, IOException { + + String requestBody = EntityUtils.toString(request.getEntity()); + WWWFormCodec.parse(requestBody, StandardCharsets.UTF_8).stream() + .filter(pair -> pair.getName().equals("grant_type") && pair.getValue().equals("client_credentials")) + .findAny().orElseThrow(() -> new HttpException("Invalid or missing grant_type")); + + if (request.getHeader(HttpHeaders.AUTHORIZATION) == null || !request.getHeader(HttpHeaders.AUTHORIZATION).getValue() + .equals(HttpCredentialsHelper.generateBasicAuthHeader(clientId, clientSecret))) + throw new HttpException("Invalid credentials"); + + Map<String, String> responseEntity = new HashMap<>(); + responseEntity.put("access_token", expectedToken); + + response.setEntity(new StringEntity(Jsoner.serialize(responseEntity), ContentType.APPLICATION_JSON)); + } + +}