Skip to content
Closed
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
255 changes: 155 additions & 100 deletions src/main/java/io/cdap/plugin/http/common/BaseHttpConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@
import io.cdap.cdap.etl.api.validation.InvalidConfigPropertyException;
import io.cdap.plugin.common.ReferencePluginConfig;
import io.cdap.plugin.http.common.http.AuthType;
import io.cdap.plugin.http.common.http.OAuthClientAuthentication;
import io.cdap.plugin.http.common.http.OAuthGrantType;
import io.cdap.plugin.http.common.http.OAuthUtil;

import io.cdap.plugin.http.source.common.BaseHttpSourceConfig;
import java.io.File;
import java.util.Optional;
import javax.annotation.Nullable;
Expand All @@ -41,6 +44,8 @@ public abstract class BaseHttpConfig extends ReferencePluginConfig {
public static final String PROPERTY_OAUTH2_ENABLED = "oauth2Enabled";
public static final String PROPERTY_AUTH_URL = "authUrl";
public static final String PROPERTY_TOKEN_URL = "tokenUrl";
public static final String PROPERTY_OAUTH2_GRANT_TYPE = "oauth2GrantType";
public static final String PROPERTY_OAUTH2_CLIENT_AUTHENTICATION = "oauth2ClientAuthentication";
public static final String PROPERTY_CLIENT_ID = "clientId";
public static final String PROPERTY_CLIENT_SECRET = "clientSecret";
public static final String PROPERTY_SCOPES = "scopes";
Expand Down Expand Up @@ -82,7 +87,7 @@ public abstract class BaseHttpConfig extends ReferencePluginConfig {
"OAuth2, Service account, Basic Authentication types are available.")
protected String authType;

@Name(PROPERTY_OAUTH2_ENABLED)
@Name(PROPERTY_OAUTH2_ENABLED)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert unintended changes

@Description("If true, plugin will perform OAuth2 authentication.")
@Nullable
protected String oauth2Enabled;
Expand All @@ -93,6 +98,18 @@ public abstract class BaseHttpConfig extends ReferencePluginConfig {
@Macro
protected String authUrl;

@Nullable
@Name(PROPERTY_OAUTH2_GRANT_TYPE)
@Description("Which Oauth2 grant type flow is used.")
@Macro
protected String oauth2GrantType;

@Nullable
@Name(PROPERTY_OAUTH2_CLIENT_AUTHENTICATION)
@Description("Which Oauth2 client authentication flow is used.")
@Macro
protected String oauth2ClientAuthentication;

@Nullable
@Name(PROPERTY_TOKEN_URL)
@Description("Endpoint for the resource server, which exchanges the authorization code for an access token.")
Expand Down Expand Up @@ -210,9 +227,22 @@ public String getOAuth2Enabled() {

@Nullable
public String getAuthUrl() {
return authUrl;
return authUrl;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Revert unintended changes

}

public OAuthGrantType getOauth2GrantType() {
OAuthGrantType grantType = OAuthGrantType.getGrantType(oauth2GrantType);
return BaseHttpSourceConfig.getEnumValueByString(OAuthGrantType.class, grantType.getValue(),
PROPERTY_OAUTH2_GRANT_TYPE);
}

public OAuthClientAuthentication getOauth2ClientAuthentication() {
OAuthClientAuthentication clientAuthentication = OAuthClientAuthentication.getClientAuthentication(
oauth2ClientAuthentication);
return BaseHttpSourceConfig.getEnumValueByString(OAuthClientAuthentication.class,
clientAuthentication.getValue(), PROPERTY_OAUTH2_CLIENT_AUTHENTICATION);
}

@Nullable
public String getTokenUrl() {
return tokenUrl;
Expand Down Expand Up @@ -314,113 +344,138 @@ public Boolean isServiceAccountFilePath() {
}

public boolean validateServiceAccount(FailureCollector collector) {
if (containsMacro(PROPERTY_NAME_SERVICE_ACCOUNT_FILE_PATH) ||
containsMacro(PROPERTY_NAME_SERVICE_ACCOUNT_JSON)) {
return false;
}
final Boolean bServiceAccountFilePath = isServiceAccountFilePath();
final Boolean bServiceAccountJson = isServiceAccountJson();

// we don't want the validation to fail because the VM used during the validation
// may be different from the VM used during runtime and may not have the Google Drive Api scope.
if (bServiceAccountFilePath && PROPERTY_AUTO_DETECT_VALUE.equalsIgnoreCase(serviceAccountFilePath)) {
return false;
if (containsMacro(PROPERTY_NAME_SERVICE_ACCOUNT_FILE_PATH) ||
containsMacro(PROPERTY_NAME_SERVICE_ACCOUNT_JSON)) {
return false;
}
final Boolean bServiceAccountFilePath = isServiceAccountFilePath();
final Boolean bServiceAccountJson = isServiceAccountJson();

// we don't want the validation to fail because the VM used during the validation
// may be different from the VM used during runtime and may not have the Google Drive Api scope.
if (bServiceAccountFilePath && PROPERTY_AUTO_DETECT_VALUE.equalsIgnoreCase(
serviceAccountFilePath)) {
return false;
}

if (bServiceAccountFilePath != null && bServiceAccountFilePath) {
if (!PROPERTY_AUTO_DETECT_VALUE.equals(serviceAccountFilePath) &&
!new File(serviceAccountFilePath).exists()) {
collector.addFailure("Service Account File Path is not available.",
"Please provide path to existing Service Account file.")
.withConfigProperty(PROPERTY_NAME_SERVICE_ACCOUNT_FILE_PATH);
}
}
if (bServiceAccountJson != null && bServiceAccountJson) {
if (!Optional.ofNullable(getServiceAccountJson()).isPresent()) {
collector.addFailure("Service Account JSON can not be empty.",
"Please provide Service Account JSON.")
.withConfigProperty(PROPERTY_NAME_SERVICE_ACCOUNT_JSON);
}
}
return collector.getValidationFailures().size() == 0;
}

public void validate(FailureCollector failureCollector) {
// Validate OAuth2 properties
if (!containsMacro(PROPERTY_OAUTH2_ENABLED) && this.getOauth2Enabled()) {
String reasonOauth2 = "OAuth2 is enabled";
assertIsSetWithFailureCollector(getTokenUrl(), PROPERTY_TOKEN_URL, reasonOauth2,
failureCollector);
assertIsSetWithFailureCollector(getClientId(), PROPERTY_CLIENT_ID, reasonOauth2,
failureCollector);
assertIsSetWithFailureCollector(getClientSecret(), PROPERTY_CLIENT_SECRET, reasonOauth2,
failureCollector);
assertIsSetWithFailureCollector(getRefreshToken(), PROPERTY_REFRESH_TOKEN, reasonOauth2,
failureCollector);
}

if (bServiceAccountFilePath != null && bServiceAccountFilePath) {
if (!PROPERTY_AUTO_DETECT_VALUE.equals(serviceAccountFilePath) &&
!new File(serviceAccountFilePath).exists()) {
collector.addFailure("Service Account File Path is not available.",
"Please provide path to existing Service Account file.")
.withConfigProperty(PROPERTY_NAME_SERVICE_ACCOUNT_FILE_PATH);
}
}
if (bServiceAccountJson != null && bServiceAccountJson) {
if (!Optional.ofNullable(getServiceAccountJson()).isPresent()) {
collector.addFailure("Service Account JSON can not be empty.",
"Please provide Service Account JSON.")
.withConfigProperty(PROPERTY_NAME_SERVICE_ACCOUNT_JSON);
}
}
return collector.getValidationFailures().size() == 0;
if (!containsMacro(PROPERTY_WAIT_TIME_BETWEEN_PAGES) && waitTimeBetweenPages != null
&& waitTimeBetweenPages < 0) {
failureCollector.addFailure("Wait Time Between Pages cannot be a negative number.",
null).withConfigProperty(PROPERTY_WAIT_TIME_BETWEEN_PAGES);
}

public void validate(FailureCollector failureCollector) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes are not removed, yet it is showing in diff, let the sequence of methods remain same to avoid unnecessary confusion.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Amit-CloudSufi this comment is still pending

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sequence is same as before, it;s just that previous indentation was wrong I.e. 4 spaces, and currently the check style which was recommended by Ankit has 2 spaces, because of that it got updated when made changes. That's why it is shown is removed and added which updated spaces.

// Validate OAuth2 properties
if (!containsMacro(PROPERTY_OAUTH2_ENABLED) && this.getOauth2Enabled()) {
String reasonOauth2 = "OAuth2 is enabled";
assertIsSetWithFailureCollector(getTokenUrl(), PROPERTY_TOKEN_URL, reasonOauth2, failureCollector);
assertIsSetWithFailureCollector(getClientId(), PROPERTY_CLIENT_ID, reasonOauth2, failureCollector);
assertIsSetWithFailureCollector(getClientSecret(), PROPERTY_CLIENT_SECRET, reasonOauth2, failureCollector);
assertIsSetWithFailureCollector(getRefreshToken(), PROPERTY_REFRESH_TOKEN, reasonOauth2, failureCollector);
// Validate Authentication properties
AuthType authType = getAuthType();
switch (authType) {
case OAUTH2:
validateOAuth2Fields(failureCollector);
break;
case SERVICE_ACCOUNT:
String reasonSA = "Service Account is enabled";
assertIsSet(getServiceAccountType(), PROPERTY_NAME_SERVICE_ACCOUNT_TYPE, reasonSA);
boolean propertiesAreValid = validateServiceAccount(failureCollector);
if (propertiesAreValid) {
try {
AccessToken accessToken = OAuthUtil.getAccessToken(this);
} catch (Exception e) {
failureCollector.addFailure("Unable to authenticate given service account info. ",
"Please make sure all infomation entered correctly")
.withStacktrace(e.getStackTrace());
}
}

if (!containsMacro(PROPERTY_WAIT_TIME_BETWEEN_PAGES) && waitTimeBetweenPages != null
&& waitTimeBetweenPages < 0) {
failureCollector.addFailure("Wait Time Between Pages cannot be a negative number.",
null).withConfigProperty(PROPERTY_WAIT_TIME_BETWEEN_PAGES);
break;
case BASIC_AUTH:
String reasonBasicAuth = "Basic Authentication is enabled";
if (!containsMacro(PROPERTY_USERNAME)) {
assertIsSetWithFailureCollector(getUsername(), PROPERTY_USERNAME, reasonBasicAuth,
failureCollector);
}

// Validate Authentication properties
AuthType authType = getAuthType();
switch (authType) {
case OAUTH2:
String reasonOauth2 = "OAuth2 is enabled";
if (!containsMacro(PROPERTY_TOKEN_URL)) {
assertIsSetWithFailureCollector(getTokenUrl(), PROPERTY_TOKEN_URL, reasonOauth2, failureCollector);
}
if (!containsMacro(PROPERTY_CLIENT_ID)) {
assertIsSetWithFailureCollector(getClientId(), PROPERTY_CLIENT_ID, reasonOauth2, failureCollector);
}
if (!containsMacro((PROPERTY_CLIENT_SECRET))) {
assertIsSetWithFailureCollector(getClientSecret(), PROPERTY_CLIENT_SECRET, reasonOauth2,
failureCollector);
}
if (!containsMacro(PROPERTY_REFRESH_TOKEN)) {
assertIsSetWithFailureCollector(getRefreshToken(), PROPERTY_REFRESH_TOKEN, reasonOauth2,
failureCollector);
}
break;
case SERVICE_ACCOUNT:
String reasonSA = "Service Account is enabled";
assertIsSet(getServiceAccountType(), PROPERTY_NAME_SERVICE_ACCOUNT_TYPE, reasonSA);
boolean propertiesAreValid = validateServiceAccount(failureCollector);
if (propertiesAreValid) {
try {
AccessToken accessToken = OAuthUtil.getAccessToken(this);
} catch (Exception e) {
failureCollector.addFailure("Unable to authenticate given service account info. ",
"Please make sure all infomation entered correctly")
.withStacktrace(e.getStackTrace());
}
}
break;
case BASIC_AUTH:
String reasonBasicAuth = "Basic Authentication is enabled";
if (!containsMacro(PROPERTY_USERNAME)) {
assertIsSetWithFailureCollector(getUsername(), PROPERTY_USERNAME, reasonBasicAuth,
failureCollector);
}
if (!containsMacro(PROPERTY_PASSWORD)) {
assertIsSetWithFailureCollector(getPassword(), PROPERTY_PASSWORD, reasonBasicAuth,
failureCollector);
}
break;
if (!containsMacro(PROPERTY_PASSWORD)) {
assertIsSetWithFailureCollector(getPassword(), PROPERTY_PASSWORD, reasonBasicAuth,
failureCollector);
}
break;
}

public static void assertIsSet(Object propertyValue, String propertyName, String reason) {
if (propertyValue == null) {
throw new InvalidConfigPropertyException(
String.format("Property '%s' must be set, since %s", propertyName, reason), propertyName);
}
}

private void validateOAuth2Fields(FailureCollector failureCollector) {
String reasonOauth2GrantType = String.format("OAuth2 is enabled and grant type is %s.",
getOauth2GrantType().getValue());
if (!containsMacro(PROPERTY_TOKEN_URL)) {
assertIsSetWithFailureCollector(getTokenUrl(), PROPERTY_TOKEN_URL, reasonOauth2GrantType,
failureCollector);
}
if (!containsMacro(PROPERTY_CLIENT_ID)) {
assertIsSetWithFailureCollector(getClientId(), PROPERTY_CLIENT_ID, reasonOauth2GrantType,
failureCollector);
}
if (!containsMacro(PROPERTY_CLIENT_SECRET)) {
assertIsSetWithFailureCollector(getClientSecret(), PROPERTY_CLIENT_SECRET,
reasonOauth2GrantType, failureCollector);
}
if (!containsMacro(PROPERTY_OAUTH2_CLIENT_AUTHENTICATION)) {
assertIsSetWithFailureCollector(getOauth2ClientAuthentication(),
PROPERTY_OAUTH2_CLIENT_AUTHENTICATION, reasonOauth2GrantType, failureCollector);
}
// in case of refresh token grant type, also check 2 additional fields
if (OAuthGrantType.REFRESH_TOKEN.equals(getOauth2GrantType())) {
if (!containsMacro(PROPERTY_AUTH_URL)) {
assertIsSetWithFailureCollector(getAuthUrl(), PROPERTY_AUTH_URL, reasonOauth2GrantType,
failureCollector);
}
if (!containsMacro(PROPERTY_REFRESH_TOKEN)) {
assertIsSetWithFailureCollector(getRefreshToken(), PROPERTY_REFRESH_TOKEN,
reasonOauth2GrantType, failureCollector);
}
}
}

public static void assertIsSetWithFailureCollector(Object propertyValue, String propertyName, String reason,
FailureCollector failureCollector) {
if (propertyValue == null) {
failureCollector.addFailure(String.format("Property '%s' must be set, since %s", propertyName, reason),
null).withConfigProperty(propertyName);
}
public static void assertIsSet(Object propertyValue, String propertyName, String reason) {
if (propertyValue == null) {
throw new InvalidConfigPropertyException(
String.format("Property '%s' must be set, since %s", propertyName, reason), propertyName);
}
}

public static void assertIsSetWithFailureCollector(Object propertyValue, String propertyName,
String reason,
FailureCollector failureCollector) {
if (propertyValue == null) {
failureCollector.addFailure(
String.format("Property '%s' must be set, since %s", propertyName, reason),
null).withConfigProperty(propertyName);
}
}
}
12 changes: 12 additions & 0 deletions src/main/java/io/cdap/plugin/http/common/http/HttpClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;

import java.io.Closeable;
Expand Down Expand Up @@ -133,6 +134,17 @@ public CloseableHttpClient createHttpClient(String pageUriStr) throws IOExceptio
}
httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);

ArrayList<Header> clientHeaders = new ArrayList<>();

// oAuth2
if (config.getOauth2Enabled()) {
clientHeaders.add(new BasicHeader("Authorization",
"Bearer " + OAuthUtil.getAccessToken(HttpClients.createDefault(), config)
.getTokenValue()));
}

httpClientBuilder.setDefaultHeaders(clientHeaders);

return httpClientBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright © 2025 Cask Data, Inc.
*
* Licensed 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 io.cdap.plugin.http.common.http;

import io.cdap.plugin.http.common.EnumWithValue;
import java.util.Objects;

/**
* Enum encoding the handled Oauth2 Client Authentication
*/
public enum OAuthClientAuthentication implements EnumWithValue {
BODY("body", "Body"),
REQUEST_PARAMETER("request_parameter", "Request Parameter");

private final String value;
private final String label;

OAuthClientAuthentication(String value, String label) {
this.value = value;
this.label = label;
}

public static OAuthClientAuthentication getClientAuthentication(String clientAuthentication) {
if (Objects.equals(clientAuthentication, BODY.getLabel())) {
return BODY;
} else {
return REQUEST_PARAMETER;
}
}

@Override
public String getValue() {
return value;
}

public String getLabel() {
return label;
}

@Override
public String toString() {
return this.getValue();
}
}

Loading