nifi-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mcgil...@apache.org
Subject [2/2] nifi git commit: NIFI-1952 Updated StandardPolicyBasedAuthorizerDAO to throw ResourceNotFoundExceptions when user/group/policy not found Added spec for StandardPolicyBasedAuthorizerDAO Added exception mapper for AuthorizationAccessException, added
Date Mon, 27 Jun 2016 02:23:57 GMT
NIFI-1952 Updated StandardPolicyBasedAuthorizerDAO to throw ResourceNotFoundExceptions when user/group/policy not found
Added spec for StandardPolicyBasedAuthorizerDAO
Added exception mapper for AuthorizationAccessException, added mapper to nifi-web-api-context.xml
Added rest endpoints to get all users and user groups
Merged UsersResource and UserGroupsResource into TenantsResource
This closes #582


Project: http://git-wip-us.apache.org/repos/asf/nifi/repo
Commit: http://git-wip-us.apache.org/repos/asf/nifi/commit/64719b6f
Tree: http://git-wip-us.apache.org/repos/asf/nifi/tree/64719b6f
Diff: http://git-wip-us.apache.org/repos/asf/nifi/diff/64719b6f

Branch: refs/heads/master
Commit: 64719b6f9bb7bf6a70c6ffb5b287f998f3d10f6a
Parents: b911c9d
Author: Jeff Storck <jtswork@gmail.com>
Authored: Wed Jun 22 20:47:53 2016 -0400
Committer: Matt Gilman <matt.c.gilman@gmail.com>
Committed: Sun Jun 26 22:23:25 2016 -0400

----------------------------------------------------------------------
 .../org/apache/nifi/web/api/dto/UserDTO.java    |  10 +-
 .../nifi/web/api/entity/UserGroupsEntity.java   |  44 +
 .../apache/nifi/web/api/entity/UsersEntity.java |  33 +-
 .../nifi/authorization/FileAuthorizer.java      |   7 +-
 .../nifi/authorization/RoleAccessPolicy.java    |   3 +-
 .../nifi/authorization/FileAuthorizerTest.java  |  15 +-
 .../authorization/resource/ResourceFactory.java |  87 +-
 .../authorization/resource/ResourceType.java    |   7 +-
 .../resource/TenantAuthorizable.java            |  33 +
 .../resource/UserGroupsAuthorizable.java        |  31 -
 .../resource/UsersAuthorizable.java             |  33 -
 .../org/apache/nifi/web/AuthorizableLookup.java |  12 +-
 .../org/apache/nifi/web/NiFiServiceFacade.java  |  14 +
 .../nifi/web/StandardAuthorizableLookup.java    |  15 +-
 .../nifi/web/StandardNiFiServiceFacade.java     |  58 +-
 .../apache/nifi/web/api/TenantsResource.java    | 848 +++++++++++++++++++
 .../apache/nifi/web/api/UserGroupsResource.java | 381 ---------
 .../org/apache/nifi/web/api/UsersResource.java  | 381 ---------
 .../AuthorizationAccessExceptionMapper.java     |  44 +
 .../org/apache/nifi/web/api/dto/DtoFactory.java |   2 +-
 .../java/org/apache/nifi/web/dao/UserDAO.java   |   9 +
 .../org/apache/nifi/web/dao/UserGroupDAO.java   |   9 +
 .../impl/StandardPolicyBasedAuthorizerDAO.java  |  92 +-
 .../src/main/resources/nifi-web-api-context.xml |  10 +-
 .../StandardPolicyBasedAuthorizerDAOSpec.groovy | 561 ++++++++++++
 25 files changed, 1718 insertions(+), 1021 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/UserDTO.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/UserDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/UserDTO.java
