NIFI-4127:

- Introducing composite ConfigurableUserGroupProvider and UserGroupProvider.
- Adding appropriate unit tests.
- Updating object model to support per resource (user/group/policy) configuration.
- Updating UI to support per resource (user/group/policy) configuration.
- Adding necessary documentation.
- Updating documentation to clarify integrity checks.
- Providing an example of configuring a composite implementation.

Signed-off-by: Pierre Villard <pierre.villard.fr@gmail.com>

This closes #1978.
This commit is contained in:
Matt Gilman 2017-06-28 16:40:41 -04:00 committed by Pierre Villard
parent 325fe53fa1
commit eefad29167
23 changed files with 1327 additions and 9 deletions

View File

@ -425,6 +425,8 @@ The 'authorizers.xml' file is used to define and configure available authorizers
and a AccessPolicyProvider. The users, group, and access policies will be loaded and optionally configured through these providers. The managed authorizer will make all access decisions based on and a AccessPolicyProvider. The users, group, and access policies will be loaded and optionally configured through these providers. The managed authorizer will make all access decisions based on
these provided users, groups, and access policies. these provided users, groups, and access policies.
During startup there is a check to ensure that there are no two users/groups with the same identity/name. This check is executed regardless of the configured implementation. This is necessary because this is how users/groups are identified and authorized during access decisions.
The default UserGroupProvider is the FileUserGroupProvider, however, you can develop additional UserGroupProviders as extensions. The FileUserGroupProvider has the following properties: The default UserGroupProvider is the FileUserGroupProvider, however, you can develop additional UserGroupProviders as extensions. The FileUserGroupProvider has the following properties:
* Users File - The file where the FileUserGroupProvider stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen. * Users File - The file where the FileUserGroupProvider stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen.
@ -464,6 +466,17 @@ Another option for the UserGroupProvider is the LdapUserGroupProvider. By defaul
* Group Name Attribute - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used. * Group Name Attribute - Attribute to use to extract group name (i.e. cn). Optional. If not set, the entire DN is used.
* Group Member Attribute - Attribute to use to define group membership (i.e. member). Optional. If not set group membership will not be calculated through the groups. Will rely on group member being defined through 'User Group Name Attribute' if set. * Group Member Attribute - Attribute to use to define group membership (i.e. member). Optional. If not set group membership will not be calculated through the groups. Will rely on group member being defined through 'User Group Name Attribute' if set.
Another option for the UserGroupProvider are composite implementations. This means that multiple sources/implementations can be configured and composed. For instance, an admin can configure users/groups to be loaded from a file and a directory server. There are two composite implementations, one that supports multiple UserGroupProviders and one that supports multiple UserGroupProviders and a single configurable UserGroupProvider.
The CompositeUserGroupProvider will provide support for retrieving users and groups from multiple sources. The CompositeUserGroupProvider has the following properties:
* User Group Provider - The identifier of user group providers to load from. The name of each property must be unique, for example: "User Group Provider A", "User Group Provider B", "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
The CompositeConfigurableUserGroupProvider will provide support for retrieving users and groups from multiple sources. Additionally, a single configurable user group provider is required. Users from the configurable user group provider are configurable, however users loaded from one of the User Group Provider [unique key] will not be. The CompositeConfigurableUserGroupProvider has the following properties:
* Configurable User Group Provider - A configurable user group provider.
* User Group Provider - The identifier of user group providers to load from. The name of each property must be unique, for example: "User Group Provider A", "User Group Provider B", "User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties: The default AccessPolicyProvider is the FileAccessPolicyProvider, however, you can develop additional AccessPolicyProvider as extensions. The FileAccessPolicyProvider has the following properties:
* User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies. * User Group Provider - The identifier for an User Group Provider defined above that will be used to access users and groups for use in the managed access policies.
@ -621,6 +634,85 @@ Here is an example loading users and groups from LDAP but still using file based
The 'Initial Admin Identity' value would have loaded from the cn from John Smith's entry based on the 'User Identity Attribute' value. The 'Initial Admin Identity' value would have loaded from the cn from John Smith's entry based on the 'User Identity Attribute' value.
Here is an example composite implementation loading users from LDAP and a local file. The users from LDAP will be read only while the users loaded from the file will be configurable in UI.
----
<authorizers>
<userGroupProvider>
<identifier>file-user-group-provider</identifier>
<class>org.apache.nifi.authorization.FileUserGroupProvider</class>
<property name="Users File">./conf/users.xml</property>
<property name="Legacy Authorized Users File"></property>
<property name="Initial User Identity 1">cn=nifi-node1,ou=servers,dc=example,dc=com</property>
<property name="Initial User Identity 2">cn=nifi-node2,ou=servers,dc=example,dc=com</property>
</userGroupProvider>
<userGroupProvider>
<identifier>ldap-user-group-provider</identifier>
<class>org.apache.nifi.ldap.tenants.LdapUserGroupProvider</class>
<property name="Authentication Strategy">ANONYMOUS</property>
<property name="Manager DN"></property>
<property name="Manager Password"></property>
<property name="TLS - Keystore"></property>
<property name="TLS - Keystore Password"></property>
<property name="TLS - Keystore Type"></property>
<property name="TLS - Truststore"></property>
<property name="TLS - Truststore Password"></property>
<property name="TLS - Truststore Type"></property>
<property name="TLS - Client Auth"></property>
<property name="TLS - Protocol"></property>
<property name="TLS - Shutdown Gracefully"></property>
<property name="Referral Strategy">FOLLOW</property>
<property name="Connect Timeout">10 secs</property>
<property name="Read Timeout">10 secs</property>
<property name="Url">ldap://localhost:10389</property>
<property name="Page Size"></property>
<property name="Sync Interval">30 mins</property>
<property name="User Search Base">ou=users,o=nifi</property>
<property name="User Object Class">person</property>
<property name="User Search Scope">ONE_LEVEL</property>
<property name="User Search Filter"></property>
<property name="User Identity Attribute">cn</property>
<property name="User Group Name Attribute"></property>
<property name="Group Search Base">ou=groups,o=nifi</property>
<property name="Group Object Class">groupOfNames</property>
<property name="Group Search Scope">ONE_LEVEL</property>
<property name="Group Search Filter"></property>
<property name="Group Name Attribute">cn</property>
<property name="Group Member Attribute">member</property>
</userGroupProvider>
<userGroupProvider>
<identifier>composite-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
<property name="Configurable User Group Provider">file-user-group-provider</property>
<property name="User Group Provider 1">ldap-user-group-provider</property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">composite-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity">John Smith</property>
<property name="Legacy Authorized Users File"></property>
<property name="Node Identity 1">cn=nifi-node1,ou=servers,dc=example,dc=com</property>
<property name="Node Identity 2">cn=nifi-node2,ou=servers,dc=example,dc=com</property>
</accessPolicyProvider>
<authorizer>
<identifier>managed-authorizer</identifier>
<class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
<property name="Access Policy Provider">file-access-policy-provider</property>
</authorizer>
</authorizers>
----
In this example, the users and groups are loaded from LDAP but the servers are managed in a local file. The 'Initial Admin Identity' value came from an attribute in a LDAP entry based on the 'User Identity Attribute'. The 'Node Identity' values are established in the local file using the 'Initial User Identity' properties.
[[legacy-authorized-users]] [[legacy-authorized-users]]
Legacy Authorized Users (NiFi Instance Upgrade) Legacy Authorized Users (NiFi Instance Upgrade)

