NIFI-3653: - Introducing UserGroup and Policy provider interfaces.

- Introducing FileUserGroupProvider and FileAccessPolicyProvider.
- Refactoring FileAuthorizer to utilize the file based implementations.
- Introducing the StandardManagedAuthorizer.
- Decorating the configured ManagedAuthorizer to ensure integrity checks are still performed.
- Loading user groups if possible to use during access decisions.
- Merging responses for requests for AccessPolicies, Users, and UserGroups.
- Adding unit tests as appropriate.
- Adding methods to the User, Group, and AccessPolicy builder that more easily supports generating UUIDs.
- Fixing typo when seeding policies during startup.
- Fixing type in documentation and error messages.

This closes #1897.

Signed-off-by: Bryan Bende <bbende@apache.org>
This commit is contained in:
Matt Gilman 2017-05-26 15:02:44 -04:00 committed by Bryan Bende
parent f447fc73fa
commit 4ed7511bee
No known key found for this signature in database
GPG Key ID: A0DDA9ED50711C39
129 changed files with 9504 additions and 2379 deletions

View File

@ -385,7 +385,32 @@ Authorizers are configured using two properties in the 'nifi.properties' file:
Authorizers.xml Setup
~~~~~~~~~~~~~~~~~~~~~
The 'authorizers.xml' file is used to define and configure available authorizers. The default authorizer is the FileAuthorizer, however, you can develop additional authorizers as extensions. The FileAuthorizer has the following properties:
The 'authorizers.xml' file is used to define and configure available authorizers. The default authorizer is the StandardManagedAuthorizer. The managed authorizer is comprised of a UserGroupProvider
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.
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.
* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically be used to load the users and groups into the Users File.
* Initial User Identity - The identity of a users and systems to seed the Users File. The name of each property must be unique, for example: "Initial User Identity A", "Initial User Identity B", "Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
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.
* Authorizations File - The file where the FileAccessPolicyProvider will store policies.
* Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and given the ability to create additional users, groups, and policies. The value of this property could be a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified.
* Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically converted to the new authorizations model. If this property is specified then an Initial Admin Identity can not be specified, and this property will only be used when there are no other users, groups, and policies defined.
* Node Identity - The identity of a NiFi cluster node. When clustered, a property for each node should be defined, so that every node knows about every other node. If not clustered these properties can be ignored. The name of each property must be unique, for example for a three node cluster: "Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3"
The identities configured in the Initial Admin Identity, the Node Identity properties, or discovered in a Legacy Authorized Users File must be available in the configured User Group Provider.
The default authorizer is the StandardManagedAuthorizer, however, you can develop additional authorizers as extensions. The StandardManagedAuthorizer has the following properties:
* Access Policy Provider - The identifier for an Access Policy Provider defined above.
The FileAuthorizer has been replaced with the more granular StandardManagedAuthorizer approach described above. However, it is still available for backwards compatibility reasons. The
FileAuthorizer has the following properties.
* Authorizations File - The file where the FileAuthorizer stores policies. By default, the 'authorizations.xml' in the 'conf' directory is chosen.
* Users File - The file where the FileAuthorizer stores users and groups. By default, the 'users.xml' in the 'conf' directory is chosen.
@ -402,17 +427,29 @@ If you are setting up a secured NiFi instance for the first time, you must manua
Here is an example LDAP entry using the name John Smith:
----
<authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
<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=John Smith,ou=people,dc=example,dc=com</property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity">cn=John Smith,ou=people,dc=example,dc=com</property>
<property name="Legacy Authorized Users File"></property>
<!--
<property name="Node Identity 1"></property>
<property name="Node Identity 2"></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>
----
@ -420,17 +457,29 @@ Here is an example LDAP entry using the name John Smith:
Here is an example Kerberos entry using the name John Smith and realm `NIFI.APACHE.ORG`:
----
<authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
<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">johnsmith@NIFI.APACHE.ORG</property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity">johnsmith@NIFI.APACHE.ORG</property>
<property name="Legacy Authorized Users File"></property>
<!--
<property name="Node Identity 1"></property>
<property name="Node Identity 2"></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>
----
@ -449,13 +498,28 @@ Here is an example entry:
----
<authorizers>
<authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
<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">/Users/johnsmith/config_files/authorized-users.xml</property>
<property name="Initial User Identity 1"></property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity"></property>
<property name="Legacy Authorized Users File">/Users/johnsmith/config_files/authorized-users.xml</property>
<property name="Node Identity 1"></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>
----
@ -514,15 +578,32 @@ cn=nifi-2,ou=people,dc=example,dc=com
----
----
<authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
<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">johnsmith@NIFI.APACHE.ORG</property>
<property name="Initial User Identity 2">cn=nifi-1,ou=people,dc=example,dc=com</property>
<property name="Initial User Identity 3">cn=nifi-2,ou=people,dc=example,dc=com</property>
</userGroupProvider>
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity">johnsmith@NIFI.APACHE.ORG</property>
<property name="Legacy Authorized Users File"></property>
<property name="Node Identity 1">cn=nifi-1,ou=people,dc=example,dc=com</property>
<property name="Node Identity 2">cn=nifi-2,ou=people,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>
----
@ -535,7 +616,11 @@ Now that initial authorizations have been created, additional users, groups and
Configuring Users & Access Policies
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section describes:
Depending on the capabilities of the configured UserGroupProvider and AccessPolicyProvider the users, groups, and policies will be configurable in the UI. If the extensions are not configurable the
users, groups, and policies will read-only in the UI. If the configured authorizer does not use UserGroupProvider and AccessPolicyProvider the users and policies may or may not be visible and
configurable in the UI based on the underlying implementation.
This section assumes the users, groups, and policies are configurable in the UI and describes:
* How to create users and groups
* How access policies are used to define authorizations

View File

@ -18,6 +18,8 @@ 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.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
@ -43,7 +45,7 @@ import java.util.Set;
/**
* An Authorizer that provides management of users, groups, and policies.
*/
public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
public abstract class AbstractPolicyBasedAuthorizer implements ManagedAuthorizer {
static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
@ -60,32 +62,9 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
static final String RESOURCE_ATTR = "resource";
static final String ACTIONS_ATTR = "actions";
public static final String EMPTY_FINGERPRINT = "EMPTY";
@Override
public final void onConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
doOnConfigured(configurationContext);
// ensure that only one policy per resource-action exists
for (AccessPolicy accessPolicy : getAccessPolicies()) {
if (policyExists(accessPolicy)) {
throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
}
// ensure that only one group exists per identity
for (User user : getUsers()) {
if (tenantExists(user.getIdentifier(), user.getIdentity())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
}
}
// ensure that only one group exists per identity
for (Group group : getGroups()) {
if (tenantExists(group.getIdentifier(), group.getName())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
}
}
}
/**
@ -96,48 +75,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
*/
protected abstract void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Checks if another policy exists with the same resource and action as the given policy.
*
* @param checkAccessPolicy an access policy being checked
* @return true if another access policy exists with the same resource and action, false otherwise
*/
private boolean policyExists(final AccessPolicy checkAccessPolicy) {
for (AccessPolicy accessPolicy : getAccessPolicies()) {
if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
&& accessPolicy.getResource().equals(checkAccessPolicy.getResource())
&& accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
return true;
}
}
return false;
}
/**
* Checks if another user exists with the same identity.
*
* @param identifier identity of the user
* @param identity identity of the user
* @return true if another user exists with the same identity, false otherwise
*/
private boolean tenantExists(final String identifier, final String identity) {
for (User user : getUsers()) {
if (!user.getIdentifier().equals(identifier)
&& user.getIdentity().equals(identity)) {
return true;
}
}
for (Group group : getGroups()) {
if (!group.getIdentifier().equals(identifier)
&& group.getName().equals(identity)) {
return true;
}
}
return false;
}
@Override
public final AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = getUsersAndAccessPolicies();
@ -191,9 +128,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
* @throws IllegalStateException if a group with the same name already exists
*/
public final synchronized Group addGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
return doAddGroup(group);
}
@ -224,9 +158,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
* @throws IllegalStateException if there is already a group with the same name
*/
public final synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
return doUpdateGroup(group);
}
@ -266,9 +197,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
* @throws IllegalStateException if there is already a user with the same identity
*/
public final synchronized User addUser(User user) throws AuthorizationAccessException {
if (tenantExists(user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
return doAddUser(user);
}
@ -308,9 +236,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
* @throws IllegalStateException if there is already a user with the same identity
*/
public final synchronized User updateUser(final User user) throws AuthorizationAccessException {
if (tenantExists(user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
return doUpdateUser(user);
}
@ -348,9 +273,6 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
public final synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (policyExists(accessPolicy)) {
throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
return doAddAccessPolicy(accessPolicy);
}
@ -406,18 +328,55 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
*/
public abstract UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException;
/**
* Returns whether the proposed fingerprint is inheritable.
*
* @param proposedFingerprint the proposed fingerprint
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
@Override
public final void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try {
// ensure we understand the proposed fingerprint
parsePoliciesUsersAndGroups(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse proposed fingerprint: " + e);
}
final List<User> users = getSortedUsers();
final List<Group> groups = getSortedGroups();
final List<AccessPolicy> accessPolicies = getSortedAccessPolicies();
// ensure we're in a state to inherit
if (!users.isEmpty() || !groups.isEmpty() || !accessPolicies.isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current Authorizations is not empty..");
}
}
/**
* Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
*/
@Override
public final void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException {
if (fingerprint == null || fingerprint.trim().isEmpty()) {
return;
}
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
final PoliciesUsersAndGroups policiesUsersAndGroups = parsePoliciesUsersAndGroups(fingerprint);
policiesUsersAndGroups.getUsers().forEach(user -> addUser(user));
policiesUsersAndGroups.getGroups().forEach(group -> addGroup(group));
policiesUsersAndGroups.getAccessPolicies().forEach(policy -> addAccessPolicy(policy));
}
private PoliciesUsersAndGroups parsePoliciesUsersAndGroups(final String fingerprint) {
final List<AccessPolicy> accessPolicies = new ArrayList<>();
final List<User> users = new ArrayList<>();
final List<Group> groups = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
@ -427,29 +386,27 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
for (int i=0; i < userNodes.getLength(); i++) {
Node userNode = userNodes.item(i);
User user = parseUser((Element) userNode);
addUser(user);
users.add(parseUser((Element) userNode));
}
// parse all the groups and add them to the current authorizer
NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
for (int i=0; i < groupNodes.getLength(); i++) {
Node groupNode = groupNodes.item(i);
Group group = parseGroup((Element) groupNode);
addGroup(group);
groups.add(parseGroup((Element) groupNode));
}
// parse all the policies and add them to the current authorizer
NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
for (int i=0; i < policyNodes.getLength(); i++) {
Node policyNode = policyNodes.item(i);
AccessPolicy policy = parsePolicy((Element) policyNode);
addAccessPolicy(policy);
accessPolicies.add(parsePolicy((Element) policyNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return new PoliciesUsersAndGroups(accessPolicies, users, groups);
}
private User parseUser(final Element element) {
@ -503,6 +460,181 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
return builder.build();
}
@Override
public final AccessPolicyProvider getAccessPolicyProvider() {
return new ConfigurableAccessPolicyProvider() {
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getAccessPolicies();
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getAccessPolicy(identifier);
}
@Override
public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
return usersAndAccessPolicies.getAccessPolicy(resourceIdentifier, action);
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public UserGroupProvider getUserGroupProvider() {
return new ConfigurableUserGroupProvider() {
@Override
public User addUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addUser(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteUser(user);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.addGroup(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.deleteGroup(group);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUsers();
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUser(identifier);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getUserByIdentity(identity);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getGroups();
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return AbstractPolicyBasedAuthorizer.this.getGroup(identifier);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
final UsersAndAccessPolicies usersAndAccessPolicies = AbstractPolicyBasedAuthorizer.this.getUsersAndAccessPolicies();
final User user = usersAndAccessPolicies.getUser(identity);
final Set<Group> groups = usersAndAccessPolicies.getGroups(identity);
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return groups;
}
};
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
// fingerprint is managed by the encapsulating class
throw new UnsupportedOperationException();
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of users,
@ -510,17 +642,12 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
*
* @return the fingerprint for this Authorizer
*/
@Override
public final String getFingerprint() throws AuthorizationAccessException {
final List<User> users = getSortedUsers();
final List<Group> groups = getSortedGroups();
final List<AccessPolicy> policies = getSortedAccessPolicies();
// when there are no users, groups, policies we want to always return a simple indicator so
// it can easily be determined when comparing fingerprints
if (users.isEmpty() && groups.isEmpty() && policies.isEmpty()) {
return EMPTY_FINGERPRINT;
}
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
@ -611,38 +738,43 @@ public abstract class AbstractPolicyBasedAuthorizer implements Authorizer {
private List<AccessPolicy> getSortedAccessPolicies() {
final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
Collections.sort(policies, new Comparator<AccessPolicy>() {
@Override
public int compare(AccessPolicy p1, AccessPolicy p2) {
return p1.getIdentifier().compareTo(p2.getIdentifier());
}
});
Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
return policies;
}
private List<Group> getSortedGroups() {
final List<Group> groups = new ArrayList<>(getGroups());
Collections.sort(groups, new Comparator<Group>() {
@Override
public int compare(Group g1, Group g2) {
return g1.getIdentifier().compareTo(g2.getIdentifier());
}
});
Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
return groups;
}
private List<User> getSortedUsers() {
final List<User> users = new ArrayList<>(getUsers());
Collections.sort(users, new Comparator<User>() {
@Override
public int compare(User u1, User u2) {
return u1.getIdentifier().compareTo(u2.getIdentifier());
}
});
Collections.sort(users, Comparator.comparing(User::getIdentifier));
return users;
}
private static class PoliciesUsersAndGroups {
final List<AccessPolicy> accessPolicies;
final List<User> users;
final List<Group> groups;
public PoliciesUsersAndGroups(List<AccessPolicy> accessPolicies, List<User> users, List<Group> groups) {
this.accessPolicies = accessPolicies;
this.users = users;
this.groups = groups;
}
public List<AccessPolicy> getAccessPolicies() {
return accessPolicies;
}
public List<User> getUsers() {
return users;
}
public List<Group> getGroups() {
return groups;
}
}
}

View File

@ -16,10 +16,12 @@
*/
package org.apache.nifi.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* Defines a policy for a set of userIdentifiers to perform a set of actions on a given resource.
@ -173,6 +175,41 @@ public class AccessPolicy {
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
*/
public Builder identifierGenerateRandom() {
if (fromPolicy) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing policy");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Policy
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromPolicy) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing policy");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the policy identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the resource of the builder.
*

View File

@ -0,0 +1,90 @@
/*
* 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;
/**
* Provides access to AccessPolicies and the configured UserGroupProvider.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface AccessPolicyProvider {
/**
* Retrieves all access policies. Must be non null
*
* @return a list of policies
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException;
/**
* Retrieves the policy with the given identifier.
*
* @param identifier the id of the policy to retrieve
* @return the policy with the given id, or null if no matching policy exists
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException;
/**
* Gets the access policies for the specified resource identifier and request action.
*
* @param resourceIdentifier the resource identifier
* @param action the request action
* @return the policy matching the resouce and action, or null if no matching policy exists
* @throws AuthorizationAccessException if there was any unexpected error performing the operation
*/
AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException;
/**
* Returns the UserGroupProvider for this managed Authorizer. Must be non null
*
* @return the UserGroupProvider
*/
UserGroupProvider getUserGroupProvider();
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
*/
void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException;
/**
* Called to configure the Authorizer.
*
* @param configurationContext at the time of configuration
* @throws AuthorizerCreationException for any issues configuring the provider
*/
void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws AuthorizerDestructionException If pre-destruction fails.
*/
void preDestruction() throws AuthorizerDestructionException;
}

View File

@ -0,0 +1,30 @@
/*
* 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;
/**
* Initialization content for AccessPolicyProviders.
*/
public interface AccessPolicyProviderInitializationContext extends UserGroupProviderInitializationContext {
/**
* The lookup for accessing other configured AccessPolicyProviders.
*
* @return The AccessPolicyProvider lookup
*/
AccessPolicyProviderLookup getAccessPolicyProviderLookup();
}

View File

@ -0,0 +1,31 @@
/*
* 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;
/**
*
*/
public interface AccessPolicyProviderLookup {
/**
* Looks up the AccessPolicyProvider with the specified identifier
*
* @param identifier The identifier of the AccessPolicyProvider
* @return The AccessPolicyProvider
*/
AccessPolicyProvider getAccessPolicyProvider(String identifier);
}

View File

@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier;
/**
@ -31,6 +32,7 @@ public class AuthorizationRequest {
private final Resource resource;
private final String identity;
private final Set<String> groups;
private final RequestAction action;
private final boolean isAccessAttempt;
private final boolean isAnonymous;
@ -46,6 +48,7 @@ public class AuthorizationRequest {
this.resource = builder.resource;
this.identity = builder.identity;
this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
this.action = builder.action;
this.isAccessAttempt = builder.isAccessAttempt;
this.isAnonymous = builder.isAnonymous;
@ -81,6 +84,16 @@ public class AuthorizationRequest {
return identity;
}
/**
* The groups the user making this request belongs to. May be null if this NiFi is not configured to load user
* groups or empty if the user has no groups
*
* @return The groups
*/
public Set<String> getGroups() {
return groups;
}
/**
* Whether this is a direct access attempt of the Resource if if it's being checked as part of another response.
*
@ -142,6 +155,7 @@ public class AuthorizationRequest {
private Resource resource;
private String identity;
private Set<String> groups;
private Boolean isAnonymous;
private Boolean isAccessAttempt;
private RequestAction action;
@ -159,6 +173,11 @@ public class AuthorizationRequest {
return this;
}
public Builder groups(final Set<String> groups) {
this.groups = groups;
return this;
}
public Builder anonymous(final Boolean isAnonymous) {
this.isAnonymous = isAnonymous;
return this;

View File

@ -27,6 +27,10 @@ public interface Authorizer {
/**
* Determines if the specified user/entity is authorized to access the specified resource within the given context.
* These details are all contained in the AuthorizationRequest.
*
* NOTE: This method will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* @param request The authorization request
* @return the authorization result

View File

@ -42,9 +42,7 @@ public interface AuthorizerConfigurationContext {
/**
* @param property to lookup the descriptor and value of
* @return the value the component currently understands for the given
* PropertyDescriptor. This method does not substitute default
* PropertyDescriptor values, so the value returned will be null if not set
* @return the value the component currently understands for the given PropertyDescriptor
*/
PropertyValue getProperty(String property);
}

View File

@ -19,19 +19,12 @@ package org.apache.nifi.authorization;
/**
* Initialization content for Authorizers.
*/
public interface AuthorizerInitializationContext {
/**
* The identifier of the Authorizer.
*
* @return The identifier
*/
public String getIdentifier();
public interface AuthorizerInitializationContext extends AccessPolicyProviderInitializationContext {
/**
* The lookup for accessing other configured Authorizers.
*
* @return The Authorizer lookup
*/
public AuthorizerLookup getAuthorizerLookup();
AuthorizerLookup getAuthorizerLookup();
}

View File

@ -0,0 +1,84 @@
/*
* 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;
/**
* Provides support for configuring AccessPolicies.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface ConfigurableAccessPolicyProvider extends AccessPolicyProvider {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of policies.
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any policies to the current AccessPolicyProvider.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Adds the given policy ensuring that multiple policies can not be added for the same resource and action.
*
* @param accessPolicy the policy to add
* @return the policy that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* The policy represented by the provided instance will be updated based on the provided instance.
*
* @param accessPolicy an updated policy
* @return the updated policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
/**
* Deletes the given policy.
*
* @param accessPolicy the policy to delete
* @return the deleted policy, or null if no matching policy was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException;
}

View File

@ -0,0 +1,115 @@
/*
* 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;
/**
* Provides support for configuring Users and Groups.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface ConfigurableUserGroupProvider extends UserGroupProvider {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two policy-based authorizers represent a compatible set of users and/or groups.
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any users and groups to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Adds the given user.
*
* @param user the user to add
* @return the user that was added
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
User addUser(User user) throws AuthorizationAccessException;
/**
* The user represented by the provided instance will be updated based on the provided instance.
*
* @param user an updated user instance
* @return the updated user instance, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a user with the same identity
*/
User updateUser(final User user) throws AuthorizationAccessException;
/**
* Deletes the given user.
*
* @param user the user to delete
* @return the user that was deleted, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User deleteUser(User user) throws AuthorizationAccessException;
/**
* Adds a new group.
*
* @param group the Group to add
* @return the added Group
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if a group with the same name already exists
*/
Group addGroup(Group group) throws AuthorizationAccessException;
/**
* The group represented by the provided instance will be updated based on the provided instance.
*
* @param group an updated group instance
* @return the updated group instance, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws IllegalStateException if there is already a group with the same name
*/
Group updateGroup(Group group) throws AuthorizationAccessException;
/**
* Deletes the given group.
*
* @param group the group to delete
* @return the deleted group, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Group deleteGroup(Group group) throws AuthorizationAccessException;
}

View File

@ -16,10 +16,12 @@
*/
package org.apache.nifi.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
/**
* A group that users can belong to.
@ -141,6 +143,41 @@ public class Group { // TODO rename to UserGroup
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
*/
public Builder identifierGenerateRandom() {
if (fromGroup) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing group");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing Group
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromGroup) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing group");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the group identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the name of the builder.
*

View File

@ -0,0 +1,59 @@
/*
* 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;
public interface ManagedAuthorizer extends Authorizer {
/**
* Returns a fingerprint representing the authorizations managed by this authorizer. The fingerprint will be
* used for comparison to determine if two managed authorizers represent a compatible set of users,
* groups, and/or policies. Must be non null
*
* @return the fingerprint for this Authorizer
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
String getFingerprint() throws AuthorizationAccessException;
/**
* Parses the fingerprint and adds any users, groups, and policies to the current Authorizer.
*
* @param fingerprint the fingerprint that was obtained from calling getFingerprint() on another Authorizer.
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
void inheritFingerprint(final String fingerprint) throws AuthorizationAccessException;
/**
* When the fingerprints are not equal, this method will check if the proposed fingerprint is inheritable.
* If the fingerprint is an exact match, this method will not be invoked as there is nothing to inherit.
*
* @param proposedFingerprint the proposed fingerprint
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
* @throws UninheritableAuthorizationsException if the proposed fingerprint was uninheritable
*/
void checkInheritability(final String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException;
/**
* Returns the AccessPolicy provider for this managed Authorizer. Must be non null
*
* @return the AccessPolicy provider
*/
AccessPolicyProvider getAccessPolicyProvider();
}

View File

@ -16,7 +16,9 @@
*/
package org.apache.nifi.authorization;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
import java.util.UUID;
/**
* A user to create authorization policies for.
@ -128,6 +130,41 @@ public class User {
return this;
}
/**
* Sets the identifier of the builder to a random UUID.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing User
*/
public Builder identifierGenerateRandom() {
if (fromUser) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing user");
}
this.identifier = UUID.randomUUID().toString();
return this;
}
/**
* Sets the identifier of the builder with a UUID generated from the specified seed string.
*
* @return the builder
* @throws IllegalStateException if this method is called when this builder was constructed from an existing User
*/
public Builder identifierGenerateFromSeed(final String seed) {
if (fromUser) {
throw new IllegalStateException(
"Identifier can not be changed when initialized from an existing user");
}
if (seed == null) {
throw new IllegalArgumentException("Cannot seed the user identifier with a null value.");
}
this.identifier = UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
return this;
}
/**
* Sets the identity of the builder.
*

View File

@ -0,0 +1,40 @@
/*
* 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 java.util.Set;
/**
* A holder object to provide atomic access to a user and their groups.
*/
public interface UserAndGroups {
/**
* Retrieves the user, or null if the user is unknown
*
* @return the user with the given identity
*/
User getUser();
/**
* Retrieves the groups for the user, or null if the user is unknown or has no groups.
*
* @return the set of groups for the given user identity
*/
Set<Group> getGroups();
}

View File

@ -0,0 +1,108 @@
/*
* 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;
/**
* Provides access to Users and Groups.
*
* NOTE: Extensions will be called often and frequently. Because of this, if the underlying implementation needs to
* make remote calls or expensive calculations those should probably be done asynchronously and/or cache the results.
*
* Additionally, extensions need to be thread safe.
*/
public interface UserGroupProvider {
/**
* Retrieves all users. Must be non null
*
* @return a list of users
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<User> getUsers() throws AuthorizationAccessException;
/**
* Retrieves the user with the given identifier.
*
* @param identifier the id of the user to retrieve
* @return the user with the given id, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User getUser(String identifier) throws AuthorizationAccessException;
/**
* Retrieves the user with the given identity.
*
* @param identity the identity of the user to retrieve
* @return the user with the given identity, or null if no matching user was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
User getUserByIdentity(String identity) throws AuthorizationAccessException;
/**
* Retrieves all groups. Must be non null
*
* @return a list of groups
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Set<Group> getGroups() throws AuthorizationAccessException;
/**
* Retrieves a Group by id.
*
* @param identifier the identifier of the Group to retrieve
* @return the Group with the given identifier, or null if no matching group was found
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
Group getGroup(String identifier) throws AuthorizationAccessException;
/**
* Gets a user and their groups. Must be non null. If the user is not known the UserAndGroups.getUser() and
* UserAndGroups.getGroups() should return null
*
* @return the UserAndGroups for the specified identity
* @throws AuthorizationAccessException if there was an unexpected error performing the operation
*/
UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException;
/**
* Called immediately after instance creation for implementers to perform additional setup
*
* @param initializationContext in which to initialize
*/
void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException;
/**
* Called to configure the Authorizer.
*
* @param configurationContext at the time of configuration
* @throws AuthorizerCreationException for any issues configuring the provider
*/
void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException;
/**
* Called immediately before instance destruction for implementers to release resources.
*
* @throws AuthorizerDestructionException If pre-destruction fails.
*/
void preDestruction() throws AuthorizerDestructionException;
}

View File

@ -0,0 +1,37 @@
/*
* 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;
/**
* Initialization content for UserGroupProviders.
*/
public interface UserGroupProviderInitializationContext {
/**
* The identifier of the UserGroupProvider.
*
* @return The identifier
*/
String getIdentifier();
/**
* The lookup for accessing other configured UserGroupProviders.
*
* @return The UserGroupProvider lookup
*/
UserGroupProviderLookup getUserGroupProviderLookup();
}

View File

@ -0,0 +1,31 @@
/*
* 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;
/**
*
*/
public interface UserGroupProviderLookup {
/**
* Looks up the UserGroupProvider with the specified identifier
*
* @param identifier The identifier of the UserGroupProvider
* @return The UserGroupProvider
*/
UserGroupProvider getUserGroupProvider(String identifier);
}

View File

@ -0,0 +1,28 @@
/*
* 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.exception;
/**
* Represents the case when the proposed authorizations are not inheritable.
*/
public class UninheritableAuthorizationsException extends RuntimeException {
public UninheritableAuthorizationsException(String message) {
super(message);
}
}

View File

@ -84,6 +84,7 @@ public interface Authorizable {
final Resource resource = getResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(action)
@ -188,6 +189,7 @@ public interface Authorizable {
final Resource resource = getResource();
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)

View File

@ -17,6 +17,8 @@
package org.apache.nifi.authorization.user;
import java.util.Set;
/**
* A representation of a NiFi user that has logged into the application
*/
@ -27,6 +29,11 @@ public interface NiFiUser {
*/
String getIdentity();
/**
* @return the groups that this user belongs to if this nifi is configured to load user groups, null otherwise.
*/
Set<String> getGroups();
/**
* @return the next user in the proxied entities chain, or <code>null</code> if no more users exist in the chain.
*/

View File

@ -16,7 +16,6 @@
*/
package org.apache.nifi.authorization;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
@ -251,10 +250,7 @@ public class TestAbstractPolicyBasedAuthorizer {
when(authorizer2.getAccessPolicies()).thenReturn(policies2);
// compare the fingerprints
assertEquals(authorizer1.getFingerprint(), authorizer2.getFingerprint());
//System.out.println(authorizer1.getFingerprint());
}
@Test
@ -332,211 +328,4 @@ public class TestAbstractPolicyBasedAuthorizer {
Assert.assertTrue(fingerprint.length() > 0);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() {
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
AccessPolicy policy1 = new AccessPolicy.Builder()
.identifier("policy-id-1")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
AccessPolicy policy2 = new AccessPolicy.Builder()
.identifier("policy-id-2")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
Set<AccessPolicy> policies = new LinkedHashSet<>();
policies.add(policy1);
policies.add(policy2);
Set<User> users = new LinkedHashSet<>();
users.add(user1);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies);
authorizer.onConfigured(context);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenUsersWithSameIdentity() {
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
Set<User> users = new LinkedHashSet<>();
users.add(user1);
users.add(user2);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>());
authorizer.onConfigured(context);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenGroupsWithSameName() {
Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
Set<Group> groups = new LinkedHashSet<>();
groups.add(group1);
groups.add(group2);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>());
authorizer.onConfigured(context);
}
@Test
public void testAddPoliciesWithSameResourceAndAction() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
authorizer.addUser(user1);
AccessPolicy policy1 = new AccessPolicy.Builder()
.identifier("policy-id-1")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
authorizer.addAccessPolicy(policy1);
AccessPolicy policy2 = new AccessPolicy.Builder()
.identifier("policy-id-2")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
try {
authorizer.addAccessPolicy(policy2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddUsersWithSameIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
authorizer.addUser(user1);
User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
try {
authorizer.addUser(user2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddGroupsWithSameName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
authorizer.addGroup(group1);
Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
try {
authorizer.addGroup(group2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddUsersWithSameIdentityAsGroupName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
authorizer.addGroup(group1);
User user = new User.Builder().identifier("user-id-2").identity("abc").build();
try {
authorizer.addUser(user);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddGroupWithSameNameAsUserIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
User user = new User.Builder().identifier("user-id-2").identity("abc").build();
authorizer.addUser(user);
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
try {
authorizer.addGroup(group1);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testUpdateUserWithSameIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
User user1 = new User.Builder().identifier("user-id-1").identity("abc").build();
authorizer.addUser(user1);
User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build();
authorizer.addUser(user2);
try {
User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build();
authorizer.updateUser(user1Updated);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testUpdateGroupWithSameName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
AbstractPolicyBasedAuthorizer authorizer = new MockPolicyBasedAuthorizer();
authorizer.onConfigured(context);
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
authorizer.addGroup(group1);
Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build();
authorizer.addGroup(group2);
try {
Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build();
authorizer.updateGroup(group1Updated);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
}

View File

@ -63,10 +63,6 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-nar-utils</artifactId>
@ -75,6 +71,22 @@
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-properties</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-nar-utils</artifactId>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-framework-authorization</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,179 @@
/*
* 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.nar.NarCloseable;
import java.util.Set;
public final class AccessPolicyProviderFactory {
public static AccessPolicyProvider withNarLoader(final AccessPolicyProvider baseAccessPolicyProvider) {
if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
return new ConfigurableAccessPolicyProvider() {
@Override
public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
}
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
}
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
}
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.getAccessPolicies();
}
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
}
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
}
}
@Override
public UserGroupProvider getUserGroupProvider() {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.getUserGroupProvider();
}
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
}
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableAccessPolicyProvider.getFingerprint();
}
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableAccessPolicyProvider.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableAccessPolicyProvider.preDestruction();
}
}
};
} else {
return new AccessPolicyProvider() {
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAccessPolicyProvider.getAccessPolicies();
}
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAccessPolicyProvider.getAccessPolicy(identifier);
}
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
}
}
@Override
public UserGroupProvider getUserGroupProvider() {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAccessPolicyProvider.getUserGroupProvider();
}
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAccessPolicyProvider.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAccessPolicyProvider.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAccessPolicyProvider.preDestruction();
}
}
};
}
}
private AccessPolicyProviderFactory() {}
}

View File

@ -0,0 +1,45 @@
/*
* 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;
public final class AuthorizerCapabilityDetection {
public static boolean isManagedAuthorizer(final Authorizer authorizer) {
return authorizer instanceof ManagedAuthorizer;
}
public static boolean isConfigurableAccessPolicyProvider(final Authorizer authorizer) {
if (!isManagedAuthorizer(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
return managedAuthorizer.getAccessPolicyProvider() instanceof ConfigurableAccessPolicyProvider;
}
public static boolean isConfigurableUserGroupProvider(final Authorizer authorizer) {
if (!isManagedAuthorizer(authorizer)) {
return false;
}
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final AccessPolicyProvider accessPolicyProvider = managedAuthorizer.getAccessPolicyProvider();
return accessPolicyProvider.getUserGroupProvider() instanceof ConfigurableUserGroupProvider;
}
private AuthorizerCapabilityDetection() {}
}

View File

@ -0,0 +1,426 @@
/*
* 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.nar.NarCloseable;
import java.util.Set;
public final class AuthorizerFactory {
/**
* Checks if another policy exists with the same resource and action as the given policy.
*
* @param checkAccessPolicy an access policy being checked
* @return true if another access policy exists with the same resource and action, false otherwise
*/
private static boolean policyExists(final AccessPolicyProvider accessPolicyProvider, final AccessPolicy checkAccessPolicy) {
for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
if (!accessPolicy.getIdentifier().equals(checkAccessPolicy.getIdentifier())
&& accessPolicy.getResource().equals(checkAccessPolicy.getResource())
&& accessPolicy.getAction().equals(checkAccessPolicy.getAction())) {
return true;
}
}
return false;
}
/**
* Checks if another user exists with the same identity.
*
* @param identifier identity of the user
* @param identity identity of the user
* @return true if another user exists with the same identity, false otherwise
*/
private static boolean tenantExists(final UserGroupProvider userGroupProvider, final String identifier, final String identity) {
for (User user : userGroupProvider.getUsers()) {
if (!user.getIdentifier().equals(identifier)
&& user.getIdentity().equals(identity)) {
return true;
}
}
for (Group group : userGroupProvider.getGroups()) {
if (!group.getIdentifier().equals(identifier)
&& group.getName().equals(identity)) {
return true;
}
}
return false;
}
public static Authorizer installIntegrityChecks(final Authorizer baseAuthorizer) {
if (baseAuthorizer instanceof ManagedAuthorizer) {
final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
return new ManagedAuthorizer() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseManagedAuthorizer.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseManagedAuthorizer.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseManagedAuthorizer.checkInheritability(proposedFingerprint);
}
@Override
public AccessPolicyProvider getAccessPolicyProvider() {
final AccessPolicyProvider baseAccessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
if (baseAccessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
final ConfigurableAccessPolicyProvider baseConfigurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) baseAccessPolicyProvider;
return new ConfigurableAccessPolicyProvider() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseConfigurableAccessPolicyProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseConfigurableAccessPolicyProvider.checkInheritability(proposedFingerprint);
}
@Override
public AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (policyExists(baseConfigurableAccessPolicyProvider, accessPolicy)) {
throw new IllegalStateException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
return baseConfigurableAccessPolicyProvider.addAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.updateAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.deleteAccessPolicy(accessPolicy);
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicies();
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(identifier);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
return baseConfigurableAccessPolicyProvider.getAccessPolicy(resourceIdentifier, action);
}
@Override
public UserGroupProvider getUserGroupProvider() {
final UserGroupProvider baseUserGroupProvider = baseConfigurableAccessPolicyProvider.getUserGroupProvider();
if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
return new ConfigurableUserGroupProvider() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getFingerprint();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
}
@Override
public User addUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
return baseConfigurableUserGroupProvider.addUser(user);
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", user.getIdentity()));
}
return baseConfigurableUserGroupProvider.updateUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.deleteUser(user);
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
return baseConfigurableUserGroupProvider.addGroup(group);
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
if (tenantExists(baseConfigurableUserGroupProvider, group.getIdentifier(), group.getName())) {
throw new IllegalStateException(String.format("User/user group already exists with the identity '%s'.", group.getName()));
}
return baseConfigurableUserGroupProvider.updateGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.deleteGroup(group);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUsers();
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUser(identifier);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getGroups();
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getGroup(identifier);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
baseConfigurableUserGroupProvider.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseConfigurableUserGroupProvider.onConfigured(configurationContext);
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseConfigurableUserGroupProvider.preDestruction();
}
};
} else {
return baseUserGroupProvider;
}
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
baseConfigurableAccessPolicyProvider.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseConfigurableAccessPolicyProvider.onConfigured(configurationContext);
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseConfigurableAccessPolicyProvider.preDestruction();
}
};
} else {
return baseAccessPolicyProvider;
}
}
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
return baseManagedAuthorizer.authorize(request);
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
baseManagedAuthorizer.initialize(initializationContext);
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
baseManagedAuthorizer.onConfigured(configurationContext);
final AccessPolicyProvider accessPolicyProvider = baseManagedAuthorizer.getAccessPolicyProvider();
final UserGroupProvider userGroupProvider = accessPolicyProvider.getUserGroupProvider();
// ensure that only one policy per resource-action exists
for (AccessPolicy accessPolicy : accessPolicyProvider.getAccessPolicies()) {
if (policyExists(accessPolicyProvider, accessPolicy)) {
throw new AuthorizerCreationException(String.format("Found multiple policies for '%s' with '%s'.", accessPolicy.getResource(), accessPolicy.getAction()));
}
}
// ensure that only one group exists per identity
for (User user : userGroupProvider.getUsers()) {
if (tenantExists(userGroupProvider, user.getIdentifier(), user.getIdentity())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with identity '%s'.", user.getIdentity()));
}
}
// ensure that only one group exists per identity
for (Group group : userGroupProvider.getGroups()) {
if (tenantExists(userGroupProvider, group.getIdentifier(), group.getName())) {
throw new AuthorizerCreationException(String.format("Found multiple users/user groups with name '%s'.", group.getName()));
}
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
baseManagedAuthorizer.preDestruction();
}
};
} else {
return baseAuthorizer;
}
}
/**
* Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods.
*
* @param baseAuthorizer base authorizer
* @return authorizer
*/
public static Authorizer withNarLoader(final Authorizer baseAuthorizer) {
if (baseAuthorizer instanceof ManagedAuthorizer) {
final ManagedAuthorizer baseManagedAuthorizer = (ManagedAuthorizer) baseAuthorizer;
return new ManagedAuthorizer() {
@Override
public String getFingerprint() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseManagedAuthorizer.getFingerprint();
}
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseManagedAuthorizer.inheritFingerprint(fingerprint);
}
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseManagedAuthorizer.checkInheritability(proposedFingerprint);
}
}
@Override
public AccessPolicyProvider getAccessPolicyProvider() {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseManagedAuthorizer.getAccessPolicyProvider();
}
}
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseManagedAuthorizer.authorize(request);
}
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseManagedAuthorizer.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseManagedAuthorizer.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseManagedAuthorizer.preDestruction();
}
}
};
} else {
return new Authorizer() {
@Override
public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAuthorizer.authorize(request);
}
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.preDestruction();
}
}
};
}
}
private AuthorizerFactory() {}
}

