Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/HTTP-batchsink.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,36 @@ Skip on error - Ignores erroneous records.

**Wait Time Between Request:** Time in milliseconds to wait between HTTP requests. Defaults to 0. (Macro enabled)

### Authentication

* **OAuth2**
* **Grant Type:** Which OAuth2 grant type flow is used. It can be Refresh Token or Client Credentials Flow.
* **Client Authentication:** Send OAuth2 Credentials in the Request Body or as Query Parameter or as Basic Auth
Header.
* **Auth URL:** Endpoint for the authorization server used to retrieve the authorization code.
* **Token URL:** Endpoint for the resource server, which exchanges the authorization code for an access token.
* **Client ID:** Client identifier obtained during the Application registration process.
* **Client Secret:** Client secret obtained during the Application registration process.
* **Scopes:** Scope of the access request, which might have multiple space-separated values.
* **Refresh Token:** Token used to receive accessToken, which is end product of OAuth2.
* **Service Account** - service account key used for authorization
* **File Path**: Path on the local file system of the service account key used for
authorization. Can be set to 'auto-detect' when running on a Dataproc cluster.
When running on other clusters, the file must be present on every node in the cluster.
* **JSON**: Contents of the service account JSON file.
* **Scope**: The additional Google credential scopes required to access entered url, cloud-platform is included by
default, visit https://developers.google.com/identity/protocols/oauth2/scopes for more information.
* Scope example:

```
https://www.googleapis.com/auth/bigquery
https://www.googleapis.com/auth/cloud-platform
```

* **Basic Authentication**
* **Username:** Username for basic authentication.
* **Password:** Password for basic authentication.

### HTTP Proxy

**Proxy URL:** Proxy URL. Must contain a protocol, address and port.
Expand Down
3 changes: 3 additions & 0 deletions docs/HTTP-batchsource.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ The newline delimiter cannot be within quotes.

### Authentication
* **OAuth2**
* **Grant Type:** Which OAuth2 grant type flow is used. It can be Refresh Token or Client Credentials Flow.
* **Client Authentication:** Send OAuth2 Credentials in the Request Body or as Query Parameter or as Basic Auth
Header.
* **Auth URL:** Endpoint for the authorization server used to retrieve the authorization code.
* **Token URL:** Endpoint for the resource server, which exchanges the authorization code for an access token.
* **Client ID:** Client identifier obtained during the Application registration process.
Expand Down
3 changes: 3 additions & 0 deletions docs/HTTP-streamingsource.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ can be omitted as long as the field is present in schema.