View File

@ -64,6 +64,21 @@ public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider {
*/ */
AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException; AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Determines whether the specified access policy is configurable. Provides the opportunity for a ConfigurableAccessPolicyProvider to prevent
* editing of a specific access policy. By default, all known access policies are configurable.
*
* @param accessPolicy the access policy
* @return is configurable
*/
default boolean isConfigurable(AccessPolicy accessPolicy) {
if (accessPolicy == null) {
throw new IllegalArgumentException("Access policy cannot be null");
}
return getAccessPolicy(accessPolicy.getIdentifier()) != null;
}
/** /**
* The policy represented by the provided instance will be updated based on the provided instance. * The policy represented by the provided instance will be updated based on the provided instance.
* *

View File

@ -65,6 +65,21 @@ public interface ConfigurableUserGroupProvider extends UserGroupProvider {
*/ */
User addUser(User user) throws AuthorizationAccessException; User addUser(User user) throws AuthorizationAccessException;
/**
* Determines whether the specified user is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific user. By default, all known users are configurable.
*
* @param user the user
* @return is configurable
*/
default boolean isConfigurable(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
return getUser(user.getIdentifier()) != null;
}
/** /**
* The user represented by the provided instance will be updated based on the provided instance. * The user represented by the provided instance will be updated based on the provided instance.
* *
@ -94,6 +109,21 @@ public interface ConfigurableUserGroupProvider extends UserGroupProvider {
*/ */
Group addGroup(Group group) throws AuthorizationAccessException; Group addGroup(Group group) throws AuthorizationAccessException;
/**
* Determines whether the specified group is configurable. Provides the opportunity for a ConfigurableUserGroupProvider to prevent
* editing of a specific group. By default, all known groups are configurable.
*
* @param group the group
* @return is configurable
*/
default boolean isConfigurable(Group group) {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
return getGroup(group.getIdentifier()) != null;
}
/** /**
* The group represented by the provided instance will be updated based on the provided instance. * The group represented by the provided instance will be updated based on the provided instance.
* *

View File

@ -37,6 +37,13 @@ public final class AccessPolicyProviderFactory {
} }
} }
@Override
public boolean isConfigurable(AccessPolicy accessPolicy) {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
}
@Override @Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {

View File

@ -41,5 +41,35 @@ public final class AuthorizerCapabilityDetection {
return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider; return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider;
} }
public static boolean isUserConfigurable(final Authorizer authorizer, final User user) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(user);
}
public static boolean isGroupConfigurable(final Authorizer authorizer, final Group group) {
if (!isConfigurableUserGroupProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
return configurableUserGroupProvider.isConfigurable(group);
}
public static boolean isAccessPolicyConfigurable(final Authorizer authorizer, final AccessPolicy accessPolicy) {
if (!isConfigurableAccessPolicyProvider(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
return configurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
private AuthorizerCapabilityDetection() {} private AuthorizerCapabilityDetection() {}
} }

View File

@ -116,13 +116,24 @@ public final class AuthorizerFactory {
return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy); return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
} }
@Override
public boolean isConfigurable(AccessPolicy accessPolicy) {
return baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy);
}
@Override @Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy); return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
} }
@Override @Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException { public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (!baseConfigurableAccessPolicyProvider.isConfigurable(accessPolicy)) {
throw new IllegalArgumentException("The specified access policy is not support modification.");
}
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy); return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
} }
@ -170,16 +181,27 @@ public final class AuthorizerFactory {
return baseConfigurableUserGroupProvider.addUser(user); return baseConfigurableUserGroupProvider.addUser(user);
} }
@Override
public boolean isConfigurable(User user) {
return baseConfigurableUserGroupProvider.isConfigurable(user);
}
@Override @Override
public User updateUser(User user) throws AuthorizationAccessException { public User updateUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) { if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity())); throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
} }
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.updateUser(user); return baseConfigurableUserGroupProvider.updateUser(user);
} }
@Override @Override
public User deleteUser(User user) throws AuthorizationAccessException { public User deleteUser(User user) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(user)) {
throw new IllegalArgumentException("The specified user does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteUser(user); return baseConfigurableUserGroupProvider.deleteUser(user);
} }
@ -191,16 +213,27 @@ public final class AuthorizerFactory {
return baseConfigurableUserGroupProvider.addGroup(group); return baseConfigurableUserGroupProvider.addGroup(group);
} }
@Override
public boolean isConfigurable(Group group) {
return baseConfigurableUserGroupProvider.isConfigurable(group);
}
@Override @Override
public Group updateGroup(Group group) throws AuthorizationAccessException { public Group updateGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) { if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName())); throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
} }
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.updateGroup(group); return baseConfigurableUserGroupProvider.updateGroup(group);
} }
@Override @Override
public Group deleteGroup(Group group) throws AuthorizationAccessException { public Group deleteGroup(Group group) throws AuthorizationAccessException {
if (!baseConfigurableUserGroupProvider.isConfigurable(group)) {
throw new IllegalArgumentException("The specified group does not support modification.");
}
return baseConfigurableUserGroupProvider.deleteGroup(group); return baseConfigurableUserGroupProvider.deleteGroup(group);
} }

View File

@ -37,6 +37,13 @@ public final class UserGroupProviderFactory {
} }
} }
@Override
public boolean isConfigurable(User user) {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.isConfigurable(user);
}
}
@Override @Override
public User updateUser(User user) throws AuthorizationAccessException { public User updateUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
@ -58,6 +65,13 @@ public final class UserGroupProviderFactory {
} }
} }
@Override
public boolean isConfigurable(Group group) {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.isConfigurable(group);
}
}
@Override @Override
public Group updateGroup(Group group) throws AuthorizationAccessException { public Group updateGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) { try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {

View File

@ -30,6 +30,7 @@ public class AccessPolicySummaryDTO extends ComponentDTO {
private String resource; private String resource;
private String action; private String action;
private ComponentReferenceEntity componentReference; private ComponentReferenceEntity componentReference;
private Boolean configurable;
/** /**
* @return The action associated with this access policy. * @return The action associated with this access policy.
@ -69,4 +70,16 @@ public class AccessPolicySummaryDTO extends ComponentDTO {
public void setComponentReference(ComponentReferenceEntity componentReference) { public void setComponentReference(ComponentReferenceEntity componentReference) {
this.componentReference = componentReference; this.componentReference = componentReference;
} }
/**
* @return whether this policy is configurable
*/
@ApiModelProperty(value = "Whether this policy is configurable.")
public Boolean getConfigurable() {
return configurable;
}
public void setConfigurable(Boolean configurable) {
this.configurable = configurable;
}
} }