View File

@ -25,7 +25,6 @@ import org.apache.nifi.authorization.generated.Authorizers;
import org.apache.nifi.authorization.generated.Property;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -49,12 +48,11 @@ import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Factory bean for loading the configured authorizer.
*/
public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, AuthorizerLookup {
public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, UserGroupProviderLookup, AccessPolicyProviderLookup, AuthorizerLookup {
private static final Logger logger = LoggerFactory.getLogger(AuthorizerFactoryBean.class);
private static final String AUTHORIZERS_XSD = "/authorizers.xsd";
@ -74,8 +72,19 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
private Authorizer authorizer;
private NiFiProperties properties;
private final Map<String, UserGroupProvider> userGroupProviders = new HashMap<>();
private final Map<String, AccessPolicyProvider> accessPolicyProviders = new HashMap<>();
private final Map<String, Authorizer> authorizers = new HashMap<>();
@Override
public UserGroupProvider getUserGroupProvider(String identifier) {
return userGroupProviders.get(identifier);
}
@Override
public AccessPolicyProvider getAccessPolicyProvider(String identifier) {
return accessPolicyProviders.get(identifier);
}
@Override
public Authorizer getAuthorizer(String identifier) {
@ -98,6 +107,28 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
} else {
final Authorizers authorizerConfiguration = loadAuthorizersConfiguration();
// create each user group provider
for (final org.apache.nifi.authorization.generated.UserGroupProvider userGroupProvider : authorizerConfiguration.getUserGroupProvider()) {
userGroupProviders.put(userGroupProvider.getIdentifier(), createUserGroupProvider(userGroupProvider.getIdentifier(), userGroupProvider.getClazz()));
}
// configure each user group provider
for (final org.apache.nifi.authorization.generated.UserGroupProvider provider : authorizerConfiguration.getUserGroupProvider()) {
final UserGroupProvider instance = userGroupProviders.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// create each access policy provider
for (final org.apache.nifi.authorization.generated.AccessPolicyProvider accessPolicyProvider : authorizerConfiguration.getAccessPolicyProvider()) {
accessPolicyProviders.put(accessPolicyProvider.getIdentifier(), createAccessPolicyProvider(accessPolicyProvider.getIdentifier(), accessPolicyProvider.getClazz()));
}
// configure each access policy provider
for (final org.apache.nifi.authorization.generated.AccessPolicyProvider provider : authorizerConfiguration.getAccessPolicyProvider()) {
final AccessPolicyProvider instance = accessPolicyProviders.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// create each authorizer
for (final org.apache.nifi.authorization.generated.Authorizer authorizer : authorizerConfiguration.getAuthorizer()) {
authorizers.put(authorizer.getIdentifier(), createAuthorizer(authorizer.getIdentifier(), authorizer.getClazz()));
@ -106,7 +137,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
// configure each authorizer
for (final org.apache.nifi.authorization.generated.Authorizer provider : authorizerConfiguration.getAuthorizer()) {
final Authorizer instance = authorizers.get(provider.getIdentifier());
instance.onConfigured(loadAuthorizerConfiguration(provider));
instance.onConfigured(loadAuthorizerConfiguration(provider.getIdentifier(), provider.getProperty()));
}
// get the authorizer instance
@ -146,6 +177,102 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
}
}
private UserGroupProvider createUserGroupProvider(final String identifier, final String userGroupProviderClassName) throws Exception {
// get the classloader for the specified user group provider
final List<Bundle> userGroupProviderBundles = ExtensionManager.getBundles(userGroupProviderClassName);
if (userGroupProviderBundles.size() == 0) {
throw new Exception(String.format("The specified user group provider class '%s' is not known to this nifi.", userGroupProviderClassName));
}
if (userGroupProviderBundles.size() > 1) {
throw new Exception(String.format("Multiple bundles found for the specified user group provider class '%s', only one is allowed.", userGroupProviderClassName));
}
final Bundle userGroupProviderBundle = userGroupProviderBundles.get(0);
final ClassLoader userGroupProviderClassLoader = userGroupProviderBundle.getClassLoader();
// get the current context classloader
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
final UserGroupProvider instance;
try {
// set the appropriate class loader
Thread.currentThread().setContextClassLoader(userGroupProviderClassLoader);
// attempt to load the class
Class<?> rawUserGroupProviderClass = Class.forName(userGroupProviderClassName, true, userGroupProviderClassLoader);
Class<? extends UserGroupProvider> userGroupProviderClass = rawUserGroupProviderClass.asSubclass(UserGroupProvider.class);
// otherwise create a new instance
Constructor constructor = userGroupProviderClass.getConstructor();
instance = (UserGroupProvider) constructor.newInstance();
// method injection
performMethodInjection(instance, userGroupProviderClass);
// field injection
performFieldInjection(instance, userGroupProviderClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
} finally {
if (currentClassLoader != null) {
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
return UserGroupProviderFactory.withNarLoader(instance);
}
private AccessPolicyProvider createAccessPolicyProvider(final String identifier, final String accessPolicyProviderClassName) throws Exception {
// get the classloader for the specified access policy provider
final List<Bundle> accessPolicyProviderBundles = ExtensionManager.getBundles(accessPolicyProviderClassName);
if (accessPolicyProviderBundles.size() == 0) {
throw new Exception(String.format("The specified access policy provider class '%s' is not known to this nifi.", accessPolicyProviderClassName));
}
if (accessPolicyProviderBundles.size() > 1) {
throw new Exception(String.format("Multiple bundles found for the specified access policy provider class '%s', only one is allowed.", accessPolicyProviderClassName));
}
final Bundle accessPolicyProviderBundle = accessPolicyProviderBundles.get(0);
final ClassLoader accessPolicyProviderClassLoader = accessPolicyProviderBundle.getClassLoader();
// get the current context classloader
final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
final AccessPolicyProvider instance;
try {
// set the appropriate class loader
Thread.currentThread().setContextClassLoader(accessPolicyProviderClassLoader);
// attempt to load the class
Class<?> rawAccessPolicyProviderClass = Class.forName(accessPolicyProviderClassName, true, accessPolicyProviderClassLoader);
Class<? extends AccessPolicyProvider> accessPolicyClass = rawAccessPolicyProviderClass.asSubclass(AccessPolicyProvider.class);
// otherwise create a new instance
Constructor constructor = accessPolicyClass.getConstructor();
instance = (AccessPolicyProvider) constructor.newInstance();
// method injection
performMethodInjection(instance, accessPolicyClass);
// field injection
performFieldInjection(instance, accessPolicyClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
} finally {
if (currentClassLoader != null) {
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
return AccessPolicyProviderFactory.withNarLoader(instance);
}
private Authorizer createAuthorizer(final String identifier, final String authorizerClassName) throws Exception {
// get the classloader for the specified authorizer
final List<Bundle> authorizerBundles = ExtensionManager.getBundles(authorizerClassName);
@ -184,26 +311,26 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
performFieldInjection(instance, authorizerClass);
// call post construction lifecycle event
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this));
instance.initialize(new StandardAuthorizerInitializationContext(identifier, this, this, this));
} finally {
if (currentClassLoader != null) {
Thread.currentThread().setContextClassLoader(currentClassLoader);
}
}
return withNarLoader(instance);
return AuthorizerFactory.installIntegrityChecks(AuthorizerFactory.withNarLoader(instance));
}
private AuthorizerConfigurationContext loadAuthorizerConfiguration(final org.apache.nifi.authorization.generated.Authorizer authorizer) {
private AuthorizerConfigurationContext loadAuthorizerConfiguration(final String identifier, final List<Property> properties) {
final Map<String, String> authorizerProperties = new HashMap<>();
for (final Property property : authorizer.getProperty()) {
for (final Property property : properties) {
authorizerProperties.put(property.getName(), property.getValue());
}
return new StandardAuthorizerConfigurationContext(authorizer.getIdentifier(), authorizerProperties);
return new StandardAuthorizerConfigurationContext(identifier, authorizerProperties);
}
private void performMethodInjection(final Authorizer instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
private void performMethodInjection(final Object instance, final Class authorizerClass) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
for (final Method method : authorizerClass.getMethods()) {
if (method.isAnnotationPresent(AuthorizerContext.class)) {
// make the method accessible
@ -235,7 +362,7 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
}
}
private void performFieldInjection(final Authorizer instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException {
private void performFieldInjection(final Object instance, final Class authorizerClass) throws IllegalArgumentException, IllegalAccessException {
for (final Field field : authorizerClass.getDeclaredFields()) {
if (field.isAnnotationPresent(AuthorizerContext.class)) {
// make the method accessible
@ -291,189 +418,6 @@ public class AuthorizerFactoryBean implements FactoryBean, DisposableBean, Autho
};
}
/**
* Decorates the base authorizer to ensure the nar context classloader is used when invoking the underlying methods.
*
* @param baseAuthorizer base authorizer
* @return authorizer
*/
public Authorizer withNarLoader(final Authorizer baseAuthorizer) {
if (baseAuthorizer instanceof AbstractPolicyBasedAuthorizer) {
AbstractPolicyBasedAuthorizer policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) baseAuthorizer;
return new AbstractPolicyBasedAuthorizer() {
@Override
public Group doAddGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.addGroup(group);
}
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getGroup(identifier);
}
}
@Override
public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.updateGroup(group);
}
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.deleteGroup(group);
}
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getGroups();
}
}
@Override
public User doAddUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.addUser(user);
}
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getUser(identifier);
}
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getUserByIdentity(identity);
}
}
@Override
public User doUpdateUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.updateUser(user);
}
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.deleteUser(user);
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getUsers();
}
}
@Override
public AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.addAccessPolicy(accessPolicy);
}
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getAccessPolicy(identifier);
}
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.updateAccessPolicy(accessPolicy);
}
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.deleteAccessPolicy(accessPolicy);
}
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getAccessPolicies();
}
}
@Override
public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return policyBasedAuthorizer.getUsersAndAccessPolicies();
}
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
policyBasedAuthorizer.initialize(initializationContext);
}
}
@Override
public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
policyBasedAuthorizer.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.preDestruction();
}
}
};
} else {
return new Authorizer() {
@Override
public AuthorizationResult authorize(final AuthorizationRequest request) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseAuthorizer.authorize(request);
}
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseAuthorizer.preDestruction();
}
}
};
}
}
@Override
public Class getObjectType() {
return Authorizer.class;

View File

@ -0,0 +1,228 @@
/*
* 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.nar.NarCloseable;
import java.util.Set;
public final class UserGroupProviderFactory {
public static UserGroupProvider withNarLoader(final UserGroupProvider baseUserGroupProvider) {
if (baseUserGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider baseConfigurableUserGroupProvider = (ConfigurableUserGroupProvider) baseUserGroupProvider;
return new ConfigurableUserGroupProvider() {
@Override
public User addUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.addUser(user);
}
}
@Override
public User updateUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.updateUser(user);
}
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.deleteUser(user);
}
}
@Override
public Group addGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.addGroup(group);
}
}
@Override
public Group updateGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.updateGroup(group);
}
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.deleteGroup(group);
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getUsers();
}
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getUser(identifier);
}
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getUserByIdentity(identity);
}
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getGroups();
}
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getGroup(identifier);
}
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getUserAndGroups(identity);
}
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableUserGroupProvider.inheritFingerprint(fingerprint);
}
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableUserGroupProvider.checkInheritability(proposedFingerprint);
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseConfigurableUserGroupProvider.getFingerprint();
}
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableUserGroupProvider.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableUserGroupProvider.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseConfigurableUserGroupProvider.preDestruction();
}
}
};
} else {
return new UserGroupProvider() {
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getUsers();
}
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getUser(identifier);
}
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getUserByIdentity(identity);
}
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getGroups();
}
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getGroup(identifier);
}
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
return baseUserGroupProvider.getUserAndGroups(identity);
}
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseUserGroupProvider.initialize(initializationContext);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseUserGroupProvider.onConfigured(configurationContext);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
try (final NarCloseable narCloseable = NarCloseable.withNarLoader()) {
baseUserGroupProvider.preDestruction();
}
}
};
}
}
private UserGroupProviderFactory() {}
}

View File

@ -14,7 +14,25 @@
limitations under the License.
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- role -->
<!-- user group providers type -->
<xs:complexType name="UserGroupProvider">
<xs:sequence>
<xs:element name="identifier" type="NonEmptyStringType"/>
<xs:element name="class" type="NonEmptyStringType"/>
<xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!-- access policy provider type -->
<xs:complexType name="AccessPolicyProvider">
<xs:sequence>
<xs:element name="identifier" type="NonEmptyStringType"/>
<xs:element name="class" type="NonEmptyStringType"/>
<xs:element name="property" type="Property" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
<!-- authorizers type -->
<xs:complexType name="Authorizer">
<xs:sequence>
<xs:element name="identifier" type="NonEmptyStringType"/>
@ -38,10 +56,12 @@
</xs:restriction>
</xs:simpleType>
<!-- users -->
<!-- authorizers -->
<xs:element name="authorizers">
<xs:complexType>
<xs:sequence>
<xs:element name="userGroupProvider" type="UserGroupProvider" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="accessPolicyProvider" type="AccessPolicyProvider" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="authorizer" type="Authorizer" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

View File

@ -0,0 +1,264 @@
/*
* 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.AuthorizerCreationException;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
public class AuthorizerFactoryTest {
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenPoliciesWithSameResourceAndAction() {
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
AccessPolicy policy1 = new AccessPolicy.Builder()
.identifier("policy-id-1")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
AccessPolicy policy2 = new AccessPolicy.Builder()
.identifier("policy-id-2")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
Set<AccessPolicy> policies = new LinkedHashSet<>();
policies.add(policy1);
policies.add(policy2);
Set<User> users = new LinkedHashSet<>();
users.add(user1);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, policies));
authorizer.onConfigured(context);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenUsersWithSameIdentity() {
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
Set<User> users = new LinkedHashSet<>();
users.add(user1);
users.add(user2);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(new HashSet<>(), users, new HashSet<>()));
authorizer.onConfigured(context);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenGroupsWithSameName() {
Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
Set<Group> groups = new LinkedHashSet<>();
groups.add(group1);
groups.add(group2);
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
Authorizer authorizer = AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer(groups, new HashSet<>(), new HashSet<>()));
authorizer.onConfigured(context);
}
@Test
public void testAddPoliciesWithSameResourceAndAction() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
userGroupProvider.addUser(user1);
AccessPolicy policy1 = new AccessPolicy.Builder()
.identifier("policy-id-1")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
accessPolicyProvider.addAccessPolicy(policy1);
AccessPolicy policy2 = new AccessPolicy.Builder()
.identifier("policy-id-2")
.resource("resource1")
.action(RequestAction.READ)
.addUser(user1.getIdentifier())
.build();
try {
accessPolicyProvider.addAccessPolicy(policy2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddUsersWithSameIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
User user1 = new User.Builder().identifier("user-id-1").identity("user-1").build();
userGroupProvider.addUser(user1);
User user2 = new User.Builder().identifier("user-id-2").identity("user-1").build();
try {
userGroupProvider.addUser(user2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddGroupsWithSameName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
Group group1 = new Group.Builder().identifier("group-id-1").name("group-1").build();
userGroupProvider.addGroup(group1);
Group group2 = new Group.Builder().identifier("group-id-2").name("group-1").build();
try {
userGroupProvider.addGroup(group2);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddUsersWithSameIdentityAsGroupName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
userGroupProvider.addGroup(group1);
User user = new User.Builder().identifier("user-id-2").identity("abc").build();
try {
userGroupProvider.addUser(user);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testAddGroupWithSameNameAsUserIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
User user = new User.Builder().identifier("user-id-2").identity("abc").build();
userGroupProvider.addUser(user);
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
try {
userGroupProvider.addGroup(group1);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testUpdateUserWithSameIdentity() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
User user1 = new User.Builder().identifier("user-id-1").identity("abc").build();
userGroupProvider.addUser(user1);
User user2 = new User.Builder().identifier("user-id-2").identity("xyz").build();
userGroupProvider.addUser(user2);
try {
User user1Updated = new User.Builder().identifier("user-id-1").identity("xyz").build();
userGroupProvider.updateUser(user1Updated);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
@Test
public void testUpdateGroupWithSameName() {
AuthorizerConfigurationContext context = Mockito.mock(AuthorizerConfigurationContext.class);
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(new MockPolicyBasedAuthorizer());
managedAuthorizer.onConfigured(context);
final ConfigurableAccessPolicyProvider accessPolicyProvider = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
final ConfigurableUserGroupProvider userGroupProvider = (ConfigurableUserGroupProvider) accessPolicyProvider.getUserGroupProvider();
Group group1 = new Group.Builder().identifier("group-id-1").name("abc").build();
userGroupProvider.addGroup(group1);
Group group2 = new Group.Builder().identifier("group-id-2").name("xyz").build();
userGroupProvider.addGroup(group2);
try {
Group group1Updated = new Group.Builder().identifier("group-id-1").name("xyz").build();
userGroupProvider.updateGroup(group1Updated);
Assert.fail("Should have thrown exception");
} catch (IllegalStateException e) {
}
}
}

View File

@ -0,0 +1,183 @@
/*
* 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.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Mock implementation of AbstractPolicyBasedAuthorizer.
*/
public class MockPolicyBasedAuthorizer extends AbstractPolicyBasedAuthorizer {
private Set<Group> groups = new HashSet<>();
private Set<User> users = new HashSet<>();
private Set<AccessPolicy> policies = new HashSet<>();
public MockPolicyBasedAuthorizer() {
}
public MockPolicyBasedAuthorizer(Set<Group> groups, Set<User> users, Set<AccessPolicy> policies) {
if (groups != null) {
this.groups.addAll(groups);
}
if (users != null) {
this.users.addAll(users);
}
if (policies != null) {
this.policies.addAll(policies);
}
}
@Override
public Group doAddGroup(Group group) throws AuthorizationAccessException {
groups.add(group);
return group;
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
return groups.stream().filter(g -> g.getIdentifier().equals(identifier)).findFirst().get();
}
@Override
public Group doUpdateGroup(Group group) throws AuthorizationAccessException {
deleteGroup(group);
return addGroup(group);
}
@Override
public Group deleteGroup(Group group) throws AuthorizationAccessException {
groups.remove(group);
return group;
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return groups;
}
@Override
public User doAddUser(User user) throws AuthorizationAccessException {
users.add(user);
return user;
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
return users.stream().filter(u -> u.getIdentifier().equals(identifier)).findFirst().get();
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
return users.stream().filter(u -> u.getIdentity().equals(identity)).findFirst().get();
}
@Override
public User doUpdateUser(User user) throws AuthorizationAccessException {
deleteUser(user);
return addUser(user);
}
@Override
public User deleteUser(User user) throws AuthorizationAccessException {
users.remove(user);
return user;
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return users;
}
@Override
protected AccessPolicy doAddAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
policies.add(accessPolicy);
return accessPolicy;
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
return policies.stream().filter(p -> p.getIdentifier().equals(identifier)).findFirst().get();
}
@Override
public AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
deleteAccessPolicy(accessPolicy);
return addAccessPolicy(accessPolicy);
}
@Override
public AccessPolicy deleteAccessPolicy(AccessPolicy policy) throws AuthorizationAccessException {
policies.remove(policy);
return policy;
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return policies;
}
@Override
public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
return new UsersAndAccessPolicies() {
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) {
return null;
}
@Override
public User getUser(String identity) {
return getUserByIdentity(identity);
}
@Override
public Set<Group> getGroups(String userIdentity) {
User user = getUserByIdentity(userIdentity);
if (user == null) {
return new HashSet<>();
} else {
return groups.stream()
.filter(g -> g.getUsers().contains(user.getIdentifier()))
.collect(Collectors.toSet());
}
}
};
}
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void doOnConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
}

View File

