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 7a79e8c01f NIFI-12037 Update AzureUserGroupProvider to allow
configuration of the graph endpoint and API scope to support regional clouds.
7a79e8c01f is described below
commit 7a79e8c01fe9b725cb00d7f308a9930be2d3cb94
Author: Justin <[email protected]>
AuthorDate: Fri Sep 8 17:53:56 2023 -0600
NIFI-12037 Update AzureUserGroupProvider to allow configuration of the
graph endpoint and API scope to support regional clouds.
Signed-off-by: Pierre Villard <[email protected]>
This closes #7675.
---
.../src/main/asciidoc/administration-guide.adoc | 4 +-
.../azure/AzureGraphUserGroupProvider.java | 46 +++++++++++++---------
.../azure/ClientCredentialAuthProvider.java | 34 ++++++++++------
3 files changed, 54 insertions(+), 30 deletions(-)
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc
b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index b2a5c209b9..74cba4e12e 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -838,7 +838,9 @@ The AzureGraphUserGroupProvider has the following
properties:
|==================================================================================================================================================
| Property Name | Description
|`Refresh Delay` | Duration of delay between each user and group refresh.
Default is `5 mins`.
-|`Authority Endpoint` | The endpoint of the Azure AD login. This can be found
in the Azure portal under Azure Active Directory -> App registrations ->
[application name] -> Endpoints. For example, the global authority endpoint is
https://login.microsoftonline.com.
+|`Authority Endpoint` | The endpoint of the Azure AD login. This can be found
in the Azure portal under Azure Active Directory -> App registrations ->
[application name] -> Endpoints. For example, the global authority endpoint is
`https://login.microsoftonline.com`.
+|`Graph Endpoint` | The endpoint of the Azure Graph API, with the version
identifier attached. The base url can be found in the Azure portal under Azure
Active Directory -> App registrations -> [application name] -> Endpoints. For
example, the global graph endpoint is `https://graph.microsoft.com/v1.0`, which
is also the default setting.
+|`Graph Scope` | The url for the Graph api scope. See
https://learn.microsoft.com/en-us/azure/active-directory/develop/scopes-oidc
for an explanation of scopes. This usually only needs to be changed if you are
connecting to a different `Graph Endpoint`. The Azure global default scope is
`https://graph.microsoft.com/.default`, which is also the default setting.
|`Directory ID` | Tenant ID or Directory ID of the Azure AD tenant. This can
be found in the Azure portal under Azure Active Directory -> App registrations
-> [application name] -> Directory (tenant) ID.
|`Application ID` | Client ID or Application ID of the Azure app registration.
This can be found in the Azure portal under Azure Active Directory -> App
registrations -> [application name] -> Overview -> Application (client) ID.
|`Client Secret` | A client secret from the Azure app registration. Secrets
can be created in the Azure portal under Azure Active Directory -> App
registrations -> [application name] -> Certificates & secrets -> Client secrets
-> [+] New client secret.
diff --git
a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/AzureGraphUserGroupProvider.java
b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/AzureGraphUserGroupProvider.java
index 6a45cfe1f1..0bffadd35f 100644
---
a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/AzureGraphUserGroupProvider.java
+++
b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/AzureGraphUserGroupProvider.java
@@ -56,12 +56,13 @@ import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.StopWatch;
import org.apache.nifi.util.StringUtils;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The AzureGraphUserGroupProvider provides support for retrieving users and
- * groups from Azure Activy Driectory (AAD) using graph rest-api & SDK.
+ * groups from Azure Active Directory (AAD) using graph rest-api & SDK.
*/
public class AzureGraphUserGroupProvider implements UserGroupProvider {
private final static Logger logger =
LoggerFactory.getLogger(AzureGraphUserGroupProvider.class);
@@ -73,6 +74,8 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
public static final String REFRESH_DELAY_PROPERTY = "Refresh Delay";
private static final long MINIMUM_SYNC_INTERVAL_MILLISECONDS = 10_000;
public static final String AUTHORITY_ENDPOINT_PROPERTY = "Authority
Endpoint";
+ public static final String GRAPH_ENDPOINT_PROPERTY = "Graph Endpoint";
+ public static final String GRAPH_SCOPE_PROPERTY = "Graph Scope";
public static final String TENANT_ID_PROPERTY = "Directory ID";
public static final String APP_REG_CLIENT_ID_PROPERTY = "Application ID";
public static final String APP_REG_CLIENT_SECRET_PROPERTY = "Client
Secret";
@@ -80,9 +83,9 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
public static final String GROUP_FILTER_LIST_PROPERTY = "Group Filter List
Inclusion";
// group filter with startswith
public static final String GROUP_FILTER_PREFIX_PROPERTY = "Group Filter
Prefix";
- // client side group filter 'endswith' operator, due to support limiation
of azure graph rest-api
+ // client side group filter 'endswith' operator, due to support limitation
of azure graph rest-api
public static final String GROUP_FILTER_SUFFIX_PROPERTY = "Group Filter
Suffix";
- // client side group filter 'contains' operator, due to support limiation
of azure graph rest-api
+ // client side group filter 'contains' operator, due to support limitation
of azure graph rest-api
public static final String GROUP_FILTER_SUBSTRING_PROPERTY = "Group Filter
Substring";
public static final String PAGE_SIZE_PROPERTY = "Page Size";
// default: upn (or userPrincipalName). possible choices ['upn', 'email']
@@ -93,12 +96,12 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
public static final String DEFAULT_CLAIM_FOR_USERNAME = "upn";
public static final int MAX_PAGE_SIZE = 999;
public static final String AZURE_PUBLIC_CLOUD =
"https://login.microsoftonline.com/";
+ public static final String AZURE_PUBLIC_GRAPH_DEFAULT_SCOPE =
"https://graph.microsoft.com/.default";
static final List<String> REST_CALL_KEYWORDS = Arrays.asList("$select",
"$top", "$expand", "$search", "$filter", "$format", "$count", "$skip",
"$orderby");
- private ClientCredentialAuthProvider authProvider;
private IGraphServiceClient graphClient;
- private final AtomicReference<ImmutableAzureGraphUserGroup>
azureGraphUserGroupRef = new AtomicReference<ImmutableAzureGraphUserGroup>();
+ private final AtomicReference<ImmutableAzureGraphUserGroup>
azureGraphUserGroupRef = new AtomicReference<>();
@Override
public Group getGroup(String identifier) throws
AuthorizationAccessException {
@@ -135,7 +138,7 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
throws AuthorizerCreationException {
this.scheduler = Executors.newSingleThreadScheduledExecutor(new
ThreadFactory() {
@Override
- public Thread newThread(Runnable r) {
+ public Thread newThread(@NotNull Runnable r) {
final Thread thread =
Executors.defaultThreadFactory().newThread(r);
thread.setName(String.format("%s (%s) - UserGroup Refresh",
getClass().getSimpleName(), initializationContext.getIdentifier()));
return thread;
@@ -179,6 +182,8 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
public void onConfigured(AuthorizerConfigurationContext
configurationContext) throws AuthorizerCreationException {
final long fixedDelay = getDelayProperty(configurationContext,
REFRESH_DELAY_PROPERTY, DEFAULT_REFRESH_DELAY);
final String authorityEndpoint = getProperty(configurationContext,
AUTHORITY_ENDPOINT_PROPERTY, AZURE_PUBLIC_CLOUD);
+ final String graphEndpoint = getProperty(configurationContext,
GRAPH_ENDPOINT_PROPERTY, null);
+ final String graphScope = getProperty(configurationContext,
GRAPH_SCOPE_PROPERTY, AZURE_PUBLIC_GRAPH_DEFAULT_SCOPE);
final String tenantId = getProperty(configurationContext,
TENANT_ID_PROPERTY, null);
final String clientId = getProperty(configurationContext,
APP_REG_CLIENT_ID_PROPERTY, null);
final String clientSecret = getProperty(configurationContext,
APP_REG_CLIENT_SECRET_PROPERTY, null);
@@ -199,20 +204,24 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
}
try {
- authProvider = new ClientCredentialAuthProvider.Builder()
- .authorityEndpoint(authorityEndpoint)
- .tenantId(tenantId)
- .clientId(clientId)
- .clientSecret(clientSecret)
- .build();
+ ClientCredentialAuthProvider authProvider = new
ClientCredentialAuthProvider.Builder()
+ .authorityEndpoint(authorityEndpoint)
+ .tenantId(tenantId)
+ .clientId(clientId)
+ .clientSecret(clientSecret)
+ .graphScope(graphScope)
+ .build();
graphClient =
GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
+ if ( ! StringUtils.isBlank(graphEndpoint)) {
+ graphClient.setServiceRoot(graphEndpoint);
+ }
} catch (final ClientException e) {
throw new AuthorizerCreationException(String.format("Failed to
create a GraphServiceClient due to %s", e.getMessage()), e);
}
// first, load list of group name if there is any prefix, suffix,
substring
- // filter defined, paging thru groups.
- // then, add additonal group list if there is group list inclusion
defined.
+ // filter defined, paging through groups.
+ // then, add additional group list if there is group list inclusion
defined.
final String prefix = getProperty(configurationContext,
GROUP_FILTER_PREFIX_PROPERTY, null);
final String suffix = getProperty(configurationContext,
GROUP_FILTER_SUFFIX_PROPERTY, null);
final String substring = getProperty(configurationContext,
GROUP_FILTER_SUBSTRING_PROPERTY, null);
@@ -277,7 +286,7 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
/**
* Get a set of group display names after filtering prefix, suffix, and
substring
* @param prefix prefix filter string matching against displayName of
group directory objects
- * @param suffix suffix fitler string matching against displayName of
group directory objects
+ * @param suffix suffix filter string matching against displayName of
group directory objects
* @param substring string matching against displayName of group directory
objects
* @param pageSize page size to make graph rest calls in pagination
* @return set of group display names
@@ -288,7 +297,7 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
IGroupCollectionPage filterResults;
if (prefix != null && !prefix.isEmpty()) {
// build a $filter query option and create a graph request if
prefix is given
- final List<Option> requestOptions = Arrays.asList(new
QueryOption("$filter", String.format("startswith(displayName, '%s')", prefix)));
+ final List<Option> requestOptions = List.of(new
QueryOption("$filter", String.format("startswith(displayName, '%s')", prefix)));
gRequest =
graphClient.groups().buildRequest(requestOptions).select("displayName");
} else {
// default group graph request
@@ -333,11 +342,11 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
private UserGroupQueryResult getUsersFrom(String groupName, int pageSize)
throws IOException, ClientException {
final Set<User> users = new HashSet<>();
- final List<Option> requestOptions = Arrays.asList(new
QueryOption("$filter", String.format("displayName eq '%s'", groupName)));
+ final List<Option> requestOptions = List.of(new QueryOption("$filter",
String.format("displayName eq '%s'", groupName)));
final IGroupCollectionPage results =
graphClient.groups().buildRequest(requestOptions).get();
final List<com.microsoft.graph.models.extensions.Group> currentPage =
results.getCurrentPage();
- if (currentPage != null && currentPage.size() > 0) {
+ if (currentPage != null && !currentPage.isEmpty()) {
final com.microsoft.graph.models.extensions.Group graphGroup =
results.getCurrentPage().get(0);
final Group.Builder groupBuilder =
new Group.Builder()
@@ -445,3 +454,4 @@ public class AzureGraphUserGroupProvider implements
UserGroupProvider {
}
}
+
diff --git
a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/ClientCredentialAuthProvider.java
b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/ClientCredentialAuthProvider.java
index 552bac9f73..b926d51b8e 100644
---
a/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/ClientCredentialAuthProvider.java
+++
b/nifi-nar-bundles/nifi-azure-bundle/nifi-azure-graph-authorizer/src/main/java/org/apache/nifi/authorization/azure/ClientCredentialAuthProvider.java
@@ -43,9 +43,9 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
private final String tenantId;
private final String clientId;
private final String clientSecret;
+ private final String graphScope;
private LocalDateTime tokenExpiresOnDate;
- private String lastAcessToken;
- private static final String GRAPH_DEFAULT_SCOPE =
"https://graph.microsoft.com/.default";
+ private String lastAccessToken;
private static final Logger logger =
LoggerFactory.getLogger(ClientCredentialAuthProvider.class);
private ClientCredentialAuthProvider(final Builder builder){
@@ -53,11 +53,12 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
this.tenantId = builder.getTenantId();
this.clientId = builder.getClientId();
this.clientSecret = builder.getClientSecret();
+ this.graphScope = builder.getGraphScope();
}
@Override
public int hashCode() {
- return Objects.hash(authorityEndpoint, tenantId, clientId,
clientSecret);
+ return Objects.hash(authorityEndpoint, tenantId, clientId,
clientSecret, graphScope);
}
@Override
@@ -67,6 +68,7 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
", tenantId='" + tenantId + "'" +
", clientId='" + clientId + "'" +
", clientSecret='" + clientSecret + "'" +
+ ", graphScope='" + graphScope + "'" +
"}";
}
@@ -79,7 +81,7 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
.authority(String.format("%s/%s", authorityEndpoint, tenantId))
.build();
ClientCredentialParameters clientCredentialParam =
ClientCredentialParameters.builder(
- Collections.singleton(GRAPH_DEFAULT_SCOPE))
+ Collections.singleton(graphScope))
.build();
CompletableFuture<IAuthenticationResult> future =
app.acquireToken(clientCredentialParam);
@@ -93,18 +95,16 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
}
private String getAccessToken() {
- if ((lastAcessToken != null) && (tokenExpiresOnDate != null) &&
(tokenExpiresOnDate.isAfter(LocalDateTime.now().plusMinutes(1)))) {
- return lastAcessToken;
- } else {
+ if ((lastAccessToken == null) || (tokenExpiresOnDate == null) ||
(!tokenExpiresOnDate.isAfter(LocalDateTime.now().plusMinutes(1)))) {
try {
IAuthenticationResult result =
getAccessTokenByClientCredentialGrant();
tokenExpiresOnDate =
convertToLocalDateTime(result.expiresOnDate());
- lastAcessToken = result.accessToken();
- } catch(final Exception e) {
+ lastAccessToken = result.accessToken();
+ } catch (final Exception e) {
logger.error("Failed to get access token due to {}",
e.getMessage(), e);
}
- return lastAcessToken;
}
+ return lastAccessToken;
}
@Override
@@ -121,6 +121,7 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
private String tenantId = "";
private String clientId = "";
private String clientSecret = "";
+ private String graphScope = "";
public Builder authorityEndpoint(final String authorityEndpoint){
this.authorityEndpoint = authorityEndpoint;
@@ -131,6 +132,15 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
return this.authorityEndpoint;
}
+ public Builder graphScope(final String graphDefaultScope){
+ this.graphScope = graphDefaultScope;
+ return this;
+ }
+
+ public String getGraphScope() {
+ return this.graphScope;
+ }
+
public Builder tenantId(final String tenantId){
this.tenantId = tenantId;
return this;
@@ -160,7 +170,7 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
@Override
public int hashCode() {
- return Objects.hash(authorityEndpoint, tenantId, clientId,
clientSecret);
+ return Objects.hash(authorityEndpoint, tenantId, clientId,
clientSecret, graphScope);
}
@Override
@@ -170,6 +180,7 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
", tenantId='" + getTenantId() + "'" +
", clientId='" + getClientId() + "'" +
", clientSecret='" + getClientSecret() + "'" +
+ ", graphScope='" + getGraphScope() + "'" +
"}";
}
public ClientCredentialAuthProvider build() {
@@ -177,3 +188,4 @@ public class ClientCredentialAuthProvider implements
IAuthenticationProvider {
}
}
}
+