index 04d60aa..0d2ecde 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/UserDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/UserDTO.java
@@ -29,7 +29,7 @@ import java.util.Set;
 public class UserDTO extends ComponentDTO {
 
     private String identity;
-    private Set<UserGroupEntity> groups;
+    private Set<UserGroupEntity> userGroups;
 
     /**
      * @return users identity
@@ -49,11 +49,11 @@ public class UserDTO extends ComponentDTO {
      * @return groups to which the user belongs
      */
     @ApiModelProperty(value = "The groups to which the user belongs.")
-    public Set<UserGroupEntity> getGroups() {
-        return groups;
+    public Set<UserGroupEntity> getUserGroups() {
+        return userGroups;
     }
 
-    public void setGroups(Set<UserGroupEntity> groups) {
-        this.groups = groups;
+    public void setUserGroups(Set<UserGroupEntity> userGroups) {
+        this.userGroups = userGroups;
     }
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
new file mode 100644
index 0000000..bdde662
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UserGroupsEntity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.web.api.entity;
+
+import javax.xml.bind.annotation.XmlRootElement;
+import java.util.Collection;
+
+/**
+ * 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 collection of
+ * UserGroupEntity objects.
+ */
+@XmlRootElement(name = "userGroupsEntity")
+public class UserGroupsEntity {
+
+    private Collection<UserGroupEntity> userGroups;
+
+    /**
+     * The collection of UserGroupEntity objects that are being serialized.
+     *
+     * @return The UserGroupEntity objects
+     */
+    public Collection<UserGroupEntity> getUserGroups() {
+        return userGroups;
+    }
+
+    public void setUserGroups(Collection<UserGroupEntity> userGroups) {
+        this.userGroups = userGroups;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UsersEntity.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UsersEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UsersEntity.java
index 4acc7cb..6624338 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UsersEntity.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/UsersEntity.java
@@ -16,45 +16,28 @@
  */
 package org.apache.nifi.web.api.entity;
 
-import java.util.Collection;
-import java.util.Date;
 import javax.xml.bind.annotation.XmlRootElement;
-import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
-import org.apache.nifi.web.api.dto.UserDTO;
-import org.apache.nifi.web.api.dto.util.TimeAdapter;
+import java.util.Collection;
 
 /**
- * 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 collection of UserDTO.
+ * 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 collection of UserEntity
+ * objects.
  */
 @XmlRootElement(name = "usersEntity")
 public class UsersEntity extends Entity {
 
-    private Collection<UserDTO> users;
-    private Date generated;
+    private Collection<UserEntity> users;
 
     /**
-     * The collection of UserDTOs that are being serialized.
+     * The collection of UserEntity objects that are being serialized.
      *
-     * @return The UserDTO object
+     * @return The UserEntity objects
      */
-    public Collection<UserDTO> getUsers() {
+    public Collection<UserEntity> getUsers() {
         return users;
     }
 
-    public void setUsers(Collection<UserDTO> users) {
+    public void setUsers(Collection<UserEntity> users) {
         this.users = users;
     }
-
-    /**
-     * @return When this content was generated
-     */
-    @XmlJavaTypeAdapter(TimeAdapter.class)
-    public Date getGenerated() {
-        return generated;
-    }
-
-    public void setGenerated(Date generated) {
-        this.generated = generated;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
index 906d369..eb5cf48 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/FileAuthorizer.java
@@ -327,11 +327,8 @@ public class FileAuthorizer extends AbstractPolicyBasedAuthorizer {
             addAccessPolicy(authorizations, ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
         }
 
-        // grant the user read/write access to the /users resource
-        addAccessPolicy(authorizations, ResourceType.User.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
-
-        // grant the user read/write access to the /groups resource
-        addAccessPolicy(authorizations, ResourceType.Group.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
+        // grant the user read/write access to the /tenants resource
+        addAccessPolicy(authorizations, ResourceType.Tenant.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);
 
         // grant the user read/write access to the /policies resource
         addAccessPolicy(authorizations, ResourceType.Policy.getValue(), adminUser.getIdentifier(), READ_CODE + WRITE_CODE);

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java
index 6757402..32b7ce9 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/main/java/org/apache/nifi/authorization/RoleAccessPolicy.java
@@ -79,8 +79,7 @@ public final class RoleAccessPolicy {
         if (rootGroupId != null) {
             adminPolicies.add(new RoleAccessPolicy(ResourceType.ProcessGroup.getValue() + "/" + rootGroupId, READ_ACTION));
         }
-        adminPolicies.add(new RoleAccessPolicy(ResourceType.User.getValue(), READ_WRITE_ACTION));
-        adminPolicies.add(new RoleAccessPolicy(ResourceType.Group.getValue(), READ_WRITE_ACTION));
+        adminPolicies.add(new RoleAccessPolicy(ResourceType.Tenant.getValue(), READ_WRITE_ACTION));
         adminPolicies.add(new RoleAccessPolicy(ResourceType.Policy.getValue(), READ_WRITE_ACTION));
         roleAccessPolicies.put(Role.ROLE_ADMIN, Collections.unmodifiableSet(adminPolicies));
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
index 30a1230..0b2fea4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-file-authorizer/src/test/java/org/apache/nifi/authorization/FileAuthorizerTest.java
@@ -235,7 +235,7 @@ public class FileAuthorizerTest {
 
         // verify user4's policies
         final Map<String,Set<RequestAction>> user4Policies = getResourceActions(policies, user4);
-        assertEquals(6, user4Policies.size());
+        assertEquals(5, user4Policies.size());
 
         assertTrue(user4Policies.containsKey(ResourceType.Flow.getValue()));
         assertEquals(1, user4Policies.get(ResourceType.Flow.getValue()).size());
@@ -245,11 +245,8 @@ public class FileAuthorizerTest {
         assertEquals(1, user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).size());
         assertTrue(user4Policies.get(ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID).contains(RequestAction.READ));
 
-        assertTrue(user4Policies.containsKey(ResourceType.User.getValue()));
-        assertEquals(2, user4Policies.get(ResourceType.User.getValue()).size());
-
-        assertTrue(user4Policies.containsKey(ResourceType.Group.getValue()));
-        assertEquals(2, user4Policies.get(ResourceType.Group.getValue()).size());
+        assertTrue(user4Policies.containsKey(ResourceType.Tenant.getValue()));
+        assertEquals(2, user4Policies.get(ResourceType.Tenant.getValue()).size());
 
         assertTrue(user4Policies.containsKey(ResourceType.Policy.getValue()));
         assertEquals(2, user4Policies.get(ResourceType.Policy.getValue()).size());
@@ -338,7 +335,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(5, policies.size());
+        assertEquals(4, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 
@@ -375,7 +372,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(4, policies.size());
+        assertEquals(3, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 
@@ -412,7 +409,7 @@ public class FileAuthorizerTest {
         assertEquals(adminIdentity, adminUser.getIdentity());
 
         final Set<AccessPolicy> policies = authorizer.getAccessPolicies();
-        assertEquals(4, policies.size());
+        assertEquals(3, policies.size());
 
         final String rootGroupResource = ResourceType.ProcessGroup.getValue() + "/" + ROOT_GROUP_ID;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
index 0454ad7..5fc56af 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceFactory.java
@@ -17,9 +17,7 @@
 package org.apache.nifi.authorization.resource;
 
 import org.apache.nifi.authorization.AccessPolicy;
-import org.apache.nifi.authorization.Group;
 import org.apache.nifi.authorization.Resource;
-import org.apache.nifi.authorization.User;
 
 import java.util.Objects;
 
@@ -85,18 +83,6 @@ public final class ResourceFactory {
         }
     };
 
-    private final static Resource GROUP_RESOURCE = new Resource() {
-        @Override
-        public String getIdentifier() {
-            return ResourceType.Group.getValue();
-        }
-
-        @Override
-        public String getName() {
-            return "Group";
-        }
-    };
-
     private final static Resource INPUT_PORT_RESOURCE = new Resource() {
         @Override
         public String getIdentifier() {
@@ -277,27 +263,27 @@ public final class ResourceFactory {
         }
     };
 
-    private final static Resource TOKEN_RESOURCE = new Resource() {
+    private final static Resource TENANT_RESOURCE = new Resource() {
         @Override
         public String getIdentifier() {
-            return ResourceType.Token.getValue();
+            return ResourceType.Tenant.getValue();
         }
 
         @Override
         public String getName() {
-            return "API access token";
+            return "Tenant";
         }
     };
 
-    private final static Resource USER_RESOURCE = new Resource() {
+    private final static Resource TOKEN_RESOURCE = new Resource() {
         @Override
         public String getIdentifier() {
-            return ResourceType.User.getValue();
+            return ResourceType.Token.getValue();
         }
 
         @Override
         public String getName() {
-            return "User";
+            return "API access token";
         }
     };
 
@@ -314,32 +300,6 @@ public final class ResourceFactory {
         }
     };
 
-    private final static Resource USERS_RESOURCE = new Resource() {
-
-        @Override
-        public String getIdentifier() {
-            return "/users";
-        }
-
-        @Override
-        public String getName() {
-            return "Users";
-        }
-    };
-
-    private final static Resource USERGROUPS_RESOURCE = new Resource() {
-
-        @Override
-        public String getIdentifier() {
-            return "/user-groups";
-        }
-
-        @Override
-        public String getName() {
-            return "User Groups";
-        }
-    };
-
     /**
      * Gets the Resource for accessing Connections.
      *
@@ -386,15 +346,6 @@ public final class ResourceFactory {
     }
 
     /**
-     * Gets the Resource for accessing Groups which allows management of user groups.
-     *
-     * @return The resource for accessing Groups
-     */
-    public static Resource getGroupResource() {
-        return GROUP_RESOURCE;
-    }
-
-    /**
      * Gets the Resource for accessing Input Ports.
      *
      * @return The resource for accessing Input Ports
@@ -541,12 +492,12 @@ public final class ResourceFactory {
     }
 
     /**
-     * Gets the Resource for accessing Users which includes creating, modifying, and deleting Users.
+     * Gets the Resource for accessing Tenants which includes creating, modifying, and deleting Users and UserGroups.
      *
-     * @return The Resource for accessing Users
+     * @return The Resource for accessing Tenants
      */
-    public static Resource getUserResource() {
-        return USER_RESOURCE;
+    public static Resource getTenantResource() {
+        return TENANT_RESOURCE;
     }
 
     /**
@@ -603,24 +554,6 @@ public final class ResourceFactory {
     }
 
     /**
-     * Gets a Resource for accessing {@link User} configurations.
-     *
-     * @return              The resource
-     */
-    public static Resource getUsersResource() {
-        return USERS_RESOURCE;
-    }
-
-    /**
-     * Gets a Resource for accessing {@link Group}s configuration.
-     *
-     * @return              The resource
-     */
-    public static Resource getUserGroupsResource() {
-        return USERGROUPS_RESOURCE;
-    }
-
-    /**
      * Gets a Resource for accessing a component configuration.
      *
      * @param resourceType  The type of resource being accessed

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
index 94b6118..eb08cec 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/ResourceType.java
@@ -20,9 +20,9 @@ public enum ResourceType {
     Connection("/connections"),
     Controller("/controller"),
     ControllerService("/controller-services"),
+    Counters("/counters"),
     Funnel("/funnel"),
     Flow("/flow"),
-    Group("/groups"),
     InputPort("/input-ports"),
     Label("/labels"),
     OutputPort("/output-ports"),
@@ -37,9 +37,8 @@ public enum ResourceType {
     SiteToSite("/site-to-site"),
     System("/system"),
     Template("/templates"),
-    Token("/token"),
-    Counters("/counters"),
-    User("/users");
+    Tenant("/tenants"),
+    Token("/token");
 
     final String value;
 

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/TenantAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/TenantAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/TenantAuthorizable.java
new file mode 100644
index 0000000..e7fe42e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/TenantAuthorizable.java
@@ -0,0 +1,33 @@
+/*
+ * 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.resource;
+
+import org.apache.nifi.authorization.Resource;
+
+public class TenantAuthorizable implements Authorizable {
+
+    @Override
+    public Authorizable getParentAuthorizable() {
+        return null;
+    }
+
+    @Override
+    public Resource getResource() {
+        return ResourceFactory.getTenantResource();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UserGroupsAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UserGroupsAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UserGroupsAuthorizable.java
deleted file mode 100644
index 938371c..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UserGroupsAuthorizable.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.resource;
-
-import org.apache.nifi.authorization.Resource;
-
-public class UserGroupsAuthorizable implements Authorizable {
-    @Override
-    public Authorizable getParentAuthorizable() {
-        return null;
-    }
-
-    @Override
-    public Resource getResource() {
-        return ResourceFactory.getUserGroupsResource();
-    }
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UsersAuthorizable.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UsersAuthorizable.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UsersAuthorizable.java
deleted file mode 100644
index 2b255d4..0000000
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-authorization/src/main/java/org/apache/nifi/authorization/resource/UsersAuthorizable.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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.resource;
-
-import org.apache.nifi.authorization.Resource;
-
-public class UsersAuthorizable implements Authorizable {
-
-    @Override
-    public Authorizable getParentAuthorizable() {
-        return null;
-    }
-
-    @Override
-    public Resource getResource() {
-        return ResourceFactory.getUsersResource();
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/AuthorizableLookup.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/AuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/AuthorizableLookup.java
index f988780..f5421e5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/AuthorizableLookup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/AuthorizableLookup.java
@@ -17,8 +17,6 @@
 package org.apache.nifi.web;
 
 import org.apache.nifi.authorization.AccessPolicy;
-import org.apache.nifi.authorization.Group;
-import org.apache.nifi.authorization.User;
 import org.apache.nifi.authorization.resource.Authorizable;
 import org.apache.nifi.controller.Snippet;
 
@@ -163,16 +161,10 @@ public interface AuthorizableLookup {
     Snippet getSnippet(String id);
 
     /**
-     * Get the {@link Authorizable} that represents the resource of {@link User}s.
+     * Get the {@link Authorizable} that represents the resource of users and user groups.
      * @return authorizable
      */
-    Authorizable getUsersAuthorizable();
-
-    /**
-     * Get the {@link Authorizable} that represents the resource of {@link Group}s.
-     * @return authorizable
-     */
-    Authorizable getUserGroupsAuthorizable();
+    Authorizable getTenantAuthorizable();
 
     /**
      * Get the {@link Authorizable} the represents the parent resource of {@link AccessPolicy} resources.

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 1c08f75..6eaa8d0 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -1221,6 +1221,13 @@ public interface NiFiServiceFacade {
     UserEntity getUser(String userId, boolean prune);
 
     /**
+     * Gets all the users.
+     * @param prune If true, the users in the groups to which the users belong will not be returned
+     * @return The user transfer objects
+     */
+    Set<UserEntity> getUsers(boolean prune);
+
+    /**
      * Updates the specified user.
      * @param revision Revision to compare with current base revision
      * @param userDTO The user DTO
@@ -1256,6 +1263,13 @@ public interface NiFiServiceFacade {
     UserGroupEntity getUserGroup(String userGroupId, boolean prune);
 
     /**
+     * Gets all user groups.
+     * @param prune If true, the user groups of the users in the user groups will not be returned
+     * @return The user group transfer objects
+     */
+    Set<UserGroupEntity> getUserGroups(boolean prune);
+
+    /**
      * Updates the specified user group.
      * @param revision Revision to compare with current base revision
      * @param userGroupDTO The user group DTO

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardAuthorizableLookup.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardAuthorizableLookup.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardAuthorizableLookup.java
index f702ffc..13a5c20 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardAuthorizableLookup.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardAuthorizableLookup.java
@@ -19,8 +19,7 @@ package org.apache.nifi.web;
 import org.apache.nifi.authorization.resource.AccessPoliciesAuthorizable;
 import org.apache.nifi.authorization.resource.AccessPolicyAuthorizable;
 import org.apache.nifi.authorization.resource.Authorizable;
-import org.apache.nifi.authorization.resource.UserGroupsAuthorizable;
-import org.apache.nifi.authorization.resource.UsersAuthorizable;
+import org.apache.nifi.authorization.resource.TenantAuthorizable;
 import org.apache.nifi.controller.ConfiguredComponent;
 import org.apache.nifi.controller.Snippet;
 import org.apache.nifi.controller.service.ControllerServiceNode;
@@ -44,8 +43,7 @@ import org.apache.nifi.web.dao.TemplateDAO;
 
 class StandardAuthorizableLookup implements AuthorizableLookup {
 
-    private static final UsersAuthorizable USERS_AUTHORIZABLE = new UsersAuthorizable();
-    private static final UserGroupsAuthorizable USER_GROUPS_AUTHORIZABLE = new UserGroupsAuthorizable();
+    private static final TenantAuthorizable TENANT_AUTHORIZABLE = new TenantAuthorizable();
     private static final Authorizable ACCESS_POLICIES_AUTHORIZABLE = new AccessPoliciesAuthorizable();
 
     // nifi core components
@@ -159,13 +157,8 @@ class StandardAuthorizableLookup implements AuthorizableLookup {
     }
 
     @Override
-    public Authorizable getUsersAuthorizable() {
-        return USERS_AUTHORIZABLE;
-    }
-
-    @Override
-    public Authorizable getUserGroupsAuthorizable() {
-        return USER_GROUPS_AUTHORIZABLE;
+    public Authorizable getTenantAuthorizable() {
+        return TENANT_AUTHORIZABLE;
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index eaaef98..4373472 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -535,7 +535,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
     @Override
     public UserEntity updateUser(final Revision revision, final UserDTO userDTO) {
-        final Authorizable usersAuthorizable = authorizableLookup.getUsersAuthorizable();
+        final Authorizable usersAuthorizable = authorizableLookup.getTenantAuthorizable();
         final RevisionUpdate<UserDTO> snapshot = updateComponent(revision,
                 usersAuthorizable,
                 () -> userDAO.updateUser(userDTO),
@@ -547,7 +547,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
     @Override
     public UserGroupEntity updateUserGroup(final Revision revision, final UserGroupDTO userGroupDTO) {
-        final Authorizable userGroupsAuthorizable = authorizableLookup.getUserGroupsAuthorizable();
+        final Authorizable userGroupsAuthorizable = authorizableLookup.getTenantAuthorizable();
         final RevisionUpdate<UserGroupDTO> snapshot = updateComponent(revision,
                 userGroupsAuthorizable,
                 () -> userGroupDAO.updateUserGroup(userGroupDTO),
@@ -985,7 +985,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         final Set<UserGroupEntity> userGroups = user != null ? user.getGroups().stream().map(userGroupId -> getUserGroup(userGroupId, true)).collect(Collectors.toSet()) : null;
         final UserDTO snapshot = deleteComponent(
                 revision,
-                authorizableLookup.getUsersAuthorizable(),
+                authorizableLookup.getTenantAuthorizable(),
                 () -> userDAO.deleteUser(userId),
                 dtoFactory.createUserDto(user, userGroups));
 
@@ -999,7 +999,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
                 null;
         final UserGroupDTO snapshot = deleteComponent(
                 revision,
-                authorizableLookup.getUserGroupsAuthorizable(),
+                authorizableLookup.getTenantAuthorizable(),
                 () -> userGroupDAO.deleteUserGroup(userGroupId),
                 dtoFactory.createUserGroupDto(userGroup, users));
 
@@ -1283,7 +1283,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         final User newUser = userDAO.createUser(userDTO);
         final UserDTO newUserDto = dtoFactory.createUserDto(newUser, newUser.getGroups().stream().map(userGroupId -> getUserGroup(userGroupId, true)).collect(Collectors.toSet()));
 
-        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(authorizableLookup.getUsersAuthorizable());
+        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(authorizableLookup.getTenantAuthorizable());
         return entityFactory.createUserEntity(newUserDto, dtoFactory.createRevisionDTO(new FlowModification(revision, creator)), accessPolicy);
     }
 
@@ -1296,7 +1296,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
         final Group newUserGroup = userGroupDAO.createUserGroup(userGroupDTO);
         final UserGroupDTO newUserGroupDto = dtoFactory.createUserGroupDto(newUserGroup, newUserGroup.getUsers().stream().map(userId -> getUser(userId, true)).collect(Collectors.toSet()));
 
-        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(authorizableLookup.getUserGroupsAuthorizable());
+        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(authorizableLookup.getTenantAuthorizable());
         return entityFactory.createUserGroupEntity(newUserGroupDto, dtoFactory.createRevisionDTO(new FlowModification(revision, creator)), accessPolicy);
     }
 
@@ -2354,7 +2354,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     @Override
     public UserEntity getUser(final String userId, final boolean prune) {
         return revisionManager.get(userId, rev -> {
-            final Authorizable usersAuthorizable = authorizableLookup.getUsersAuthorizable();
+            final Authorizable usersAuthorizable = authorizableLookup.getTenantAuthorizable();
             final RevisionDTO revision = dtoFactory.createRevisionDTO(rev);
             final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(usersAuthorizable);
             final User user = userDAO.getUser(userId);
@@ -2367,7 +2367,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
     private UserEntity getUserPruned(final String userId) {
         return revisionManager.get(userId, rev -> {
-            final Authorizable usersAuthorizable = authorizableLookup.getUsersAuthorizable();
+            final Authorizable usersAuthorizable = authorizableLookup.getTenantAuthorizable();
             final RevisionDTO revision = dtoFactory.createRevisionDTO(rev);
             final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(usersAuthorizable);
             final User user = userDAO.getUser(userId);
@@ -2376,9 +2376,28 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     }
 
     @Override
+    public Set<UserEntity> getUsers(boolean prune) {
+        final Authorizable userAuthorizable = authorizableLookup.getTenantAuthorizable();
+        final Set<User> users = userDAO.getUsers();
+        final Set<String> ids = users.stream().map(user -> user.getIdentifier()).collect(Collectors.toSet());
+        return revisionManager.get(ids, () -> {
+            return users.stream()
+                    .map(user -> {
+                        final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(user.getIdentifier()));
+                        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(userAuthorizable);
+                        final Set<UserGroupEntity> userGroups = user.getGroups().stream()
+                                .map(userGroupId -> prune ? getUserGroupPruned(userGroupId) : getUserGroup(userGroupId, false))
+                                .collect(Collectors.toSet());
+                        return entityFactory.createUserEntity(dtoFactory.createUserDto(user, userGroups), revision, accessPolicy);
+                    })
+                    .collect(Collectors.toSet());
+        });
+    }
+
+    @Override
     public UserGroupEntity getUserGroup(final String userGroupId, final boolean prune) {
         return revisionManager.get(userGroupId, rev -> {
-            final Authorizable userGroupsAuthorizable = authorizableLookup.getUserGroupsAuthorizable();
+            final Authorizable userGroupsAuthorizable = authorizableLookup.getTenantAuthorizable();
             final RevisionDTO revision = dtoFactory.createRevisionDTO(rev);
             final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(userGroupsAuthorizable);
             final Group userGroup = userGroupDAO.getUserGroup(userGroupId);
@@ -2390,7 +2409,7 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
 
     private UserGroupEntity getUserGroupPruned(final String userGroupId) {
         return revisionManager.get(userGroupId, rev -> {
-            final Authorizable userGroupsAuthorizable = authorizableLookup.getUserGroupsAuthorizable();
+            final Authorizable userGroupsAuthorizable = authorizableLookup.getTenantAuthorizable();
             final RevisionDTO revision = dtoFactory.createRevisionDTO(rev);
             final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(userGroupsAuthorizable);
             final Group userGroup = userGroupDAO.getUserGroup(userGroupId);
@@ -2399,6 +2418,25 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
     }
 
     @Override
+    public Set<UserGroupEntity> getUserGroups(boolean prune) {
+        final Authorizable userGroupAuthorizable = authorizableLookup.getTenantAuthorizable();
+        final Set<Group> userGroups = userGroupDAO.getUserGroups();
+        final Set<String> ids = userGroups.stream().map(userGroup -> userGroup.getIdentifier()).collect(Collectors.toSet());
+        return revisionManager.get(ids, () -> {
+            return userGroups.stream()
+                    .map(userGroup -> {
+                        final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(userGroup.getIdentifier()));
+                        final AccessPolicyDTO accessPolicy = dtoFactory.createAccessPolicyDto(userGroupAuthorizable);
+                        final Set<UserEntity> users = userGroup.getUsers().stream()
+                                .map(userGroupId -> prune ? getUserPruned(userGroupId) : getUser(userGroupId, false))
+                                .collect(Collectors.toSet());
+                        return entityFactory.createUserGroupEntity(dtoFactory.createUserGroupDto(userGroup, users), revision, accessPolicy);
+                    })
+                    .collect(Collectors.toSet());
+        });
+    }
+
+    @Override
     public Set<LabelEntity> getLabels(final String groupId) {
         final Set<Label> labels = labelDAO.getLabels(groupId);
         final Set<String> ids = labels.stream().map(label -> label.getIdentifier()).collect(Collectors.toSet());

http://git-wip-us.apache.org/repos/asf/nifi/blob/64719b6f/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
----------------------------------------------------------------------
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
new file mode 100644
index 0000000..37b8c69
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/TenantsResource.java
@@ -0,0 +1,848 @@
+/*
+ * 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.web.api;
+
+import com.wordnik.swagger.annotations.Api;
+import com.wordnik.swagger.annotations.ApiOperation;
+import com.wordnik.swagger.annotations.ApiParam;
+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.Authorizer;
+import org.apache.nifi.authorization.RequestAction;
+import org.apache.nifi.authorization.resource.Authorizable;
+import org.apache.nifi.cluster.coordination.ClusterCoordinator;
+import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
+import org.apache.nifi.util.NiFiProperties;
+import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.Revision;
+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.UserEntity;
+import org.apache.nifi.web.api.entity.UserGroupEntity;
+import org.apache.nifi.web.api.entity.UserGroupsEntity;
+import org.apache.nifi.web.api.entity.UsersEntity;
+import org.apache.nifi.web.api.request.ClientIdParameter;
+import org.apache.nifi.web.api.request.LongParameter;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.DefaultValue;
+import javax.ws.rs.GET;
+import javax.ws.rs.HttpMethod;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.Set;
+
+@Path("tenants")
+@Api(
+        value = "tenants",
+        description = "Endpoint for managing users and user groups."
+)
+public class TenantsResource extends ApplicationResource {
+
+    private final NiFiServiceFacade serviceFacade;
+    private final Authorizer authorizer;
+
+    public TenantsResource(NiFiServiceFacade serviceFacade, Authorizer authorizer, NiFiProperties properties, RequestReplicator requestReplicator, ClusterCoordinator clusterCoordinator) {
+        this.serviceFacade = serviceFacade;
+        this.authorizer = authorizer;
+        setProperties(properties);
+        setRequestReplicator(requestReplicator);
+        setClusterCoordinator(clusterCoordinator);
+    }
+
+    /**
+     * Populates the uri for the specified users.
+     *
+     * @param userEntities users
+     * @return user entities
+     */
+    public Set<UserEntity> populateRemainingUserEntitiesContent(Set<UserEntity> userEntities) {
+        for (UserEntity userEntity : userEntities) {
+            populateRemainingUserEntityContent(userEntity);
+        }
+        return userEntities;
+    }
+
+    /**
+     * Populates the uri for the specified user.
+     *
+     * @param userEntity userEntity
+     * @return userEntity
+     */
+    public UserEntity populateRemainingUserEntityContent(UserEntity userEntity) {
+        if (userEntity.getComponent() != null) {
+            populateRemainingUserContent(userEntity.getComponent());
+        }
+        return userEntity;
+    }
+
+    /**
+     * Populates the uri for the specified user.
+     */
+    public UserDTO populateRemainingUserContent(UserDTO user) {
+        // populate the user href
+        user.setUri(generateResourceUri("tenants/users", user.getId()));
+        return user;
+    }
+
+    /**
+     * Populates the uri for the specified users.
+     *
+     * @param users users
+     * @return user data transfer objects
+     */
+    public Set<UserDTO> populateRemainingUsersContent(Set<UserDTO> users) {
+        for (UserDTO userDTO : users) {
+            populateRemainingUserContent(userDTO);
+        }
+        return users;
+    }
+
+    /**
+     * Creates a new user.
+     *
+     * @param httpServletRequest request
+     * @param userEntity         An userEntity.
+     * @return An userEntity.
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Creates a user",
+            response = UserEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 createUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The user configuration details.",
+                    required = true
+            ) final UserEntity userEntity) {
+
+        if (userEntity == null || userEntity.getComponent() == null) {
+            throw new IllegalArgumentException("User details must be specified.");
+        }
+
+        if (userEntity.getRevision() == null || (userEntity.getRevision().getVersion() == null || userEntity.getRevision().getVersion() != 0)) {
+            throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Processor.");
+        }
+
+        if (userEntity.getComponent().getId() != null) {
+            throw new IllegalArgumentException("User ID cannot be specified.");
+        }
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.POST, userEntity);
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final boolean validationPhase = isValidationPhase(httpServletRequest);
+        if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) {
+            // authorize access
+            serviceFacade.authorizeAccess(lookup -> {
+                final Authorizable users = lookup.getTenantAuthorizable();
+                users.authorize(authorizer, RequestAction.WRITE);
+            });
+        }
+        if (validationPhase) {
+            return generateContinueResponse().build();
+        }
+
+        // set the user id as appropriate
+        userEntity.getComponent().setId(generateUuid());
+
+        // get revision from the config
+        final RevisionDTO revisionDTO = userEntity.getRevision();
+        Revision revision = new Revision(revisionDTO.getVersion(), revisionDTO.getClientId(), userEntity.getComponent().getId());
+
+        // create the user and generate the json
+        final UserEntity entity = serviceFacade.createUser(revision, userEntity.getComponent());
+        populateRemainingUserEntityContent(entity);
+
+        // build the response
+        return clusterContext(generateCreatedResponse(URI.create(entity.getComponent().getUri()), entity)).build();
+    }
+
+    /**
+     * Retrieves the specified user.
+     *
+     * @param id The id of the user to retrieve
+     * @return An userEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @ApiOperation(
+            value = "Gets a user",
+            response = UserEntity.class,
+            authorizations = {
+                    @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
+                    @Authorization(value = "Administrator", type = "ROLE_ADMIN")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 getUser(
+            @ApiParam(
+                    value = "The user id.",
+                    required = true
+            )
+            @PathParam("id") final String id) {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        // authorize access
+        serviceFacade.authorizeAccess(lookup -> {
+            final Authorizable users = lookup.getTenantAuthorizable();
+            users.authorize(authorizer, RequestAction.READ);
+        });
+
+        // get the user
+        final UserEntity entity = serviceFacade.getUser(id, true);
+        populateRemainingUserEntityContent(entity);
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Retrieves all the of users in this NiFi.
+     *
+     * @return A UsersEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users")
+    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @ApiOperation(
+            value = "Gets all users",
+            response = UsersEntity.class,
+            authorizations = {
+                    @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
+                    @Authorization(value = "Administrator", type = "ROLE_ADMIN")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 getUsers() {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        // authorize access
+        serviceFacade.authorizeAccess(lookup -> {
+            final Authorizable users = lookup.getTenantAuthorizable();
+            users.authorize(authorizer, RequestAction.READ);
+        });
+
+        // get all the users
+        final Set<UserEntity> users = serviceFacade.getUsers(true);
+
+        // create the response entity
+        final UsersEntity entity = new UsersEntity();
+        entity.setUsers(populateRemainingUserEntitiesContent(users));
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Updates a user.
+     *
+     * @param httpServletRequest request
+     * @param id                 The id of the user to update.
+     * @param userEntity         An userEntity.
+     * @return An userEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Updates a user",
+            response = UserEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 updateUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The user id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The user configuration details.",
+                    required = true
+            ) final UserEntity userEntity) {
+
+        if (userEntity == null || userEntity.getComponent() == null) {
+            throw new IllegalArgumentException("User details must be specified.");
+        }
+
+        if (userEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        // ensure the ids are the same
+        final UserDTO userDTO = userEntity.getComponent();
+        if (!id.equals(userDTO.getId())) {
+            throw new IllegalArgumentException(String.format("The user id (%s) in the request body does not equal the "
+                    + "user id of the requested resource (%s).", userDTO.getId(), id));
+        }
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, userEntity);
+        }
+
+        // Extract the revision
+        final Revision revision = getRevision(userEntity, id);
+        return withWriteLock(
+                serviceFacade,
+                revision,
+                lookup -> {
+                    final Authorizable users = lookup.getTenantAuthorizable();
+                    users.authorize(authorizer, RequestAction.WRITE);
+                },
+                null,
+                () -> {
+                    // update the user
+                    final UserEntity entity = serviceFacade.updateUser(revision, userDTO);
+                    populateRemainingUserEntityContent(entity);
+
+                    return clusterContext(generateOkResponse(entity)).build();
+                }
+        );
+    }
+
+    /**
+     * Removes the specified user.
+     *
+     * @param httpServletRequest request
+     * @param version            The revision is used to verify the client is working with
+     *                           the latest version of the flow.
+     * @param clientId           Optional client id. If the client id is not specified, a
+     *                           new one will be generated. This value (whether specified or generated) is
+     *                           included in the response.
+     * @param id                 The id of the user to remove.
+     * @return A entity containing the client id and an updated revision.
+     */
+    @DELETE
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("users/{id}")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Deletes a user",
+            response = UserEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 removeUser(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The revision is used to verify the client is working with the latest version of the flow.",
+                    required = false
+            )
+            @QueryParam(VERSION) final LongParameter version,
+            @ApiParam(
+                    value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
+                    required = false
+            )
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
+            @ApiParam(
+                    value = "The user id.",
+                    required = true
+            )
+            @PathParam("id") final String id) {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.DELETE);
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision revision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
+        return withWriteLock(
+                serviceFacade,
+                revision,
+                lookup -> {
+                    final Authorizable users = lookup.getTenantAuthorizable();
+                    users.authorize(authorizer, RequestAction.READ);
+                },
+                null,
+                () -> {
+                    // delete the specified user
+                    final UserEntity entity = serviceFacade.deleteUser(revision, id);
+                    return clusterContext(generateOkResponse(entity)).build();
+                }
+        );
+    }
+
+    /**
+     * Populates the uri for the specified user groups.
+     *
+     * @param userGroupEntities user groups
+     * @return user group entities
+     */
+    public Set<UserGroupEntity> populateRemainingUserGroupEntitiesContent(Set<UserGroupEntity> userGroupEntities) {
+        for (UserGroupEntity userGroupEntity : userGroupEntities) {
+            populateRemainingUserGroupEntityContent(userGroupEntity);
+        }
+        return userGroupEntities;
+    }
+
+    /**
+     * Populates the uri for the specified user group.
+     *
+     * @param userGroupEntity userGroupEntity
+     * @return userGroupEntity
+     */
+    public UserGroupEntity populateRemainingUserGroupEntityContent(UserGroupEntity userGroupEntity) {
+        if (userGroupEntity.getComponent() != null) {
+            populateRemainingUserGroupContent(userGroupEntity.getComponent());
+        }
+        return userGroupEntity;
+    }
+
+    /**
+     * Populates the uri for the specified userGroup.
+     */
+    public UserGroupDTO populateRemainingUserGroupContent(UserGroupDTO userGroup) {
+        // populate the user group href
+        userGroup.setUri(generateResourceUri("tenants/user-groups", userGroup.getId()));
+        return userGroup;
+    }
+
+    /**
+     * Populates the uri for the specified user groups.
+     *
+     * @param userGroups user groups
+     * @return user group data transfer objects
+     */
+    public Set<UserGroupDTO> populateRemainingUserGroupsContent(Set<UserGroupDTO> userGroups) {
+        for (UserGroupDTO userGroup : userGroups) {
+            populateRemainingUserGroupContent(userGroup);
+        }
+        return userGroups;
+    }
+
+
+    /**
+     * Creates a new user group.
+     *
+     * @param httpServletRequest request
+     * @param userGroupEntity    An userGroupEntity.
+     * @return An userGroupEntity.
+     */
+    @POST
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Creates a user group",
+            response = UserGroupEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 createUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The user group configuration details.",
+                    required = true
+            ) final UserGroupEntity userGroupEntity) {
+
+        if (userGroupEntity == null || userGroupEntity.getComponent() == null) {
+            throw new IllegalArgumentException("User group details must be specified.");
+        }
+
+        if (userGroupEntity.getRevision() == null || (userGroupEntity.getRevision().getVersion() == null || userGroupEntity.getRevision().getVersion() != 0)) {
+            throw new IllegalArgumentException("A revision of 0 must be specified when creating a new Processor.");
+        }
+
+        if (userGroupEntity.getComponent().getId() != null) {
+            throw new IllegalArgumentException("User group ID cannot be specified.");
+        }
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.POST, userGroupEntity);
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final boolean validationPhase = isValidationPhase(httpServletRequest);
+        if (validationPhase || !isTwoPhaseRequest(httpServletRequest)) {
+            // authorize access
+            serviceFacade.authorizeAccess(lookup -> {
+                final Authorizable userGroups = lookup.getTenantAuthorizable();
+                userGroups.authorize(authorizer, RequestAction.WRITE);
+            });
+        }
+        if (validationPhase) {
+            return generateContinueResponse().build();
+        }
+
+        // set the user group id as appropriate
+        userGroupEntity.getComponent().setId(generateUuid());
+
+        // get revision from the config
+        final RevisionDTO revisionDTO = userGroupEntity.getRevision();
+        Revision revision = new Revision(revisionDTO.getVersion(), revisionDTO.getClientId(), userGroupEntity.getComponent().getId());
+
+        // create the user group and generate the json
+        final UserGroupEntity entity = serviceFacade.createUserGroup(revision, userGroupEntity.getComponent());
+        populateRemainingUserGroupEntityContent(entity);
+
+        // build the response
+        return clusterContext(generateCreatedResponse(URI.create(entity.getComponent().getUri()), entity)).build();
+    }
+
+    /**
+     * Retrieves the specified user group.
+     *
+     * @param id The id of the user group to retrieve
+     * @return An userGroupEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @ApiOperation(
+            value = "Gets a user group",
+            response = UserGroupEntity.class,
+            authorizations = {
+                    @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
+                    @Authorization(value = "Administrator", type = "ROLE_ADMIN")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 getUserGroup(
+            @ApiParam(
+                    value = "The user group id.",
+                    required = true
+            )
+            @PathParam("id") final String id) {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        // authorize access
+        serviceFacade.authorizeAccess(lookup -> {
+            final Authorizable userGroups = lookup.getTenantAuthorizable();
+            userGroups.authorize(authorizer, RequestAction.READ);
+        });
+
+        // get the user group
+        final UserGroupEntity entity = serviceFacade.getUserGroup(id, true);
+        populateRemainingUserGroupEntityContent(entity);
+
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Retrieves all the of user groups in this NiFi.
+     *
+     * @return A UserGroupsEntity.
+     */
+    @GET
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups")
+    // TODO - @PreAuthorize("hasAnyRole('ROLE_MONITOR', 'ROLE_DFM', 'ROLE_ADMIN')")
+    @ApiOperation(
+            value = "Gets all user groups",
+            response = UserGroupsEntity.class,
+            authorizations = {
+                    @Authorization(value = "Read Only", type = "ROLE_MONITOR"),
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM"),
+                    @Authorization(value = "Administrator", type = "ROLE_ADMIN")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 getUserGroups() {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.GET);
+        }
+
+        // authorize access
+        serviceFacade.authorizeAccess(lookup -> {
+            final Authorizable userGroups = lookup.getTenantAuthorizable();
+            userGroups.authorize(authorizer, RequestAction.READ);
+        });
+
+        // get all the user groups
+        final Set<UserGroupEntity> users = serviceFacade.getUserGroups(true);
+
+        // create the response entity
+        final UserGroupsEntity entity = new UserGroupsEntity();
+        entity.setUserGroups(populateRemainingUserGroupEntitiesContent(users));
+
+        // generate the response
+        return clusterContext(generateOkResponse(entity)).build();
+    }
+
+    /**
+     * Updates a user group.
+     *
+     * @param httpServletRequest request
+     * @param id                 The id of the user group to update.
+     * @param userGroupEntity    An userGroupEntity.
+     * @return An userGroupEntity.
+     */
+    @PUT
+    @Consumes(MediaType.APPLICATION_JSON)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Updates a user group",
+            response = UserGroupEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 updateUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The user group id.",
+                    required = true
+            )
+            @PathParam("id") final String id,
+            @ApiParam(
+                    value = "The user group configuration details.",
+                    required = true
+            ) final UserGroupEntity userGroupEntity) {
+
+        if (userGroupEntity == null || userGroupEntity.getComponent() == null) {
+            throw new IllegalArgumentException("User group details must be specified.");
+        }
+
+        if (userGroupEntity.getRevision() == null) {
+            throw new IllegalArgumentException("Revision must be specified.");
+        }
+
+        // ensure the ids are the same
+        final UserGroupDTO userGroupDTO = userGroupEntity.getComponent();
+        if (!id.equals(userGroupDTO.getId())) {
+            throw new IllegalArgumentException(String.format("The user group id (%s) in the request body does not equal the "
+                    + "user group id of the requested resource (%s).", userGroupDTO.getId(), id));
+        }
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.PUT, userGroupEntity);
+        }
+
+        // Extract the revision
+        final Revision revision = getRevision(userGroupEntity, id);
+        return withWriteLock(
+                serviceFacade,
+                revision,
+                lookup -> {
+                    final Authorizable userGroups = lookup.getTenantAuthorizable();
+                    userGroups.authorize(authorizer, RequestAction.WRITE);
+                },
+                null,
+                () -> {
+                    // update the user group
+                    final UserGroupEntity entity = serviceFacade.updateUserGroup(revision, userGroupDTO);
+                    populateRemainingUserGroupEntityContent(entity);
+
+                    return clusterContext(generateOkResponse(entity)).build();
+                }
+        );
+    }
+
+    /**
+     * Removes the specified user group.
+     *
+     * @param httpServletRequest request
+     * @param version            The revision is used to verify the client is working with
+     *                           the latest version of the flow.
+     * @param clientId           Optional client id. If the client id is not specified, a
+     *                           new one will be generated. This value (whether specified or generated) is
+     *                           included in the response.
+     * @param id                 The id of the user group to remove.
+     * @return A entity containing the client id and an updated revision.
+     */
+    @DELETE
+    @Consumes(MediaType.WILDCARD)
+    @Produces(MediaType.APPLICATION_JSON)
+    @Path("user-groups/{id}")
+    // TODO - @PreAuthorize("hasRole('ROLE_DFM')")
+    @ApiOperation(
+            value = "Deletes a user group",
+            response = UserGroupEntity.class,
+            authorizations = {
+                    @Authorization(value = "Data Flow Manager", type = "ROLE_DFM")
+            }
+    )
+    @ApiResponses(
+            value = {
+                    @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+                    @ApiResponse(code = 401, message = "Client could not be authenticated."),
+                    @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+                    @ApiResponse(code = 404, message = "The specified resource could not be found."),
+                    @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 removeUserGroup(
+            @Context final HttpServletRequest httpServletRequest,
+            @ApiParam(
+                    value = "The revision is used to verify the client is working with the latest version of the flow.",
+                    required = false
+            )
+            @QueryParam(VERSION) final LongParameter version,
+            @ApiParam(
+                    value = "If the client id is not specified, new one will be generated. This value (whether specified or generated) is included in the response.",
+                    required = false
+            )
+            @QueryParam(CLIENT_ID) @DefaultValue(StringUtils.EMPTY) final ClientIdParameter clientId,
+            @ApiParam(
+                    value = "The user group id.",
+                    required = true
+            )
+            @PathParam("id") final String id) {
+
+        if (isReplicateRequest()) {
+            return replicate(HttpMethod.DELETE);
+        }
+
+        // handle expects request (usually from the cluster manager)
+        final Revision revision = new Revision(version == null ? null : version.getLong(), clientId.getClientId(), id);
+        return withWriteLock(
+                serviceFacade,
+                revision,
+                lookup -> {
+                    final Authorizable userGroups = lookup.getTenantAuthorizable();
+                    userGroups.authorize(authorizer, RequestAction.READ);
+                },
+                null,
+                () -> {
+                    // delete the specified user group
+                    final UserGroupEntity entity = serviceFacade.deleteUserGroup(revision, id);
+                    return clusterContext(generateOkResponse(entity)).build();
+                }
+        );
+    }
+}


Mime
View raw message