@ -29,7 +29,9 @@ import java.util.Date;
@XmlType(name = "flowConfiguration")
public class FlowConfigurationDTO {
private Boolean supportsManagedAuthorizer;
private Boolean supportsConfigurableAuthorizer;
private Boolean supportsConfigurableUsersAndGroups;
private Long autoRefreshIntervalSeconds;
private Date currentTime;
@ -50,6 +52,37 @@ public class FlowConfigurationDTO {
this.autoRefreshIntervalSeconds = autoRefreshIntervalSeconds;
}
/**
* @return whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups,
* and policies in the UI. This value is read only
*/
@ApiModelProperty(
value = "Whether this NiFi supports a managed authorizer. Managed authorizers can visualize users, groups, and policies in the UI.",
readOnly = true
)
public Boolean getSupportsManagedAuthorizer() {
return supportsManagedAuthorizer;
}
public void setSupportsManagedAuthorizer(Boolean supportsManagedAuthorizer) {
this.supportsManagedAuthorizer = supportsManagedAuthorizer;
}
/**
* @return whether this NiFi supports configurable users and groups. This value is read only
*/
@ApiModelProperty(
value = "Whether this NiFi supports configurable users and groups.",
readOnly = true
)
public Boolean getSupportsConfigurableUsersAndGroups() {
return supportsConfigurableUsersAndGroups;
}
public void setSupportsConfigurableUsersAndGroups(Boolean supportsConfigurableUsersAndGroups) {
this.supportsConfigurableUsersAndGroups = supportsConfigurableUsersAndGroups;
}
/**
* @return whether this NiFi supports a configurable authorizer. This value is read only
*/

View File

@ -28,7 +28,7 @@ import java.util.Date;
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to an {@link AccessPolicyDTO}.
*/
@XmlRootElement(name = "accessPolicyEntity")
public class AccessPolicyEntity extends ComponentEntity {
public class AccessPolicyEntity extends ComponentEntity implements Permissible<AccessPolicyDTO> {
private Date generated;
private AccessPolicyDTO component;

View File

@ -24,7 +24,7 @@ import java.util.Collection;
* TenantEntity objects.
*/
@XmlRootElement(name = "tenantsEntity")
public class TenantsEntity {
public class TenantsEntity extends Entity {
private Collection<TenantEntity> users;
private Collection<TenantEntity> userGroups;

View File

@ -24,7 +24,7 @@ import javax.xml.bind.annotation.XmlRootElement;
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserDTO.
*/
@XmlRootElement(name = "userEntity")
public class UserEntity extends ComponentEntity {
public class UserEntity extends ComponentEntity implements Permissible<UserDTO> {
private UserDTO component;

View File

@ -16,14 +16,15 @@
*/
package org.apache.nifi.web.api.entity;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import javax.xml.bind.annotation.XmlRootElement;
/**
* A serialized representation of this class can be placed in the entity body of a request or response to or from the API. This particular entity holds a reference to a UserGroupDTO.
*/
@XmlRootElement(name = "userGroupEntity")
public class UserGroupEntity extends ComponentEntity {
public class UserGroupEntity extends ComponentEntity implements Permissible<UserGroupDTO> {
private UserGroupDTO component;

View File

@ -24,7 +24,7 @@ import java.util.Collection;
* UserGroupEntity objects.
*/
@XmlRootElement(name = "userGroupsEntity")
public class UserGroupsEntity {
public class UserGroupsEntity extends Entity {
private Collection<UserGroupEntity> userGroups;

View File

@ -41,8 +41,9 @@
<goal>xjc</goal>
</goals>
<configuration>
<schemaDirectory>src/main/xsd</schemaDirectory>
<schemaFiles>authorizations.xsd</schemaFiles>
<sources>
<source>src/main/xsd/authorizations.xsd</source>
</sources>
<packageName>org.apache.nifi.authorization.file.generated</packageName>
</configuration>
</execution>
@ -52,8 +53,9 @@
<goal>xjc</goal>
</goals>
<configuration>
<schemaDirectory>src/main/xsd</schemaDirectory>
<schemaFiles>tenants.xsd</schemaFiles>
<sources>
<source>src/main/xsd/tenants.xsd</source>
</sources>
<packageName>org.apache.nifi.authorization.file.tenants.generated</packageName>
<clearOutputDir>false</clearOutputDir>
</configuration>
@ -64,8 +66,9 @@
<goal>xjc</goal>
</goals>
<configuration>
<schemaDirectory>src/main/xsd</schemaDirectory>
<schemaFiles>legacy-users.xsd</schemaFiles>
<sources>
<source>src/main/xsd/legacy-users.xsd</source>
</sources>
<packageName>org.apache.nifi.user.generated</packageName>
<clearOutputDir>false</clearOutputDir>
</configuration>

View File

@ -19,9 +19,6 @@ package org.apache.nifi.authorization;
import org.apache.nifi.authorization.file.generated.Authorizations;
import org.apache.nifi.authorization.file.generated.Policies;
import org.apache.nifi.authorization.file.tenants.generated.Groups;
import org.apache.nifi.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.authorization.file.tenants.generated.Users;
import java.util.Collections;
import java.util.HashMap;
@ -32,56 +29,26 @@ import java.util.Set;
/**
* A holder to provide atomic access to data structures.
*/
public class AuthorizationsHolder implements UsersAndAccessPolicies {
public class AuthorizationsHolder {
private final Tenants tenants;
private final Authorizations authorizations;
private final Set<AccessPolicy> allPolicies;
private final Map<String, Set<AccessPolicy>> policiesByResource;
private final Map<String, AccessPolicy> policiesById;
private final Set<User> allUsers;
private final Map<String,User> usersById;
private final Map<String,User> usersByIdentity;
private final Set<Group> allGroups;
private final Map<String,Group> groupsById;
private final Map<String, Set<Group>> groupsByUserIdentity;
/**
* Creates a new holder and populates all convenience data structures.
* Creates a new holder and populates all convenience authorizations data structures.
*
* @param authorizations the current authorizations instance
*/
public AuthorizationsHolder(final Authorizations authorizations, final Tenants tenants) {
public AuthorizationsHolder(final Authorizations authorizations) {
this.authorizations = authorizations;
this.tenants = tenants;
// load all users
final Users users = tenants.getUsers();
final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
// load all groups
final Groups groups = tenants.getGroups();
final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
// load all access policies
final Policies policies = authorizations.getPolicies();
final Set<AccessPolicy> allPolicies = Collections.unmodifiableSet(createAccessPolicies(policies));
// create a convenience map to retrieve a user by id
final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
// create a convenience map to retrieve a user by identity
final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
// create a convenience map to retrieve a group by id
final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
// create a convenience map to retrieve the groups for a user identity
final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
// create a convenience map from resource id to policies
final Map<String, Set<AccessPolicy>> policiesByResourceMap = Collections.unmodifiableMap(createResourcePolicyMap(allPolicies));
@ -89,13 +56,7 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
final Map<String, AccessPolicy> policiesByIdMap = Collections.unmodifiableMap(createPoliciesByIdMap(allPolicies));
// set all the holders
this.allUsers = allUsers;
this.allGroups = allGroups;
this.allPolicies = allPolicies;
this.usersById = userByIdMap;
this.usersByIdentity = userByIdentityMap;
this.groupsById = groupByIdMap;
this.groupsByUserIdentity = groupsByUserIdentityMap;
this.policiesByResource = policiesByResourceMap;
this.policiesById = policiesByIdMap;
}
@ -134,9 +95,9 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
// add the appropriate request actions
final String authorizationCode = policy.getAction();
if (authorizationCode.equals(FileAuthorizer.READ_CODE)) {
if (authorizationCode.equals(FileAccessPolicyProvider.READ_CODE)) {
builder.action(RequestAction.READ);
} else if (authorizationCode.equals(FileAuthorizer.WRITE_CODE)){
} else if (authorizationCode.equals(FileAccessPolicyProvider.WRITE_CODE)){
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + authorizationCode);
@ -149,57 +110,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
return allPolicies;
}
/**
* Creates a set of Users from the JAXB Users.
*
* @param users the JAXB Users
* @return a set of API Users matching the provided JAXB Users
*/
private Set<User> createUsers(org.apache.nifi.authorization.file.tenants.generated.Users users) {
Set<User> allUsers = new HashSet<>();
if (users == null || users.getUser() == null) {
return allUsers;
}
for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) {
final User.Builder builder = new User.Builder()
.identity(user.getIdentity())
.identifier(user.getIdentifier());
allUsers.add(builder.build());
}
return allUsers;
}
/**
* Creates a set of Groups from the JAXB Groups.
*
* @param groups the JAXB Groups
* @return a set of API Groups matching the provided JAXB Groups
*/
private Set<Group> createGroups(org.apache.nifi.authorization.file.tenants.generated.Groups groups,
org.apache.nifi.authorization.file.tenants.generated.Users users) {
Set<Group> allGroups = new HashSet<>();
if (groups == null || groups.getGroup() == null) {
return allGroups;
}
for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) {
final Group.Builder builder = new Group.Builder()
.identifier(group.getIdentifier())
.name(group.getName());
for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
builder.addUser(groupUser.getIdentifier());
}
allGroups.add(builder.build());
}
return allGroups;
}
/**
* Creates a map from resource identifier to the set of policies for the given resource.
*
@ -221,74 +131,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
return resourcePolicies;
}
/**
* Creates a Map from user identifier to User.
*
* @param users the set of all users
* @return the Map from user identifier to User
*/
private Map<String,User> createUserByIdMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentifier(), user);
}
return usersMap;
}
/**
* Creates a Map from user identity to User.
*
* @param users the set of all users
* @return the Map from user identity to User
*/
private Map<String,User> createUserByIdentityMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentity(), user);
}
return usersMap;
}
/**
* Creates a Map from group identifier to Group.
*
* @param groups the set of all groups
* @return the Map from group identifier to Group
*/
private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
Map<String,Group> groupsMap = new HashMap<>();
for (Group group : groups) {
groupsMap.put(group.getIdentifier(), group);
}
return groupsMap;
}
/**
* Creates a Map from user identity to the set of Groups for that identity.
*
* @param groups all groups
* @param users all users
* @return a Map from User identity to the set of Groups for that identity
*/
private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
for (User user : users) {
Set<Group> userGroups = new HashSet<>();
for (Group group : groups) {
for (String groupUser : group.getUsers()) {
if (groupUser.equals(user.getIdentifier())) {
userGroups.add(group);
}
}
}
groupsByUserIdentity.put(user.getIdentity(), userGroups);
}
return groupsByUserIdentity;
}
/**
* Creates a Map from policy identifier to AccessPolicy.
*
@ -307,10 +149,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
return authorizations;
}
public Tenants getTenants() {
return tenants;
}
public Set<AccessPolicy> getAllPolicies() {
return allPolicies;
}
@ -323,27 +161,6 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
return policiesById;
}
public Set<User> getAllUsers() {
return allUsers;
}
public Map<String, User> getUsersById() {
return usersById;
}
public Map<String, User> getUsersByIdentity() {
return usersByIdentity;
}
public Set<Group> getAllGroups() {
return allGroups;
}
public Map<String, Group> getGroupsById() {
return groupsById;
}
@Override
public AccessPolicy getAccessPolicy(final String resourceIdentifier, final RequestAction action) {
if (resourceIdentifier == null) {
throw new IllegalArgumentException("Resource Identifier cannot be null");
@ -363,20 +180,4 @@ public class AuthorizationsHolder implements UsersAndAccessPolicies {
return null;
}
@Override
public User getUser(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Identity cannot be null");
}
return usersByIdentity.get(identity);
}
@Override
public Set<Group> getGroups(String userIdentity) {
if (userIdentity == null) {
throw new IllegalArgumentException("User Identity cannot be null");
}
return groupsByUserIdentity.get(userIdentity);
}
}

View File

@ -0,0 +1,947 @@
/*
* 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.annotation.AuthorizerContext;
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.authorization.file.generated.Authorizations;
import org.apache.nifi.authorization.file.generated.Policies;
import org.apache.nifi.authorization.file.generated.Policy;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
import org.apache.nifi.authorization.util.IdentityMapping;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.user.generated.Users;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.file.FileUtils;
import org.apache.nifi.web.api.dto.PortDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileAccessPolicyProvider implements ConfigurableAccessPolicyProvider {
private static final Logger logger = LoggerFactory.getLogger(FileAccessPolicyProvider.class);
private static final String AUTHORIZATIONS_XSD = "/authorizations.xsd";
private static final String JAXB_AUTHORIZATIONS_PATH = "org.apache.nifi.authorization.file.generated";
private static final String USERS_XSD = "/legacy-users.xsd";
private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated";
private static final JAXBContext JAXB_AUTHORIZATIONS_CONTEXT = initializeJaxbContext(JAXB_AUTHORIZATIONS_PATH);
private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH);
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext(final String contextPath) {
try {
return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext.");
}
}
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String POLICY_ELEMENT = "policy";
private static final String POLICY_USER_ELEMENT = "policyUser";
private static final String POLICY_GROUP_ELEMENT = "policyGroup";
private static final String IDENTIFIER_ATTR = "identifier";
private static final String RESOURCE_ATTR = "resource";
private static final String ACTIONS_ATTR = "actions";
static final String READ_CODE = "R";
static final String WRITE_CODE = "W";
static final String PROP_NODE_IDENTITY_PREFIX = "Node Identity ";
static final String PROP_USER_GROUP_PROVIDER = "User Group Provider";
static final String PROP_AUTHORIZATIONS_FILE = "Authorizations File";
static final String PROP_INITIAL_ADMIN_IDENTITY = "Initial Admin Identity";
static final Pattern NODE_IDENTITY_PATTERN = Pattern.compile(PROP_NODE_IDENTITY_PREFIX + "\\S+");
private Schema usersSchema;
private Schema authorizationsSchema;
private NiFiProperties properties;
private File authorizationsFile;
private File restoreAuthorizationsFile;
private String rootGroupId;
private String initialAdminIdentity;
private String legacyAuthorizedUsersFile;
private Set<String> nodeIdentities;
private List<PortDTO> ports = new ArrayList<>();
private List<IdentityMapping> identityMappings;
private UserGroupProvider userGroupProvider;
private UserGroupProviderLookup userGroupProviderLookup;
private final AtomicReference<AuthorizationsHolder> authorizationsHolder = new AtomicReference<>();
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
userGroupProviderLookup = initializationContext.getUserGroupProviderLookup();
try {
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
authorizationsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(AUTHORIZATIONS_XSD));
usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
} catch (Exception e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try {
final PropertyValue userGroupProviderIdentifier = configurationContext.getProperty(PROP_USER_GROUP_PROVIDER);
if (!userGroupProviderIdentifier.isSet()) {
throw new AuthorizerCreationException("The user group provider must be specified.");
}
userGroupProvider = userGroupProviderLookup.getUserGroupProvider(userGroupProviderIdentifier.getValue());
if (userGroupProvider == null) {
throw new AuthorizerCreationException("Unable to locate user group provider with identifier " + userGroupProviderIdentifier.getValue());
}
final PropertyValue authorizationsPath = configurationContext.getProperty(PROP_AUTHORIZATIONS_FILE);
if (StringUtils.isBlank(authorizationsPath.getValue())) {
throw new AuthorizerCreationException("The authorizations file must be specified.");
}
// get the authorizations file and ensure it exists
authorizationsFile = new File(authorizationsPath.getValue());
if (!authorizationsFile.exists()) {
logger.info("Creating new authorizations file at {}", new Object[] {authorizationsFile.getAbsolutePath()});
saveAuthorizations(new Authorizations());
}
final File authorizationsFileDirectory = authorizationsFile.getAbsoluteFile().getParentFile();
// the restore directory is optional and may be null
final File restoreDirectory = properties.getRestoreDirectory();
if (restoreDirectory != null) {
// sanity check that restore directory is a directory, creating it if necessary
FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory);
// check that restore directory is not the same as the authorizations directory
if (authorizationsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
throw new AuthorizerCreationException(String.format("Authorizations file directory '%s' is the same as restore directory '%s' ",
authorizationsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
}
// the restore copy will have same file name, but reside in a different directory
restoreAuthorizationsFile = new File(restoreDirectory, authorizationsFile.getName());
try {
// sync the primary copy with the restore copy
FileUtils.syncWithRestore(authorizationsFile, restoreAuthorizationsFile, logger);
} catch (final IOException | IllegalStateException ioe) {
throw new AuthorizerCreationException(ioe);
}
}
// extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
// get the value of the initial admin identity
final PropertyValue initialAdminIdentityProp = configurationContext.getProperty(PROP_INITIAL_ADMIN_IDENTITY);
initialAdminIdentity = initialAdminIdentityProp.isSet() ? IdentityMappingUtil.mapIdentity(initialAdminIdentityProp.getValue(), identityMappings) : null;
// get the value of the legacy authorized users file
final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null;
// extract any node identities
nodeIdentities = new HashSet<>();
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = NODE_IDENTITY_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
nodeIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
}
}
// load the authorizations
load();
// if we've copied the authorizations file to a restore directory synchronize it
if (restoreAuthorizationsFile != null) {
FileUtils.copyFile(authorizationsFile, restoreAuthorizationsFile, false, false, logger);
}
logger.info(String.format("Authorizations file loaded at %s", new Date().toString()));
} catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public UserGroupProvider getUserGroupProvider() {
return userGroupProvider;
}
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
return authorizationsHolder.get().getAllPolicies();
}
@Override
public synchronized AccessPolicy addAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
// create the new JAXB Policy
final Policy policy = createJAXBPolicy(accessPolicy);
// add the new Policy to the top-level list of policies
final AuthorizationsHolder holder = authorizationsHolder.get();
final Authorizations authorizations = holder.getAuthorizations();
authorizations.getPolicies().getPolicy().add(policy);
saveAndRefreshHolder(authorizations);
return authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
}
@Override
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
final AuthorizationsHolder holder = authorizationsHolder.get();
return holder.getPoliciesById().get(identifier);
}
@Override
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
return authorizationsHolder.get().getAccessPolicy(resourceIdentifier, action);
}
@Override
public synchronized AccessPolicy updateAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
final AuthorizationsHolder holder = this.authorizationsHolder.get();
final Authorizations authorizations = holder.getAuthorizations();
// try to find an existing Authorization that matches the policy id
Policy updatePolicy = null;
for (Policy policy : authorizations.getPolicies().getPolicy()) {
if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
updatePolicy = policy;
break;
}
}
// no matching Policy so return null
if (updatePolicy == null) {
return null;
}
// update the Policy, save, reload, and return
transferUsersAndGroups(accessPolicy, updatePolicy);
saveAndRefreshHolder(authorizations);
return this.authorizationsHolder.get().getPoliciesById().get(accessPolicy.getIdentifier());
}
@Override
public synchronized AccessPolicy deleteAccessPolicy(AccessPolicy accessPolicy) throws AuthorizationAccessException {
if (accessPolicy == null) {
throw new IllegalArgumentException("AccessPolicy cannot be null");
}
final AuthorizationsHolder holder = this.authorizationsHolder.get();
final Authorizations authorizations = holder.getAuthorizations();
// find the matching Policy and remove it
boolean deletedPolicy = false;
Iterator<Policy> policyIter = authorizations.getPolicies().getPolicy().iterator();
while (policyIter.hasNext()) {
final Policy policy = policyIter.next();
if (policy.getIdentifier().equals(accessPolicy.getIdentifier())) {
policyIter.remove();
deletedPolicy = true;
break;
}
}
// never found a matching Policy so return null
if (!deletedPolicy) {
return null;
}
saveAndRefreshHolder(authorizations);
return accessPolicy;
}
AuthorizationsHolder getAuthorizationsHolder() {
return authorizationsHolder.get();
}
@AuthorizerContext
public void setNiFiProperties(NiFiProperties properties) {
this.properties = properties;
}
@Override
public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
parsePolicies(fingerprint).forEach(policy -> addAccessPolicy(policy));
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
try {
// ensure we can understand the proposed fingerprint
parsePolicies(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
}
// ensure we are in a proper state to inherit the fingerprint
if (!getAccessPolicies().isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current access policies is not empty.");
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
final List<AccessPolicy> policies = new ArrayList<>(getAccessPolicies());
Collections.sort(policies, Comparator.comparing(AccessPolicy::getIdentifier));
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("accessPolicies");
for (AccessPolicy policy : policies) {
writePolicy(writer, policy);
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
private List<AccessPolicy> parsePolicies(final String fingerprint) {
final List<AccessPolicy> policies = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
// parse all the policies and add them to the current access policy provider
NodeList policyNodes = rootElement.getElementsByTagName(POLICY_ELEMENT);
for (int i = 0; i < policyNodes.getLength(); i++) {
Node policyNode = policyNodes.item(i);
policies.add(parsePolicy((Element) policyNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return policies;
}
private AccessPolicy parsePolicy(final Element element) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.resource(element.getAttribute(RESOURCE_ATTR));
final String actions = element.getAttribute(ACTIONS_ATTR);
if (actions.equals(RequestAction.READ.name())) {
builder.action(RequestAction.READ);
} else if (actions.equals(RequestAction.WRITE.name())) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + actions);
}
NodeList policyUsers = element.getElementsByTagName(POLICY_USER_ELEMENT);
for (int i=0; i < policyUsers.getLength(); i++) {
Element policyUserNode = (Element) policyUsers.item(i);
builder.addUser(policyUserNode.getAttribute(IDENTIFIER_ATTR));
}
NodeList policyGroups = element.getElementsByTagName(POLICY_GROUP_ELEMENT);
for (int i=0; i < policyGroups.getLength(); i++) {
Element policyGroupNode = (Element) policyGroups.item(i);
builder.addGroup(policyGroupNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
private void writePolicy(final XMLStreamWriter writer, final AccessPolicy policy) throws XMLStreamException {
// sort the users for the policy
List<String> policyUsers = new ArrayList<>(policy.getUsers());
Collections.sort(policyUsers);
// sort the groups for this policy
List<String> policyGroups = new ArrayList<>(policy.getGroups());
Collections.sort(policyGroups);
writer.writeStartElement(POLICY_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policy.getIdentifier());
writer.writeAttribute(RESOURCE_ATTR, policy.getResource());
writer.writeAttribute(ACTIONS_ATTR, policy.getAction().name());
for (String policyUser : policyUsers) {
writer.writeStartElement(POLICY_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyUser);
writer.writeEndElement();
}
for (String policyGroup : policyGroups) {
writer.writeStartElement(POLICY_GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, policyGroup);
writer.writeEndElement();
}
writer.writeEndElement();
}
/**
* Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
*
* @throws JAXBException Unable to reload the authorized users file
* @throws IOException Unable to sync file with restore
* @throws IllegalStateException Unable to sync file with restore
*/
private synchronized void load() throws JAXBException, IOException, IllegalStateException, SAXException {
// attempt to unmarshal
final Authorizations authorizations = unmarshallAuthorizations();
if (authorizations.getPolicies() == null) {
authorizations.setPolicies(new Policies());
}
final AuthorizationsHolder authorizationsHolder = new AuthorizationsHolder(authorizations);
final boolean emptyAuthorizations = authorizationsHolder.getAllPolicies().isEmpty();
final boolean hasInitialAdminIdentity = (initialAdminIdentity != null && !StringUtils.isBlank(initialAdminIdentity));
final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
// if we are starting fresh then we might need to populate an initial admin or convert legacy users
if (emptyAuthorizations) {
parseFlow();
if (hasInitialAdminIdentity && hasLegacyAuthorizedUsers) {
throw new AuthorizerCreationException("Cannot provide an Initial Admin Identity and a Legacy Authorized Users File");
} else if (hasInitialAdminIdentity) {
logger.info("Populating authorizations for Initial Admin: " + initialAdminIdentity);
populateInitialAdmin(authorizations);
} else if (hasLegacyAuthorizedUsers) {
logger.info("Converting " + legacyAuthorizedUsersFile + " to new authorizations model");
convertLegacyAuthorizedUsers(authorizations);
}
populateNodes(authorizations);
// save any changes that were made and repopulate the holder
saveAndRefreshHolder(authorizations);
} else {
this.authorizationsHolder.set(authorizationsHolder);
}
}
private void saveAuthorizations(final Authorizations authorizations) throws JAXBException {
final Marshaller marshaller = JAXB_AUTHORIZATIONS_CONTEXT.createMarshaller();
marshaller.setSchema(authorizationsSchema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(authorizations, authorizationsFile);
}
private Authorizations unmarshallAuthorizations() throws JAXBException {
final Unmarshaller unmarshaller = JAXB_AUTHORIZATIONS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(authorizationsSchema);
final JAXBElement<Authorizations> element = unmarshaller.unmarshal(new StreamSource(authorizationsFile), Authorizations.class);
return element.getValue();
}
/**
* Try to parse the flow configuration file to extract the root group id and port information.
*
* @throws SAXException if an error occurs creating the schema
*/
private void parseFlow() throws SAXException {
final FlowParser flowParser = new FlowParser();
final FlowInfo flowInfo = flowParser.parse(properties.getFlowConfigurationFile());
if (flowInfo != null) {
rootGroupId = flowInfo.getRootGroupId();
ports = flowInfo.getPorts() == null ? new ArrayList<>() : flowInfo.getPorts();
}
}
/**
* Creates the initial admin user and policies for access the flow and managing users and policies.
*/
private void populateInitialAdmin(final Authorizations authorizations) {
final User initialAdmin = userGroupProvider.getUserByIdentity(initialAdminIdentity);
if (initialAdmin == null) {
throw new AuthorizerCreationException("Unable to locate initial admin " + initialAdminIdentity + " to seed policies");
}
// grant the user read access to the /flow resource
addUserToAccessPolicy(authorizations, ResourceType.Flow.getValue(), initialAdmin.getIdentifier(), READ_CODE);
// grant the user read access to the root process group resource
if (rootGroupId != null) {
addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE);
addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, initialAdmin.getIdentifier(), WRITE_CODE);
}
// grant the user write to restricted components
addUserToAccessPolicy(authorizations, ResourceType.RestrictedComponents.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
// grant the user read/write access to the /tenants resource
addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.Tenant.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
// grant the user read/write access to the /policies resource
addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.Policy.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
// grant the user read/write access to the /controller resource
addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.Controller.getValue(), initialAdmin.getIdentifier(), WRITE_CODE);
}
/**
* Creates a user for each node and gives the nodes write permission to /proxy.
*
* @param authorizations the overall authorizations
*/
private void populateNodes(Authorizations authorizations) {
for (String nodeIdentity : nodeIdentities) {
final User node = userGroupProvider.getUserByIdentity(nodeIdentity);
if (node == null) {
throw new AuthorizerCreationException("Unable to locate node " + nodeIdentity + " to seed policies.");
}
// grant access to the proxy resource
addUserToAccessPolicy(authorizations, ResourceType.Proxy.getValue(), node.getIdentifier(), WRITE_CODE);
// grant the user read/write access data of the root group
if (rootGroupId != null) {
addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), READ_CODE);
addUserToAccessPolicy(authorizations, ResourceType.Data.getValue() + ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, node.getIdentifier(), WRITE_CODE);
}
}
}
/**
* Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
*
* @param authorizations the current Authorizations instance that policies will be added to
* @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
* @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
*/
private void convertLegacyAuthorizedUsers(final Authorizations authorizations) throws AuthorizerCreationException, JAXBException {
final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
if (!authorizedUsersFile.exists()) {
throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
}
final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(usersSchema);
final JAXBElement<Users> element = unmarshaller.unmarshal(
new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class);
final org.apache.nifi.user.generated.Users users = element.getValue();
if (users.getUser().isEmpty()) {
logger.info("Legacy Authorized Users File contained no users, nothing to convert");
return;
}
// get all the user DNs into a list
List<String> userIdentities = new ArrayList<>();
for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
userIdentities.add(IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings));
}
// sort the list and pull out the first identity
Collections.sort(userIdentities);
final String seedIdentity = userIdentities.get(0);
// create mapping from Role to access policies
final Map<Role,Set<RoleAccessPolicy>> roleAccessPolicies = RoleAccessPolicy.getMappings(rootGroupId);
final List<Policy> allPolicies = new ArrayList<>();
for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
// create the identifier of the new user based on the DN
final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
final User user = userGroupProvider.getUserByIdentity(legacyUserDn);
if (user == null) {
throw new AuthorizerCreationException("Unable to locate legacy user " + legacyUserDn + " to seed policies.");
}
// create policies based on the given role
for (org.apache.nifi.user.generated.Role jaxbRole : legacyUser.getRole()) {
Role role = Role.valueOf(jaxbRole.getName());
Set<RoleAccessPolicy> policies = roleAccessPolicies.get(role);
for (RoleAccessPolicy roleAccessPolicy : policies) {
// get the matching policy, or create a new one
Policy policy = getOrCreatePolicy(
allPolicies,
seedIdentity,
roleAccessPolicy.getResource(),
roleAccessPolicy.getAction());
// add the user to the policy if it doesn't exist
addUserToPolicy(user.getIdentifier(), policy);
}
}
}
// convert any access controls on ports to the appropriate policies
for (PortDTO portDTO : ports) {
final Resource resource;
if (portDTO.getType() != null && portDTO.getType().equals("inputPort")) {
resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.InputPort, portDTO.getId(), portDTO.getName()));
} else {
resource = ResourceFactory.getDataTransferResource(ResourceFactory.getComponentResource(ResourceType.OutputPort, portDTO.getId(), portDTO.getName()));
}
if (portDTO.getUserAccessControl() != null) {
for (String userAccessControl : portDTO.getUserAccessControl()) {
// need to perform the identity mapping on the access control so it matches the identities in the User objects
final String mappedUserAccessControl = IdentityMappingUtil.mapIdentity(userAccessControl, identityMappings);
final User foundUser = userGroupProvider.getUserByIdentity(mappedUserAccessControl);
// couldn't find the user matching the access control so log a warning and skip
if (foundUser == null) {
logger.warn("Found port with user access control for {} but no user exists with this identity, skipping...",
new Object[] {mappedUserAccessControl});
continue;
}
// we found the user so create the appropriate policy and add the user to it
Policy policy = getOrCreatePolicy(
allPolicies,
seedIdentity,
resource.getIdentifier(),
WRITE_CODE);
addUserToPolicy(foundUser.getIdentifier(), policy);
}
}
if (portDTO.getGroupAccessControl() != null) {
for (String groupAccessControl : portDTO.getGroupAccessControl()) {
// find a group where the name is the groupAccessControl
Group foundGroup = null;
for (Group group : userGroupProvider.getGroups()) {
if (group.getName().equals(groupAccessControl)) {
foundGroup = group;
break;
}
}
// couldn't find the group matching the access control so log a warning and skip
if (foundGroup == null) {
logger.warn("Found port with group access control for {} but no group exists with this name, skipping...",
new Object[] {groupAccessControl});
continue;
}
// we found the group so create the appropriate policy and add all the users to it
Policy policy = getOrCreatePolicy(
allPolicies,
seedIdentity,
resource.getIdentifier(),
WRITE_CODE);
addGroupToPolicy(IdentifierUtil.getIdentifier(groupAccessControl), policy);
}
}
}
authorizations.getPolicies().getPolicy().addAll(allPolicies);
}
/**
* Creates and adds an access policy for the given resource, identity, and actions to the specified authorizations.
*
* @param authorizations the Authorizations instance to add the policy to
* @param resource the resource for the policy
* @param userIdentifier the identifier for the user to add to the policy
* @param action the action for the policy
*/
private void addUserToAccessPolicy(final Authorizations authorizations, final String resource, final String userIdentifier, final String action) {
// first try to find an existing policy for the given resource and action
Policy foundPolicy = null;
for (Policy policy : authorizations.getPolicies().getPolicy()) {
if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
foundPolicy = policy;
break;
}
}
if (foundPolicy == null) {
// if we didn't find an existing policy create a new one
final String uuidSeed = resource + action;
final AccessPolicy.Builder builder = new AccessPolicy.Builder()
.identifierGenerateFromSeed(uuidSeed)
.resource(resource)
.addUser(userIdentifier);
if (action.equals(READ_CODE)) {
builder.action(RequestAction.READ);
} else if (action.equals(WRITE_CODE)) {
builder.action(RequestAction.WRITE);
} else {
throw new IllegalStateException("Unknown Policy Action: " + action);
}
final AccessPolicy accessPolicy = builder.build();
final Policy jaxbPolicy = createJAXBPolicy(accessPolicy);
authorizations.getPolicies().getPolicy().add(jaxbPolicy);
} else {
// otherwise add the user to the existing policy
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
foundPolicy.getUser().add(policyUser);
}
}
private Policy createJAXBPolicy(final AccessPolicy accessPolicy) {
final Policy policy = new Policy();
policy.setIdentifier(accessPolicy.getIdentifier());
policy.setResource(accessPolicy.getResource());
switch (accessPolicy.getAction()) {
case READ:
policy.setAction(READ_CODE);
break;
case WRITE:
policy.setAction(WRITE_CODE);
break;
default:
break;
}
transferUsersAndGroups(accessPolicy, policy);
return policy;
}
/**
* Sets the given Policy to the state of the provided AccessPolicy. Users and Groups will be cleared and
* set to match the AccessPolicy, the resource and action will be set to match the AccessPolicy.
*
* Does not set the identifier.
*
* @param accessPolicy the AccessPolicy to transfer state from
* @param policy the Policy to transfer state to
*/
private void transferUsersAndGroups(AccessPolicy accessPolicy, Policy policy) {
// add users to the policy
policy.getUser().clear();
for (String userIdentifier : accessPolicy.getUsers()) {
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
policy.getUser().add(policyUser);
}
// add groups to the policy
policy.getGroup().clear();
for (String groupIdentifier : accessPolicy.getGroups()) {
Policy.Group policyGroup = new Policy.Group();
policyGroup.setIdentifier(groupIdentifier);
policy.getGroup().add(policyGroup);
}
}
/**
* Adds the given user identifier to the policy if it doesn't already exist.
*
* @param userIdentifier a user identifier
* @param policy a policy to add the user to
*/
private void addUserToPolicy(final String userIdentifier, final Policy policy) {
// determine if the user already exists in the policy
boolean userExists = false;
for (Policy.User policyUser : policy.getUser()) {
if (policyUser.getIdentifier().equals(userIdentifier)) {
userExists = true;
break;
}
}
// add the user to the policy if doesn't already exist
if (!userExists) {
Policy.User policyUser = new Policy.User();
policyUser.setIdentifier(userIdentifier);
policy.getUser().add(policyUser);
}
}
/**
* Adds the given group identifier to the policy if it doesn't already exist.
*
* @param groupIdentifier a group identifier
* @param policy a policy to add the user to
*/
private void addGroupToPolicy(final String groupIdentifier, final Policy policy) {
// determine if the group already exists in the policy
boolean groupExists = false;
for (Policy.Group policyGroup : policy.getGroup()) {
if (policyGroup.getIdentifier().equals(groupIdentifier)) {
groupExists = true;
break;
}
}
// add the group to the policy if doesn't already exist
if (!groupExists) {
Policy.Group policyGroup = new Policy.Group();
policyGroup.setIdentifier(groupIdentifier);
policy.getGroup().add(policyGroup);
}
}
/**
* Finds the Policy matching the resource and action, or creates a new one and adds it to the list of policies.
*
* @param policies the policies to search through
* @param seedIdentity the seedIdentity to use when creating identifiers for new policies
* @param resource the resource for the policy
* @param action the action string for the police (R or RW)
* @return the matching policy or a new policy
*/
private Policy getOrCreatePolicy(final List<Policy> policies, final String seedIdentity, final String resource, final String action) {
Policy foundPolicy = null;
// try to find a policy with the same resource and actions
for (Policy policy : policies) {
if (policy.getResource().equals(resource) && policy.getAction().equals(action)) {
foundPolicy = policy;
break;
}
}
// if a matching policy wasn't found then create one
if (foundPolicy == null) {
final String uuidSeed = resource + action + seedIdentity;
final String policyIdentifier = IdentifierUtil.getIdentifier(uuidSeed);
foundPolicy = new Policy();
foundPolicy.setIdentifier(policyIdentifier);
foundPolicy.setResource(resource);
foundPolicy.setAction(action);
policies.add(foundPolicy);
}
return foundPolicy;
}
/**
* Saves the Authorizations instance by marshalling to a file, then re-populates the
* in-memory data structures and sets the new holder.
*
* Synchronized to ensure only one thread writes the file at a time.
*
* @param authorizations the authorizations to save and populate from
* @throws AuthorizationAccessException if an error occurs saving the authorizations
*/
private synchronized void saveAndRefreshHolder(final Authorizations authorizations) throws AuthorizationAccessException {
try {
saveAuthorizations(authorizations);
this.authorizationsHolder.set(new AuthorizationsHolder(authorizations));
} catch (JAXBException e) {
throw new AuthorizationAccessException("Unable to save Authorizations", e);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
}

View File

@ -0,0 +1,820 @@
/*
* 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.annotation.AuthorizerContext;
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.authorization.file.tenants.generated.Groups;
import org.apache.nifi.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.authorization.file.tenants.generated.Users;
import org.apache.nifi.authorization.util.IdentityMapping;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.file.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FileUserGroupProvider implements ConfigurableUserGroupProvider {
private static final Logger logger = LoggerFactory.getLogger(FileUserGroupProvider.class);
private static final String TENANTS_XSD = "/tenants.xsd";
private static final String JAXB_TENANTS_PATH = "org.apache.nifi.authorization.file.tenants.generated";
private static final String USERS_XSD = "/legacy-users.xsd";
private static final String JAXB_USERS_PATH = "org.apache.nifi.user.generated";
private static final JAXBContext JAXB_TENANTS_CONTEXT = initializeJaxbContext(JAXB_TENANTS_PATH);
private static final JAXBContext JAXB_USERS_CONTEXT = initializeJaxbContext(JAXB_USERS_PATH);
/**
* Load the JAXBContext.
*/
private static JAXBContext initializeJaxbContext(final String contextPath) {
try {
return JAXBContext.newInstance(contextPath, FileAuthorizer.class.getClassLoader());
} catch (JAXBException e) {
throw new RuntimeException("Unable to create JAXBContext.");
}
}
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String USER_ELEMENT = "user";
private static final String GROUP_USER_ELEMENT = "groupUser";
private static final String GROUP_ELEMENT = "group";
private static final String IDENTIFIER_ATTR = "identifier";
private static final String IDENTITY_ATTR = "identity";
private static final String NAME_ATTR = "name";
static final String PROP_INITIAL_USER_IDENTITY_PREFIX = "Initial User Identity ";
static final String PROP_TENANTS_FILE = "Users File";
static final Pattern INITIAL_USER_IDENTITY_PATTERN = Pattern.compile(PROP_INITIAL_USER_IDENTITY_PREFIX + "\\S+");
private Schema usersSchema;
private Schema tenantsSchema;
private NiFiProperties properties;
private File tenantsFile;
private File restoreTenantsFile;
private String legacyAuthorizedUsersFile;
private Set<String> initialUserIdentities;
private List<IdentityMapping> identityMappings;
private final AtomicReference<UserGroupHolder> userGroupHolder = new AtomicReference<>();
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
try {
final SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
tenantsSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(TENANTS_XSD));
usersSchema = schemaFactory.newSchema(FileAuthorizer.class.getResource(USERS_XSD));
} catch (Exception e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
try {
final PropertyValue tenantsPath = configurationContext.getProperty(PROP_TENANTS_FILE);
if (StringUtils.isBlank(tenantsPath.getValue())) {
throw new AuthorizerCreationException("The users file must be specified.");
}
// get the tenants file and ensure it exists
tenantsFile = new File(tenantsPath.getValue());
if (!tenantsFile.exists()) {
logger.info("Creating new users file at {}", new Object[] {tenantsFile.getAbsolutePath()});
saveTenants(new Tenants());
}
final File tenantsFileDirectory = tenantsFile.getAbsoluteFile().getParentFile();
// the restore directory is optional and may be null
final File restoreDirectory = properties.getRestoreDirectory();
if (restoreDirectory != null) {
// sanity check that restore directory is a directory, creating it if necessary
FileUtils.ensureDirectoryExistAndCanAccess(restoreDirectory);
// check that restore directory is not the same as the user's directory
if (tenantsFileDirectory.getAbsolutePath().equals(restoreDirectory.getAbsolutePath())) {
throw new AuthorizerCreationException(String.format("Users file directory '%s' is the same as restore directory '%s' ",
tenantsFileDirectory.getAbsolutePath(), restoreDirectory.getAbsolutePath()));
}
// the restore copy will have same file name, but reside in a different directory
restoreTenantsFile = new File(restoreDirectory, tenantsFile.getName());
try {
// sync the primary copy with the restore copy
FileUtils.syncWithRestore(tenantsFile, restoreTenantsFile, logger);
} catch (final IOException | IllegalStateException ioe) {
throw new AuthorizerCreationException(ioe);
}
}
// extract the identity mappings from nifi.properties if any are provided
identityMappings = Collections.unmodifiableList(IdentityMappingUtil.getIdentityMappings(properties));
// get the value of the legacy authorized users file
final PropertyValue legacyAuthorizedUsersProp = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
legacyAuthorizedUsersFile = legacyAuthorizedUsersProp.isSet() ? legacyAuthorizedUsersProp.getValue() : null;
// extract any node identities
initialUserIdentities = new HashSet<>();
for (Map.Entry<String,String> entry : configurationContext.getProperties().entrySet()) {
Matcher matcher = INITIAL_USER_IDENTITY_PATTERN.matcher(entry.getKey());
if (matcher.matches() && !StringUtils.isBlank(entry.getValue())) {
initialUserIdentities.add(IdentityMappingUtil.mapIdentity(entry.getValue(), identityMappings));
}
}
load();
// if we've copied the authorizations file to a restore directory synchronize it
if (restoreTenantsFile != null) {
FileUtils.copyFile(tenantsFile, restoreTenantsFile, false, false, logger);
}
logger.info(String.format("Users/Groups file loaded at %s", new Date().toString()));
} catch (IOException | AuthorizerCreationException | JAXBException | IllegalStateException | SAXException e) {
throw new AuthorizerCreationException(e);
}
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
return userGroupHolder.get().getAllUsers();
}
@Override
public synchronized User addUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = createJAXBUser(user);
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
tenants.getUsers().getUser().add(jaxbUser);
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getUsersById().get(user.getIdentifier());
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
final UserGroupHolder holder = userGroupHolder.get();
return holder.getUsersById().get(identifier);
}
@Override
public synchronized User updateUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
// fine the User that needs to be updated
org.apache.nifi.authorization.file.tenants.generated.User updateUser = null;
for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
updateUser = jaxbUser;
break;
}
}
// if user wasn't found return null, otherwise update the user and save changes
if (updateUser == null) {
return null;
} else {
updateUser.setIdentity(user.getIdentity());
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getUsersById().get(user.getIdentifier());
}
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
if (identity == null) {
return null;
}
final UserGroupHolder holder = userGroupHolder.get();
return holder.getUsersByIdentity().get(identity);
}
@Override
public synchronized User deleteUser(User user) throws AuthorizationAccessException {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
final List<org.apache.nifi.authorization.file.tenants.generated.User> users = tenants.getUsers().getUser();
// for each group iterate over the user references and remove the user reference if it matches the user being deleted
for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
Iterator<org.apache.nifi.authorization.file.tenants.generated.Group.User> groupUserIter = group.getUser().iterator();
while (groupUserIter.hasNext()) {
org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = groupUserIter.next();
if (groupUser.getIdentifier().equals(user.getIdentifier())) {
groupUserIter.remove();
break;
}
}
}
// remove the actual user if it exists
boolean removedUser = false;
Iterator<org.apache.nifi.authorization.file.tenants.generated.User> iter = users.iterator();
while (iter.hasNext()) {
org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = iter.next();
if (user.getIdentifier().equals(jaxbUser.getIdentifier())) {
iter.remove();
removedUser = true;
break;
}
}
if (removedUser) {
saveAndRefreshHolder(tenants);
return user;
} else {
return null;
}
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
return userGroupHolder.get().getAllGroups();
}
@Override
public synchronized Group addGroup(Group group) throws AuthorizationAccessException {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
// determine that all users in the group exist before doing anything, throw an exception if they don't
checkGroupUsers(group, tenants.getUsers().getUser());
// create a new JAXB Group based on the incoming Group
final org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
jaxbGroup.setIdentifier(group.getIdentifier());
jaxbGroup.setName(group.getName());
// add each user to the group
for (String groupUser : group.getUsers()) {
org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
jaxbGroupUser.setIdentifier(groupUser);
jaxbGroup.getUser().add(jaxbGroupUser);
}
tenants.getGroups().getGroup().add(jaxbGroup);
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
if (identifier == null) {
return null;
}
return userGroupHolder.get().getGroupsById().get(identifier);
}
@Override
public UserAndGroups getUserAndGroups(final String identity) throws AuthorizationAccessException {
final UserGroupHolder holder = userGroupHolder.get();
final User user = holder.getUser(identity);
final Set<Group> groups = holder.getGroups(identity);
return new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return groups;
}
};
}
@Override
public synchronized Group updateGroup(Group group) throws AuthorizationAccessException {
if (group == null) {
throw new IllegalArgumentException("Group cannot be null");
}
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
// find the group that needs to be update
org.apache.nifi.authorization.file.tenants.generated.Group updateGroup = null;
for (org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup : tenants.getGroups().getGroup()) {
if (jaxbGroup.getIdentifier().equals(group.getIdentifier())) {
updateGroup = jaxbGroup;
break;
}
}
// if the group wasn't found return null, otherwise update the group and save changes
if (updateGroup == null) {
return null;
}
// reset the list of users and add each user to the group
updateGroup.getUser().clear();
for (String groupUser : group.getUsers()) {
org.apache.nifi.authorization.file.tenants.generated.Group.User jaxbGroupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
jaxbGroupUser.setIdentifier(groupUser);
updateGroup.getUser().add(jaxbGroupUser);
}
updateGroup.setName(group.getName());
saveAndRefreshHolder(tenants);
return userGroupHolder.get().getGroupsById().get(group.getIdentifier());
}
@Override
public synchronized Group deleteGroup(Group group) throws AuthorizationAccessException {
final UserGroupHolder holder = userGroupHolder.get();
final Tenants tenants = holder.getTenants();
final List<org.apache.nifi.authorization.file.tenants.generated.Group> groups = tenants.getGroups().getGroup();
// now remove the actual group from the top-level list of groups
boolean removedGroup = false;
Iterator<org.apache.nifi.authorization.file.tenants.generated.Group> iter = groups.iterator();
while (iter.hasNext()) {
org.apache.nifi.authorization.file.tenants.generated.Group jaxbGroup = iter.next();
if (group.getIdentifier().equals(jaxbGroup.getIdentifier())) {
iter.remove();
removedGroup = true;
break;
}
}
if (removedGroup) {
saveAndRefreshHolder(tenants);
return group;
} else {
return null;
}
}
UserGroupHolder getUserGroupHolder() {
return userGroupHolder.get();
}
@AuthorizerContext
public void setNiFiProperties(NiFiProperties properties) {
this.properties = properties;
}
@Override
public synchronized void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
final UsersAndGroups usersAndGroups = parseUsersAndGroups(fingerprint);
usersAndGroups.getUsers().forEach(user -> addUser(user));
usersAndGroups.getGroups().forEach(group -> addGroup(group));
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException {
try {
// ensure we understand the proposed fingerprint
parseUsersAndGroups(proposedFingerprint);
} catch (final AuthorizationAccessException e) {
throw new UninheritableAuthorizationsException("Unable to parse the proposed fingerprint: " + e);
}
final UserGroupHolder usersAndGroups = userGroupHolder.get();
// ensure we are in a proper state to inherit the fingerprint
if (!usersAndGroups.getAllUsers().isEmpty() || !usersAndGroups.getAllGroups().isEmpty()) {
throw new UninheritableAuthorizationsException("Proposed fingerprint is not inheritable because the current users and groups is not empty.");
}
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
final UserGroupHolder usersAndGroups = userGroupHolder.get();
final List<User> users = new ArrayList<>(usersAndGroups.getAllUsers());
Collections.sort(users, Comparator.comparing(User::getIdentifier));
final List<Group> groups = new ArrayList<>(usersAndGroups.getAllGroups());
Collections.sort(groups, Comparator.comparing(Group::getIdentifier));
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("tenants");
for (User user : users) {
writeUser(writer, user);
}
for (Group group : groups) {
writeGroup(writer, group);
}
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
private UsersAndGroups parseUsersAndGroups(final String fingerprint) {
final List<User> users = new ArrayList<>();
final List<Group> groups = new ArrayList<>();
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
// parse all the users and add them to the current user group provider
NodeList userNodes = rootElement.getElementsByTagName(USER_ELEMENT);
for (int i=0; i < userNodes.getLength(); i++) {
Node userNode = userNodes.item(i);
users.add(parseUser((Element) userNode));
}
// parse all the groups and add them to the current user group provider
NodeList groupNodes = rootElement.getElementsByTagName(GROUP_ELEMENT);
for (int i=0; i < groupNodes.getLength(); i++) {
Node groupNode = groupNodes.item(i);
groups.add(parseGroup((Element) groupNode));
}
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
return new UsersAndGroups(users, groups);
}
private User parseUser(final Element element) {
final User.Builder builder = new User.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.identity(element.getAttribute(IDENTITY_ATTR));
return builder.build();
}
private Group parseGroup(final Element element) {
final Group.Builder builder = new Group.Builder()
.identifier(element.getAttribute(IDENTIFIER_ATTR))
.name(element.getAttribute(NAME_ATTR));
NodeList groupUsers = element.getElementsByTagName(GROUP_USER_ELEMENT);
for (int i=0; i < groupUsers.getLength(); i++) {
Element groupUserNode = (Element) groupUsers.item(i);
builder.addUser(groupUserNode.getAttribute(IDENTIFIER_ATTR));
}
return builder.build();
}
private void writeUser(final XMLStreamWriter writer, final User user) throws XMLStreamException {
writer.writeStartElement(USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user.getIdentifier());
writer.writeAttribute(IDENTITY_ATTR, user.getIdentity());
writer.writeEndElement();
}
private void writeGroup(final XMLStreamWriter writer, final Group group) throws XMLStreamException {
List<String> users = new ArrayList<>(group.getUsers());
Collections.sort(users);
writer.writeStartElement(GROUP_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, group.getIdentifier());
writer.writeAttribute(NAME_ATTR, group.getName());
for (String user : users) {
writer.writeStartElement(GROUP_USER_ELEMENT);
writer.writeAttribute(IDENTIFIER_ATTR, user);
writer.writeEndElement();
}
writer.writeEndElement();
}
private org.apache.nifi.authorization.file.tenants.generated.User createJAXBUser(User user) {
final org.apache.nifi.authorization.file.tenants.generated.User jaxbUser = new org.apache.nifi.authorization.file.tenants.generated.User();
jaxbUser.setIdentifier(user.getIdentifier());
jaxbUser.setIdentity(user.getIdentity());
return jaxbUser;
}
private Set<org.apache.nifi.authorization.file.tenants.generated.User> checkGroupUsers(final Group group, final List<org.apache.nifi.authorization.file.tenants.generated.User> users) {
final Set<org.apache.nifi.authorization.file.tenants.generated.User> jaxbUsers = new HashSet<>();
for (String groupUser : group.getUsers()) {
boolean found = false;
for (org.apache.nifi.authorization.file.tenants.generated.User jaxbUser : users) {
if (jaxbUser.getIdentifier().equals(groupUser)) {
jaxbUsers.add(jaxbUser);
found = true;
break;
}
}
if (!found) {
throw new IllegalStateException("Unable to add group because user " + groupUser + " does not exist");
}
}
return jaxbUsers;
}
/**
* Loads the authorizations file and populates the AuthorizationsHolder, only called during start-up.
*
* @throws JAXBException Unable to reload the authorized users file
* @throws IllegalStateException Unable to sync file with restore
* @throws SAXException Unable to unmarshall tenants
*/
private synchronized void load() throws JAXBException, IllegalStateException, SAXException {
final Tenants tenants = unmarshallTenants();
if (tenants.getUsers() == null) {
tenants.setUsers(new Users());
}
if (tenants.getGroups() == null) {
tenants.setGroups(new Groups());
}
final UserGroupHolder userGroupHolder = new UserGroupHolder(tenants);
final boolean emptyTenants = userGroupHolder.getAllUsers().isEmpty() && userGroupHolder.getAllGroups().isEmpty();
final boolean hasLegacyAuthorizedUsers = (legacyAuthorizedUsersFile != null && !StringUtils.isBlank(legacyAuthorizedUsersFile));
if (emptyTenants) {
if (hasLegacyAuthorizedUsers) {
logger.info("Loading users from legacy model " + legacyAuthorizedUsersFile + " into new users file.");
convertLegacyAuthorizedUsers(tenants);
}
populateInitialUsers(tenants);
// save any changes that were made and repopulate the holder
saveAndRefreshHolder(tenants);
} else {
this.userGroupHolder.set(userGroupHolder);
}
}
private void saveTenants(final Tenants tenants) throws JAXBException {
final Marshaller marshaller = JAXB_TENANTS_CONTEXT.createMarshaller();
marshaller.setSchema(tenantsSchema);
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.marshal(tenants, tenantsFile);
}
private Tenants unmarshallTenants() throws JAXBException {
final Unmarshaller unmarshaller = JAXB_TENANTS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(tenantsSchema);
final JAXBElement<Tenants> element = unmarshaller.unmarshal(new StreamSource(tenantsFile), Tenants.class);
return element.getValue();
}
private void populateInitialUsers(final Tenants tenants) {
for (String initialUserIdentity : initialUserIdentities) {
getOrCreateUser(tenants, initialUserIdentity);
}
}
/**
* Unmarshalls an existing authorized-users.xml and converts the object model to the new model.
*
* @param tenants the current Tenants instance users and groups will be added to
* @throws AuthorizerCreationException if the legacy authorized users file that was provided does not exist
* @throws JAXBException if the legacy authorized users file that was provided could not be unmarshalled
*/
private void convertLegacyAuthorizedUsers(final Tenants tenants) throws AuthorizerCreationException, JAXBException {
final File authorizedUsersFile = new File(legacyAuthorizedUsersFile);
if (!authorizedUsersFile.exists()) {
throw new AuthorizerCreationException("Legacy Authorized Users File '" + legacyAuthorizedUsersFile + "' does not exists");
}
final Unmarshaller unmarshaller = JAXB_USERS_CONTEXT.createUnmarshaller();
unmarshaller.setSchema(usersSchema);
final JAXBElement<org.apache.nifi.user.generated.Users> element = unmarshaller.unmarshal(
new StreamSource(authorizedUsersFile), org.apache.nifi.user.generated.Users.class);
final org.apache.nifi.user.generated.Users users = element.getValue();
if (users.getUser().isEmpty()) {
logger.info("Legacy Authorized Users File contained no users, nothing to convert");
return;
}
for (org.apache.nifi.user.generated.User legacyUser : users.getUser()) {
// create the identifier of the new user based on the DN
final String legacyUserDn = IdentityMappingUtil.mapIdentity(legacyUser.getDn(), identityMappings);
org.apache.nifi.authorization.file.tenants.generated.User user = getOrCreateUser(tenants, legacyUserDn);
// if there was a group name find or create the group and add the user to it
org.apache.nifi.authorization.file.tenants.generated.Group group = getOrCreateGroup(tenants, legacyUser.getGroup());
if (group != null) {
org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser = new org.apache.nifi.authorization.file.tenants.generated.Group.User();
groupUser.setIdentifier(user.getIdentifier());
group.getUser().add(groupUser);
}
}
}
/**
* Finds the User with the given identity, or creates a new one and adds it to the Tenants.
*
* @param tenants the Tenants reference
* @param userIdentity the user identity to find or create
* @return the User from Tenants with the given identity, or a new instance that was added to Tenants
*/
private org.apache.nifi.authorization.file.tenants.generated.User getOrCreateUser(final Tenants tenants, final String userIdentity) {
if (StringUtils.isBlank(userIdentity)) {
return null;
}
org.apache.nifi.authorization.file.tenants.generated.User foundUser = null;
for (org.apache.nifi.authorization.file.tenants.generated.User user : tenants.getUsers().getUser()) {
if (user.getIdentity().equals(userIdentity)) {
foundUser = user;
break;
}
}
if (foundUser == null) {
final String userIdentifier = IdentifierUtil.getIdentifier(userIdentity);
foundUser = new org.apache.nifi.authorization.file.tenants.generated.User();
foundUser.setIdentifier(userIdentifier);
foundUser.setIdentity(userIdentity);
tenants.getUsers().getUser().add(foundUser);
}
return foundUser;
}
/**
* Finds the Group with the given name, or creates a new one and adds it to Tenants.
*
* @param tenants the Tenants reference
* @param groupName the name of the group to look for
* @return the Group from Tenants with the given name, or a new instance that was added to Tenants
*/
private org.apache.nifi.authorization.file.tenants.generated.Group getOrCreateGroup(final Tenants tenants, final String groupName) {
if (StringUtils.isBlank(groupName)) {
return null;
}
org.apache.nifi.authorization.file.tenants.generated.Group foundGroup = null;
for (org.apache.nifi.authorization.file.tenants.generated.Group group : tenants.getGroups().getGroup()) {
if (group.getName().equals(groupName)) {
foundGroup = group;
break;
}
}
if (foundGroup == null) {
final String newGroupIdentifier = IdentifierUtil.getIdentifier(groupName);
foundGroup = new org.apache.nifi.authorization.file.tenants.generated.Group();
foundGroup.setIdentifier(newGroupIdentifier);
foundGroup.setName(groupName);
tenants.getGroups().getGroup().add(foundGroup);
}
return foundGroup;
}
/**
* Saves the Authorizations instance by marshalling to a file, then re-populates the
* in-memory data structures and sets the new holder.
*
* Synchronized to ensure only one thread writes the file at a time.
*
* @param tenants the tenants to save and populate from
* @throws AuthorizationAccessException if an error occurs saving the authorizations
*/
private synchronized void saveAndRefreshHolder(final Tenants tenants) throws AuthorizationAccessException {
try {
saveTenants(tenants);
this.userGroupHolder.set(new UserGroupHolder(tenants));
} catch (JAXBException e) {
throw new AuthorizationAccessException("Unable to save Authorizations", e);
}
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
private static class UsersAndGroups {
final List<User> users;
final List<Group> groups;
public UsersAndGroups(List<User> users, List<Group> groups) {
this.users = users;
this.groups = groups;
}
public List<User> getUsers() {
return users;
}
public List<Group> getGroups() {
return groups;
}
}
}

View File

@ -0,0 +1,35 @@
/*
* 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.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.UUID;
public final class IdentifierUtil {
static String getIdentifier(final String seed) {
if (StringUtils.isBlank(seed)) {
return null;
}
return UUID.nameUUIDFromBytes(seed.getBytes(StandardCharsets.UTF_8)).toString();
}
private IdentifierUtil() {}
}

View File

@ -0,0 +1,239 @@
/*
* 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.file.tenants.generated.Groups;
import org.apache.nifi.authorization.file.tenants.generated.Tenants;
import org.apache.nifi.authorization.file.tenants.generated.Users;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* A holder to provide atomic access to user group data structures.
*/
public class UserGroupHolder {
private final Tenants tenants;
private final Set<User> allUsers;
private final Map<String,User> usersById;
private final Map<String,User> usersByIdentity;
private final Set<Group> allGroups;
private final Map<String,Group> groupsById;
private final Map<String, Set<Group>> groupsByUserIdentity;
/**
* Creates a new holder and populates all convenience data structures.
*
* @param tenants the current tenants instance
*/
public UserGroupHolder(final Tenants tenants) {
this.tenants = tenants;
// load all users
final Users users = tenants.getUsers();
final Set<User> allUsers = Collections.unmodifiableSet(createUsers(users));
// load all groups
final Groups groups = tenants.getGroups();
final Set<Group> allGroups = Collections.unmodifiableSet(createGroups(groups, users));
// create a convenience map to retrieve a user by id
final Map<String, User> userByIdMap = Collections.unmodifiableMap(createUserByIdMap(allUsers));
// create a convenience map to retrieve a user by identity
final Map<String, User> userByIdentityMap = Collections.unmodifiableMap(createUserByIdentityMap(allUsers));
// create a convenience map to retrieve a group by id
final Map<String, Group> groupByIdMap = Collections.unmodifiableMap(createGroupByIdMap(allGroups));
// create a convenience map to retrieve the groups for a user identity
final Map<String, Set<Group>> groupsByUserIdentityMap = Collections.unmodifiableMap(createGroupsByUserIdentityMap(allGroups, allUsers));
// set all the holders
this.allUsers = allUsers;
this.allGroups = allGroups;
this.usersById = userByIdMap;
this.usersByIdentity = userByIdentityMap;
this.groupsById = groupByIdMap;
this.groupsByUserIdentity = groupsByUserIdentityMap;
}
/**
* Creates a set of Users from the JAXB Users.
*
* @param users the JAXB Users
* @return a set of API Users matching the provided JAXB Users
*/
private Set<User> createUsers(Users users) {
Set<User> allUsers = new HashSet<>();
if (users == null || users.getUser() == null) {
return allUsers;
}
for (org.apache.nifi.authorization.file.tenants.generated.User user : users.getUser()) {
final User.Builder builder = new User.Builder()
.identity(user.getIdentity())
.identifier(user.getIdentifier());
allUsers.add(builder.build());
}
return allUsers;
}
/**
* Creates a set of Groups from the JAXB Groups.
*
* @param groups the JAXB Groups
* @return a set of API Groups matching the provided JAXB Groups
*/
private Set<Group> createGroups(Groups groups,
Users users) {
Set<Group> allGroups = new HashSet<>();
if (groups == null || groups.getGroup() == null) {
return allGroups;
}
for (org.apache.nifi.authorization.file.tenants.generated.Group group : groups.getGroup()) {
final Group.Builder builder = new Group.Builder()
.identifier(group.getIdentifier())
.name(group.getName());
for (org.apache.nifi.authorization.file.tenants.generated.Group.User groupUser : group.getUser()) {
builder.addUser(groupUser.getIdentifier());
}
allGroups.add(builder.build());
}
return allGroups;
}
/**
* Creates a Map from user identifier to User.
*
* @param users the set of all users
* @return the Map from user identifier to User
*/
private Map<String,User> createUserByIdMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentifier(), user);
}
return usersMap;
}
/**
* Creates a Map from user identity to User.
*
* @param users the set of all users
* @return the Map from user identity to User
*/
private Map<String,User> createUserByIdentityMap(final Set<User> users) {
Map<String,User> usersMap = new HashMap<>();
for (User user : users) {
usersMap.put(user.getIdentity(), user);
}
return usersMap;
}
/**
* Creates a Map from group identifier to Group.
*
* @param groups the set of all groups
* @return the Map from group identifier to Group
*/
private Map<String,Group> createGroupByIdMap(final Set<Group> groups) {
Map<String,Group> groupsMap = new HashMap<>();
for (Group group : groups) {
groupsMap.put(group.getIdentifier(), group);
}
return groupsMap;
}
/**
* Creates a Map from user identity to the set of Groups for that identity.
*
* @param groups all groups
* @param users all users
* @return a Map from User identity to the set of Groups for that identity
*/
private Map<String, Set<Group>> createGroupsByUserIdentityMap(final Set<Group> groups, final Set<User> users) {
Map<String, Set<Group>> groupsByUserIdentity = new HashMap<>();
for (User user : users) {
Set<Group> userGroups = new HashSet<>();
for (Group group : groups) {
for (String groupUser : group.getUsers()) {
if (groupUser.equals(user.getIdentifier())) {
userGroups.add(group);
}
}
}
groupsByUserIdentity.put(user.getIdentity(), userGroups);
}
return groupsByUserIdentity;
}
public Tenants getTenants() {
return tenants;
}
public Set<User> getAllUsers() {
return allUsers;
}
public Map<String, User> getUsersById() {
return usersById;
}
public Map<String, User> getUsersByIdentity() {
return usersByIdentity;
}
public Set<Group> getAllGroups() {
return allGroups;
}
public Map<String, Group> getGroupsById() {
return groupsById;
}
public User getUser(String identity) {
if (identity == null) {
throw new IllegalArgumentException("Identity cannot be null");
}
return usersByIdentity.get(identity);
}
public Set<Group> getGroups(String userIdentity) {
if (userIdentity == null) {
throw new IllegalArgumentException("User Identity cannot be null");
}
return groupsByUserIdentity.get(userIdentity);
}
}

