From f8024eee7e039ea7ca2b7dcfcca280e6e133bf4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alaksiej=20=C5=A0=C4=8Darbaty?= Date: Mon, 3 Mar 2025 14:55:37 +0100 Subject: [PATCH] NIFI-14308 Make as-user optional in JsonConfigBasedBoxClientService Signed-off-by: Pierre Villard This closes #9759. --- .../box/controllerservices/BoxAppActor.java | 52 +++++++++++++++++++ .../JsonConfigBasedBoxClientService.java | 26 ++++++++-- .../additionalDetails.md | 2 +- ...igBasedBoxClientServiceTestRunnerTest.java | 31 +++++++++++ 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/BoxAppActor.java diff --git a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/BoxAppActor.java b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/BoxAppActor.java new file mode 100644 index 000000000000..821a793f1e6f --- /dev/null +++ b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/BoxAppActor.java @@ -0,0 +1,52 @@ +/* + * 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.nifi.box.controllerservices; + +import org.apache.nifi.components.DescribedValue; + +/** + * An enumeration of the possible actors a Box App will act on behalf of. + */ +public enum BoxAppActor implements DescribedValue { + SERVICE_ACCOUNT("service-account", "Service Account", "Make Box API calls with a service account associated with the app"), + IMPERSONATED_USER("impersonated-user", "Impersonated User", "Make Box API call on behalf of the specified Box user with as-user header"); + + private final String value; + private final String displayName; + private final String description; + + BoxAppActor(String value, String displayName, String description) { + this.value = value; + this.displayName = displayName; + this.description = description; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String getDisplayName() { + return displayName; + } + + @Override + public String getDescription() { + return description; + } +} diff --git a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientService.java b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientService.java index a43fe7c22150..e6cbcf077260 100644 --- a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientService.java +++ b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientService.java @@ -57,12 +57,23 @@ @CapabilityDescription("Provides Box client objects through which Box API calls can be used.") @Tags({"box", "client", "provider"}) public class JsonConfigBasedBoxClientService extends AbstractControllerService implements BoxClientService, VerifiableControllerService { + + public static final PropertyDescriptor APP_ACTOR = new PropertyDescriptor.Builder() + .name("App Actor") + .description("Specifies on behalf of whom Box API calls will be made.") + .required(true) + .allowableValues(BoxAppActor.class) + .defaultValue(BoxAppActor.IMPERSONATED_USER) + .expressionLanguageSupported(ExpressionLanguageScope.NONE) + .build(); + public static final PropertyDescriptor ACCOUNT_ID = new PropertyDescriptor.Builder() .name("box-account-id") .displayName("Account ID") - .description("The ID of the Box account who owns the accessed resource. Same as 'User Id' under 'App Info' in the App 'General Settings'.") + .description("The ID of the Box account which the app will act on behalf of.") .required(true) .addValidator(StandardValidators.NON_EMPTY_VALIDATOR) + .dependsOn(APP_ACTOR, BoxAppActor.IMPERSONATED_USER) .expressionLanguageSupported(ExpressionLanguageScope.ENVIRONMENT) .build(); @@ -88,6 +99,7 @@ public class JsonConfigBasedBoxClientService extends AbstractControllerService i private static final ProxySpec[] PROXY_SPECS = {ProxySpec.HTTP, ProxySpec.HTTP_AUTH}; private static final List PROPERTY_DESCRIPTORS = List.of( + APP_ACTOR, ACCOUNT_ID, APP_CONFIG_FILE, APP_CONFIG_JSON, @@ -168,8 +180,6 @@ public BoxAPIConnection getBoxApiConnection() { } private BoxAPIConnection createBoxApiConnection(ConfigurationContext context) { - - final String accountId = context.getProperty(ACCOUNT_ID).evaluateAttributeExpressions().getValue(); final ProxyConfiguration proxyConfiguration = ProxyConfiguration.getConfiguration(context); final BoxConfig boxConfig; @@ -200,7 +210,15 @@ private BoxAPIConnection createBoxApiConnection(ConfigurationContext context) { } } - api.asUser(accountId); + final BoxAppActor appActor = context.getProperty(APP_ACTOR).asAllowableValue(BoxAppActor.class); + switch (appActor) { + case SERVICE_ACCOUNT -> api.asSelf(); + case IMPERSONATED_USER -> { + final String accountId = context.getProperty(ACCOUNT_ID).evaluateAttributeExpressions().getValue(); + api.asUser(accountId); + } + default -> throw new IllegalArgumentException("Unrecognized App actor:" + appActor); + } if (!Proxy.Type.DIRECT.equals(proxyConfiguration.getProxyType())) { api.setProxy(proxyConfiguration.createProxy()); diff --git a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/resources/docs/org.apache.nifi.box.controllerservices.JsonConfigBasedBoxClientService/additionalDetails.md b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/resources/docs/org.apache.nifi.box.controllerservices.JsonConfigBasedBoxClientService/additionalDetails.md index d3d0877797a4..476584421295 100644 --- a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/resources/docs/org.apache.nifi.box.controllerservices.JsonConfigBasedBoxClientService/additionalDetails.md +++ b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/main/resources/docs/org.apache.nifi.box.controllerservices.JsonConfigBasedBoxClientService/additionalDetails.md @@ -28,7 +28,7 @@ The App should have the following configuration: * Should have a 'Client ID' and 'Client Secret'. * 'App Access Level' should be 'App + Enterprise Access'. * 'Application Scopes' should have 'Write all files and folders in Box' enabled. -* 'Advanced Features' should have 'Generate user access tokens' and 'Make API calls using the as-user header' enabled. +* If 'App Actor' is 'Impersonated User', 'Advanced Features' should have 'Generate user access tokens' and 'Make API calls using the as-user header' enabled. * Under 'Add and Manage Public Keys' generate a Public/Private Keypair and download the configuration JSON file (under App Settings). The full path of this file should be set in the 'Box Config File' property. Note that you can only download the configuration JSON with the keypair details only once, when you generate the diff --git a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/test/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientServiceTestRunnerTest.java b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/test/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientServiceTestRunnerTest.java index 98ecdf168a68..da971e186b15 100644 --- a/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/test/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientServiceTestRunnerTest.java +++ b/nifi-extension-bundles/nifi-box-bundle/nifi-box-services/src/test/java/org/apache/nifi/box/controllerservices/JsonConfigBasedBoxClientServiceTestRunnerTest.java @@ -55,6 +55,7 @@ void validWhenAppConfigJsonIsSet() { @Test void invalidWhenAccountIdIsMissing() { testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "{}"); + // App Actor is Impersonated User by default. testRunner.assertNotValid(testSubject); } @@ -85,4 +86,34 @@ void invalidWhenAppConfigJsonIsNotValid() { testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "not_valid_json_string"); testRunner.assertNotValid(testSubject); } + + @Test + void validWhenAppActorIsServiceAccount() { + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_ACTOR, BoxAppActor.SERVICE_ACCOUNT); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "{}"); + testRunner.assertValid(testSubject); + } + + @Test + void invalidWhenAppActorIsServiceAccountAndAccountIdIsSet() { + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_ACTOR, BoxAppActor.SERVICE_ACCOUNT); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.ACCOUNT_ID, "account_id"); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "{}"); + testRunner.assertValid(testSubject); + } + + @Test + void validWhenAppActorIsImpersonatedUserAndAccountIdIsSet() { + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_ACTOR, BoxAppActor.IMPERSONATED_USER); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.ACCOUNT_ID, "account_id"); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "{}"); + testRunner.assertValid(testSubject); + } + + @Test + void invalidWhenAppActorIsImpersonatedUserAndAccountIdIsMissing() { + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_ACTOR, BoxAppActor.IMPERSONATED_USER); + testRunner.setProperty(testSubject, JsonConfigBasedBoxClientService.APP_CONFIG_JSON, "{}"); + testRunner.assertNotValid(testSubject); + } }