-
Notifications
You must be signed in to change notification settings - Fork 1
Add 'client_credentials' OAuth2 authentication flow #17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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"; | ||
|
|
@@ -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) | ||
| @Description("If true, plugin will perform OAuth2 authentication.") | ||
| @Nullable | ||
| protected String oauth2Enabled; | ||
|
|
@@ -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.") | ||
|
|
@@ -210,9 +227,22 @@ public String getOAuth2Enabled() { | |
|
|
||
| @Nullable | ||
| public String getAuthUrl() { | ||
| return authUrl; | ||
| return authUrl; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
|
|
@@ -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. | ||
Amit-CloudSufi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Amit-CloudSufi this comment is still pending
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
Amit-CloudSufi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } 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); | ||
| } | ||
| } | ||
| } | ||
| 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(); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Revert unintended changes