### Authentication
* **OAuth2**
* **Grant Type:** Which OAuth2 grant type flow is used. It can be Refresh Token or Client Credentials Flow.
* **Client Authentication:** Send OAuth2 Credentials in the Request Body or as Query Parameter or as Basic Auth
Header.
* **Auth URL:** Endpoint for the authorization server used to retrieve the authorization code.
* **Token URL:** Endpoint for the resource server, which exchanges the authorization code for an access token.
* **Client ID:** Client identifier obtained during the Application registration process.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
*/
public enum OAuth2ClientAuthentication implements EnumWithValue {
BODY("body", "Body"),
REQUEST_PARAMETER("request_parameter", "Request Parameter");
REQUEST_PARAMETER("request_parameter", "Request Parameter"),
BASIC_AUTH_HEADER("basic_auth_header", "Basic Auth Header");

private final String value;
private final String label;
Expand All @@ -37,21 +38,22 @@ public enum OAuth2ClientAuthentication implements EnumWithValue {
* Determines the OAuth2 client authentication method based on the provided input.
*
* <p>This method checks if the given client authentication type matches the predefined
* BODY authentication type. If it matches, the method returns the BODY authentication. Otherwise,
* it defaults to REQUEST_PARAMETER authentication.</p>
* authentication type. If it matches, the method returns the same authentication. Otherwise,
* it defaults to BASIC_AUTH_HEADER authentication.</p>
*
* @param clientAuthentication The client authentication type as a {@link String}. It can be
* either the value or the label of the BODY authentication method.
* @return {@link OAuth2ClientAuthentication} The corresponding authentication type. Returns
* {@code BODY} if the input matches its value or label; otherwise, returns
* {@code REQUEST_PARAMETER}.
* either the value or the label of the authentication method.
* @return {@link OAuth2ClientAuthentication} The corresponding authentication type.
*/
public static OAuth2ClientAuthentication getClientAuthentication(String clientAuthentication) {
if (Objects.equals(clientAuthentication, BODY.getValue()) || Objects.equals(
clientAuthentication, BODY.getLabel())) {
return BODY;
} else {
} else if (Objects.equals(clientAuthentication, REQUEST_PARAMETER.getValue()) || Objects.equals(
clientAuthentication, REQUEST_PARAMETER.getLabel())) {
return REQUEST_PARAMETER;
} else {
return BASIC_AUTH_HEADER;
}
}

Expand Down
105 changes: 73 additions & 32 deletions src/main/java/io/cdap/plugin/http/common/http/OAuthUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import com.google.common.collect.ImmutableSet;
import com.google.gson.JsonElement;
import io.cdap.plugin.http.common.BaseHttpConfig;
import io.cdap.plugin.http.common.OAuth2ClientAuthentication;
import io.cdap.plugin.http.common.OAuth2GrantType;
import io.cdap.plugin.http.common.pagination.page.JSONUtil;
import io.cdap.plugin.http.source.common.BaseHttpSourceConfig;
Expand All @@ -31,6 +30,7 @@
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

Expand All @@ -44,16 +44,22 @@
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.annotation.Nullable;

/**
* A class which contains utilities to make OAuth2 specific calls.
*/
public class OAuthUtil {

private static final String PARAM_GRANT_TYPE = "grant_type";
private static final String PARAM_CLIENT_ID = "client_id";
private static final String PARAM_CLIENT_SECRET = "client_secret";
private static final String PARAM_REFRESH_TOKEN = "refresh_token";
private static final String PARAM_SCOPE = "scope";

/**
* Get Authorization header based on the config parameters provided
*
Expand Down Expand Up @@ -116,8 +122,8 @@ public static AccessToken getAccessToken(CloseableHttpClient httpclient, BaseHtt
* Retrieves an OAuth2 access token using the Client Credentials grant type.
*
* <p>This method constructs an HTTP POST request to fetch an access token from the authorization
* server. The client authentication method (either "BODY" or "REQUEST") determines whether client
* credentials are sent in the request body or as query parameters in the URL.</p>
* server. The client authentication method (either "BODY" or "REQUEST" or "BASIC_AUTH_HEADER") determines whether
* client credentials are sent in the request body or as query parameters or as basic auth header.</p>
*
* <p>Steps:
* 1. If client authentication is set to "BODY": - Constructs a URI using the token URL. - Adds
Expand All @@ -127,7 +133,11 @@ public static AccessToken getAccessToken(CloseableHttpClient httpclient, BaseHtt
* 2. If client authentication is set to "REQUEST": - Constructs a URI with client credentials as
* query parameters. - Creates an HTTP POST request with the URI.
* <br>
* 3. Calls `fetchAccessToken(httpclient,httppost)` to execute the request and retrieve the
* 3. If client authentication is set to "BASIC_AUTH_HEADER": - Constructs a URI with client credentials first
* concatenated and encoded to Base64 and passed a Basic Authorization Header and
* grant type and scope as part of body.
* <br>
* 4. Calls `fetchAccessToken(httpclient,httppost)` to execute the request and retrieve the
* token.
*
* @param httpclient The HTTP client to execute the request.
Expand All @@ -139,29 +149,51 @@ public static AccessToken getAccessTokenByClientCredentials(CloseableHttpClient
BaseHttpConfig config) throws IOException {
URI uri;
HttpPost httppost;

try {
if (Objects.equals(config.getOauth2ClientAuthentication().getValue(),
OAuth2ClientAuthentication.BODY.getValue())) {
uri = new URIBuilder(config.getTokenUrl()).build();
List<BasicNameValuePair> nameValuePairs = new ArrayList<>();
nameValuePairs.add(
new BasicNameValuePair("grant_type", OAuth2GrantType.CLIENT_CREDENTIALS.getValue()));
nameValuePairs.add(new BasicNameValuePair("client_id", config.getClientId()));
nameValuePairs.add(new BasicNameValuePair("client_secret", config.getClientSecret()));
if (!Strings.isNullOrEmpty(config.getScopes())) {
nameValuePairs.add(new BasicNameValuePair("scope", config.getScopes()));
}
httppost = new HttpPost(uri);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
} else {
URIBuilder uriBuilder = new URIBuilder(config.getTokenUrl()).setParameter("client_id",
config.getClientId()).setParameter("client_secret", config.getClientSecret())
.setParameter("grant_type", OAuth2GrantType.CLIENT_CREDENTIALS.getValue());
if (!Strings.isNullOrEmpty(config.getScopes())) {
uriBuilder.setParameter("scope", config.getScopes());
}
uri = uriBuilder.build();
httppost = new HttpPost(uri);
List<BasicNameValuePair> nameValuePairs = new ArrayList<>();
switch (config.getOauth2ClientAuthentication()) {
case BODY:
uri = new URIBuilder(config.getTokenUrl()).build();
nameValuePairs.add(
new BasicNameValuePair(PARAM_GRANT_TYPE, OAuth2GrantType.CLIENT_CREDENTIALS.getValue()));
nameValuePairs.add(new BasicNameValuePair(PARAM_CLIENT_ID, config.getClientId()));
nameValuePairs.add(new BasicNameValuePair(PARAM_CLIENT_SECRET, config.getClientSecret()));
if (!Strings.isNullOrEmpty(config.getScopes())) {
nameValuePairs.add(new BasicNameValuePair(PARAM_SCOPE, config.getScopes()));
}
httppost = new HttpPost(uri);
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
break;

case REQUEST_PARAMETER:
URIBuilder uriBuilder = new URIBuilder(config.getTokenUrl()).setParameter(PARAM_CLIENT_ID,
config.getClientId())
.setParameter(PARAM_CLIENT_SECRET, config.getClientSecret())
.setParameter(PARAM_GRANT_TYPE, OAuth2GrantType.CLIENT_CREDENTIALS.getValue());
if (!Strings.isNullOrEmpty(config.getScopes())) {
uriBuilder.setParameter(PARAM_SCOPE, config.getScopes());
}
uri = uriBuilder.build();
httppost = new HttpPost(uri);
break;

case BASIC_AUTH_HEADER:
String credentials = config.getClientId() + ":" + config.getClientSecret();
String basicAuthHeader = String.format("Basic %s", Base64.getEncoder()
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8)));
nameValuePairs.add(new BasicNameValuePair(PARAM_SCOPE, config.getScopes()));
nameValuePairs.add(new BasicNameValuePair(PARAM_GRANT_TYPE, OAuth2GrantType.CLIENT_CREDENTIALS.getValue()));
uri = new URIBuilder(config.getTokenUrl()).build();
httppost = new HttpPost(uri);
httppost.setHeader(new BasicHeader("Authorization", basicAuthHeader));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
break;

default:
throw new IllegalArgumentException(
String.format("Unknown OAuth client authentication '%s'",
config.getOauth2ClientAuthentication().getValue()));
}
return fetchAccessToken(httpclient, httppost);
} catch (URISyntaxException e) {
Expand Down Expand Up @@ -205,10 +237,10 @@ public static AccessToken getAccessTokenByRefreshToken(CloseableHttpClient httpc
URI uri;
try {
uri = new URIBuilder(config.getTokenUrl())
.setParameter("client_id", config.getClientId())
.setParameter("client_secret", config.getClientSecret())
.setParameter("refresh_token", config.getRefreshToken())
.setParameter("grant_type", "refresh_token")
.setParameter(PARAM_CLIENT_ID, config.getClientId())
.setParameter(PARAM_CLIENT_SECRET, config.getClientSecret())
.setParameter(PARAM_REFRESH_TOKEN, config.getRefreshToken())
.setParameter(PARAM_GRANT_TYPE, OAuth2GrantType.REFRESH_TOKEN.getValue())
.build();
HttpPost httppost = new HttpPost(uri);
return fetchAccessToken(httpclient, httppost);
Expand All @@ -232,7 +264,16 @@ private static AccessToken fetchAccessToken(CloseableHttpClient httpclient, Http

JsonElement accessTokenElement = JSONUtil.toJsonObject(responseString).get("access_token");
if (accessTokenElement == null) {
throw new IllegalArgumentException("Access token not found");
String errorResponse;
if (response.getStatusLine() != null) {
errorResponse = String.format("Response Code: '%s', Error Message:'%s'.",
response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
} else {
errorResponse = response.toString();
}
throw new IllegalArgumentException(
"Access token not found with Details: " + errorResponse);
}

JsonElement expiresInElement = JSONUtil.toJsonObject(responseString).get("expires_in");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import io.cdap.plugin.http.common.http.HttpClient;
import io.cdap.plugin.http.common.http.OAuthUtil;
import io.cdap.plugin.http.source.common.BaseHttpSourceConfig;

import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpStatus;
Expand Down Expand Up @@ -69,7 +68,8 @@ private void validateOAuth2Credentials(FailureCollector collector) {
if (!containsMacro(PROPERTY_CLIENT_ID) && !containsMacro(PROPERTY_CLIENT_SECRET) &&
!containsMacro(PROPERTY_TOKEN_URL) && !containsMacro(PROPERTY_REFRESH_TOKEN) &&
!containsMacro(PROPERTY_PROXY_PASSWORD) && !containsMacro(PROPERTY_PROXY_USERNAME) &&
!containsMacro(PROPERTY_PROXY_URL)) {
!containsMacro(PROPERTY_PROXY_URL) && !containsMacro(PROPERTY_OAUTH2_CLIENT_AUTHENTICATION) &&
!containsMacro(PROPERTY_OAUTH2_GRANT_TYPE)) {
HttpClientBuilder httpclientBuilder = HttpClients.custom();
if (!Strings.isNullOrEmpty(getProxyUrl())) {
HttpHost proxyHost = HttpHost.create(getProxyUrl());
Expand Down
Loading