View File

@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlType;
@XmlType(name = "tenant") @XmlType(name = "tenant")
public class TenantDTO extends ComponentDTO { public class TenantDTO extends ComponentDTO {
private String identity; private String identity;
private Boolean configurable;
/** /**
* @return tenant's identity * @return tenant's identity
@ -39,5 +40,15 @@ public class TenantDTO extends ComponentDTO {
this.identity = identity; this.identity = identity;
} }
/**
* @return whether this tenant is configurable
*/
@ApiModelProperty(value = "Whether this tenant is configurable.")
public Boolean getConfigurable() {
return configurable;
}
public void setConfigurable(Boolean configurable) {
this.configurable = configurable;
}
} }

View File

@ -0,0 +1,187 @@
/*
* 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.authorization;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.components.PropertyValue;
import java.util.HashSet;
import java.util.Set;
public class CompositeConfigurableUserGroupProvider extends CompositeUserGroupProvider implements ConfigurableUserGroupProvider {
static final String PROP_CONFIGURABLE_USER_GROUP_PROVIDER = "Configurable User Group Provider";
private UserGroupProviderLookup userGroupProviderLookup;
private ConfigurableUserGroupProvider configurableUserGroupProvider;
public CompositeConfigurableUserGroupProvider() {
super(true);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
// initialize the CompositeUserGroupProvider
super.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final PropertyValue configurableUserGroupProviderKey = configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER);
if (!configurableUserGroupProviderKey.isSet()) {
throw new AuthorizerCreationException("The Configurable User Group Provider must be set.");
}
final UserGroupProvider userGroupProvider = userGroupProviderLookup.getUserGroupProvider(configurableUserGroupProviderKey.getValue());
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate the Configurable User Group Provider: %s", configurableUserGroupProviderKey));
}
if (!(userGroupProvider instanceof ConfigurableUserGroupProvider)) {
throw new AuthorizerCreationException(String.format("The Configurable User Group Provider is not configurable: %s", configurableUserGroupProviderKey));
}
configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
// configure the CompositeUserGroupProvider
super.onConfigured(configurationContext);
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
return configurableUserGroupProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
configurableUserGroupProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
configurableUserGroupProvider.checkInheritability(proposedFingerprint);
}
@Override
public User addUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.addUser(user);
}
@Override
public boolean isConfigurable(User user) {
return configurableUserGroupProvider.isConfigurable(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteUser(user);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.addGroup(group);
}
@Override
public boolean isConfigurable(Group group) {
return configurableUserGroupProvider.isConfigurable(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
return configurableUserGroupProvider.deleteGroup(group);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
final Set<User> users = new HashSet<>(configurableUserGroupProvider.getUsers());
users.addAll(super.getUsers());
return users;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
User user = configurableUserGroupProvider.getUser(identifier);
if (user == null) {
user = super.getUser(identifier);
}
return user;
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
User user = configurableUserGroupProvider.getUserByIdentity(identity);
if (user == null) {
user = super.getUserByIdentity(identity);
}
return user;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
final Set<Group> groups = new HashSet<>(configurableUserGroupProvider.getGroups());
groups.addAll(super.getGroups());
return groups;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
Group group = configurableUserGroupProvider.getGroup(identifier);
if (group == null) {
group = super.getGroup(identifier);
}
return group;
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
UserAndGroups userAndGroups = configurableUserGroupProvider.getUserAndGroups(identity);
if (userAndGroups.getUser() == null) {
userAndGroups = super.getUserAndGroups(identity);
}
return userAndGroups;
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
super.preDestruction();
}
}

View File

@ -0,0 +1,177 @@
/*
* 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.authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CompositeUserGroupProvider implements UserGroupProvider {
static final String PROP_USER_GROUP_PROVIDER_PREFIX = "User Group Provider ";
static final Pattern USER_GROUP_PROVIDER_PATTERN = Pattern.compile(PROP_USER_GROUP_PROVIDER_PREFIX + "\\S+");
private final boolean allowEmptyProviderList;
private UserGroupProviderLookup userGroupProviderLookup;
private List<UserGroupProvider> userGroupProviders = new ArrayList<>(); // order matters
public CompositeUserGroupProvider() {
this(false);
}
public CompositeUserGroupProvider(boolean allowEmptyProviderList) {
this.allowEmptyProviderList = allowEmptyProviderList;
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = USER_GROUP_PROVIDER_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
final String userGroupProviderKey = entry.getValue();
final UserGroupProvider userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderKey);
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate the configured User Group Provider: %s", userGroupProviderKey));
}
userGroupProviders.add(userGroupProvider);
}
}
if (!allowEmptyProviderList && userGroupProviders.isEmpty()) {
throw new AuthorizerCreationException("At least one User Group Provider must be configured.");
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
final Set<User> users = new HashSet<>();
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
users.addAll(userGroupProvider.getUsers());
}
return users;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
User user = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
user = userGroupProvider.getUser(identifier);
if (user != null) {
break;
}
}
return user;
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
User user = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
user = userGroupProvider.getUserByIdentity(identity);
if (user != null) {
break;
}
}
return user;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
final Set<Group> groups = new HashSet<>();
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
groups.addAll(userGroupProvider.getGroups());
}
return groups;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
Group group = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
group = userGroupProvider.getGroup(identifier);
if (group != null) {
break;
}
}
return group;
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
UserAndGroups userAndGroups = null;
for (final UserGroupProvider userGroupProvider : userGroupProviders) {
userAndGroups = userGroupProvider.getUserAndGroups(identity);
if (userAndGroups.getUser() != null) {
break;
}
}
if (userAndGroups == null) {
// per API, returning non null with null user/groups
return new UserAndGroups() {
@Override
public User getUser() {
return null;
}
@Override
public Set<Group> getGroups() {
return null;
}
};
} else {
// a delegated provider contained a matching user
return userAndGroups;
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
}

View File

@ -21,6 +21,7 @@ import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException; import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException; import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException; import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.components.PropertyValue;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
import org.w3c.dom.Node; import org.w3c.dom.Node;
@ -58,8 +59,12 @@ public class StandardManagedAuthorizer implements ManagedAuthorizer {
@Override @Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException { public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final String accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider").getValue(); final PropertyValue accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider");
accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey); if (!accessPolicyProviderKey.isSet()) {
throw new AuthorizerCreationException("The Access Policy Provider must be set.");
}
accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey.getValue());
// ensure the desired access policy provider was found // ensure the desired access policy provider was found
if (accessPolicyProvider == null) { if (accessPolicyProvider == null) {

View File

@ -0,0 +1,16 @@
# 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.
org.apache.nifi.authorization.CompositeUserGroupProvider
org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider

View File

@ -0,0 +1,151 @@
/*
* 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.authorization;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.junit.Test;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider.PROP_CONFIGURABLE_USER_GROUP_PROVIDER;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CompositeConfigurableUserGroupProviderTest extends CompositeUserGroupProviderTest {
public static final String USER_5_IDENTIFIER = "user-identifier-5";
public static final String USER_5_IDENTITY = "user-identity-5";
public static final String CONFIGURABLE_USER_GROUP_PROVIDER = "configurable-user-group-provider";
public static final String NOT_CONFIGURABLE_USER_GROUP_PROVIDER = "not-configurable-user-group-provider";
@Test(expected = AuthorizerCreationException.class)
public void testNoConfigurableUserGroupProviderSpecified() throws Exception {
initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), null, configurationContext -> {
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new StandardPropertyValue(null, null));
});
}
@Test(expected = AuthorizerCreationException.class)
public void testUnknownConfigurableUserGroupProvider() throws Exception {
initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), null, configurationContext -> {
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new StandardPropertyValue("unknown-user-group-provider", null));
});
}
@Test(expected = AuthorizerCreationException.class)
public void testNonConfigurableUserGroupProvider() throws Exception {
initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), lookup -> {
when(lookup.getUserGroupProvider(eq(NOT_CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(mock(UserGroupProvider.class));
}, configurationContext -> {
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new StandardPropertyValue(NOT_CONFIGURABLE_USER_GROUP_PROVIDER, null));
});
}
@Test
public void testConfigurableUserGroupProviderOnly() throws Exception {
final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), lookup -> {
when(lookup.getUserGroupProvider(eq(CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(getConfigurableUserGroupProvider());
}, configurationContext -> {
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new StandardPropertyValue(CONFIGURABLE_USER_GROUP_PROVIDER, null));
});
// users and groups
assertEquals(2, userGroupProvider.getUsers().size());
assertEquals(1, userGroupProvider.getGroups().size());
// unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
assertNotNull(unknownUserAndGroups);
assertNull(unknownUserAndGroups.getUser());
assertNull(unknownUserAndGroups.getGroups());
// providers
testConfigurableUserGroupProvider(userGroupProvider);
}
@Test
public void testConfigurableUserGroupProviderWithConflictingUserGroupProvider() throws Exception {
final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeConfigurableUserGroupProvider(), lookup -> {
when(lookup.getUserGroupProvider(eq(CONFIGURABLE_USER_GROUP_PROVIDER))).thenReturn(getConfigurableUserGroupProvider());
}, configurationContext -> {
when(configurationContext.getProperty(PROP_CONFIGURABLE_USER_GROUP_PROVIDER)).thenReturn(new StandardPropertyValue(CONFIGURABLE_USER_GROUP_PROVIDER, null));
}, getConflictingUserGroupProvider());
// users and groups
assertEquals(3, userGroupProvider.getUsers().size());
assertEquals(1, userGroupProvider.getGroups().size());
// unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
assertNotNull(unknownUserAndGroups);
assertNull(unknownUserAndGroups.getUser());
assertNull(unknownUserAndGroups.getGroups());
// providers
testConfigurableUserGroupProvider(userGroupProvider);
testConflictingUserGroupProvider(userGroupProvider);
}
private UserGroupProvider getConfigurableUserGroupProvider() {
final Set<User> users = Stream.of(
new User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
new User.Builder().identifier(USER_5_IDENTIFIER).identity(USER_5_IDENTITY).build()
).collect(Collectors.toSet());
final Set<Group> groups = Stream.of(
new Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_1_IDENTIFIER).build()
).collect(Collectors.toSet());
return new SimpleConfigurableUserGroupProvider(users, groups);
}
private void testConfigurableUserGroupProvider(final UserGroupProvider userGroupProvider) {
assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY));
final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
assertNotNull(user1AndGroups);
assertNotNull(user1AndGroups.getUser());
assertEquals(1, user1AndGroups.getGroups().size());
assertNotNull(userGroupProvider.getUser(USER_5_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_5_IDENTITY));
final UserAndGroups user5AndGroups = userGroupProvider.getUserAndGroups(USER_5_IDENTITY);
assertNotNull(user5AndGroups);
assertNotNull(user5AndGroups.getUser());
assertTrue(user5AndGroups.getGroups().isEmpty());
assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
}
}

View File

@ -0,0 +1,301 @@
/*
* 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.authorization;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.components.PropertyValue;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.nifi.authorization.CompositeUserGroupProvider.PROP_USER_GROUP_PROVIDER_PREFIX;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class CompositeUserGroupProviderTest {
public static final String USER_1_IDENTIFIER = "user-identifier-1";
public static final String USER_1_IDENTITY = "user-identity-1";
public static final String USER_2_IDENTIFIER = "user-identifier-2";
public static final String USER_2_IDENTITY = "user-identity-2";
public static final String USER_3_IDENTIFIER = "user-identifier-3";
public static final String USER_3_IDENTITY = "user-identity-3";
public static final String USER_4_IDENTIFIER = "user-identifier-4";
public static final String USER_4_IDENTITY = "user-identity-4";
public static final String GROUP_1_IDENTIFIER = "group-identifier-1";
public static final String GROUP_1_NAME = "group-name-1";
public static final String GROUP_2_IDENTIFIER = "group-identifier-2";
public static final String GROUP_2_NAME = "group-name-2";
public static final String NOT_A_REAL_USER_IDENTIFIER = "not-a-real-user-identifier";
public static final String NOT_A_REAL_USER_IDENTITY = "not-a-real-user-identity";
@Test(expected = AuthorizerCreationException.class)
public void testNoConfiguredProviders() throws Exception {
initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null);
}
@Test
public void testNoConfiguredProvidersAllowed() throws Exception {
initCompositeUserGroupProvider(new CompositeUserGroupProvider(true), null, null);
}
@Test(expected = AuthorizerCreationException.class)
public void testUnknownProvider() throws Exception {
// initialization
final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
when(initializationContext.getUserGroupProviderLookup()).thenReturn(new UserGroupProviderLookup() {
@Override
public UserGroupProvider getUserGroupProvider(String identifier) {
return null;
}
});
// configuration
final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
when(configurationContext.getProperty(eq(PROP_USER_GROUP_PROVIDER_PREFIX + "1"))).thenReturn(new StandardPropertyValue(String.valueOf("1"), null));
mockProperties(configurationContext);
final CompositeUserGroupProvider compositeUserGroupProvider = new CompositeUserGroupProvider();
compositeUserGroupProvider.initialize(initializationContext);
compositeUserGroupProvider.onConfigured(configurationContext);
}
@Test
public void testOneProvider() throws Exception {
final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null, getUserGroupProviderOne());
// users and groups
assertEquals(2, userGroupProvider.getUsers().size());
assertEquals(1, userGroupProvider.getGroups().size());
// unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
assertNotNull(unknownUserAndGroups);
assertNull(unknownUserAndGroups.getUser());
assertNull(unknownUserAndGroups.getGroups());
// providers
testUserGroupProviderOne(userGroupProvider);
}
@Test
public void testMultipleProviders() throws Exception {
final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
getUserGroupProviderOne(), getUserGroupProviderTwo());
// users and groups
assertEquals(3, userGroupProvider.getUsers().size());
assertEquals(2, userGroupProvider.getGroups().size());
// unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
assertNotNull(unknownUserAndGroups);
assertNull(unknownUserAndGroups.getUser());
assertNull(unknownUserAndGroups.getGroups());
// providers
testUserGroupProviderOne(userGroupProvider);
testUserGroupProviderTwo(userGroupProvider);
}
@Test
public void testMultipleProvidersWithConflictingUsers() throws Exception {
final UserGroupProvider userGroupProvider = initCompositeUserGroupProvider(new CompositeUserGroupProvider(), null, null,
getUserGroupProviderOne(), getUserGroupProviderTwo(), getConflictingUserGroupProvider());
// users and groups
assertEquals(4, userGroupProvider.getUsers().size());
assertEquals(2, userGroupProvider.getGroups().size());
// unknown
assertNull(userGroupProvider.getUser(NOT_A_REAL_USER_IDENTIFIER));
assertNull(userGroupProvider.getUserByIdentity(NOT_A_REAL_USER_IDENTITY));
final UserAndGroups unknownUserAndGroups = userGroupProvider.getUserAndGroups(NOT_A_REAL_USER_IDENTITY);
assertNotNull(unknownUserAndGroups);
assertNull(unknownUserAndGroups.getUser());
assertNull(unknownUserAndGroups.getGroups());
// providers
testUserGroupProviderOne(userGroupProvider);
testUserGroupProviderTwo(userGroupProvider);
testConflictingUserGroupProvider(userGroupProvider);
}
protected UserGroupProvider getUserGroupProviderOne() {
final Set<User> users = Stream.of(
new User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
new User.Builder().identifier(USER_2_IDENTIFIER).identity(USER_2_IDENTITY).build()
).collect(Collectors.toSet());
final Set<Group> groups = Stream.of(
new Group.Builder().identifier(GROUP_1_IDENTIFIER).name(GROUP_1_NAME).addUser(USER_1_IDENTIFIER).build()
).collect(Collectors.toSet());
return new SimpleUserGroupProvider(users, groups);
}
protected void testUserGroupProviderOne(final UserGroupProvider userGroupProvider) {
// users
assertNotNull(userGroupProvider.getUser(USER_1_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_1_IDENTITY));
assertNotNull(userGroupProvider.getUser(USER_2_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_2_IDENTITY));
final UserAndGroups user1AndGroups = userGroupProvider.getUserAndGroups(USER_1_IDENTITY);
assertNotNull(user1AndGroups);
assertNotNull(user1AndGroups.getUser());
assertEquals(1, user1AndGroups.getGroups().size());
final UserAndGroups user2AndGroups = userGroupProvider.getUserAndGroups(USER_2_IDENTITY);
assertNotNull(user2AndGroups);
assertNotNull(user2AndGroups.getUser());
assertTrue(user2AndGroups.getGroups().isEmpty());
// groups
assertNotNull(userGroupProvider.getGroup(GROUP_1_IDENTIFIER));
assertEquals(1, userGroupProvider.getGroup(GROUP_1_IDENTIFIER).getUsers().size());
}
protected UserGroupProvider getUserGroupProviderTwo() {
final Set<User> users = Stream.of(
new User.Builder().identifier(USER_3_IDENTIFIER).identity(USER_3_IDENTITY).build()
).collect(Collectors.toSet());
final Set<Group> groups = Stream.of(
new Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_3_IDENTIFIER).build()
).collect(Collectors.toSet());
return new SimpleUserGroupProvider(users, groups);
}
protected void testUserGroupProviderTwo(final UserGroupProvider userGroupProvider) {
// users
assertNotNull(userGroupProvider.getUser(USER_3_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_3_IDENTITY));
final UserAndGroups user3AndGroups = userGroupProvider.getUserAndGroups(USER_3_IDENTITY);
assertNotNull(user3AndGroups);
assertNotNull(user3AndGroups.getUser());
assertEquals(1, user3AndGroups.getGroups().size());
// groups
assertNotNull(userGroupProvider.getGroup(GROUP_2_IDENTIFIER));
assertEquals(1, userGroupProvider.getGroup(GROUP_2_IDENTIFIER).getUsers().size());
}
protected UserGroupProvider getConflictingUserGroupProvider() {
final Set<User> users = Stream.of(
new User.Builder().identifier(USER_1_IDENTIFIER).identity(USER_1_IDENTITY).build(),
new User.Builder().identifier(USER_4_IDENTIFIER).identity(USER_4_IDENTITY).build()
).collect(Collectors.toSet());
final Set<Group> groups = Stream.of(
new Group.Builder().identifier(GROUP_2_IDENTIFIER).name(GROUP_2_NAME).addUser(USER_1_IDENTIFIER).addUser(USER_4_IDENTIFIER).build()
).collect(Collectors.toSet());
return new SimpleUserGroupProvider(users, groups);
}
protected void testConflictingUserGroupProvider(final UserGroupProvider userGroupProvider) {
assertNotNull(userGroupProvider.getUser(USER_4_IDENTIFIER));
assertNotNull(userGroupProvider.getUserByIdentity(USER_4_IDENTITY));
}
private void mockProperties(final AuthorizerConfigurationContext configurationContext) {
when(configurationContext.getProperties()).then((invocation) -> {
final Map<String, String> properties = new HashMap<>();
int i = 1;
while (true) {
final String key = PROP_USER_GROUP_PROVIDER_PREFIX + i++;
final PropertyValue value = configurationContext.getProperty(key);
if (value == null) {
break;
} else {
properties.put(key, value.getValue());
}
}
return properties;
});
}
protected UserGroupProvider initCompositeUserGroupProvider(
final CompositeUserGroupProvider compositeUserGroupProvider, final Consumer<UserGroupProviderLookup> lookupConsumer,
final Consumer<AuthorizerConfigurationContext> configurationContextConsumer, final UserGroupProvider... providers) {
// initialization
final UserGroupProviderLookup lookup = mock(UserGroupProviderLookup.class);
for (int i = 1; i <= providers.length; i++) {
when(lookup.getUserGroupProvider(eq(String.valueOf(i)))).thenReturn(providers[i - 1]);
}
// allow callers to mock additional providers
if (lookupConsumer != null) {
lookupConsumer.accept(lookup);
}
final UserGroupProviderInitializationContext initializationContext = mock(UserGroupProviderInitializationContext.class);
when(initializationContext.getUserGroupProviderLookup()).thenReturn(lookup);
compositeUserGroupProvider.initialize(initializationContext);
// configuration
final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
for (int i = 1; i <= providers.length; i++) {
when(configurationContext.getProperty(eq(PROP_USER_GROUP_PROVIDER_PREFIX + i))).thenReturn(new StandardPropertyValue(String.valueOf(i), null));
}
// allow callers to mock additional properties
if (configurationContextConsumer != null) {
configurationContextConsumer.accept(configurationContext);
}
mockProperties(configurationContext);
compositeUserGroupProvider.onConfigured(configurationContext);
return compositeUserGroupProvider;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.authorization;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
import java.util.Set;
public class SimpleConfigurableUserGroupProvider extends SimpleUserGroupProvider implements ConfigurableUserGroupProvider {
private final Set<User> users;
private final Set<Group> groups;
public SimpleConfigurableUserGroupProvider(Set<User> users, Set<Group> groups) {
super(users, groups);
this.users = users;
this.groups = groups;
}
@Override
public User addUser(User user) throws AuthorizationAccessException {
users.add(user);
return user;
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
users.remove(user);
users.add(user);
return user;
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
users.remove(user);
return user;
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
groups.add(group);
return group;
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
groups.remove(group);
groups.add(group);
return group;
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
groups.remove(group);
return group;
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
return "fingerprint";
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.authorization;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
import java.util.Set;
import java.util.stream.Collectors;
public class SimpleUserGroupProvider implements UserGroupProvider {
private final Set<User> users;
private final Set<Group> groups;
public SimpleUserGroupProvider(Set<User> users, Set<Group> groups) {
this.users = users;
this.groups = groups;
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return users;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return users.stream().filter(user -> user.getIdentifier().equals(identifier)).findFirst().orElse(null);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return users.stream().filter(user -> user.getIdentity().equals(identity)).findFirst().orElse(null);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return groups;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return groups.stream().filter(groups -> groups.getIdentifier().equals(identifier)).findFirst().orElse(null);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
final User user = users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().orElse(null);
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
if (user == null) {
return null;
} else {
return groups.stream().filter(group -> group.getUsers().contains(user.getIdentifier())).collect(Collectors.toSet());
}
}
};
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
}

View File

@ -152,6 +152,47 @@
</userGroupProvider> </userGroupProvider>
To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. --> To enable the ldap-user-group-provider remove 2 lines. This is 2 of 2. -->
<!--
The CompositeUserGroupProvider will provide support for retrieving users and groups from multiple sources.
- User Group Provider [unique key] - The identifier of user group providers to load from. The name of
each property must be unique, for example: "User Group Provider A", "User Group Provider B",
"User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior
would need to be applied by the base implementation.
-->
<!-- To enable the composite-user-group-provider remove 2 lines. This is 1 of 2.
<userGroupProvider>
<identifier>composite-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeUserGroupProvider</class>
<property name="User Group Provider 1"></property>
</userGroupProvider>
To enable the composite-user-group-provider remove 2 lines. This is 2 of 2. -->
<!--
The CompositeConfigurableUserGroupProvider will provide support for retrieving users and groups from multiple sources.
Additionally, a single configurable user group provider is required. Users from the configurable user group provider
are configurable, however users loaded from one of the User Group Provider [unique key] will not be.
- Configurable User Group Provider - A configurable user group provider.
- User Group Provider [unique key] - The identifier of user group providers to load from. The name of
each property must be unique, for example: "User Group Provider A", "User Group Provider B",
"User Group Provider C" or "User Group Provider 1", "User Group Provider 2", "User Group Provider 3"
NOTE: Any identity mapping rules specified in nifi.properties are not applied in this implementation. This behavior
would need to be applied by the base implementation.
-->
<!-- To enable the composite-configurable-user-group-provider remove 2 lines. This is 1 of 2.
<userGroupProvider>
<identifier>composite-configurable-user-group-provider</identifier>
<class>org.apache.nifi.authorization.CompositeConfigurableUserGroupProvider</class>
<property name="Configurable User Group Provider">file-user-group-provider</property>
<property name="User Group Provider 1"></property>
</userGroupProvider>
To enable the composite-configurable-user-group-provider remove 2 lines. This is 2 of 2. -->
<!-- <!--
The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file
on the local file system. on the local file system.

View File

@ -929,6 +929,7 @@ public class TenantsResource extends ApplicationResource {
final TenantDTO tenant = new TenantDTO(); final TenantDTO tenant = new TenantDTO();
tenant.setId(user.getId()); tenant.setId(user.getId());
tenant.setIdentity(user.getIdentity()); tenant.setIdentity(user.getIdentity());
tenant.setConfigurable(user.getConfigurable());
final TenantEntity entity = new TenantEntity(); final TenantEntity entity = new TenantEntity();
entity.setPermissions(userEntity.getPermissions()); entity.setPermissions(userEntity.getPermissions());
@ -947,6 +948,7 @@ public class TenantsResource extends ApplicationResource {
final TenantDTO tenant = new TenantDTO(); final TenantDTO tenant = new TenantDTO();
tenant.setId(userGroup.getId()); tenant.setId(userGroup.getId());
tenant.setIdentity(userGroup.getIdentity()); tenant.setIdentity(userGroup.getIdentity());
tenant.setConfigurable(userGroup.getConfigurable());
final TenantEntity entity = new TenantEntity(); final TenantEntity entity = new TenantEntity();
entity.setPermissions(userGroupEntity.getPermissions()); entity.setPermissions(userGroupEntity.getPermissions());

View File

@ -721,6 +721,7 @@ public final class DtoFactory {
dto.setId(user.getIdentifier()); dto.setId(user.getIdentifier());
dto.setUserGroups(groups); dto.setUserGroups(groups);
dto.setIdentity(user.getIdentity()); dto.setIdentity(user.getIdentity());
dto.setConfigurable(AuthorizerCapabilityDetection.isUserConfigurable(authorizer, user));
dto.setAccessPolicies(accessPolicies); dto.setAccessPolicies(accessPolicies);
return dto; return dto;
@ -740,6 +741,7 @@ public final class DtoFactory {
final TenantDTO dto = new TenantDTO(); final TenantDTO dto = new TenantDTO();
dto.setId(user.getIdentifier()); dto.setId(user.getIdentifier());
dto.setIdentity(user.getIdentity()); dto.setIdentity(user.getIdentity());
dto.setConfigurable(AuthorizerCapabilityDetection.isUserConfigurable(authorizer, user));
return dto; return dto;
} }
@ -765,6 +767,7 @@ public final class DtoFactory {
final AccessPolicySummaryDTO summary = summaryEntity.getComponent(); final AccessPolicySummaryDTO summary = summaryEntity.getComponent();
policy.setResource(summary.getResource()); policy.setResource(summary.getResource());
policy.setAction(summary.getAction()); policy.setAction(summary.getAction());
policy.setConfigurable(summary.getConfigurable());
policy.setComponentReference(summary.getComponentReference()); policy.setComponentReference(summary.getComponentReference());
} }
@ -775,6 +778,7 @@ public final class DtoFactory {
dto.setId(userGroup.getIdentifier()); dto.setId(userGroup.getIdentifier());
dto.setUsers(users); dto.setUsers(users);
dto.setIdentity(userGroup.getName()); dto.setIdentity(userGroup.getName());
dto.setConfigurable(AuthorizerCapabilityDetection.isGroupConfigurable(authorizer, userGroup));
dto.setAccessPolicies(policies); dto.setAccessPolicies(policies);
return dto; return dto;
@ -794,6 +798,7 @@ public final class DtoFactory {
final TenantDTO dto = new TenantDTO(); final TenantDTO dto = new TenantDTO();
dto.setId(userGroup.getIdentifier()); dto.setId(userGroup.getIdentifier());
dto.setIdentity(userGroup.getName()); dto.setIdentity(userGroup.getName());
dto.setConfigurable(AuthorizerCapabilityDetection.isGroupConfigurable(authorizer, userGroup));
return dto; return dto;
} }
@ -1677,6 +1682,7 @@ public final class DtoFactory {
dto.setId(accessPolicy.getIdentifier()); dto.setId(accessPolicy.getIdentifier());
dto.setResource(accessPolicy.getResource()); dto.setResource(accessPolicy.getResource());
dto.setAction(accessPolicy.getAction().toString()); dto.setAction(accessPolicy.getAction().toString());
dto.setConfigurable(AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy));
dto.setComponentReference(componentReference); dto.setComponentReference(componentReference);
return dto; return dto;
} }
@ -1694,6 +1700,7 @@ public final class DtoFactory {
dto.setId(accessPolicy.getIdentifier()); dto.setId(accessPolicy.getIdentifier());
dto.setResource(accessPolicy.getResource()); dto.setResource(accessPolicy.getResource());
dto.setAction(accessPolicy.getAction().toString()); dto.setAction(accessPolicy.getAction().toString());
dto.setConfigurable(AuthorizerCapabilityDetection.isAccessPolicyConfigurable(authorizer, accessPolicy));
dto.setComponentReference(componentReference); dto.setComponentReference(componentReference);
return dto; return dto;
} }

View File

@ -328,9 +328,12 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
// remove any references to the user group being deleted from policies if possible // remove any references to the user group being deleted from policies if possible
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) { for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
if (policy.getGroups().contains(removedGroup.getIdentifier())) { final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
// ensure this policy contains a reference to the user group and this policy is configurable (check proactively to prevent an exception)
if (policy.getGroups().contains(removedGroup.getIdentifier()) && configurableAccessPolicyProvider.isConfigurable(policy)) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeGroup(removedGroup.getIdentifier()); final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeGroup(removedGroup.getIdentifier());
((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build()); configurableAccessPolicyProvider.updateAccessPolicy(builder.build());
} }
} }
} }
@ -405,9 +408,12 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
// remove any references to the user being deleted from policies if possible // remove any references to the user being deleted from policies if possible
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) { if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) { for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
if (policy.getUsers().contains(removedUser.getIdentifier())) { final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
// ensure this policy contains a reference to the user and this policy is configurable (check proactively to prevent an exception)
if (policy.getUsers().contains(removedUser.getIdentifier()) && configurableAccessPolicyProvider.isConfigurable(policy)) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeUser(removedUser.getIdentifier()); final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeUser(removedUser.getIdentifier());
((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build()); configurableAccessPolicyProvider.updateAccessPolicy(builder.build());
} }
} }
} }

View File

@ -879,10 +879,10 @@
if (resourceAndAction.resource === policy.resource) { if (resourceAndAction.resource === policy.resource) {
if (nfCanvasUtils.isConfigurableAuthorizer()) { if (nfCanvasUtils.isConfigurableAuthorizer()) {
// allow remove when policy is not inherited // allow remove when policy is not inherited
$('#delete-policy-button').prop('disabled', policyEntity.permissions.canWrite === false); $('#delete-policy-button').prop('disabled', policy.configurable === false || policyEntity.permissions.canWrite === false);
// allow modification if allowed // allow modification if allowed
$('#new-policy-user-button').prop('disabled', policyEntity.permissions.canWrite === false); $('#new-policy-user-button').prop('disabled', policy.configurable === false || policyEntity.permissions.canWrite === false);
} }
} else { } else {
$('#policy-message').append(getResourceMessage(policy.resource)); $('#policy-message').append(getResourceMessage(policy.resource));

View File

@ -797,7 +797,7 @@
var markup = ''; var markup = '';
// ensure user can modify the user // ensure user can modify the user
if (configurableUsersAndGroups && nfCommon.canModifyTenants()) { if (configurableUsersAndGroups && dataContext.component.configurable === true && nfCommon.canModifyTenants()) {
markup += '<div title="Edit" class="pointer edit-user fa fa-pencil" style="margin-right: 3px;"></div>'; markup += '<div title="Edit" class="pointer edit-user fa fa-pencil" style="margin-right: 3px;"></div>';
markup += '<div title="Remove" class="pointer delete-user fa fa-trash"></div>'; markup += '<div title="Remove" class="pointer delete-user fa fa-trash"></div>';
} }