This is an automated email from the ASF dual-hosted git repository.
pvillard pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 53911bbf78 NIFI-14154 Add support for oAuth to GetWorkdayReport
processor
53911bbf78 is described below
commit 53911bbf7808ae51ba109cd0ada1629ed35c97b3
Author: Marcin Gemra <[email protected]>
AuthorDate: Wed Jan 15 15:41:44 2025 +0100
NIFI-14154 Add support for oAuth to GetWorkdayReport processor
Signed-off-by: Pierre Villard <[email protected]>
This closes #9631.
---
.../nifi-workday-processors/pom.xml | 5 +
.../nifi/processors/workday/GetWorkdayReport.java | 52 ++++++-
.../processors/workday/GetWorkdayReportTest.java | 154 ++++++++++++++++-----
3 files changed, 175 insertions(+), 36 deletions(-)
diff --git
a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/pom.xml
b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/pom.xml
index c242f868f2..ed6afaa647 100644
--- a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/pom.xml
+++ b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/pom.xml
@@ -33,6 +33,11 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-web-client-provider-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.nifi</groupId>
+ <artifactId>nifi-oauth2-provider-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-utils</artifactId>
diff --git
a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/main/java/org/apache/nifi/processors/workday/GetWorkdayReport.java
b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/main/java/org/apache/nifi/processors/workday/GetWorkdayReport.java
index 1f8df56c6b..f4f3d39c9f 100644
---
a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/main/java/org/apache/nifi/processors/workday/GetWorkdayReport.java
+++
b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/main/java/org/apache/nifi/processors/workday/GetWorkdayReport.java
@@ -46,9 +46,11 @@ import org.apache.nifi.annotation.behavior.WritesAttributes;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.annotation.lifecycle.OnScheduled;
+import org.apache.nifi.components.AllowableValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
import org.apache.nifi.processor.AbstractProcessor;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
@@ -90,6 +92,7 @@ public class GetWorkdayReport extends AbstractProcessor {
protected static final String GET_WORKDAY_REPORT_JAVA_EXCEPTION_MESSAGE =
"getworkdayreport.java.exception.message";
protected static final String RECORD_COUNT = "record.count";
protected static final String BASIC_PREFIX = "Basic ";
+ protected static final String BEARER_PREFIX = "Bearer ";
protected static final String HEADER_AUTHORIZATION = "Authorization";
protected static final String HEADER_CONTENT_TYPE = "Content-Type";
protected static final String USERNAME_PASSWORD_SEPARATOR = ":";
@@ -103,10 +106,31 @@ public class GetWorkdayReport extends AbstractProcessor {
.addValidator(URL_VALIDATOR)
.build();
+ public static AllowableValue BASIC_AUTH_TYPE = new AllowableValue(
+ "BASIC_AUTH",
+ "Basic Auth",
+ "Used to access resources using Workday password and username."
+ );
+
+ public static AllowableValue OAUTH_TYPE = new AllowableValue(
+ "OAUTH",
+ "OAuth",
+ "Used to get fresh access tokens based on a previously acquired
refresh token. Requires Client ID, Client Secret and Refresh Token."
+ );
+
+ public static final PropertyDescriptor AUTH_TYPE = new
PropertyDescriptor.Builder()
+ .name("Authorization Type")
+ .description("The type of authorization for retrieving data from
Workday resources.")
+ .required(true)
+ .allowableValues(BASIC_AUTH_TYPE, OAUTH_TYPE)
+ .defaultValue(BASIC_AUTH_TYPE.getValue())
+ .build();
+
protected static final PropertyDescriptor WORKDAY_USERNAME = new
PropertyDescriptor.Builder()
.name("Workday Username")
.displayName("Workday Username")
.description("The username provided for authentication of Workday
requests. Encoded using Base64 for HTTP Basic Authentication as described in
RFC 7617.")
+ .dependsOn(AUTH_TYPE, BASIC_AUTH_TYPE)
.required(true)
.addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x39\\x3b-\\x7e\\x80-\\xff]+$")))
.expressionLanguageSupported(FLOWFILE_ATTRIBUTES)
@@ -116,6 +140,7 @@ public class GetWorkdayReport extends AbstractProcessor {
.name("Workday Password")
.displayName("Workday Password")
.description("The password provided for authentication of Workday
requests. Encoded using Base64 for HTTP Basic Authentication as described in
RFC 7617.")
+ .dependsOn(AUTH_TYPE, BASIC_AUTH_TYPE)
.required(true)
.sensitive(true)
.addValidator(StandardValidators.createRegexMatchingValidator(Pattern.compile("^[\\x20-\\x7e\\x80-\\xff]+$")))
@@ -129,6 +154,14 @@ public class GetWorkdayReport extends AbstractProcessor {
.identifiesControllerService(WebClientServiceProvider.class)
.build();
+ public static final PropertyDescriptor OAUTH2_ACCESS_TOKEN_PROVIDER = new
PropertyDescriptor.Builder()
+ .name("Access Token Provider")
+ .description("Enables managed retrieval of OAuth2 Bearer Token.")
+ .dependsOn(AUTH_TYPE, OAUTH_TYPE)
+ .identifiesControllerService(OAuth2AccessTokenProvider.class)
+ .required(true)
+ .build();
+
protected static final PropertyDescriptor RECORD_READER_FACTORY = new
PropertyDescriptor.Builder()
.name("record-reader")
.displayName("Record Reader")
@@ -170,6 +203,8 @@ public class GetWorkdayReport extends AbstractProcessor {
protected static final List<PropertyDescriptor> PROPERTIES = List.of(
REPORT_URL,
+ AUTH_TYPE,
+ OAUTH2_ACCESS_TOKEN_PROVIDER,
WORKDAY_USERNAME,
WORKDAY_PASSWORD,
WEB_CLIENT_SERVICE,
@@ -178,6 +213,7 @@ public class GetWorkdayReport extends AbstractProcessor {
);
private final AtomicReference<WebClientService> webClientReference = new
AtomicReference<>();
+ private final AtomicReference<OAuth2AccessTokenProvider>
tokenProviderReference = new AtomicReference<>();
private final AtomicReference<RecordReaderFactory>
recordReaderFactoryReference = new AtomicReference<>();
private final AtomicReference<RecordSetWriterFactory>
recordSetWriterFactoryReference = new AtomicReference<>();
@@ -193,10 +229,12 @@ public class GetWorkdayReport extends AbstractProcessor {
@OnScheduled
public void setUpClient(final ProcessContext context) {
+ OAuth2AccessTokenProvider tokenProvider =
context.getProperty(OAUTH2_ACCESS_TOKEN_PROVIDER).asControllerService(OAuth2AccessTokenProvider.class);
WebClientServiceProvider standardWebClientServiceProvider =
context.getProperty(WEB_CLIENT_SERVICE).asControllerService(WebClientServiceProvider.class);
RecordReaderFactory recordReaderFactory =
context.getProperty(RECORD_READER_FACTORY).asControllerService(RecordReaderFactory.class);
RecordSetWriterFactory recordSetWriterFactory =
context.getProperty(RECORD_WRITER_FACTORY).asControllerService(RecordSetWriterFactory.class);
WebClientService webClientService =
standardWebClientServiceProvider.getWebClientService();
+ tokenProviderReference.set(tokenProvider);
webClientReference.set(webClientService);
recordReaderFactoryReference.set(recordReaderFactory);
recordSetWriterFactoryReference.set(recordSetWriterFactory);
@@ -296,10 +334,16 @@ public class GetWorkdayReport extends AbstractProcessor {
}
private String createAuthorizationHeader(ProcessContext context, FlowFile
flowfile) {
- String userName =
context.getProperty(WORKDAY_USERNAME).evaluateAttributeExpressions(flowfile).getValue();
- String password =
context.getProperty(WORKDAY_PASSWORD).evaluateAttributeExpressions(flowfile).getValue();
- String base64Credential = Base64.getEncoder().encodeToString((userName
+ USERNAME_PASSWORD_SEPARATOR + password).getBytes(StandardCharsets.UTF_8));
- return BASIC_PREFIX + base64Credential;
+ String authType = context.getProperty(AUTH_TYPE).getValue();
+ if (BASIC_AUTH_TYPE.getValue().equals(authType)) {
+ String userName =
context.getProperty(WORKDAY_USERNAME).evaluateAttributeExpressions(flowfile).getValue();
+ String password =
context.getProperty(WORKDAY_PASSWORD).evaluateAttributeExpressions(flowfile).getValue();
+ String base64Credential =
Base64.getEncoder().encodeToString((userName + USERNAME_PASSWORD_SEPARATOR +
password).getBytes(StandardCharsets.UTF_8));
+ return BASIC_PREFIX + base64Credential;
+ } else {
+ OAuth2AccessTokenProvider tokenProvider =
tokenProviderReference.get();
+ return BEARER_PREFIX +
tokenProvider.getAccessDetails().getAccessToken();
+ }
}
private TransformResult transformRecords(ProcessSession session, FlowFile
flowfile, FlowFile responseFlowFile, InputStream responseBodyStream)
diff --git
a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/test/java/org/apache/nifi/processors/workday/GetWorkdayReportTest.java
b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/test/java/org/apache/nifi/processors/workday/GetWorkdayReportTest.java
index d487ddfc68..6a10a32e68 100644
---
a/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/test/java/org/apache/nifi/processors/workday/GetWorkdayReportTest.java
+++
b/nifi-extension-bundles/nifi-workday-bundle/nifi-workday-processors/src/test/java/org/apache/nifi/processors/workday/GetWorkdayReportTest.java
@@ -31,6 +31,8 @@ import static
org.apache.nifi.processors.workday.GetWorkdayReport.WEB_CLIENT_SER
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -44,6 +46,7 @@ import okhttp3.mockwebserver.RecordedRequest;
import org.apache.nifi.csv.CSVRecordSetWriter;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
import org.apache.nifi.json.JsonTreeReader;
+import org.apache.nifi.oauth2.OAuth2AccessTokenProvider;
import org.apache.nifi.reporting.InitializationException;
import org.apache.nifi.serialization.RecordReaderFactory;
import org.apache.nifi.serialization.RecordSetWriterFactory;
@@ -55,7 +58,9 @@ import
org.apache.nifi.web.client.provider.api.WebClientServiceProvider;
import
org.apache.nifi.web.client.provider.service.StandardWebClientServiceProvider;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.mockito.Answers;
class GetWorkdayReportTest {
@@ -84,40 +89,92 @@ class GetWorkdayReportTest {
mockWebServer.shutdown();
}
- @Test
- public void testNotValidWithoutReportUrlProperty() throws
InitializationException {
- withWebClientService();
- runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
- runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
-
- runner.assertNotValid();
- }
-
- @Test
- public void testNotValidWithInvalidReportUrlProperty() throws
InitializationException {
- withWebClientService();
- runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
- runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
- runner.setProperty(GetWorkdayReport.REPORT_URL, INVALID_URL);
- runner.assertNotValid();
+ @Nested
+ class BasicAuthPropertiesValidation {
+ @Test
+ void testNotValidWithoutReportUrlProperty() throws
InitializationException {
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
+ runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
+
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithInvalidReportUrlProperty() throws
InitializationException {
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
+ runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
+ runner.setProperty(GetWorkdayReport.REPORT_URL, INVALID_URL);
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithoutUserName() throws InitializationException {
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
+ runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
+
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithoutPassword() throws InitializationException {
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
+ runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
+
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithoutWebClient() {
+ runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
+ runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
+ runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
+
+ runner.assertNotValid();
+ }
}
- @Test
- public void testNotValidWithoutUserName() throws InitializationException {
- withWebClientService();
- runner.setProperty(GetWorkdayReport.WORKDAY_PASSWORD, PASSWORD);
- runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
-
- runner.assertNotValid();
- }
-
- @Test
- public void testNotValidWithoutPassword() throws InitializationException {
- withWebClientService();
- runner.setProperty(GetWorkdayReport.WORKDAY_USERNAME, USER_NAME);
- runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
-
- runner.assertNotValid();
+ @Nested
+ class OAuthPropertiesValidation {
+ @BeforeEach
+ void setUp() {
+ runner.setProperty(GetWorkdayReport.AUTH_TYPE,
GetWorkdayReport.OAUTH_TYPE);
+ }
+
+ @Test
+ void testNotValidWithoutOAuth2AccessTokenProvider() throws
InitializationException {
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
+
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithInvalidReportUrlProperty() throws
InitializationException {
+ withWebClientService();
+ withAccessTokenProvider();
+ runner.setProperty(GetWorkdayReport.REPORT_URL, INVALID_URL);
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithoutReportUrlProperty() throws
InitializationException {
+ withWebClientService();
+ withAccessTokenProvider();
+
+ runner.assertNotValid();
+ }
+
+ @Test
+ void testNotValidWithoutWebClient() throws InitializationException {
+ withAccessTokenProvider();
+ runner.setProperty(GetWorkdayReport.REPORT_URL, REPORT_URL);
+
+ runner.assertNotValid();
+ }
}
@Test
@@ -296,6 +353,26 @@ class GetWorkdayReportTest {
flowFile.assertContentEquals(csvContent);
}
+ @Test
+ void testOAuthAuthorization() throws InitializationException,
InterruptedException {
+ runner.setIncomingConnection(false);
+ withWebClientService();
+ runner.setProperty(GetWorkdayReport.REPORT_URL, getMockWebServerUrl());
+ runner.setProperty(GetWorkdayReport.AUTH_TYPE,
GetWorkdayReport.OAUTH_TYPE);
+ withAccessTokenProvider();
+
+ mockWebServer.enqueue(new
MockResponse().setResponseCode(200).setHeader(CONTENT_TYPE, APPLICATION_JSON));
+
+ runner.run();
+
+ RecordedRequest recordedRequest = mockWebServer.takeRequest(1,
TimeUnit.SECONDS);
+ String authorization = recordedRequest.getHeader(HEADER_AUTHORIZATION);
+ assertNotNull(authorization, "Authorization Header not found");
+
+ Pattern bearerPattern = Pattern.compile("^Bearer \\S+$");
+ assertTrue(bearerPattern.matcher(authorization).matches(), "OAuth
bearer not matched");
+ }
+
@Test
void testBasicAuthentication() throws InitializationException,
InterruptedException {
runner.setIncomingConnection(false);
@@ -320,6 +397,19 @@ class GetWorkdayReportTest {
return
mockWebServer.url("workdayReport").newBuilder().host(LOCALHOST).build().toString();
}
+ private void withAccessTokenProvider() throws InitializationException {
+ String oauth2AccessTokenProviderId = "oauth2AccessTokenProviderId";
+ String accessToken = "access_token";
+
+ OAuth2AccessTokenProvider oauth2AccessTokenProvider =
mock(OAuth2AccessTokenProvider.class, Answers.RETURNS_DEEP_STUBS);
+
when(oauth2AccessTokenProvider.getIdentifier()).thenReturn(oauth2AccessTokenProviderId);
+
when(oauth2AccessTokenProvider.getAccessDetails().getAccessToken()).thenReturn(accessToken);
+
+ runner.addControllerService(oauth2AccessTokenProviderId,
oauth2AccessTokenProvider);
+ runner.enableControllerService(oauth2AccessTokenProvider);
+ runner.setProperty(GetWorkdayReport.OAUTH2_ACCESS_TOKEN_PROVIDER,
oauth2AccessTokenProviderId);
+ }
+
private void withWebClientService() throws InitializationException {
String serviceIdentifier =
StandardWebClientServiceProvider.class.getName();
WebClientServiceProvider webClientServiceProvider = new
StandardWebClientServiceProvider();