Skip to content

Commit

Permalink
NIFI-14308 Make as-user optional in JsonConfigBasedBoxClientService
Browse files Browse the repository at this point in the history
Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com>

This closes #9759.
  • Loading branch information
awelless authored and pvillard31 committed Mar 4, 2025
1 parent 1877e7c commit f8024ee
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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<PropertyDescriptor> PROPERTY_DESCRIPTORS = List.of(
APP_ACTOR,
ACCOUNT_ID,
APP_CONFIG_FILE,
APP_CONFIG_JSON,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
}
}

0 comments on commit f8024ee

Please sign in to comment.