View File

@ -0,0 +1,15 @@
# 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.FileAccessPolicyProvider

View File

@ -0,0 +1,15 @@
# 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.FileUserGroupProvider

View File

@ -21,10 +21,10 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.file.FileUtils;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
@ -46,6 +46,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -188,8 +189,44 @@ public class FileAuthorizerTest {
when(properties.getFlowConfigurationFile()).thenReturn(flow);
configurationContext = mock(AuthorizerConfigurationContext.class);
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null));
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE))).thenReturn(new StandardPropertyValue(primaryAuthorizations.getPath(), null));
when(configurationContext.getProperty(Mockito.eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
when(configurationContext.getProperties()).then((invocation) -> {
final Map<String, String> properties = new HashMap<>();
final PropertyValue authFile = configurationContext.getProperty(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE);
if (authFile != null) {
properties.put(FileAccessPolicyProvider.PROP_AUTHORIZATIONS_FILE, authFile.getValue());
}
final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
if (tenantFile != null) {
properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
}
final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
if (legacyAuthFile != null) {
properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
}
final PropertyValue initialAdmin = configurationContext.getProperty(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY);
if (initialAdmin != null) {
properties.put(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY, initialAdmin.getValue());
}
int i = 1;
while (true) {
final String key = FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + i++;
final PropertyValue value = configurationContext.getProperty(key);
if (value == null) {
break;
} else {
properties.put(key, value.getValue());
}
}
return properties;
});
authorizer = new FileAuthorizer();
authorizer.setNiFiProperties(properties);
@ -464,7 +501,7 @@ public class FileAuthorizerTest {
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenInitialAdminAndLegacyUsersProvided() throws Exception {
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
@ -492,7 +529,7 @@ public class FileAuthorizerTest {
public void testOnConfiguredWhenInitialAdminProvided() throws Exception {
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@ -530,7 +567,7 @@ public class FileAuthorizerTest {
authorizer.setNiFiProperties(properties);
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@ -568,7 +605,7 @@ public class FileAuthorizerTest {
authorizer.setNiFiProperties(properties);
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@ -609,7 +646,7 @@ public class FileAuthorizerTest {
authorizer.setNiFiProperties(properties);
final String adminIdentity = "CN=localhost, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
@ -626,18 +663,15 @@ public class FileAuthorizerTest {
@Test
public void testOnConfiguredWhenNodeIdentitiesProvided() throws Exception {
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
final String nodeIdentity1 = "node1";
final String nodeIdentity2 = "node2";
final Map<String,String> props = new HashMap<>();
props.put("Node Identity 1", nodeIdentity1);
props.put("Node Identity 2", nodeIdentity2);
when(configurationContext.getProperties()).thenReturn(props);
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
.thenReturn(new StandardPropertyValue(nodeIdentity1, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
.thenReturn(new StandardPropertyValue(nodeIdentity2, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
@ -662,18 +696,15 @@ public class FileAuthorizerTest {
@Test
public void testOnConfiguredWhenNodeIdentitiesProvidedAndUsersAlreadyExist() throws Exception {
final String adminIdentity = "admin-user";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
final String nodeIdentity1 = "node1";
final String nodeIdentity2 = "node2";
final Map<String,String> props = new HashMap<>();
props.put("Node Identity 1", nodeIdentity1);
props.put("Node Identity 2", nodeIdentity2);
when(configurationContext.getProperties()).thenReturn(props);
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
.thenReturn(new StandardPropertyValue(nodeIdentity1, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
.thenReturn(new StandardPropertyValue(nodeIdentity2, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
writeFile(primaryTenants, TENANTS_FOR_ADMIN_AND_NODES);
@ -709,17 +740,15 @@ public class FileAuthorizerTest {
authorizer.setNiFiProperties(properties);
final String adminIdentity = "CN=user1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
when(configurationContext.getProperty(Mockito.eq(FileAuthorizer.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
final String nodeIdentity1 = "CN=node1, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
final String nodeIdentity2 = "CN=node2, OU=Apache NiFi, O=Apache, L=Santa Monica, ST=CA, C=US";
final Map<String,String> nodeProps = new HashMap<>();
nodeProps.put("Node Identity 1", nodeIdentity1);
nodeProps.put("Node Identity 2", nodeIdentity2);
when(configurationContext.getProperties()).thenReturn(nodeProps);
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_INITIAL_ADMIN_IDENTITY)))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "1")))
.thenReturn(new StandardPropertyValue(nodeIdentity1, null));
when(configurationContext.getProperty(Mockito.eq(FileAccessPolicyProvider.PROP_NODE_IDENTITY_PREFIX + "2")))
.thenReturn(new StandardPropertyValue(nodeIdentity2, null));
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
@ -735,9 +764,12 @@ public class FileAuthorizerTest {
assertNotNull(nodeUser2);
}
@Test
public void testOnConfiguredWhenTenantsAndAuthorizationsFileDoesNotExist() {
authorizer.onConfigured(configurationContext);
assertEquals(0, authorizer.getAccessPolicies().size());
assertEquals(0, authorizer.getUsers().size());
assertEquals(0, authorizer.getGroups().size());
}
@Test
@ -745,6 +777,8 @@ public class FileAuthorizerTest {
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
authorizer.onConfigured(configurationContext);
assertEquals(0, authorizer.getAccessPolicies().size());
assertEquals(0, authorizer.getUsers().size());
assertEquals(0, authorizer.getGroups().size());
}
@Test
@ -752,6 +786,8 @@ public class FileAuthorizerTest {
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS_CONCISE);
authorizer.onConfigured(configurationContext);
assertEquals(0, authorizer.getAccessPolicies().size());
assertEquals(0, authorizer.getUsers().size());
assertEquals(0, authorizer.getGroups().size());
}
@Test
@ -1007,9 +1043,6 @@ public class FileAuthorizerTest {
final User user = authorizer.getUser("user-1");
assertEquals("user-1", user.getIdentifier());
final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1");
assertTrue(policy1.getUsers().contains("user-1"));
// delete user-1
final User deletedUser = authorizer.deleteUser(user);
assertNotNull(deletedUser);
@ -1018,10 +1051,6 @@ public class FileAuthorizerTest {
// should be one less user
assertEquals(1, authorizer.getUsers().size());
assertNull(authorizer.getUser(user.getIdentifier()));
// verify policy-1 no longer has a reference to user-1
final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1");
assertFalse(updatedPolicy1.getUsers().contains("user-1"));
}
@Test
@ -1121,7 +1150,6 @@ public class FileAuthorizerTest {
assertEquals(3, groups.size());
}
@Test(expected = IllegalStateException.class)
public void testAddGroupWhenUserDoesNotExist() throws Exception {
writeFile(primaryAuthorizations, EMPTY_AUTHORIZATIONS);
@ -1170,9 +1198,6 @@ public class FileAuthorizerTest {
authorizer.onConfigured(configurationContext);
assertEquals(2, authorizer.getGroups().size());
final AccessPolicy policy1 = authorizer.getAccessPolicy("policy-1");
assertTrue(policy1.getGroups().contains("group-1"));
// retrieve group-1
final Group group = authorizer.getGroup("group-1");
assertEquals("group-1", group.getIdentifier());
@ -1187,10 +1212,6 @@ public class FileAuthorizerTest {
// verify we can no longer retrieve group-1 by identifier
assertNull(authorizer.getGroup(group.getIdentifier()));
// verify group-1 is no longer in policy-1
final AccessPolicy updatedPolicy1 = authorizer.getAccessPolicy("policy-1");
assertFalse(updatedPolicy1.getGroups().contains("group-1"));
}
@Test
@ -1289,9 +1310,11 @@ public class FileAuthorizerTest {
.action(RequestAction.READ)
.build();
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) AuthorizerFactory.installIntegrityChecks(authorizer);
final ConfigurableAccessPolicyProvider accessPolicyProviderWithChecks = (ConfigurableAccessPolicyProvider) managedAuthorizer.getAccessPolicyProvider();
try {
final AccessPolicy returnedPolicy2 = authorizer.addAccessPolicy(policy2);
Assert.fail("Should have thrown exception");
final AccessPolicy returnedPolicy2 = accessPolicyProviderWithChecks.addAccessPolicy(policy2);
fail("Should have thrown exception");
} catch (Exception e) {
}

View File

@ -0,0 +1,698 @@
/*
* 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.apache.nifi.util.NiFiProperties;
import org.apache.nifi.util.file.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
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.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FileUserGroupProviderTest {
private static final String EMPTY_TENANTS_CONCISE =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<tenants/>";
private static final String EMPTY_TENANTS =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<tenants>"
+ "</tenants>";
private static final String BAD_SCHEMA_TENANTS =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
+ "<tenant>"
+ "</tenant>";
private static final String SIMPLE_TENANTS_BY_USER =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<tenants>" +
" <users>" +
" <user identifier=\"user-1\" identity=\"user-1\"/>" +
" <user identifier=\"user-2\" identity=\"user-2\"/>" +
" </users>" +
"</tenants>";
private static final String TENANTS =
"<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
"<tenants>" +
" <groups>" +
" <group identifier=\"group-1\" name=\"group-1\">" +
" <user identifier=\"user-1\" />" +
" </group>" +
" <group identifier=\"group-2\" name=\"group-2\">" +
" <user identifier=\"user-2\" />" +
" </group>" +
" </groups>" +
" <users>" +
" <user identifier=\"user-1\" identity=\"user-1\" />" +
" <user identifier=\"user-2\" identity=\"user-2\" />" +
" </users>" +
"</tenants>";
private NiFiProperties properties;
private FileUserGroupProvider userGroupProvider;
private File primaryTenants;
private File restoreTenants;
private AuthorizerConfigurationContext configurationContext;
@Before
public void setup() throws IOException {
// primary tenants
primaryTenants = new File("target/authorizations/users.xml");
FileUtils.ensureDirectoryExistAndCanAccess(primaryTenants.getParentFile());
// restore authorizations
restoreTenants = new File("target/restore/users.xml");
FileUtils.ensureDirectoryExistAndCanAccess(restoreTenants.getParentFile());
properties = mock(NiFiProperties.class);
when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
configurationContext = mock(AuthorizerConfigurationContext.class);
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE))).thenReturn(new StandardPropertyValue(null, null));
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_TENANTS_FILE))).thenReturn(new StandardPropertyValue(primaryTenants.getPath(), null));
when(configurationContext.getProperties()).then((invocation) -> {
final Map<String, String> properties = new HashMap<>();
final PropertyValue tenantFile = configurationContext.getProperty(FileUserGroupProvider.PROP_TENANTS_FILE);
if (tenantFile != null) {
properties.put(FileUserGroupProvider.PROP_TENANTS_FILE, tenantFile.getValue());
}
final PropertyValue legacyAuthFile = configurationContext.getProperty(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE);
if (legacyAuthFile != null) {
properties.put(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE, legacyAuthFile.getValue());
}
int i = 1;
while (true) {
final String key = FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + i++;
final PropertyValue value = configurationContext.getProperty(key);
if (value == null) {
break;
} else {
properties.put(key, value.getValue());
}
}
return properties;
});
userGroupProvider = new FileUserGroupProvider();
userGroupProvider.setNiFiProperties(properties);
userGroupProvider.initialize(null);
}
@After
public void cleanup() throws Exception {
deleteFile(primaryTenants);
deleteFile(restoreTenants);
}
@Test
public void testOnConfiguredWhenLegacyUsersFileProvided() throws Exception {
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
.thenReturn(new StandardPropertyValue("src/test/resources/authorized-users.xml", null));
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
// verify all users got created correctly
final Set<User> users = userGroupProvider.getUsers();
assertEquals(6, users.size());
final User user1 = userGroupProvider.getUserByIdentity("user1");
assertNotNull(user1);
final User user2 = userGroupProvider.getUserByIdentity("user2");
assertNotNull(user2);
final User user3 = userGroupProvider.getUserByIdentity("user3");
assertNotNull(user3);
final User user4 = userGroupProvider.getUserByIdentity("user4");
assertNotNull(user4);
final User user5 = userGroupProvider.getUserByIdentity("user5");
assertNotNull(user5);
final User user6 = userGroupProvider.getUserByIdentity("user6");
assertNotNull(user6);
// verify one group got created
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(1, groups.size());
final Group group1 = groups.iterator().next();
assertEquals("group1", group1.getName());
}
@Test
public void testOnConfiguredWhenLegacyUsersFileProvidedWithIdentityMappings() throws Exception {
final Properties props = new Properties();
props.setProperty("nifi.security.identity.mapping.pattern.dn1", "^CN=(.*?), OU=(.*?), O=(.*?), L=(.*?), ST=(.*?), C=(.*?)$");
props.setProperty("nifi.security.identity.mapping.value.dn1", "$1");
properties = getNiFiProperties(props);
when(properties.getRestoreDirectory()).thenReturn(restoreTenants.getParentFile());
userGroupProvider.setNiFiProperties(properties);
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
.thenReturn(new StandardPropertyValue("src/test/resources/authorized-users-with-dns.xml", null));
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
final User user1 = userGroupProvider.getUserByIdentity("user1");
assertNotNull(user1);
final User user2 = userGroupProvider.getUserByIdentity("user2");
assertNotNull(user2);
final User user3 = userGroupProvider.getUserByIdentity("user3");
assertNotNull(user3);
final User user4 = userGroupProvider.getUserByIdentity("user4");
assertNotNull(user4);
final User user5 = userGroupProvider.getUserByIdentity("user5");
assertNotNull(user5);
final User user6 = userGroupProvider.getUserByIdentity("user6");
assertNotNull(user6);
// verify one group got created
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(1, groups.size());
final Group group1 = groups.iterator().next();
assertEquals("group1", group1.getName());
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenBadLegacyUsersFileProvided() throws Exception {
when(configurationContext.getProperty(eq(FileAuthorizer.PROP_LEGACY_AUTHORIZED_USERS_FILE)))
.thenReturn(new StandardPropertyValue("src/test/resources/does-not-exist.xml", null));
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
}
@Test
public void testOnConfiguredWhenInitialUsersNotProvided() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
final Set<User> users = userGroupProvider.getUsers();
assertEquals(0, users.size());
}
@Test
public void testOnConfiguredWhenInitialUsersProvided() throws Exception {
final String adminIdentity = "admin-user";
final String nodeIdentity1 = "node-identity-1";
final String nodeIdentity2 = "node-identity-2";
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
.thenReturn(new StandardPropertyValue(nodeIdentity1, null));
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
.thenReturn(new StandardPropertyValue(nodeIdentity2, null));
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
final Set<User> users = userGroupProvider.getUsers();
assertEquals(3, users.size());
assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(adminIdentity).identity(adminIdentity).build()));
assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity1).identity(nodeIdentity1).build()));
assertTrue(users.contains(new User.Builder().identifierGenerateFromSeed(nodeIdentity2).identity(nodeIdentity2).build()));
}
@Test
public void testOnConfiguredWhenTenantsExistAndInitialUsersProvided() throws Exception {
final String adminIdentity = "admin-user";
final String nodeIdentity1 = "node-identity-1";
final String nodeIdentity2 = "node-identity-2";
// despite setting initial users, they will not be loaded as the tenants file is non-empty
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "1")))
.thenReturn(new StandardPropertyValue(adminIdentity, null));
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "2")))
.thenReturn(new StandardPropertyValue(nodeIdentity1, null));
when(configurationContext.getProperty(eq(FileUserGroupProvider.PROP_INITIAL_USER_IDENTITY_PREFIX + "3")))
.thenReturn(new StandardPropertyValue(nodeIdentity2, null));
writeFile(primaryTenants, SIMPLE_TENANTS_BY_USER);
userGroupProvider.onConfigured(configurationContext);
final Set<User> users = userGroupProvider.getUsers();
assertEquals(2, users.size());
assertTrue(users.contains(new User.Builder().identifier("user-1").identity("user-1").build()));
assertTrue(users.contains(new User.Builder().identifier("user-2").identity("user-2").build()));
}
@Test
public void testOnConfiguredWhenTenantsFileDoesNotExist() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
assertEquals(0, userGroupProvider.getUsers().size());
assertEquals(0, userGroupProvider.getGroups().size());
}
@Test
public void testOnConfiguredWhenRestoreDoesNotExist() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
assertEquals(primaryTenants.length(), restoreTenants.length());
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenPrimaryDoesNotExist() throws Exception {
writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWhenPrimaryTenantsDifferentThanRestore() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS);
writeFile(restoreTenants, EMPTY_TENANTS_CONCISE);
userGroupProvider.onConfigured(configurationContext);
}
@Test(expected = AuthorizerCreationException.class)
public void testOnConfiguredWithBadTenantsSchema() throws Exception {
writeFile(primaryTenants, BAD_SCHEMA_TENANTS);
userGroupProvider.onConfigured(configurationContext);
}
@Test
public void testGetAllUsersGroupsPolicies() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(2, groups.size());
boolean foundGroup1 = false;
boolean foundGroup2 = false;
for (Group group : groups) {
if (group.getIdentifier().equals("group-1") && group.getName().equals("group-1")
&& group.getUsers().size() == 1 && group.getUsers().contains("user-1")) {
foundGroup1 = true;
} else if (group.getIdentifier().equals("group-2") && group.getName().equals("group-2")
&& group.getUsers().size() == 1 && group.getUsers().contains("user-2")) {
foundGroup2 = true;
}
}
assertTrue(foundGroup1);
assertTrue(foundGroup2);
final Set<User> users = userGroupProvider.getUsers();
assertEquals(2, users.size());
boolean foundUser1 = false;
boolean foundUser2 = false;
for (User user : users) {
if (user.getIdentifier().equals("user-1") && user.getIdentity().equals("user-1")) {
foundUser1 = true;
} else if (user.getIdentifier().equals("user-2") && user.getIdentity().equals("user-2")) {
foundUser2 = true;
}
}
assertTrue(foundUser1);
assertTrue(foundUser2);
}
// --------------- User Tests ------------------------
@Test
public void testAddUser() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(0, userGroupProvider.getUsers().size());
final User user = new User.Builder()
.identifier("user-1")
.identity("user-identity-1")
.build();
final User addedUser = userGroupProvider.addUser(user);
assertNotNull(addedUser);
assertEquals(user.getIdentifier(), addedUser.getIdentifier());
assertEquals(user.getIdentity(), addedUser.getIdentity());
final Set<User> users = userGroupProvider.getUsers();
assertEquals(1, users.size());
}
@Test
public void testGetUserByIdentifierWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final String identifier = "user-1";
final User user = userGroupProvider.getUser(identifier);
assertNotNull(user);
assertEquals(identifier, user.getIdentifier());
}
@Test
public void testGetUserByIdentifierWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final String identifier = "user-X";
final User user = userGroupProvider.getUser(identifier);
assertNull(user);
}
@Test
public void testGetUserByIdentityWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final String identity = "user-1";
final User user = userGroupProvider.getUserByIdentity(identity);
assertNotNull(user);
assertEquals(identity, user.getIdentifier());
}
@Test
public void testGetUserByIdentityWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final String identity = "user-X";
final User user = userGroupProvider.getUserByIdentity(identity);
assertNull(user);
}
@Test
public void testDeleteUser() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
// retrieve user-1 and verify it exists
final User user = userGroupProvider.getUser("user-1");
assertEquals("user-1", user.getIdentifier());
// delete user-1
final User deletedUser = userGroupProvider.deleteUser(user);
assertNotNull(deletedUser);
assertEquals("user-1", deletedUser.getIdentifier());
// should be one less user
assertEquals(1, userGroupProvider.getUsers().size());
assertNull(userGroupProvider.getUser(user.getIdentifier()));
}
@Test
public void testDeleteUserWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
//user that doesn't exist
final User user = new User.Builder().identifier("user-X").identity("user-identity-X").build();
// should return null and still have 2 users because nothing was deleted
final User deletedUser = userGroupProvider.deleteUser(user);
assertNull(deletedUser);
assertEquals(2, userGroupProvider.getUsers().size());
}
@Test
public void testUpdateUserWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final User user = new User.Builder()
.identifier("user-1")
.identity("new-identity")
.build();
final User updatedUser = userGroupProvider.updateUser(user);
assertNotNull(updatedUser);
assertEquals(user.getIdentifier(), updatedUser.getIdentifier());
assertEquals(user.getIdentity(), updatedUser.getIdentity());
}
@Test
public void testUpdateUserWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getUsers().size());
final User user = new User.Builder()
.identifier("user-X")
.identity("new-identity")
.build();
final User updatedUser = userGroupProvider.updateUser(user);
assertNull(updatedUser);
}
// --------------- Group Tests ------------------------
@Test
public void testAddGroup() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(0, userGroupProvider.getGroups().size());
final Group group = new Group.Builder()
.identifier("group-id-1")
.name("group-name-1")
.build();
final Group addedGroup = userGroupProvider.addGroup(group);
assertNotNull(addedGroup);
assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
assertEquals(group.getName(), addedGroup.getName());
assertEquals(0, addedGroup.getUsers().size());
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(1, groups.size());
}
@Test
public void testAddGroupWithUser() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
final Group group = new Group.Builder()
.identifier("group-id-XXX")
.name("group-name-XXX")
.addUser("user-1")
.build();
final Group addedGroup = userGroupProvider.addGroup(group);
assertNotNull(addedGroup);
assertEquals(group.getIdentifier(), addedGroup.getIdentifier());
assertEquals(group.getName(), addedGroup.getName());
assertEquals(1, addedGroup.getUsers().size());
final Set<Group> groups = userGroupProvider.getGroups();
assertEquals(3, groups.size());
}
@Test(expected = IllegalStateException.class)
public void testAddGroupWhenUserDoesNotExist() throws Exception {
writeFile(primaryTenants, EMPTY_TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(0, userGroupProvider.getGroups().size());
final Group group = new Group.Builder()
.identifier("group-id-1")
.name("group-name-1")
.addUser("user1")
.build();
userGroupProvider.addGroup(group);
}
@Test
public void testGetGroupByIdentifierWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
final String identifier = "group-1";
final Group group = userGroupProvider.getGroup(identifier);
assertNotNull(group);
assertEquals(identifier, group.getIdentifier());
}
@Test
public void testGetGroupByIdentifierWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
final String identifier = "group-X";
final Group group = userGroupProvider.getGroup(identifier);
assertNull(group);
}
@Test
public void testDeleteGroupWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
// retrieve group-1
final Group group = userGroupProvider.getGroup("group-1");
assertEquals("group-1", group.getIdentifier());
// delete group-1
final Group deletedGroup = userGroupProvider.deleteGroup(group);
assertNotNull(deletedGroup);
assertEquals("group-1", deletedGroup.getIdentifier());
// verify there is one less overall group
assertEquals(1, userGroupProvider.getGroups().size());
// verify we can no longer retrieve group-1 by identifier
assertNull(userGroupProvider.getGroup(group.getIdentifier()));
}
@Test
public void testDeleteGroupWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
final Group group = new Group.Builder()
.identifier("group-id-X")
.name("group-name-X")
.build();
final Group deletedGroup = userGroupProvider.deleteGroup(group);
assertNull(deletedGroup);
assertEquals(2, userGroupProvider.getGroups().size());
}
@Test
public void testUpdateGroupWhenFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
// verify user-1 is in group-1 before the update
final Group groupBefore = userGroupProvider.getGroup("group-1");
assertEquals(1, groupBefore.getUsers().size());
assertTrue(groupBefore.getUsers().contains("user-1"));
final Group group = new Group.Builder()
.identifier("group-1")
.name("new-name")
.addUser("user-2")
.build();
final Group updatedGroup = userGroupProvider.updateGroup(group);
assertEquals(group.getIdentifier(), updatedGroup.getIdentifier());
assertEquals(group.getName(), updatedGroup.getName());
assertEquals(1, updatedGroup.getUsers().size());
assertTrue(updatedGroup.getUsers().contains("user-2"));
}
@Test
public void testUpdateGroupWhenNotFound() throws Exception {
writeFile(primaryTenants, TENANTS);
userGroupProvider.onConfigured(configurationContext);
assertEquals(2, userGroupProvider.getGroups().size());
final Group group = new Group.Builder()
.identifier("group-X")
.name("group-X")
.build();
final Group updatedGroup = userGroupProvider.updateGroup(group);
assertNull(updatedGroup);
assertEquals(2, userGroupProvider.getGroups().size());
}
private static void writeFile(final File file, final String content) throws Exception {
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
try (final FileOutputStream fos = new FileOutputStream(file)) {
fos.write(bytes);
}
}
private static boolean deleteFile(final File file) {
if (file.isDirectory()) {
FileUtils.deleteFilesInDir(file, null, null, true, true);
}
return FileUtils.deleteFile(file, null, 10);
}
private NiFiProperties getNiFiProperties(final Properties properties) {
final NiFiProperties nifiProperties = Mockito.mock(NiFiProperties.class);
when(nifiProperties.getPropertyKeys()).thenReturn(properties.stringPropertyNames());
when(nifiProperties.getProperty(anyString())).then(new Answer<String>() {
@Override
public String answer(InvocationOnMock invocationOnMock) throws Throwable {
return properties.getProperty((String)invocationOnMock.getArguments()[0]);
}
});
return nifiProperties;
}
}

View File

@ -22,10 +22,15 @@ package org.apache.nifi.authorization;
public class StandardAuthorizerInitializationContext implements AuthorizerInitializationContext {
private final String identifier;
private final UserGroupProviderLookup userGroupProviderLookup;
private final AccessPolicyProviderLookup accessPolicyProviderLookup;
private final AuthorizerLookup authorizerLookup;
public StandardAuthorizerInitializationContext(String identifier, AuthorizerLookup authorizerLookup) {
public StandardAuthorizerInitializationContext(String identifier, UserGroupProviderLookup userGroupProviderLookup,
AccessPolicyProviderLookup accessPolicyProviderLookup, AuthorizerLookup authorizerLookup) {
this.identifier = identifier;
this.userGroupProviderLookup = userGroupProviderLookup;
this.accessPolicyProviderLookup = accessPolicyProviderLookup;
this.authorizerLookup = authorizerLookup;
}
@ -38,4 +43,13 @@ public class StandardAuthorizerInitializationContext implements AuthorizerInitia
return authorizerLookup;
}
@Override
public AccessPolicyProviderLookup getAccessPolicyProviderLookup() {
return accessPolicyProviderLookup;
}
@Override
public UserGroupProviderLookup getUserGroupProviderLookup() {
return userGroupProviderLookup;
}
}

View File

@ -0,0 +1,251 @@
/*
* 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 org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.Set;
public class StandardManagedAuthorizer implements ManagedAuthorizer {
private static final DocumentBuilderFactory DOCUMENT_BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
private static final XMLOutputFactory XML_OUTPUT_FACTORY = XMLOutputFactory.newInstance();
private static final String USER_GROUP_PROVIDER_ELEMENT = "userGroupProvider";
private static final String ACCESS_POLICY_PROVIDER_ELEMENT = "accessPolicyProvider";
private AccessPolicyProviderLookup accessPolicyProviderLookup;
private AccessPolicyProvider accessPolicyProvider;
private UserGroupProvider userGroupProvider;
@Override
public void initialize(AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
accessPolicyProviderLookup = initializationContext.getAccessPolicyProviderLookup();
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
final String accessPolicyProviderKey = configurationContext.getProperty("Access Policy Provider").getValue();
accessPolicyProvider = accessPolicyProviderLookup.getAccessPolicyProvider(accessPolicyProviderKey);
// ensure the desired access policy provider was found
if (accessPolicyProvider == null) {
throw new AuthorizerCreationException(String.format("Unable to locate configured Access Policy Provider: %s", accessPolicyProviderKey));
}
userGroupProvider = accessPolicyProvider.getUserGroupProvider();
// ensure the desired access policy provider has a user group provider
if (userGroupProvider == null) {
throw new AuthorizerCreationException(String.format("Configured Access Policy Provider %s does not contain a User Group Provider", accessPolicyProviderKey));
}
}
@Override
public AuthorizationResult authorize(AuthorizationRequest request) throws AuthorizationAccessException {
final String resourceIdentifier = request.getResource().getIdentifier();
final AccessPolicy policy = accessPolicyProvider.getAccessPolicy(resourceIdentifier, request.getAction());
if (policy == null) {
return AuthorizationResult.resourceNotFound();
}
final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(request.getIdentity());
final User user = userAndGroups.getUser();
if (user == null) {
return AuthorizationResult.denied(String.format("Unknown user with identity '%s'.", request.getIdentity()));
}
final Set<Group> userGroups = userAndGroups.getGroups();
if (policy.getUsers().contains(user.getIdentifier()) || containsGroup(userGroups, policy)) {
return AuthorizationResult.approved();
}
return AuthorizationResult.denied(request.getExplanationSupplier().get());
}
/**
* Determines if the policy contains one of the user's groups.
*
* @param userGroups the set of the user's groups
* @param policy the policy
* @return true if one of the Groups in userGroups is contained in the policy
*/
private boolean containsGroup(final Set<Group> userGroups, final AccessPolicy policy) {
if (userGroups == null || userGroups.isEmpty() || policy.getGroups().isEmpty()) {
return false;
}
for (Group userGroup : userGroups) {
if (policy.getGroups().contains(userGroup.getIdentifier())) {
return true;
}
}
return false;
}
@Override
public String getFingerprint() throws AuthorizationAccessException {
XMLStreamWriter writer = null;
final StringWriter out = new StringWriter();
try {
writer = XML_OUTPUT_FACTORY.createXMLStreamWriter(out);
writer.writeStartDocument();
writer.writeStartElement("managedAuthorizations");
writer.writeStartElement(ACCESS_POLICY_PROVIDER_ELEMENT);
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
writer.writeCharacters(((ConfigurableAccessPolicyProvider) accessPolicyProvider).getFingerprint());
}
writer.writeEndElement();
writer.writeStartElement(USER_GROUP_PROVIDER_ELEMENT);
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
writer.writeCharacters(((ConfigurableUserGroupProvider) userGroupProvider).getFingerprint());
}
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
writer.flush();
} catch (XMLStreamException e) {
throw new AuthorizationAccessException("Unable to generate fingerprint", e);
} finally {
if (writer != null) {
try {
writer.close();
} catch (XMLStreamException e) {
// nothing to do here
}
}
}
return out.toString();
}
@Override
public void inheritFingerprint(String fingerprint) throws AuthorizationAccessException {
if (StringUtils.isBlank(fingerprint)) {
return;
}
final FingerprintHolder fingerprintHolder = parseFingerprint(fingerprint);
if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint()) && accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
((ConfigurableAccessPolicyProvider) accessPolicyProvider).inheritFingerprint(fingerprintHolder.getPolicyFingerprint());
}
if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint()) && userGroupProvider instanceof ConfigurableUserGroupProvider) {
((ConfigurableUserGroupProvider) userGroupProvider).inheritFingerprint(fingerprintHolder.getUserGroupFingerprint());
}
}
@Override
public void checkInheritability(String proposedFingerprint) throws AuthorizationAccessException, UninheritableAuthorizationsException {
final FingerprintHolder fingerprintHolder = parseFingerprint(proposedFingerprint);
if (StringUtils.isNotBlank(fingerprintHolder.getPolicyFingerprint())) {
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
((ConfigurableAccessPolicyProvider) accessPolicyProvider).checkInheritability(fingerprintHolder.getPolicyFingerprint());
} else {
throw new UninheritableAuthorizationsException("Policy fingerprint is not blank and the configured AccessPolicyProvider does not support fingerprinting.");
}
}
if (StringUtils.isNotBlank(fingerprintHolder.getUserGroupFingerprint())) {
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
((ConfigurableUserGroupProvider) userGroupProvider).checkInheritability(fingerprintHolder.getUserGroupFingerprint());
} else {
throw new UninheritableAuthorizationsException("User/Group fingerprint is not blank and the configured UserGroupProvider does not support fingerprinting.");
}
}
}
private final FingerprintHolder parseFingerprint(final String fingerprint) throws AuthorizationAccessException {
final byte[] fingerprintBytes = fingerprint.getBytes(StandardCharsets.UTF_8);
try (final ByteArrayInputStream in = new ByteArrayInputStream(fingerprintBytes)) {
final DocumentBuilder docBuilder = DOCUMENT_BUILDER_FACTORY.newDocumentBuilder();
final Document document = docBuilder.parse(in);
final Element rootElement = document.getDocumentElement();
final NodeList accessPolicyProviderList = rootElement.getElementsByTagName(ACCESS_POLICY_PROVIDER_ELEMENT);
if (accessPolicyProviderList.getLength() != 1) {
throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", ACCESS_POLICY_PROVIDER_ELEMENT, fingerprint));
}
final NodeList userGroupProviderList = rootElement.getElementsByTagName(USER_GROUP_PROVIDER_ELEMENT);
if (userGroupProviderList.getLength() != 1) {
throw new AuthorizationAccessException(String.format("Only one %s element is allowed: %s", USER_GROUP_PROVIDER_ELEMENT, fingerprint));
}
final Node accessPolicyProvider = accessPolicyProviderList.item(0);
final Node userGroupProvider = userGroupProviderList.item(0);
return new FingerprintHolder(accessPolicyProvider.getTextContent(), userGroupProvider.getTextContent());
} catch (SAXException | ParserConfigurationException | IOException e) {
throw new AuthorizationAccessException("Unable to parse fingerprint", e);
}
}
@Override
public AccessPolicyProvider getAccessPolicyProvider() {
return accessPolicyProvider;
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
private static class FingerprintHolder {
private final String policyFingerprint;
private final String userGroupFingerprint;
public FingerprintHolder(String policyFingerprint, String userGroupFingerprint) {
this.policyFingerprint = policyFingerprint;
this.userGroupFingerprint = userGroupFingerprint;
}
public String getPolicyFingerprint() {
return policyFingerprint;
}
public String getUserGroupFingerprint() {
return userGroupFingerprint;
}
}
}

View File

@ -16,7 +16,11 @@
*/
package org.apache.nifi.authorization.user;
import org.apache.commons.lang3.StringUtils;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
/**
* An implementation of NiFiUser.
@ -24,42 +28,20 @@ import java.util.Objects;
public class StandardNiFiUser implements NiFiUser {
public static final String ANONYMOUS_IDENTITY = "anonymous";
public static final StandardNiFiUser ANONYMOUS = new StandardNiFiUser(ANONYMOUS_IDENTITY, null, null, true);
public static final StandardNiFiUser ANONYMOUS = new Builder().identity(ANONYMOUS_IDENTITY).anonymous(true).build();
private final String identity;
private final Set<String> groups;
private final NiFiUser chain;
private final String clientAddress;
private final boolean isAnonymous;
public StandardNiFiUser(String identity) {
this(identity, null, null, false);
}
public StandardNiFiUser(String identity, String clientAddress) {
this(identity, null, clientAddress, false);
}
public StandardNiFiUser(String identity, NiFiUser chain) {
this(identity, chain, null, false);
}
public StandardNiFiUser(String identity, NiFiUser chain, String clientAddress) {
this(identity, chain, clientAddress, false);
}
/**
* This constructor is private as the only instance of this class which should have {@code isAnonymous} set to true is the singleton ANONYMOUS.
*
* @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
* @param chain the proxy chain that leads to this users
* @param clientAddress the source address of the request
* @param isAnonymous true to represent the canonical "anonymous" user
*/
private StandardNiFiUser(String identity, NiFiUser chain, String clientAddress, boolean isAnonymous) {
this.identity = identity;
this.chain = chain;
this.clientAddress = clientAddress;
this.isAnonymous = isAnonymous;
private StandardNiFiUser(final Builder builder) {
this.identity = builder.identity;
this.groups = builder.groups == null ? null : Collections.unmodifiableSet(builder.groups);
this.chain = builder.chain;
this.clientAddress = builder.clientAddress;
this.isAnonymous = builder.isAnonymous;
}
/**
@ -70,7 +52,7 @@ public class StandardNiFiUser implements NiFiUser {
* @return an anonymous user instance with the identity "anonymous"
*/
public static StandardNiFiUser populateAnonymousUser(NiFiUser chain, String clientAddress) {
return new StandardNiFiUser(ANONYMOUS_IDENTITY, chain, clientAddress, true);
return new Builder().identity(ANONYMOUS_IDENTITY).chain(chain).clientAddress(clientAddress).anonymous(true).build();
}
@Override
@ -78,6 +60,11 @@ public class StandardNiFiUser implements NiFiUser {
return identity;
}
@Override
public Set<String> getGroups() {
return groups;
}
@Override
public NiFiUser getChain() {
return chain;
@ -116,6 +103,87 @@ public class StandardNiFiUser implements NiFiUser {
@Override
public String toString() {
return String.format("identity[%s]", getIdentity());
final String formattedGroups;
if (groups == null) {
formattedGroups = "none";
} else {
formattedGroups = StringUtils.join(groups, ", ");
}
return String.format("identity[%s], groups[%s]", getIdentity(), formattedGroups);
}
/**
* Builder for a StandardNiFiUser
*/
public static class Builder {
private String identity;
private Set<String> groups;
private NiFiUser chain;
private String clientAddress;
private boolean isAnonymous = false;
/**
* Sets the identity.
*
* @param identity the identity string for the user (i.e. "Andy" or "CN=alopresto, OU=Apache NiFi")
* @return the builder
*/
public Builder identity(final String identity) {
this.identity = identity;
return this;
}
/**
* Sets the groups.
*
* @param groups the user groups
* @return the builder
*/
public Builder groups(final Set<String> groups) {
this.groups = groups;
return this;
}
/**
* Sets the chain.
*
* @param chain the proxy chain that leads to this users
* @return the builder
*/
public Builder chain(final NiFiUser chain) {
this.chain = chain;
return this;
}
/**
* Sets the client address.
*
* @param clientAddress the source address of the request
* @return the builder
*/
public Builder clientAddress(final String clientAddress) {
this.clientAddress = clientAddress;
return this;
}
/**
* Sets whether this user is the canonical "anonymous" user
*
* @param isAnonymous true to represent the canonical "anonymous" user
* @return the builder
*/
private Builder anonymous(final boolean isAnonymous) {
this.isAnonymous = isAnonymous;
return this;
}
/**
* @return builds a StandardNiFiUser from the current state of the builder
*/
public StandardNiFiUser build() {
return new StandardNiFiUser(this);
}
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.util;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.authorization.UserAndGroups;
import org.apache.nifi.authorization.UserGroupProvider;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
public class UserGroupUtil {
/**
* Gets the groups for the user with the specified identity. Returns null if the authorizer is not able to load user groups.
*
* @param authorizer the authorizer to load the groups from
* @param userIdentity the user identity
* @return the listing of groups for the user
*/
public static Set<String> getUserGroups(final Authorizer authorizer, final String userIdentity) {
if (authorizer instanceof ManagedAuthorizer) {
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
final UserGroupProvider userGroupProvider = managedAuthorizer.getAccessPolicyProvider().getUserGroupProvider();
final UserAndGroups userAndGroups = userGroupProvider.getUserAndGroups(userIdentity);
final Set<Group> userGroups = userAndGroups.getGroups();
if (userGroups == null || userGroups.isEmpty()) {
return Collections.EMPTY_SET;
} else {
return userAndGroups.getGroups().stream().map(group -> group.getName()).collect(Collectors.toSet());
}
} else {
return null;
}
}
}

View File

@ -0,0 +1,15 @@
# 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.StandardManagedAuthorizer

View File

@ -0,0 +1,438 @@
/*
* 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.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.junit.Assert;
import org.junit.Test;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class StandardManagedAuthorizerTest {
private static final String EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+ "<managedAuthorizations>"
+ "<accessPolicyProvider></accessPolicyProvider>"
+ "<userGroupProvider></userGroupProvider>"
+ "</managedAuthorizations>";
private static final String NON_EMPTY_FINGERPRINT = "<?xml version=\"1.0\" ?>"
+ "<managedAuthorizations>"
+ "<accessPolicyProvider>"
+ "&lt;accessPolicies&gt;"
+ "&lt;policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\"&gt;"
+ "&lt;policyUser identifier=\"user-id-1\"&gt;&lt;/policyUser&gt;"
+ "&lt;policyGroup identifier=\"group-id-1\"&gt;&lt;/policyGroup&gt;"
+ "&lt;/policy&gt;"
+ "&lt;/accessPolicies&gt;"
+ "</accessPolicyProvider>"
+ "<userGroupProvider>"
+ "&lt;tenants&gt;"
+ "&lt;user identifier=\"user-id-1\" identity=\"user-1\"&gt;&lt;/user&gt;"
+ "&lt;group identifier=\"group-id-1\" name=\"group-1\"&gt;"
+ "&lt;groupUser identifier=\"user-id-1\"&gt;&lt;/groupUser&gt;"
+ "&lt;/group&gt;"
+ "&lt;/tenants&gt;"
+ "</userGroupProvider>"
+ "</managedAuthorizations>";
private static final String ACCESS_POLICY_FINGERPRINT =
"<accessPolicies>"
+ "<policy identifier=\"policy-id-1\" resource=\"resource2\" actions=\"READ\">"
+ "<policyUser identifier=\"user-id-1\"></policyUser>"
+ "<policyGroup identifier=\"group-id-1\"></policyGroup>"
+ "</policy>"
+ "</accessPolicies>";
private static final String TENANT_FINGERPRINT =
"<tenants>"
+ "<user identifier=\"user-id-1\" identity=\"user-1\"></user>"
+ "<group identifier=\"group-id-1\" name=\"group-1\">"
+ "<groupUser identifier=\"user-id-1\"></groupUser>"
+ "</group>"
+ "</tenants>";
private static final Resource TEST_RESOURCE = new Resource() {
@Override
public String getIdentifier() {
return "1";
}
@Override
public String getName() {
return "resource1";
}
@Override
public String getSafeDescription() {
return "description1";
}
};
@Test(expected = AuthorizerCreationException.class)
public void testNullAccessPolicyProvider() throws Exception {
getStandardManagedAuthorizer(null);
}
@Test
public void testEmptyFingerPrint() throws Exception {
final UserGroupProvider userGroupProvider = mock(UserGroupProvider.class);
final AccessPolicyProvider accessPolicyProvider = mock(AccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
Assert.assertEquals(EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
}
@Test
public void testNonEmptyFingerPrint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
when(userGroupProvider.getFingerprint()).thenReturn(TENANT_FINGERPRINT);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getFingerprint()).thenReturn(ACCESS_POLICY_FINGERPRINT);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
Assert.assertEquals(NON_EMPTY_FINGERPRINT, managedAuthorizer.getFingerprint());
}
@Test
public void testInheritEmptyFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.inheritFingerprint(EMPTY_FINGERPRINT);
verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
}
@Test(expected = AuthorizationAccessException.class)
public void testInheritInvalidFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.inheritFingerprint("not a valid fingerprint");
}
@Test
public void testInheritNonEmptyFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.inheritFingerprint(NON_EMPTY_FINGERPRINT);
verify(userGroupProvider, times(1)).inheritFingerprint(TENANT_FINGERPRINT);
verify(accessPolicyProvider, times(1)).inheritFingerprint(ACCESS_POLICY_FINGERPRINT);
}
@Test
public void testCheckInheritEmptyFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.checkInheritability(EMPTY_FINGERPRINT);
verify(userGroupProvider, times(0)).inheritFingerprint(anyString());
verify(accessPolicyProvider, times(0)).inheritFingerprint(anyString());
}
@Test(expected = AuthorizationAccessException.class)
public void testCheckInheritInvalidFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.checkInheritability("not a valid fingerprint");
}
@Test
public void testCheckInheritNonEmptyFingerprint() throws Exception {
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
managedAuthorizer.checkInheritability(NON_EMPTY_FINGERPRINT);
verify(userGroupProvider, times(1)).checkInheritability(TENANT_FINGERPRINT);
verify(accessPolicyProvider, times(1)).checkInheritability(ACCESS_POLICY_FINGERPRINT);
}
@Test
public void testAuthorizationByUser() throws Exception {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final User user = new User.Builder()
.identity(userIdentity)
.identifier(userIdentifier)
.build();
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addUser(userIdentifier)
.action(RequestAction.READ)
.build();
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return Collections.EMPTY_SET;
}
});
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(userIdentity)
.resource(TEST_RESOURCE)
.action(RequestAction.READ)
.accessAttempt(true)
.anonymous(false)
.build();
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
}
@Test
public void testAuthorizationByGroup() throws Exception {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final String groupIdentifier = "groupIdentifier1";
final User user = new User.Builder()
.identity(userIdentity)
.identifier(userIdentifier)
.build();
final Group group = new Group.Builder()
.identifier(groupIdentifier)
.name(groupIdentifier)
.addUser(user.getIdentifier())
.build();
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addGroup(groupIdentifier)
.action(RequestAction.READ)
.build();
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return Stream.of(group).collect(Collectors.toSet());
}
});
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(userIdentity)
.resource(TEST_RESOURCE)
.action(RequestAction.READ)
.accessAttempt(true)
.anonymous(false)
.build();
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
assertEquals(AuthorizationResult.approved(), managedAuthorizer.authorize(request));
}
@Test
public void testResourceNotFound() throws Exception {
final String userIdentity = "userIdentity1";
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(null);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(userIdentity)
.resource(TEST_RESOURCE)
.action(RequestAction.READ)
.accessAttempt(true)
.anonymous(false)
.build();
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
assertEquals(AuthorizationResult.resourceNotFound(), managedAuthorizer.authorize(request));
}
@Test
public void testUnauthorizedDueToUnknownUser() throws Exception {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final String notUser1Identity = "not userIdentity1";
final User user = new User.Builder()
.identity(userIdentity)
.identifier(userIdentifier)
.build();
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addUser(userIdentifier)
.action(RequestAction.READ)
.build();
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
when(userGroupProvider.getUserAndGroups(notUser1Identity)).thenReturn(new UserAndGroups() {
@Override
public User getUser() {
return null;
}
@Override
public Set<Group> getGroups() {
return Collections.EMPTY_SET;
}
});
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(notUser1Identity)
.resource(TEST_RESOURCE)
.action(RequestAction.READ)
.accessAttempt(true)
.anonymous(false)
.build();
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
}
@Test
public void testUnauthorizedDueToLackOfPermission() throws Exception {
final String userIdentifier = "userIdentifier1";
final String userIdentity = "userIdentity1";
final User user = new User.Builder()
.identity(userIdentity)
.identifier(userIdentifier)
.build();
final AccessPolicy policy = new AccessPolicy.Builder()
.identifier("1")
.resource(TEST_RESOURCE.getIdentifier())
.addUser("userIdentity2")
.action(RequestAction.READ)
.build();
final ConfigurableUserGroupProvider userGroupProvider = mock(ConfigurableUserGroupProvider.class);
when(userGroupProvider.getUserAndGroups(userIdentity)).thenReturn(new UserAndGroups() {
@Override
public User getUser() {
return user;
}
@Override
public Set<Group> getGroups() {
return Collections.EMPTY_SET;
}
});
final ConfigurableAccessPolicyProvider accessPolicyProvider = mock(ConfigurableAccessPolicyProvider.class);
when(accessPolicyProvider.getAccessPolicy(TEST_RESOURCE.getIdentifier(), RequestAction.READ)).thenReturn(policy);
when(accessPolicyProvider.getUserGroupProvider()).thenReturn(userGroupProvider);
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.identity(userIdentity)
.resource(TEST_RESOURCE)
.action(RequestAction.READ)
.accessAttempt(true)
.anonymous(false)
.build();
final StandardManagedAuthorizer managedAuthorizer = getStandardManagedAuthorizer(accessPolicyProvider);
assertTrue(AuthorizationResult.denied().getResult().equals(managedAuthorizer.authorize(request).getResult()));
}
private StandardManagedAuthorizer getStandardManagedAuthorizer(final AccessPolicyProvider accessPolicyProvider) {
final StandardManagedAuthorizer managedAuthorizer = new StandardManagedAuthorizer();
final AuthorizerConfigurationContext configurationContext = mock(AuthorizerConfigurationContext.class);
when(configurationContext.getProperty("Access Policy Provider")).thenReturn(new StandardPropertyValue("access-policy-provider", null));
final AccessPolicyProviderLookup accessPolicyProviderLookup = mock(AccessPolicyProviderLookup.class);
when(accessPolicyProviderLookup.getAccessPolicyProvider("access-policy-provider")).thenReturn(accessPolicyProvider);
final AuthorizerInitializationContext initializationContext = mock(AuthorizerInitializationContext.class);
when(initializationContext.getAccessPolicyProviderLookup()).thenReturn(accessPolicyProviderLookup);
managedAuthorizer.initialize(initializationContext);
managedAuthorizer.onConfigured(configurationContext);
return managedAuthorizer;
}
}

View File

@ -16,14 +16,6 @@
*/
package org.apache.nifi.authorization.resource;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
@ -31,11 +23,19 @@ import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class DataAuthorizableTest {
private static final String IDENTITY_1 = "identity-1";
@ -83,20 +83,20 @@ public class DataAuthorizableTest {
@Test(expected = AccessDeniedException.class)
public void testAuthorizeUnauthorizedUser() {
final NiFiUser user = new StandardNiFiUser("unknown");
final NiFiUser user = new Builder().identity("unknown").build();
testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
}
@Test
public void testCheckAuthorizationUnauthorizedUser() {
final NiFiUser user = new StandardNiFiUser("unknown");
final NiFiUser user = new Builder().identity("unknown").build();
final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
assertEquals(Result.Denied, result.getResult());
}
@Test
public void testAuthorizedUser() {
final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
final NiFiUser user = new Builder().identity(IDENTITY_1).build();
testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
verify(testAuthorizer, times(1)).authorize(argThat(new ArgumentMatcher<AuthorizationRequest>() {
@ -109,7 +109,7 @@ public class DataAuthorizableTest {
@Test
public void testCheckAuthorizationUser() {
final NiFiUser user = new StandardNiFiUser(IDENTITY_1);
final NiFiUser user = new Builder().identity(IDENTITY_1).build();
final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
assertEquals(Result.Approved, result.getResult());
@ -123,9 +123,9 @@ public class DataAuthorizableTest {
@Test
public void testAuthorizedUserChain() {
final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
testDataAuthorizable.authorize(testAuthorizer, RequestAction.READ, user, null);
verify(testAuthorizer, times(3)).authorize(any(AuthorizationRequest.class));
@ -136,9 +136,9 @@ public class DataAuthorizableTest {
@Test
public void testCheckAuthorizationUserChain() {
final NiFiUser proxy2 = new StandardNiFiUser(PROXY_2);
final NiFiUser proxy1 = new StandardNiFiUser(PROXY_1, proxy2);
final NiFiUser user = new StandardNiFiUser(IDENTITY_1, proxy1);
final NiFiUser proxy2 = new Builder().identity(PROXY_2).build();
final NiFiUser proxy1 = new Builder().identity(PROXY_1).chain(proxy2).build();
final NiFiUser user = new Builder().identity(IDENTITY_1).chain(proxy1).build();
final AuthorizationResult result = testDataAuthorizable.checkAuthorization(testAuthorizer, RequestAction.READ, user, null);
assertEquals(Result.Approved, result.getResult());

View File

@ -16,6 +16,7 @@
*/
package org.apache.nifi.cluster.coordination.http;
import org.apache.nifi.cluster.coordination.http.endpoints.AccessPolicyEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.BulletinBoardEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ComponentStateEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ConnectionEndpointMerger;
@ -60,9 +61,14 @@ import org.apache.nifi.cluster.coordination.http.endpoints.RemoteProcessGroupsEn
import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTaskTypesEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.ReportingTasksEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.SearchUsersEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.StatusHistoryEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.SystemDiagnosticsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.TemplatesEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UserEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger;
import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.stream.io.NullOutputStream;
@ -141,6 +147,12 @@ public class StandardHttpResponseMapper implements HttpResponseMapper {
endpointMergers.add(new FunnelEndpointMerger());
endpointMergers.add(new FunnelsEndpointMerger());
endpointMergers.add(new ControllerEndpointMerger());
endpointMergers.add(new UsersEndpointMerger());
endpointMergers.add(new UserEndpointMerger());
endpointMergers.add(new UserGroupsEndpointMerger());
endpointMergers.add(new UserGroupEndpointMerger());
endpointMergers.add(new AccessPolicyEndpointMerger());
endpointMergers.add(new SearchUsersEndpointMerger());
}
@Override

View File

@ -0,0 +1,61 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.AccessPolicyEntityMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class AccessPolicyEndpointMerger extends AbstractSingleEntityEndpoint<AccessPolicyEntity> implements EndpointResponseMerger {
public static final Pattern ACCESS_POLICIES_URI_PATTERN = Pattern.compile("/nifi-api/policies");
public static final Pattern ACCESS_POLICY_URI_PATTERN = Pattern.compile("/nifi-api/policies/[a-f0-9\\-]{36}");
public static final Pattern ACCESS_POLICY_LOOKUP_URI_PATTERN = Pattern.compile("/nifi-api/policies/(?:read|write)/(?:[\\w-]+?/?)+");
private final AccessPolicyEntityMerger accessPolicyEntityMerger = new AccessPolicyEntityMerger();
@Override
public boolean canHandle(final URI uri, final String method) {
if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (ACCESS_POLICY_URI_PATTERN.matcher(uri.getPath()).matches())) {
return true;
} else if ("GET".equalsIgnoreCase(method) && ACCESS_POLICY_LOOKUP_URI_PATTERN.matcher(uri.getPath()).matches()) {
return true;
} else if ("POST".equalsIgnoreCase(method) && ACCESS_POLICIES_URI_PATTERN.matcher(uri.getPath()).matches()) {
return true;
}
return false;
}
@Override
protected Class<AccessPolicyEntity> getEntityClass() {
return AccessPolicyEntity.class;
}
@Override
protected void mergeResponses(final AccessPolicyEntity clientEntity, final Map<NodeIdentifier, AccessPolicyEntity> entityMap,
final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
accessPolicyEntityMerger.merge(clientEntity, entityMap);
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import java.net.URI;
import java.util.Collection;
import java.util.Set;
import java.util.regex.Pattern;
public class SearchUsersEndpointMerger implements EndpointResponseMerger {
public static final Pattern SEARCH_TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/search-results");
@Override
public boolean canHandle(final URI uri, final String method) {
return "GET".equalsIgnoreCase(method) && SEARCH_TENANTS_URI_PATTERN.matcher(uri.getPath()).matches();
}
@Override
public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
if (!canHandle(uri, method)) {
throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
}
final TenantsEntity responseEntity = clientResponse.getClientResponse().getEntity(TenantsEntity.class);
final Collection<TenantEntity> userEntities = responseEntity.getUsers();
final Collection<TenantEntity> userGroupEntities = responseEntity.getUserGroups();
for (final NodeResponse nodeResponse : successfulResponses) {
final TenantsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(TenantsEntity.class);
// only retain users/groups that all nodes agree on
userEntities.retainAll(nodeResponseEntity.getUsers());
userGroupEntities.retainAll(nodeResponseEntity.getUserGroups());
}
// create a new client response
return new NodeResponse(clientResponse, responseEntity);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.UserEntityMerger;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserEntity;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class UserEndpointMerger extends AbstractSingleEntityEndpoint<UserEntity> implements EndpointResponseMerger {
public static final Pattern USERS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users");
public static final Pattern USER_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users/[a-f0-9\\-]{36}");
private final UserEntityMerger userEntityMerger = new UserEntityMerger();
@Override
public boolean canHandle(final URI uri, final String method) {
if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_URI_PATTERN.matcher(uri.getPath()).matches())) {
return true;
} else if ("POST".equalsIgnoreCase(method) && USERS_URI_PATTERN.matcher(uri.getPath()).matches()) {
return true;
}
return false;
}
@Override
protected Class<UserEntity> getEntityClass() {
return UserEntity.class;
}
@Override
protected void mergeResponses(final UserEntity clientEntity, final Map<NodeIdentifier, UserEntity> entityMap,
final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
userEntityMerger.merge(clientEntity, entityMap);
}
}

View File

@ -0,0 +1,58 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.UserGroupEntityMerger;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class UserGroupEndpointMerger extends AbstractSingleEntityEndpoint<UserGroupEntity> implements EndpointResponseMerger {
public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups");
public static final Pattern USER_GROUP_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups/[a-f0-9\\-]{36}");
private final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger();
@Override
public boolean canHandle(final URI uri, final String method) {
if (("GET".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)) && (USER_GROUP_URI_PATTERN.matcher(uri.getPath()).matches())) {
return true;
} else if ("POST".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches()) {
return true;
}
return false;
}
@Override
protected Class<UserGroupEntity> getEntityClass() {
return UserGroupEntity.class;
}
@Override
protected void mergeResponses(final UserGroupEntity clientEntity, final Map<NodeIdentifier, UserGroupEntity> entityMap,
final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
userGroupEntityMerger.merge(clientEntity, entityMap);
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.UserGroupsEntityMerger;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import org.apache.nifi.web.api.entity.UserGroupsEntity;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class UserGroupsEndpointMerger implements EndpointResponseMerger {
public static final Pattern USER_GROUPS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/user-groups");
@Override
public boolean canHandle(final URI uri, final String method) {
return "GET".equalsIgnoreCase(method) && USER_GROUPS_URI_PATTERN.matcher(uri.getPath()).matches();
}
@Override
public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
if (!canHandle(uri, method)) {
throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
}
final UserGroupsEntity responseEntity = clientResponse.getClientResponse().getEntity(UserGroupsEntity.class);
final Collection<UserGroupEntity> userGroupEntities = responseEntity.getUserGroups();
final Map<String, Map<NodeIdentifier, UserGroupEntity>> entityMap = new HashMap<>();
for (final NodeResponse nodeResponse : successfulResponses) {
final UserGroupsEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UserGroupsEntity.class);
final Collection<UserGroupEntity> nodeUserGroupEntities = nodeResponseEntity.getUserGroups();
// only retain user groups that all nodes agree on
userGroupEntities.retainAll(nodeUserGroupEntities);
for (final UserGroupEntity nodeUserGroupEntity : nodeUserGroupEntities) {
final NodeIdentifier nodeId = nodeResponse.getNodeId();
Map<NodeIdentifier, UserGroupEntity> innerMap = entityMap.get(nodeId);
if (innerMap == null) {
innerMap = new HashMap<>();
entityMap.put(nodeUserGroupEntity.getId(), innerMap);
}
innerMap.put(nodeResponse.getNodeId(), nodeUserGroupEntity);
}
}
UserGroupsEntityMerger.mergeUserGroups(userGroupEntities, entityMap);
// create a new client response
return new NodeResponse(clientResponse, responseEntity);
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.apache.nifi.cluster.coordination.http.EndpointResponseMerger;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.cluster.manager.UsersEntityMerger;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UsersEntity;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class UsersEndpointMerger implements EndpointResponseMerger {
public static final Pattern TENANTS_URI_PATTERN = Pattern.compile("/nifi-api/tenants/users");
@Override
public boolean canHandle(final URI uri, final String method) {
return "GET".equalsIgnoreCase(method) && TENANTS_URI_PATTERN.matcher(uri.getPath()).matches();
}
@Override
public final NodeResponse merge(final URI uri, final String method, final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses, final NodeResponse clientResponse) {
if (!canHandle(uri, method)) {
throw new IllegalArgumentException("Cannot use Endpoint Mapper of type " + getClass().getSimpleName() + " to map responses for URI " + uri + ", HTTP Method " + method);
}
final UsersEntity responseEntity = clientResponse.getClientResponse().getEntity(UsersEntity.class);
final Collection<UserEntity> userEntities = responseEntity.getUsers();
final Map<String, Map<NodeIdentifier, UserEntity>> entityMap = new HashMap<>();
for (final NodeResponse nodeResponse : successfulResponses) {
final UsersEntity nodeResponseEntity = nodeResponse == clientResponse ? responseEntity : nodeResponse.getClientResponse().getEntity(UsersEntity.class);
final Collection<UserEntity> nodeUserEntities = nodeResponseEntity.getUsers();
// only retain users that all nodes agree on
userEntities.retainAll(nodeUserEntities);
for (final UserEntity nodeUserEntity : nodeUserEntities) {
final NodeIdentifier nodeId = nodeResponse.getNodeId();
Map<NodeIdentifier, UserEntity> innerMap = entityMap.get(nodeId);
if (innerMap == null) {
innerMap = new HashMap<>();
entityMap.put(nodeUserEntity.getId(), innerMap);
}
innerMap.put(nodeResponse.getNodeId(), nodeUserEntity);
}
}
UsersEntityMerger.mergeUsers(userEntities, entityMap);
// create a new client response
return new NodeResponse(clientResponse, responseEntity);
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AccessPolicyEntityMerger implements ComponentEntityMerger<AccessPolicyEntity> {
@Override
public void merge(AccessPolicyEntity clientEntity, Map<NodeIdentifier, AccessPolicyEntity> entityMap) {
ComponentEntityMerger.super.merge(clientEntity, entityMap);
}
/**
* Merges the AccessPolicyEntity responses.
*
* @param clientEntity the entity being returned to the client
* @param entityMap all node responses
*/
public void mergeComponents(final AccessPolicyEntity clientEntity, final Map<NodeIdentifier, AccessPolicyEntity> entityMap) {
final AccessPolicyDTO clientDto = clientEntity.getComponent();
final Map<NodeIdentifier, AccessPolicyDTO> dtoMap = new HashMap<>();
for (final Map.Entry<NodeIdentifier, AccessPolicyEntity> entry : entityMap.entrySet()) {
final AccessPolicyEntity nodeAccessPolicyEntity = entry.getValue();
final AccessPolicyDTO nodeAccessPolicyDto = nodeAccessPolicyEntity.getComponent();
dtoMap.put(entry.getKey(), nodeAccessPolicyDto);
}
mergeDtos(clientDto, dtoMap);
}
private static void mergeDtos(final AccessPolicyDTO clientDto, final Map<NodeIdentifier, AccessPolicyDTO> dtoMap) {
// if unauthorized for the client dto, simple return
if (clientDto == null) {
return;
}
final Set<TenantEntity> users = new HashSet<>(clientDto.getUsers());
final Set<TenantEntity> userGroups = new HashSet<>(clientDto.getUserGroups());
for (final Map.Entry<NodeIdentifier, AccessPolicyDTO> nodeEntry : dtoMap.entrySet()) {
final AccessPolicyDTO nodeAccessPolicy = nodeEntry.getValue();
if (nodeAccessPolicy != null) {
users.retainAll(nodeAccessPolicy.getUsers());
userGroups.retainAll(nodeAccessPolicy.getUserGroups());
}
}
clientDto.setUsers(users);
clientDto.setUserGroups(userGroups);
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.UserDTO;
import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class UserEntityMerger implements ComponentEntityMerger<UserEntity> {
@Override
public void merge(UserEntity clientEntity, Map<NodeIdentifier, UserEntity> entityMap) {
ComponentEntityMerger.super.merge(clientEntity, entityMap);
}
/**
* Merges the UserEntity responses.
*
* @param clientEntity the entity being returned to the client
* @param entityMap all node responses
*/
public void mergeComponents(final UserEntity clientEntity, final Map<NodeIdentifier, UserEntity> entityMap) {
final UserDTO clientDto = clientEntity.getComponent();
final Map<NodeIdentifier, UserDTO> dtoMap = new HashMap<>();
for (final Map.Entry<NodeIdentifier, UserEntity> entry : entityMap.entrySet()) {
final UserEntity nodeUserEntity = entry.getValue();
final UserDTO nodeUserDto = nodeUserEntity.getComponent();
dtoMap.put(entry.getKey(), nodeUserDto);
}
mergeDtos(clientDto, dtoMap);
}
private static void mergeDtos(final UserDTO clientDto, final Map<NodeIdentifier, UserDTO> dtoMap) {
// if unauthorized for the client dto, simple return
if (clientDto == null) {
return;
}
final Set<AccessPolicySummaryEntity> accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies());
final Set<TenantEntity> tenantEntities = new HashSet<>(clientDto.getUserGroups());
for (final Map.Entry<NodeIdentifier, UserDTO> nodeEntry : dtoMap.entrySet()) {
final UserDTO nodeUser = nodeEntry.getValue();
if (nodeUser != null) {
accessPolicyEntities.retainAll(nodeUser.getAccessPolicies());
tenantEntities.retainAll(nodeUser.getUserGroups());
}
}
clientDto.setAccessPolicies(accessPolicyEntities);
clientDto.setUserGroups(tenantEntities);
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class UserGroupEntityMerger implements ComponentEntityMerger<UserGroupEntity> {
@Override
public void merge(UserGroupEntity clientEntity, Map<NodeIdentifier, UserGroupEntity> entityMap) {
ComponentEntityMerger.super.merge(clientEntity, entityMap);
}
/**
* Merges the UserGroupEntity responses.
*
* @param clientEntity the entity being returned to the client
* @param entityMap all node responses
*/
public void mergeComponents(final UserGroupEntity clientEntity, final Map<NodeIdentifier, UserGroupEntity> entityMap) {
final UserGroupDTO clientDto = clientEntity.getComponent();
final Map<NodeIdentifier, UserGroupDTO> dtoMap = new HashMap<>();
for (final Map.Entry<NodeIdentifier, UserGroupEntity> entry : entityMap.entrySet()) {
final UserGroupEntity nodeUserGroupEntity = entry.getValue();
final UserGroupDTO nodeUserGroupDto = nodeUserGroupEntity.getComponent();
dtoMap.put(entry.getKey(), nodeUserGroupDto);
}
mergeDtos(clientDto, dtoMap);
}
private static void mergeDtos(final UserGroupDTO clientDto, final Map<NodeIdentifier, UserGroupDTO> dtoMap) {
// if unauthorized for the client dto, simple return
if (clientDto == null) {
return;
}
final Set<AccessPolicyEntity> accessPolicyEntities = new HashSet<>(clientDto.getAccessPolicies());
final Set<TenantEntity> userEntities = new HashSet<>(clientDto.getUsers());
for (final Map.Entry<NodeIdentifier, UserGroupDTO> nodeEntry : dtoMap.entrySet()) {
final UserGroupDTO nodeUserGroup = nodeEntry.getValue();
if (nodeUserGroup != null) {
accessPolicyEntities.retainAll(nodeUserGroup.getAccessPolicies());
userEntities.retainAll(nodeUserGroup.getUsers());
}
}
clientDto.setAccessPolicies(accessPolicyEntities);
clientDto.setUsers(userEntities);
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import java.util.Collection;
import java.util.Map;
public class UserGroupsEntityMerger implements ComponentEntityMerger<UserGroupEntity> {
private static final UserGroupEntityMerger userGroupEntityMerger = new UserGroupEntityMerger();
/**
* Merges multiple UserGroupEntity responses.
*
* @param userGroupEntities entities being returned to the client
* @param entityMap all node responses
*/
public static void mergeUserGroups(final Collection<UserGroupEntity> userGroupEntities, final Map<String, Map<NodeIdentifier, UserGroupEntity>> entityMap) {
for (final UserGroupEntity entity : userGroupEntities) {
userGroupEntityMerger.merge(entity, entityMap.get(entity.getId()));
}
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.entity.UserEntity;
import java.util.Collection;
import java.util.Map;
public class UsersEntityMerger implements ComponentEntityMerger<UserEntity> {
private static final UserEntityMerger userEntityMerger = new UserEntityMerger();
/**
* Merges multiple UserEntity responses.
*
* @param userEntities entities being returned to the client
* @param entityMap all node responses
*/
public static void mergeUsers(final Collection<UserEntity> userEntities, final Map<String, Map<NodeIdentifier, UserEntity>> entityMap) {
for (final UserEntity entity : userEntities) {
userEntityMerger.merge(entity, entityMap.get(entity.getId()));
}
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.cluster.coordination.http.endpoints;
import org.junit.Test;
import java.net.URI;
import java.util.UUID;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class AccessPolicyEndpointMergerTest {
@Test
public void testCanHandle() throws Exception {
final AccessPolicyEndpointMerger merger = new AccessPolicyEndpointMerger();
assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "POST"));
assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "GET"));
assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies"), "PUT"));
assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/" + UUID.randomUUID().toString()), "PUT"));
assertFalse(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/Read/flow"), "GET"));
assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/flow"), "GET"));
assertTrue(merger.canHandle(URI.create("http://localhost:8080/nifi-api/policies/read/processors/" + UUID.randomUUID().toString()), "GET"));
}
}

View File

@ -27,6 +27,7 @@ import org.apache.commons.collections4.map.MultiValueMap;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserDetails;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
import org.apache.nifi.cluster.coordination.node.NodeConnectionState;
import org.apache.nifi.cluster.coordination.node.NodeConnectionStatus;
@ -157,9 +158,9 @@ public class TestThreadPoolRequestReplicator {
final Entity entity = new ProcessorEntity();
// set the user
final NiFiUser proxy2 = new StandardNiFiUser(proxyIdentity2);
final NiFiUser proxy1 = new StandardNiFiUser(proxyIdentity1, proxy2);
final NiFiUser user = new StandardNiFiUser(userIdentity, proxy1);
final NiFiUser proxy2 = new Builder().identity(proxyIdentity2).build();
final NiFiUser proxy1 = new Builder().identity(proxyIdentity1).chain(proxy2).build();
final NiFiUser user = new Builder().identity(userIdentity).chain(proxy1).build();
final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(user));
SecurityContextHolder.getContext().setAuthentication(authentication);

View File

@ -0,0 +1,96 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.dto.PermissionsDTO;
import org.apache.nifi.web.api.dto.TenantDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class AccessPolicyEntityMergerTest {
@Test
public void testMergeAccessPolicy() throws Exception {
final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
final PermissionsDTO permissed = new PermissionsDTO();
permissed.setCanRead(true);
permissed.setCanWrite(true);
final TenantDTO user1DTO = new TenantDTO();
user1DTO.setId("user-1");
final TenantEntity user1Entity = new TenantEntity();
user1Entity.setPermissions(permissed);
user1Entity.setId(user1DTO.getId());
user1Entity.setComponent(user1DTO);
final TenantDTO user2DTO = new TenantDTO();
user1DTO.setId("user-2");
final TenantEntity user2Entity = new TenantEntity();
user2Entity.setPermissions(permissed);
user2Entity.setId(user2DTO.getId());
user2Entity.setComponent(user2DTO);
final AccessPolicyDTO accessPolicy1DTO = new AccessPolicyDTO();
accessPolicy1DTO.setId("policy-1");
accessPolicy1DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
accessPolicy1DTO.setUserGroups(Stream.of(user2Entity).collect(Collectors.toSet()));
final AccessPolicyEntity accessPolicy1Entity = new AccessPolicyEntity();
accessPolicy1Entity.setPermissions(permissed);
accessPolicy1Entity.setId(accessPolicy1DTO.getId());
accessPolicy1Entity.setComponent(accessPolicy1DTO);
final AccessPolicyDTO accessPolicy2DTO = new AccessPolicyDTO();
accessPolicy2DTO.setId("policy-2");
accessPolicy2DTO.setUsers(Stream.of(user1Entity).collect(Collectors.toSet()));
accessPolicy2DTO.setUserGroups(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
final AccessPolicyEntity accessPolicy2Entity = new AccessPolicyEntity();
accessPolicy2Entity.setPermissions(permissed);
accessPolicy2Entity.setId(accessPolicy2DTO.getId());
accessPolicy2Entity.setComponent(accessPolicy2DTO);
final Map<NodeIdentifier, AccessPolicyEntity> nodeMap = new HashMap<>();
nodeMap.put(node1, accessPolicy1Entity);
nodeMap.put(node2, accessPolicy2Entity);
final AccessPolicyEntityMerger merger = new AccessPolicyEntityMerger();
merger.merge(accessPolicy1Entity, nodeMap);
assertEquals(1, accessPolicy1DTO.getUserGroups().size());
assertTrue(accessPolicy1DTO.getUsers().contains(user1Entity));
assertEquals(1, accessPolicy1DTO.getUserGroups().size());
assertTrue(accessPolicy1DTO.getUserGroups().contains(user2Entity));
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.AccessPolicySummaryDTO;
import org.apache.nifi.web.api.dto.PermissionsDTO;
import org.apache.nifi.web.api.dto.TenantDTO;
import org.apache.nifi.web.api.dto.UserDTO;
import org.apache.nifi.web.api.entity.AccessPolicySummaryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class UserEntityMergerTest {
@Test
public void testMergeAccessPolicy() throws Exception {
final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
final PermissionsDTO permissed = new PermissionsDTO();
permissed.setCanRead(true);
permissed.setCanWrite(true);
final TenantDTO userGroup1DTO = new TenantDTO();
userGroup1DTO.setId("user-group-1");
final TenantEntity userGroup1Entity = new TenantEntity();
userGroup1Entity.setPermissions(permissed);
userGroup1Entity.setId(userGroup1DTO.getId());
userGroup1Entity.setComponent(userGroup1DTO);
final TenantDTO userGroup2DTO = new TenantDTO();
userGroup1DTO.setId("user-group-2");
final TenantEntity userGroup2Entity = new TenantEntity();
userGroup2Entity.setPermissions(permissed);
userGroup2Entity.setId(userGroup2DTO.getId());
userGroup2Entity.setComponent(userGroup2DTO);
final AccessPolicySummaryDTO policy1DTO = new AccessPolicySummaryDTO();
policy1DTO.setId("policy-1");
final AccessPolicySummaryEntity policy1Entity = new AccessPolicySummaryEntity();
policy1Entity.setPermissions(permissed);
policy1Entity.setId(policy1DTO.getId());
policy1Entity.setComponent(policy1DTO);
final AccessPolicySummaryDTO policy2DTO = new AccessPolicySummaryDTO();
policy2DTO.setId("policy-2");
final AccessPolicySummaryEntity policy2Entity = new AccessPolicySummaryEntity();
policy2Entity.setPermissions(permissed);
policy2Entity.setId(policy2DTO.getId());
policy2Entity.setComponent(policy2DTO);
final UserDTO user1DTO = new UserDTO();
user1DTO.setId("user-1");
user1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet()));
user1DTO.setUserGroups(Stream.of(userGroup2Entity).collect(Collectors.toSet()));
final UserEntity user1Entity = new UserEntity();
user1Entity.setPermissions(permissed);
user1Entity.setId(user1DTO.getId());
user1Entity.setComponent(user1DTO);
final UserDTO user2DTO = new UserDTO();
user2DTO.setId("user-2");
user2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet()));
user2DTO.setUserGroups(Stream.of(userGroup1Entity, userGroup2Entity).collect(Collectors.toSet()));
final UserEntity user2Entity = new UserEntity();
user2Entity.setPermissions(permissed);
user2Entity.setId(user2DTO.getId());
user2Entity.setComponent(user2DTO);
final Map<NodeIdentifier, UserEntity> nodeMap = new HashMap<>();
nodeMap.put(node1, user1Entity);
nodeMap.put(node2, user2Entity);
final UserEntityMerger merger = new UserEntityMerger();
merger.merge(user1Entity, nodeMap);
assertEquals(1, user1DTO.getUserGroups().size());
assertTrue(user1DTO.getAccessPolicies().contains(policy1Entity));
assertEquals(1, user1DTO.getUserGroups().size());
assertTrue(user1DTO.getUserGroups().contains(userGroup2Entity));
}
}

View File

@ -0,0 +1,114 @@
/*
* 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.cluster.manager;
import org.apache.nifi.cluster.protocol.NodeIdentifier;
import org.apache.nifi.web.api.dto.AccessPolicyDTO;
import org.apache.nifi.web.api.dto.PermissionsDTO;
import org.apache.nifi.web.api.dto.TenantDTO;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import org.apache.nifi.web.api.entity.AccessPolicyEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
public class UserGroupEntityMergerTest {
@Test
public void testMergeAccessPolicy() throws Exception {
final NodeIdentifier node1 = new NodeIdentifier("node-1", "host-1", 8080, "host-1", 19998, null, null, null, false);
final NodeIdentifier node2 = new NodeIdentifier("node-2", "host-2", 8081, "host-2", 19999, null, null, null, false);
final PermissionsDTO permissed = new PermissionsDTO();
permissed.setCanRead(true);
permissed.setCanWrite(true);
final TenantDTO user1DTO = new TenantDTO();
user1DTO.setId("user-1");
final TenantEntity user1Entity = new TenantEntity();
user1Entity.setPermissions(permissed);
user1Entity.setId(user1DTO.getId());
user1Entity.setComponent(user1DTO);
final TenantDTO user2DTO = new TenantDTO();
user1DTO.setId("user-2");
final TenantEntity user2Entity = new TenantEntity();
user2Entity.setPermissions(permissed);
user2Entity.setId(user2DTO.getId());
user2Entity.setComponent(user2DTO);
final AccessPolicyDTO policy1DTO = new AccessPolicyDTO();
policy1DTO.setId("policy-1");
final AccessPolicyEntity policy1Entity = new AccessPolicyEntity();
policy1Entity.setPermissions(permissed);
policy1Entity.setId(policy1DTO.getId());
policy1Entity.setComponent(policy1DTO);
final AccessPolicyDTO policy2DTO = new AccessPolicyDTO();
policy2DTO.setId("policy-2");
final AccessPolicyEntity policy2Entity = new AccessPolicyEntity();
policy2Entity.setPermissions(permissed);
policy2Entity.setId(policy2DTO.getId());
policy2Entity.setComponent(policy2DTO);
final UserGroupDTO userGroup1DTO = new UserGroupDTO();
userGroup1DTO.setId("user-1");
userGroup1DTO.setAccessPolicies(Stream.of(policy1Entity, policy2Entity).collect(Collectors.toSet()));
userGroup1DTO.setUsers(Stream.of(user2Entity).collect(Collectors.toSet()));
final UserGroupEntity userGroup1Entity = new UserGroupEntity();
userGroup1Entity.setPermissions(permissed);
userGroup1Entity.setId(userGroup1DTO.getId());
userGroup1Entity.setComponent(userGroup1DTO);
final UserGroupDTO userGroup2DTO = new UserGroupDTO();
userGroup2DTO.setId("user-2");
userGroup2DTO.setAccessPolicies(Stream.of(policy1Entity).collect(Collectors.toSet()));
userGroup2DTO.setUsers(Stream.of(user1Entity, user2Entity).collect(Collectors.toSet()));
final UserGroupEntity userGroup2Entity = new UserGroupEntity();
userGroup2Entity.setPermissions(permissed);
userGroup2Entity.setId(userGroup2DTO.getId());
userGroup2Entity.setComponent(userGroup2DTO);
final Map<NodeIdentifier, UserGroupEntity> nodeMap = new HashMap<>();
nodeMap.put(node1, userGroup1Entity);
nodeMap.put(node2, userGroup2Entity);
final UserGroupEntityMerger merger = new UserGroupEntityMerger();
merger.merge(userGroup1Entity, nodeMap);
assertEquals(1, userGroup1DTO.getUsers().size());
assertTrue(userGroup1DTO.getAccessPolicies().contains(policy1Entity));
assertEquals(1, userGroup1DTO.getUsers().size());
assertTrue(userGroup1DTO.getUsers().contains(user2Entity));
}
}

View File

@ -32,7 +32,7 @@ public interface DataFlow {
/**
* @return the raw byte array of the Authorizer's fingerprint,
* null when not using a sub-class of AbstractPolicyBasedAuthorizer
* null when not using a ManagedAuthorizer
*/
public byte[] getAuthorizerFingerprint();

View File

@ -188,6 +188,10 @@
<version>4.3.1.201605051710-r</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.nifi</groupId>
<artifactId>nifi-authorizer</artifactId>
</dependency>
</dependencies>
<build>
<plugins>

View File

@ -17,8 +17,9 @@
package org.apache.nifi.controller;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.cluster.ConnectionException;
import org.apache.nifi.cluster.coordination.ClusterCoordinator;
@ -572,8 +573,8 @@ public class StandardFlowService implements FlowService, ProtocolHandler {
}
private byte[] getAuthorizerFingerprint() {
final boolean isInternalAuthorizer = (authorizer instanceof AbstractPolicyBasedAuthorizer);
return isInternalAuthorizer ? ((AbstractPolicyBasedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null;
final boolean isInternalAuthorizer = AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer);
return isInternalAuthorizer ? ((ManagedAuthorizer) authorizer).getFingerprint().getBytes(StandardCharsets.UTF_8) : null;
}
@Override

View File

@ -18,8 +18,10 @@ package org.apache.nifi.controller;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.authorization.exception.UninheritableAuthorizationsException;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.cluster.protocol.DataFlow;
import org.apache.nifi.cluster.protocol.StandardDataFlow;
@ -224,15 +226,15 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
logger.trace("Getting Authorizer fingerprint from controller");
final byte[] existingAuthFingerprint;
final AbstractPolicyBasedAuthorizer policyBasedAuthorizer;
final ManagedAuthorizer managedAuthorizer;
final Authorizer authorizer = controller.getAuthorizer();
if (authorizer instanceof AbstractPolicyBasedAuthorizer) {
policyBasedAuthorizer = (AbstractPolicyBasedAuthorizer) authorizer;
existingAuthFingerprint = policyBasedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8);
if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
managedAuthorizer = (ManagedAuthorizer) authorizer;
existingAuthFingerprint = managedAuthorizer.getFingerprint().getBytes(StandardCharsets.UTF_8);
} else {
existingAuthFingerprint = null;
policyBasedAuthorizer = null;
managedAuthorizer = null;
}
final Set<String> missingComponents = new HashSet<>();
@ -249,7 +251,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
if (existingFlowEmpty) {
configuration = parseFlowBytes(proposedFlow.getFlow());
if (configuration != null) {
logger.trace("Checking bunde compatibility");
logger.trace("Checking bundle compatibility");
checkBundleCompatibility(configuration);
}
} else {
@ -272,7 +274,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
logger.trace("Checking authorizer inheritability");
final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(existingDataFlow, proposedFlow);
final AuthorizerInheritability authInheritability = checkAuthorizerInheritability(authorizer, existingDataFlow, proposedFlow);
if (!authInheritability.isInheritable() && authInheritability.getReason() != null) {
throw new UninheritableFlowException("Proposed Authorizer is not inheritable by the flow controller because of Authorizer differences: " + authInheritability.getReason());
}
@ -415,10 +417,10 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
}
// if auths are inheritable and we have a policy based authorizer, then inherit
if (authInheritability.isInheritable() && policyBasedAuthorizer != null) {
if (authInheritability.isInheritable() && managedAuthorizer != null) {
logger.trace("Inheriting authorizations");
final String proposedAuthFingerprint = new String(proposedFlow.getAuthorizerFingerprint(), StandardCharsets.UTF_8);
policyBasedAuthorizer.inheritFingerprint(proposedAuthFingerprint);
managedAuthorizer.inheritFingerprint(proposedAuthFingerprint);
}
logger.debug("Finished syncing flows");
@ -1391,7 +1393,7 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
* @param proposedFlow the proposed DataFlow
* @return the AuthorizerInheritability result
*/
public AuthorizerInheritability checkAuthorizerInheritability(final DataFlow existingFlow, final DataFlow proposedFlow) {
private AuthorizerInheritability checkAuthorizerInheritability(final Authorizer authorizer, final DataFlow existingFlow, final DataFlow proposedFlow) {
final byte[] existing = existingFlow.getAuthorizerFingerprint();
final byte[] proposed = proposedFlow.getAuthorizerFingerprint();
@ -1414,15 +1416,20 @@ public class StandardFlowSynchronizer implements FlowSynchronizer {
// both are internal, but not the same
if (!Arrays.equals(existing, proposed)) {
final byte[] emptyAuthBytes = AbstractPolicyBasedAuthorizer.EMPTY_FINGERPRINT.getBytes(StandardCharsets.UTF_8);
if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
final ManagedAuthorizer managedAuthorizer = (ManagedAuthorizer) authorizer;
// if current is empty then we can take all the proposed authorizations
// otherwise they are both internal authorizers and don't match so we can't proceed
if (Arrays.equals(emptyAuthBytes, existing)) {
return AuthorizerInheritability.inheritable();
try {
// if the configurations are not equal, see if the manager indicates the proposed configuration is inheritable
managedAuthorizer.checkInheritability(new String(proposed, StandardCharsets.UTF_8));
return AuthorizerInheritability.inheritable();
} catch (final UninheritableAuthorizationsException e) {
return AuthorizerInheritability.uninheritable("Proposed Authorizations do not match current Authorizations: " + e.getMessage());
}
} else {
// should never hit since the existing is only null when authorizer is not managed
return AuthorizerInheritability.uninheritable(
"Proposed Authorizations do not match current Authorizations");
"Proposed Authorizations do not match current Authorizations and are not configured with an internal Authorizer");
}
}

View File

@ -18,7 +18,9 @@ package org.apache.nifi.nar;
import org.apache.nifi.annotation.behavior.RequiresInstanceClassLoading;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.AccessPolicyProvider;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.UserGroupProvider;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
import org.apache.nifi.components.ConfigurableComponent;
@ -82,6 +84,8 @@ public class ExtensionManager {
definitionMap.put(ReportingTask.class, new HashSet<>());
definitionMap.put(ControllerService.class, new HashSet<>());
definitionMap.put(Authorizer.class, new HashSet<>());
definitionMap.put(UserGroupProvider.class, new HashSet<>());
definitionMap.put(AccessPolicyProvider.class, new HashSet<>());
definitionMap.put(LoginIdentityProvider.class, new HashSet<>());
definitionMap.put(ProvenanceRepository.class, new HashSet<>());
definitionMap.put(ComponentStatusRepository.class, new HashSet<>());

View File

@ -17,7 +17,9 @@
package org.apache.nifi.nar;
import org.apache.nifi.authentication.LoginIdentityProvider;
import org.apache.nifi.authorization.AccessPolicyProvider;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.UserGroupProvider;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.components.Validator;
import org.apache.nifi.components.state.StateProvider;
@ -64,6 +66,8 @@ public class NarThreadContextClassLoader extends URLClassLoader {
narSpecificClasses.add(StreamCallback.class);
narSpecificClasses.add(ControllerService.class);
narSpecificClasses.add(Authorizer.class);
narSpecificClasses.add(UserGroupProvider.class);
narSpecificClasses.add(AccessPolicyProvider.class);
narSpecificClasses.add(LoginIdentityProvider.class);
narSpecificClasses.add(ProvenanceRepository.class);
narSpecificClasses.add(ComponentStatusRepository.class);

View File

@ -141,7 +141,7 @@
<nifi.security.truststoreType />
<nifi.security.truststorePasswd />
<nifi.security.needClientAuth />
<nifi.security.user.authorizer>file-provider</nifi.security.user.authorizer>
<nifi.security.user.authorizer>managed-authorizer</nifi.security.user.authorizer>
<nifi.security.user.login.identity.provider />
<nifi.security.x509.principal.extractor />
<nifi.security.ocsp.responder.url />

View File

@ -14,13 +14,105 @@
limitations under the License.
-->
<!--
This file lists the authority providers to use when running securely. In order
to use a specific provider it must be configured here and it's identifier
must be specified in the nifi.properties file.
This file lists the userGroupProviders, accessPolicyProviders, and authorizers to use when running securely. In order
to use a specific authorizer it must be configured here and it's identifier must be specified in the nifi.properties file.
If the authorizer is a managedAuthorizer, it may need to be configured with an accessPolicyProvider and an userGroupProvider.
This file allows for configuration of them, but they must be configured in order:
...
all userGroupProviders
all accessPolicyProviders
all Authorizers
...
-->
<authorizers>
<!--
The FileUserGroupProvider will provide support for managing users and groups which is backed by a file
on the local file system.
- Users File - The file where the FileUserGroupProvider will store users and groups.
- Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
be used to load the users and groups into the Users File.
- Initial User Identity [unique key] - The identity of a users and systems to seed the Users File. The name of
each property must be unique, for example: "Initial User Identity A", "Initial User Identity B",
"Initial User Identity C" or "Initial User Identity 1", "Initial User Identity 2", "Initial User Identity 3"
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the user identities,
so the values should be the unmapped identities (i.e. full DN from a certificate).
-->
<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"></property>
</userGroupProvider>
<!--
The FileAccessPolicyProvider will provide support for managing access policies which is backed by a file
on the local file system.
- 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.
- Authorizations File - The file where the FileAccessPolicyProvider will store policies.
- Initial Admin Identity - The identity of an initial admin user that will be granted access to the UI and
given the ability to create additional users, groups, and policies. The value of this property could be
a DN when using certificates or LDAP, or a Kerberos principal. This property will only be used when there
are no other policies defined. If this property is specified then a Legacy Authorized Users File can not be specified.
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the initial admin identity,
so the value should be the unmapped identity. This identity must be found in the configured User Group Provider.
- Legacy Authorized Users File - The full path to an existing authorized-users.xml that will be automatically
converted to the new authorizations model. If this property is specified then an Initial Admin Identity can
not be specified, and this property will only be used when there are no other users, groups, and policies defined.
NOTE: Any users in the legacy users file must be found in the configured User Group Provider.
- Node Identity [unique key] - The identity of a NiFi cluster node. When clustered, a property for each node
should be defined, so that every node knows about every other node. If not clustered these properties can be ignored.
The name of each property must be unique, for example for a three node cluster:
"Node Identity A", "Node Identity B", "Node Identity C" or "Node Identity 1", "Node Identity 2", "Node Identity 3"
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities,
so the values should be the unmapped identities (i.e. full DN from a certificate). This identity must be found
in the configured User Group Provider.
-->
<accessPolicyProvider>
<identifier>file-access-policy-provider</identifier>
<class>org.apache.nifi.authorization.FileAccessPolicyProvider</class>
<property name="User Group Provider">file-user-group-provider</property>
<property name="Authorizations File">./conf/authorizations.xml</property>
<property name="Initial Admin Identity"></property>
<property name="Legacy Authorized Users File"></property>
<property name="Node Identity 1"></property>
</accessPolicyProvider>
<!--
The StandardManagedAuthorizer. This authorizer implementation must be configured with the
Access Policy Provider which it will use to access and manage users, groups, and policies.
These users, groups, and policies will be used to make all access decisions during authorization
requests.
- Access Policy Provider - The identifier for an Access Policy Provider defined above.
-->
<authorizer>
<identifier>managed-authorizer</identifier>
<class>org.apache.nifi.authorization.StandardManagedAuthorizer</class>
<property name="Access Policy Provider">file-access-policy-provider</property>
</authorizer>
<!--
NOTE: This Authorizer has been replaced with the more granular approach configured above with the Standard
Managed Authorizer. However, it is still available for backwards compatibility reasons.
The FileAuthorizer is NiFi's provided authorizer and has the following properties:
- Authorizations File - The file where the FileAuthorizer will store policies.
@ -48,7 +140,7 @@
NOTE: Any identity mapping rules specified in nifi.properties will also be applied to the node identities,
so the values should be the unmapped identities (i.e. full DN from a certificate).
-->
<authorizer>
<!-- <authorizer>
<identifier>file-provider</identifier>
<class>org.apache.nifi.authorization.FileAuthorizer</class>
<property name="Authorizations File">./conf/authorizations.xml</property>
@ -56,9 +148,7 @@
<property name="Initial Admin Identity"></property>
<property name="Legacy Authorized Users File"></property>
<!-- Provide the identity (typically a DN) of each node when clustered, see above description of Node Identity.
<property name="Node Identity 1"></property>
<property name="Node Identity 2"></property>
-->
</authorizer>
-->
</authorizers>

View File

@ -16,24 +16,6 @@
*/
package org.apache.nifi.remote;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.AuthorizationResult.Result;
import org.apache.nifi.authorization.Authorizer;
@ -41,9 +23,10 @@ import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.DataTransferAuthorizable;
import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser;
import org.apache.nifi.authorization.user.StandardNiFiUser.Builder;
import org.apache.nifi.authorization.util.IdentityMapping;
import org.apache.nifi.authorization.util.IdentityMappingUtil;
import org.apache.nifi.authorization.util.UserGroupUtil;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.connectable.ConnectableType;
import org.apache.nifi.controller.AbstractPort;
@ -73,6 +56,24 @@ import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static java.util.Objects.requireNonNull;
public class StandardRootGroupPort extends AbstractPort implements RootGroupPort {
private static final String CATEGORY = "Site to Site";
@ -362,8 +363,8 @@ public class StandardRootGroupPort extends AbstractPort implements RootGroupPort
}
final String identity = IdentityMappingUtil.mapIdentity(dn, identityMappings);
return checkUserAuthorization(new StandardNiFiUser(identity));
final Set<String> groups = UserGroupUtil.getUserGroups(authorizer, identity);
return checkUserAuthorization(new Builder().identity(identity).groups(groups).build());
}
@Override

View File

@ -2559,6 +2559,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getDataTransferResource(port.getResource()))
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(false)
.action(RequestAction.WRITE)

View File

@ -17,20 +17,6 @@
package org.apache.nifi.web;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.action.Action;
import org.apache.nifi.action.Component;
@ -79,6 +65,21 @@ import org.apache.nifi.web.util.ClientResponseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Implements the NiFiWebConfigurationContext interface to support a context in both standalone and clustered environments.
*/
@ -110,6 +111,7 @@ public class StandardNiFiWebConfigurationContext implements NiFiWebConfiguration
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getFlowResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.Authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -140,8 +140,8 @@ public class AccessPolicyResource extends ApplicationResource {
) @PathParam("resource") String rawResource) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
// parse the action and resource type
@ -203,8 +203,8 @@ public class AccessPolicyResource extends ApplicationResource {
) final AccessPolicyEntity requestAccessPolicyEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
}
if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) {
@ -294,8 +294,8 @@ public class AccessPolicyResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {
@ -356,8 +356,8 @@ public class AccessPolicyResource extends ApplicationResource {
) final AccessPolicyEntity requestAccessPolicyEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
}
if (requestAccessPolicyEntity == null || requestAccessPolicyEntity.getComponent() == null) {
@ -454,8 +454,8 @@ public class AccessPolicyResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_POLICIES);
}
if (isReplicateRequest()) {

View File

@ -110,6 +110,7 @@ public class ControllerResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getControllerResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)

View File

@ -94,6 +94,7 @@ public class CountersResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getCountersResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(action)

View File

@ -217,6 +217,7 @@ public class FlowResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getFlowResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)
@ -2236,6 +2237,7 @@ public class FlowResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getControllerResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -114,6 +114,7 @@ public class ProvenanceResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getProvenanceResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -74,6 +74,7 @@ public class ResourceResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getResourceResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -112,6 +112,7 @@ public class SiteToSiteResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getSiteToSiteResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -77,6 +77,7 @@ public class SystemDiagnosticsResource extends ApplicationResource {
final AuthorizationRequest request = new AuthorizationRequest.Builder()
.resource(ResourceFactory.getSystemResource())
.identity(user.getIdentity())
.groups(user.getGroups())
.anonymous(user.isAnonymous())
.accessAttempt(true)
.action(RequestAction.READ)

View File

@ -23,8 +23,8 @@ import com.wordnik.swagger.annotations.ApiResponse;
import com.wordnik.swagger.annotations.ApiResponses;
import com.wordnik.swagger.annotations.Authorization;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.user.NiFiUserUtils;
@ -149,8 +149,8 @@ public class TenantsResource extends ApplicationResource {
) final UserEntity requestUserEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (requestUserEntity == null || requestUserEntity.getComponent() == null) {
@ -234,8 +234,8 @@ public class TenantsResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {
@ -284,8 +284,8 @@ public class TenantsResource extends ApplicationResource {
public Response getUsers() {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {
@ -352,8 +352,8 @@ public class TenantsResource extends ApplicationResource {
) final UserEntity requestUserEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (requestUserEntity == null || requestUserEntity.getComponent() == null) {
@ -448,8 +448,8 @@ public class TenantsResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (isReplicateRequest()) {
@ -538,8 +538,8 @@ public class TenantsResource extends ApplicationResource {
) final UserGroupEntity requestUserGroupEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) {
@ -623,8 +623,8 @@ public class TenantsResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {
@ -673,8 +673,8 @@ public class TenantsResource extends ApplicationResource {
public Response getUserGroups() {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {
@ -740,8 +740,8 @@ public class TenantsResource extends ApplicationResource {
) final UserGroupEntity requestUserGroupEntity) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (requestUserGroupEntity == null || requestUserGroupEntity.getComponent() == null) {
@ -836,8 +836,8 @@ public class TenantsResource extends ApplicationResource {
@PathParam("id") final String id) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_CONFIGURABLE_USERS);
}
if (isReplicateRequest()) {
@ -897,7 +897,7 @@ public class TenantsResource extends ApplicationResource {
@ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
}
)
public Response searchCluster(
public Response searchTenants(
@ApiParam(
value = "Identity to search for.",
required = true
@ -905,8 +905,8 @@ public class TenantsResource extends ApplicationResource {
@QueryParam("q") @DefaultValue(StringUtils.EMPTY) String value) {
// ensure we're running with a configurable authorizer
if (!(authorizer instanceof AbstractPolicyBasedAuthorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
if (!AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
throw new IllegalStateException(AccessPolicyDAO.MSG_NON_MANAGED_AUTHORIZER);
}
if (isReplicateRequest()) {

View File

@ -55,7 +55,7 @@ public class AccessDeniedExceptionMapper implements ExceptionMapper<AccessDenied
if (user == null) {
identity = "<no user found>";
} else {
identity = user.getIdentity();
identity = user.toString();
}
logger.info(String.format("%s does not have permission to access the requested resource. %s Returning %s response.", identity, exception.getMessage(), status));

View File

@ -38,9 +38,9 @@ import org.apache.nifi.annotation.behavior.Stateful;
import org.apache.nifi.annotation.documentation.CapabilityDescription;
import org.apache.nifi.annotation.documentation.DeprecationNotice;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.AccessPolicy;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.Resource;
@ -220,7 +220,9 @@ public final class DtoFactory {
// get the refresh interval
final long refreshInterval = FormatUtils.getTimeDuration(autoRefreshInterval, TimeUnit.SECONDS);
dto.setAutoRefreshIntervalSeconds(refreshInterval);
dto.setSupportsConfigurableAuthorizer(authorizer instanceof AbstractPolicyBasedAuthorizer);
dto.setSupportsManagedAuthorizer(AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer));
dto.setSupportsConfigurableUsersAndGroups(AuthorizerCapabilityDetection.isConfigurableUserGroupProvider(authorizer));
dto.setSupportsConfigurableAuthorizer(AuthorizerCapabilityDetection.isConfigurableAccessPolicyProvider(authorizer));
final Date now = new Date();
dto.setTimeOffset(TimeZone.getDefault().getOffset(now.getTime()));

View File

@ -23,7 +23,9 @@ import org.apache.nifi.web.api.dto.AccessPolicyDTO;
public interface AccessPolicyDAO {
String MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, and policies. Please contact your system administrator.";
String MSG_NON_MANAGED_AUTHORIZER = "This NiFi is not configured to internally manage users, groups, or policies. Please contact your system administrator.";
String MSG_NON_CONFIGURABLE_POLICIES = "This NiFi is not configured to allow configurable policies. Please contact your system administrator.";
String MSG_NON_CONFIGURABLE_USERS = "This NiFi is not configured to allow configurable users and groups. Please contact your system administrator.";
/**
* Whether or not NiFi supports a configurable authorizer.

View File

@ -16,15 +16,21 @@
*/
package org.apache.nifi.web.dao.impl;
import org.apache.nifi.authorization.AbstractPolicyBasedAuthorizer;
import org.apache.nifi.authorization.AccessPolicy;
import org.apache.nifi.authorization.AccessPolicyProvider;
import org.apache.nifi.authorization.AccessPolicyProviderInitializationContext;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.AuthorizerCapabilityDetection;
import org.apache.nifi.authorization.AuthorizerConfigurationContext;
import org.apache.nifi.authorization.AuthorizerInitializationContext;
import org.apache.nifi.authorization.Group;
import org.apache.nifi.authorization.ManagedAuthorizer;
import org.apache.nifi.authorization.ConfigurableAccessPolicyProvider;
import org.apache.nifi.authorization.ConfigurableUserGroupProvider;
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.User;
import org.apache.nifi.authorization.UsersAndAccessPolicies;
import org.apache.nifi.authorization.UserAndGroups;
import org.apache.nifi.authorization.UserGroupProvider;
import org.apache.nifi.authorization.UserGroupProviderInitializationContext;
import org.apache.nifi.authorization.exception.AuthorizationAccessException;
import org.apache.nifi.authorization.exception.AuthorizerCreationException;
import org.apache.nifi.authorization.exception.AuthorizerDestructionException;
@ -44,118 +50,101 @@ import java.util.stream.Collectors;
public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGroupDAO, UserDAO {
private final AbstractPolicyBasedAuthorizer authorizer;
private final boolean supportsConfigurableAuthorizer;
private final AccessPolicyProvider accessPolicyProvider;
private final UserGroupProvider userGroupProvider;
public StandardPolicyBasedAuthorizerDAO(final Authorizer authorizer) {
if (authorizer instanceof AbstractPolicyBasedAuthorizer) {
this.authorizer = (AbstractPolicyBasedAuthorizer) authorizer;
this.supportsConfigurableAuthorizer = true;
if (AuthorizerCapabilityDetection.isManagedAuthorizer(authorizer)) {
accessPolicyProvider = ((ManagedAuthorizer) authorizer).getAccessPolicyProvider();
} else {
this.authorizer = new AbstractPolicyBasedAuthorizer() {
@Override
public Group doAddGroup(final Group group) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public Group getGroup(final String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public Group doUpdateGroup(final Group group) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public Group deleteGroup(final Group group) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public User doAddUser(final User user) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public User getUser(final String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public User getUserByIdentity(final String identity) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public User doUpdateUser(final User user) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public User deleteUser(final User user) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public AccessPolicy doAddAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public AccessPolicy getAccessPolicy(final String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public AccessPolicy updateAccessPolicy(final AccessPolicy accessPolicy) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
@Override
public AccessPolicy deleteAccessPolicy(final AccessPolicy policy) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
}
accessPolicyProvider = new AccessPolicyProvider() {
@Override
public Set<AccessPolicy> getAccessPolicies() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public UsersAndAccessPolicies getUsersAndAccessPolicies() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER);
public AccessPolicy getAccessPolicy(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public void initialize(final AuthorizerInitializationContext initializationContext) throws AuthorizerCreationException {
public AccessPolicy getAccessPolicy(String resourceIdentifier, RequestAction action) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public void doOnConfigured(final AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
public UserGroupProvider getUserGroupProvider() {
return new UserGroupProvider() {
@Override
public Set<User> getUsers() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public User getUser(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public User getUserByIdentity(String identity) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public Set<Group> getGroups() throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public Group getGroup(String identifier) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public UserAndGroups getUserAndGroups(String identity) throws AuthorizationAccessException {
throw new IllegalStateException(MSG_NON_MANAGED_AUTHORIZER);
}
@Override
public void initialize(UserGroupProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
}
@Override
public void initialize(AccessPolicyProviderInitializationContext initializationContext) throws AuthorizerCreationException {
}
@Override
public void onConfigured(AuthorizerConfigurationContext configurationContext) throws AuthorizerCreationException {
}
@Override
public void preDestruction() throws AuthorizerDestructionException {
}
};
this.supportsConfigurableAuthorizer = false;
}
userGroupProvider = accessPolicyProvider.getUserGroupProvider();
}
private AccessPolicy findAccessPolicy(final RequestAction requestAction, final String resource) {
return authorizer.getAccessPolicies().stream()
return accessPolicyProvider.getAccessPolicies().stream()
.filter(policy -> policy.getAction().equals(requestAction) && policy.getResource().equals(resource))
.findFirst()
.orElse(null);
@ -163,23 +152,28 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public boolean supportsConfigurableAuthorizer() {
return supportsConfigurableAuthorizer;
return accessPolicyProvider instanceof ConfigurableAccessPolicyProvider;
}
@Override
public boolean hasAccessPolicy(final String accessPolicyId) {
return authorizer.getAccessPolicy(accessPolicyId) != null;
return accessPolicyProvider.getAccessPolicy(accessPolicyId) != null;
}
@Override
public AccessPolicy createAccessPolicy(final AccessPolicyDTO accessPolicyDTO) {
return authorizer.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(),
accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO));
if (supportsConfigurableAuthorizer()) {
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
return configurableAccessPolicyProvider.addAccessPolicy(buildAccessPolicy(accessPolicyDTO.getId(),
accessPolicyDTO.getResource(), RequestAction.valueOfValue(accessPolicyDTO.getAction()), accessPolicyDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
}
}
@Override
public AccessPolicy getAccessPolicy(final String accessPolicyId) {
final AccessPolicy accessPolicy = authorizer.getAccessPolicy(accessPolicyId);
final AccessPolicy accessPolicy = accessPolicyProvider.getAccessPolicy(accessPolicyId);
if (accessPolicy == null) {
throw new ResourceNotFoundException(String.format("Unable to find access policy with id '%s'.", accessPolicyId));
}
@ -210,14 +204,25 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public AccessPolicy updateAccessPolicy(final AccessPolicyDTO accessPolicyDTO) {
final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId());
return authorizer.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(),
currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO));
if (supportsConfigurableAuthorizer()) {
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
final AccessPolicy currentAccessPolicy = getAccessPolicy(accessPolicyDTO.getId());
return configurableAccessPolicyProvider.updateAccessPolicy(buildAccessPolicy(currentAccessPolicy.getIdentifier(),
currentAccessPolicy.getResource(), currentAccessPolicy.getAction(), accessPolicyDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
}
}
@Override
public AccessPolicy deleteAccessPolicy(final String accessPolicyId) {
return authorizer.deleteAccessPolicy(getAccessPolicy(accessPolicyId));
if (supportsConfigurableAuthorizer()) {
final ConfigurableAccessPolicyProvider configurableAccessPolicyProvider = (ConfigurableAccessPolicyProvider) accessPolicyProvider;
return configurableAccessPolicyProvider.deleteAccessPolicy(getAccessPolicy(accessPolicyId));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_POLICIES);
}
}
private AccessPolicy buildAccessPolicy(final String identifier, final String resource, final RequestAction action, final AccessPolicyDTO accessPolicyDTO) {
@ -238,17 +243,22 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public boolean hasUserGroup(final String userGroupId) {
return authorizer.getGroup(userGroupId) != null;
return userGroupProvider.getGroup(userGroupId) != null;
}
@Override
public Group createUserGroup(final UserGroupDTO userGroupDTO) {
return authorizer.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO));
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
return configurableUserGroupProvider.addGroup(buildUserGroup(userGroupDTO.getId(), userGroupDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
@Override
public Group getUserGroup(final String userGroupId) {
final Group userGroup = authorizer.getGroup(userGroupId);
final Group userGroup = userGroupProvider.getGroup(userGroupId);
if (userGroup == null) {
throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", userGroupId));
}
@ -257,14 +267,14 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public Set<Group> getUserGroupsForUser(String userId) {
return authorizer.getGroups().stream()
return userGroupProvider.getGroups().stream()
.filter(g -> g.getUsers().contains(userId))
.collect(Collectors.toSet());
}
@Override
public Set<AccessPolicy> getAccessPoliciesForUser(String userId) {
return authorizer.getAccessPolicies().stream()
return accessPolicyProvider.getAccessPolicies().stream()
.filter(p -> {
// policy contains the user
if (p.getUsers().contains(userId)) {
@ -272,14 +282,14 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
}
// policy contains a group with the user
return !p.getGroups().stream().filter(g -> authorizer.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty();
return !p.getGroups().stream().filter(g -> userGroupProvider.getGroup(g).getUsers().contains(userId)).collect(Collectors.toSet()).isEmpty();
})
.collect(Collectors.toSet());
}
@Override
public Set<AccessPolicy> getAccessPoliciesForUserGroup(String userGroupId) {
return authorizer.getAccessPolicies().stream()
return accessPolicyProvider.getAccessPolicies().stream()
.filter(p -> {
// policy contains the user group
return p.getGroups().contains(userGroupId);
@ -289,17 +299,46 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public Set<Group> getUserGroups() {
return authorizer.getGroups();
return userGroupProvider.getGroups();
}
@Override
public Group updateUserGroup(final UserGroupDTO userGroupDTO) {
return authorizer.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO));
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
return configurableUserGroupProvider.updateGroup(buildUserGroup(getUserGroup(userGroupDTO.getId()).getIdentifier(), userGroupDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
@Override
public Group deleteUserGroup(final String userGroupId) {
return authorizer.deleteGroup(getUserGroup(userGroupId));
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
final Group group = getUserGroup(userGroupId);
final Group removedGroup = configurableUserGroupProvider.deleteGroup(group);
// ensure the user was removed
if (removedGroup == null) {
throw new ResourceNotFoundException(String.format("Unable to find user group with id '%s'.", removedGroup));
}
// remove any references to the user group being deleted from policies if possible
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
if (policy.getGroups().contains(removedGroup.getIdentifier())) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeGroup(removedGroup.getIdentifier());
((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build());
}
}
}
return removedGroup;
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
private Group buildUserGroup(final String identifier, final UserGroupDTO userGroupDTO) {
@ -313,17 +352,22 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public boolean hasUser(final String userId) {
return authorizer.getUser(userId) != null;
return userGroupProvider.getUser(userId) != null;
}
@Override
public User createUser(final UserDTO userDTO) {
return authorizer.addUser(buildUser(userDTO.getId(), userDTO));
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
return configurableUserGroupProvider.addUser(buildUser(userDTO.getId(), userDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
@Override
public User getUser(final String userId) {
final User user = authorizer.getUser(userId);
final User user = userGroupProvider.getUser(userId);
if (user == null) {
throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId));
}
@ -332,18 +376,46 @@ public class StandardPolicyBasedAuthorizerDAO implements AccessPolicyDAO, UserGr
@Override
public Set<User> getUsers() {
return authorizer.getUsers();
return userGroupProvider.getUsers();
}
@Override
public User updateUser(final UserDTO userDTO) {
return authorizer.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO));
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
return configurableUserGroupProvider.updateUser(buildUser(getUser(userDTO.getId()).getIdentifier(), userDTO));
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
@Override
public User deleteUser(final String userId) {
final User user = getUser(userId);
return authorizer.deleteUser(user);
if (userGroupProvider instanceof ConfigurableUserGroupProvider) {
final ConfigurableUserGroupProvider configurableUserGroupProvider = (ConfigurableUserGroupProvider) userGroupProvider;
final User user = getUser(userId);
final User removedUser = configurableUserGroupProvider.deleteUser(user);
// ensure the user was removed
if (removedUser == null) {
throw new ResourceNotFoundException(String.format("Unable to find user with id '%s'.", userId));
}
// remove any references to the user being deleted from policies if possible
if (accessPolicyProvider instanceof ConfigurableAccessPolicyProvider) {
for (AccessPolicy policy : accessPolicyProvider.getAccessPolicies()) {
if (policy.getUsers().contains(removedUser.getIdentifier())) {
final AccessPolicy.Builder builder = new AccessPolicy.Builder(policy).removeUser(removedUser.getIdentifier());
((ConfigurableAccessPolicyProvider) accessPolicyProvider).updateAccessPolicy(builder.build());
}
}
}
return removedUser;
} else {
throw new IllegalStateException(MSG_NON_CONFIGURABLE_USERS);
}
}
private User buildUser(final String identifier, final UserDTO userDTO) {

View File

@ -16,35 +16,51 @@
*/
package org.apache.nifi.web
import org.apache.nifi.authorization.*
import org.apache.nifi.authorization.AccessDeniedException
import org.apache.nifi.authorization.AccessPolicy
import org.apache.nifi.authorization.AuthorizableLookup
import org.apache.nifi.authorization.AuthorizationResult
import org.apache.nifi.authorization.Authorizer
import org.apache.nifi.authorization.Group
import org.apache.nifi.authorization.RequestAction
import org.apache.nifi.authorization.Resource
import org.apache.nifi.authorization.User
import org.apache.nifi.authorization.resource.Authorizable
import org.apache.nifi.authorization.resource.ResourceFactory
import org.apache.nifi.authorization.user.NiFiUser
import org.apache.nifi.authorization.user.StandardNiFiUser
import org.apache.nifi.authorization.user.NiFiUserDetails
import org.apache.nifi.authorization.user.StandardNiFiUser
import org.apache.nifi.controller.service.ControllerServiceProvider
import org.apache.nifi.reporting.Bulletin
import org.apache.nifi.reporting.BulletinRepository
import org.apache.nifi.reporting.ComponentType
import org.apache.nifi.web.api.dto.*
import org.apache.nifi.web.api.dto.AccessPolicyDTO
import org.apache.nifi.web.api.dto.BulletinDTO
import org.apache.nifi.web.api.dto.DtoFactory
import org.apache.nifi.web.api.dto.EntityFactory
import org.apache.nifi.web.api.dto.RevisionDTO
import org.apache.nifi.web.api.dto.UserDTO
import org.apache.nifi.web.api.dto.UserGroupDTO
import org.apache.nifi.web.api.entity.BulletinEntity
import org.apache.nifi.web.api.entity.UserEntity
import org.apache.nifi.web.controller.ControllerFacade
import org.apache.nifi.web.dao.AccessPolicyDAO
import org.apache.nifi.web.dao.UserDAO
import org.apache.nifi.web.dao.UserGroupDAO
import org.apache.nifi.web.revision.*
import org.apache.nifi.web.revision.DeleteRevisionTask
import org.apache.nifi.web.revision.ReadOnlyRevisionCallback
import org.apache.nifi.web.revision.RevisionClaim
import org.apache.nifi.web.revision.RevisionManager
import org.apache.nifi.web.revision.UpdateRevisionTask
import org.apache.nifi.web.security.token.NiFiAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import spock.lang.Ignore
import spock.lang.Specification
import spock.lang.Unroll
class StandardNiFiServiceFacadeSpec extends Specification {
def setup() {
final NiFiUser user = new StandardNiFiUser("nifi-user");
final NiFiUser user = new StandardNiFiUser.Builder().identity("nifi-user").build();
final NiFiAuthenticationToken auth = new NiFiAuthenticationToken(new NiFiUserDetails(user));
SecurityContextHolder.getContext().setAuthentication(auth);
}

View File

@ -27,6 +27,14 @@ import spock.lang.Unroll
class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
private AbstractPolicyBasedAuthorizer mockAuthorizer() {
def authorizer = Mock AbstractPolicyBasedAuthorizer
authorizer.getAccessPolicyProvider() >> {
callRealMethod();
}
return authorizer;
}
@Unroll
def "test non-policy-based authorizer #method throws IllegalStateException"() {
when:
@ -34,31 +42,57 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
then:
def e = thrown(IllegalStateException)
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_ABSTRACT_POLICY_BASED_AUTHORIZER)
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_MANAGED_AUTHORIZER)
where:
method | daoMethod
'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
'createUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) }
'createUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') }
'deleteUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') }
'deleteUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') }
'getAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getAccessPolicy('1') }
'getUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUser('1') }
'getUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).getUserGroup('1') }
'hasAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasAccessPolicy('1') }
'hasUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUser('1') }
'hasUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).hasUserGroup('1') }
'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
}
@Unroll
def "test non-configurable user group provider #method throws IllegalStateException"() {
when:
daoMethod()
then:
def e = thrown(IllegalStateException)
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_USERS)
where:
method | daoMethod
'createUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUser(new UserDTO(id: '1', identity: 'a')) }
'createUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
'deleteUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUser('1') }
'deleteUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteUserGroup('1') }
'updateUser' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUser(new UserDTO(id: '1', identity: 'a')) }
'updateUserGroup' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateUserGroup(new UserGroupDTO(id: '1', identity: 'a')) }
}
@Unroll
def "test non-configurable access policy provider #method throws IllegalStateException"() {
when:
daoMethod()
then:
def e = thrown(IllegalStateException)
assert e.message.equalsIgnoreCase(StandardPolicyBasedAuthorizerDAO.MSG_NON_CONFIGURABLE_POLICIES)
where:
method | daoMethod
'createAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).createAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
'deleteAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).deleteAccessPolicy('1') }
'updateAccessPolicy' | { new StandardPolicyBasedAuthorizerDAO(Mock(Authorizer)).updateAccessPolicy(new AccessPolicyDTO(id: '1', resource: '/1', action: "read")) }
}
@Unroll
def "HasAccessPolicy: accessPolicy: #accessPolicy"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -79,7 +113,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "CreateAccessPolicy: accessPolicy=#accessPolicy"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
users: [new TenantEntity(id: 'user-id-1')] as Set,
@ -92,7 +126,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
noExceptionThrown()
then:
1 * authorizer.getAccessPolicies() >> accessPolicies
1 * authorizer.doAddAccessPolicy(accessPolicy) >> accessPolicy
0 * _
result?.equals accessPolicy
@ -106,7 +139,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetAccessPolicy: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -126,7 +159,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetAccessPolicy: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -141,7 +174,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateAccessPolicy: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
users: [new TenantEntity(id: 'user-id-1')] as Set,
@ -165,7 +198,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateAccessPolicy: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new AccessPolicyDTO(id: 'policy-id-1', resource: '/fake/resource', action: "read",
users: [new TenantEntity(id: 'user-id-1')] as Set,
@ -183,7 +216,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteAccessPolicy: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -204,7 +237,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteAccessPolicy: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -219,7 +252,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "HasUserGroup: userGroup=#userGroup"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -239,7 +272,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "CreateUserGroup: userGroup=#userGroup"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
@ -250,8 +283,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
noExceptionThrown()
then:
1 * authorizer.getUsers() >> users
1 * authorizer.getGroups() >> groups
1 * authorizer.doAddGroup(userGroup) >> userGroup
0 * _
result?.equals userGroup
@ -265,7 +296,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUserGroup: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -284,7 +315,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUserGroup: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -299,7 +330,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUserGroups: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -318,7 +349,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateUserGroup: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
@ -327,8 +358,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
then:
1 * authorizer.getGroup(requestDTO.id) >> userGroup
1 * authorizer.getUsers() >> users
1 * authorizer.getGroups() >> groups
1 * authorizer.doUpdateGroup(userGroup) >> userGroup
0 * _
result?.equals(userGroup)
@ -342,7 +371,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateUserGroup: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserGroupDTO(id: 'user-group-id-1', identity: 'user group identity', users: [new TenantEntity(id: 'user-id-1')] as Set)
@ -358,7 +387,10 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteUserGroup: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
authorizer.getAccessPolicyProvider().getAccessPolicies() >> {
callRealMethod();
}
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -367,6 +399,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
then:
1 * authorizer.getGroup('user-group-id-1') >> userGroup
1 * authorizer.deleteGroup(userGroup) >> userGroup
1 * authorizer.getAccessPolicies() >> []
0 * _
assert result?.equals(userGroup)
@ -378,7 +411,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteUserGroup: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -393,7 +426,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "HasUser: user=#user"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -412,7 +445,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "CreateUser: user=#user"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
@ -423,8 +456,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
noExceptionThrown()
then:
1 * authorizer.getUsers() >> users
1 * authorizer.getGroups() >> groups
1 * authorizer.doAddUser(user) >> user
0 * _
result?.equals user
@ -438,7 +469,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUser: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -457,7 +488,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUser: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -472,7 +503,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "GetUsers: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -491,7 +522,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateUser: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
@ -500,8 +531,6 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
then:
1 * authorizer.getUser(requestDTO.id) >> user
1 * authorizer.getUsers() >> users
1 * authorizer.getGroups() >> groups
1 * authorizer.doUpdateUser(user) >> user
0 * _
result?.equals(user)
@ -515,7 +544,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "UpdateUser: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
def requestDTO = new UserDTO(id: 'user-id-1', identity: 'user identity', userGroups: [new TenantEntity(id: 'user-group-id-1')] as Set)
@ -531,7 +560,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteUser: success"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:
@ -540,6 +569,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
then:
1 * authorizer.getUser('user-id-1') >> user
1 * authorizer.deleteUser(user) >> user
1 * authorizer.getAccessPolicies() >> []
0 * _
result?.equals(user)
@ -551,7 +581,7 @@ class StandardPolicyBasedAuthorizerDAOSpec extends Specification {
@Unroll
def "DeleteUser: failure"() {
given:
def authorizer = Mock AbstractPolicyBasedAuthorizer
def authorizer = mockAuthorizer()
def dao = new StandardPolicyBasedAuthorizerDAO(authorizer)
when:

Some files were not shown because too many files have changed in this diff Show More