This is an automated email from the ASF dual-hosted git repository. pvillard pushed a commit to branch support/nifi-1.x in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/support/nifi-1.x by this push: new e5a0b58aa4 NIFI-12037 Update AzureUserGroupProvider to allow configuration of the graph endpoint and API scope to support regional clouds. e5a0b58aa4 is described below commit e5a0b58aa42a410848a073b017bf9960ecd3ff1d Author: Justin <22872623+ren...@users.noreply.github.com> 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 <pierre.villard...@gmail.com> 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 7e81718e57..530e36781a 100644 --- a/nifi-docs/src/main/asciidoc/administration-guide.adoc +++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc @@ -801,7 +801,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 { } } } +