NIFI-11461 Improve User and Group Tenants Search (#7181)

* NIFI-11461 Improved User and Group Tenants Search

- Added searchTenants method to NiFiServiceFacade and removed unnecessary object creation
- Updated TenantsResource to use delegated NiFiServiceFacade.searchTenants method
- Changed autocomplete delay from default 300 ms to 500 ms

* NIFI-11461 Adjusted implementation to use EntityFactory.createTenantEntity

This closes #7181
This commit is contained in:
exceptionfactory 2023-04-19 08:58:25 -05:00 committed by GitHub
parent 382058e154
commit bdff3abcd6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 163 additions and 54 deletions

View File

@ -128,6 +128,7 @@ import org.apache.nifi.web.api.entity.SnippetEntity;
import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TemplateEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import org.apache.nifi.web.api.entity.VariableRegistryEntity;
@ -1960,6 +1961,14 @@ public interface NiFiServiceFacade {
*/
Set<UserEntity> getUsers();
/**
* Search for User and Group Tenants based on provided query string
*
* @param query Search query where null or empty returns unfiltered results
* @return Tenants Entity with zero or more matched Users and User Groups
*/
TenantsEntity searchTenants(String query);
/**
* Updates the specified user.
* @param revision Revision to compare with current base revision

View File

@ -237,6 +237,7 @@ import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.dto.SnippetDTO;
import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO;
import org.apache.nifi.web.api.dto.TemplateDTO;
import org.apache.nifi.web.api.dto.TenantDTO;
import org.apache.nifi.web.api.dto.UserDTO;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import org.apache.nifi.web.api.dto.VariableRegistryDTO;
@ -317,6 +318,7 @@ import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TemplateEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
import org.apache.nifi.web.api.entity.VariableEntity;
@ -384,6 +386,8 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.apache.commons.lang3.StringUtils.containsIgnoreCase;
/**
* Implementation of NiFiServiceFacade that performs revision checking.
*/
@ -4461,6 +4465,46 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade {
.collect(Collectors.toSet());
}
/**
* Search for User and Group Tenants with optimized conversion from specific objects to Tenant objects
*
* @param query Search query where null or empty returns unfiltered results
* @return Tenants Entity containing zero or more matching Users and Groups
*/
@Override
public TenantsEntity searchTenants(final String query) {
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());
final Set<TenantEntity> usersFound = userDAO.getUsers()
.stream()
.filter(user -> isMatched(user.getIdentity(), query))
.map(user -> {
final TenantDTO tenant = dtoFactory.createTenantDTO(user);
final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(tenant.getId()));
return entityFactory.createTenantEntity(tenant, revision, permissions);
})
.collect(Collectors.toSet());
final Set<TenantEntity> userGroupsFound = userGroupDAO.getUserGroups()
.stream()
.filter(userGroup -> isMatched(userGroup.getName(), query))
.map(userGroup -> {
final TenantDTO tenant = dtoFactory.createTenantDTO(userGroup);
final RevisionDTO revision = dtoFactory.createRevisionDTO(revisionManager.getRevision(tenant.getId()));
return entityFactory.createTenantEntity(tenant, revision, permissions);
})
.collect(Collectors.toSet());
final TenantsEntity tenantsEntity = new TenantsEntity();
tenantsEntity.setUsers(usersFound);
tenantsEntity.setUserGroups(userGroupsFound);
return tenantsEntity;
}
private boolean isMatched(final String label, final String query) {
return StringUtils.isEmpty(query) || containsIgnoreCase(label, query);
}
private UserEntity createUserEntity(final User user, final boolean enforceUserExistence) {
final RevisionDTO userRevision = dtoFactory.createRevisionDTO(revisionManager.getRevision(user.getIdentifier()));
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(authorizableLookup.getTenant());

View File

@ -35,10 +35,8 @@ 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.TenantDTO;
import org.apache.nifi.web.api.dto.UserDTO;
import org.apache.nifi.web.api.dto.UserGroupDTO;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.api.entity.UserEntity;
import org.apache.nifi.web.api.entity.UserGroupEntity;
@ -64,9 +62,7 @@ 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.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
@Path("tenants")
@ -941,53 +937,7 @@ public class TenantsResource extends ApplicationResource {
tenants.authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
});
final List<TenantEntity> userMatches = new ArrayList<>();
final List<TenantEntity> userGroupMatches = new ArrayList<>();
// get the users
for (final UserEntity userEntity : serviceFacade.getUsers()) {
final UserDTO user = userEntity.getComponent();
if (StringUtils.isBlank(value) || StringUtils.containsIgnoreCase(user.getIdentity(), value)) {
final TenantDTO tenant = new TenantDTO();
tenant.setId(user.getId());
tenant.setIdentity(user.getIdentity());
tenant.setConfigurable(user.getConfigurable());
final TenantEntity entity = new TenantEntity();
entity.setPermissions(userEntity.getPermissions());
entity.setRevision(userEntity.getRevision());
entity.setId(userEntity.getId());
entity.setComponent(tenant);
userMatches.add(entity);
}
}
// get the user groups
for (final UserGroupEntity userGroupEntity : serviceFacade.getUserGroups()) {
final UserGroupDTO userGroup = userGroupEntity.getComponent();
if (StringUtils.isBlank(value) || StringUtils.containsIgnoreCase(userGroup.getIdentity(), value)) {
final TenantDTO tenant = new TenantDTO();
tenant.setId(userGroup.getId());
tenant.setIdentity(userGroup.getIdentity());
tenant.setConfigurable(userGroup.getConfigurable());
final TenantEntity entity = new TenantEntity();
entity.setPermissions(userGroupEntity.getPermissions());
entity.setRevision(userGroupEntity.getRevision());
entity.setId(userGroupEntity.getId());
entity.setComponent(tenant);
userGroupMatches.add(entity);
}
}
// build the response
final TenantsEntity results = new TenantsEntity();
results.setUsers(userMatches);
results.setUserGroups(userGroupMatches);
// generate an 200 - OK response
final TenantsEntity results = serviceFacade.searchTenants(value);
return noCache(Response.ok(results)).build();
}
}

View File

@ -27,7 +27,9 @@ import org.apache.nifi.authorization.AuthorizationRequest;
import org.apache.nifi.authorization.AuthorizationResult;
import org.apache.nifi.authorization.Authorizer;
import org.apache.nifi.authorization.ComponentAuthorizable;
import org.apache.nifi.authorization.Group;
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.resource.ResourceType;
@ -66,9 +68,13 @@ import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.StatusHistoryEntity;
import org.apache.nifi.web.api.entity.TenantEntity;
import org.apache.nifi.web.api.entity.TenantsEntity;
import org.apache.nifi.web.controller.ControllerFacade;
import org.apache.nifi.web.dao.ProcessGroupDAO;
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
import org.apache.nifi.web.dao.UserDAO;
import org.apache.nifi.web.dao.UserGroupDAO;
import org.apache.nifi.web.revision.RevisionManager;
import org.apache.nifi.web.revision.RevisionUpdate;
import org.apache.nifi.web.revision.StandardRevisionUpdate;
@ -81,8 +87,10 @@ import org.mockito.Mockito;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
@ -105,6 +113,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyMap;
import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.same;
@ -113,11 +122,14 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class StandardNiFiServiceFacadeTest {
private static final String USER_1 = "user-1";
private static final String USER_2 = "user-2";
private static final String USER_PREFIX = "user";
private static final String USER_1 = String.format("%s-1", USER_PREFIX);
private static final String USER_1_ID = UUID.nameUUIDFromBytes(USER_1.getBytes(StandardCharsets.UTF_8)).toString();
private static final String USER_2 = String.format("%s-2", USER_PREFIX);
private static final String USER_GROUP_1 = String.format("%s-group-1", USER_PREFIX);
private static final String USER_GROUP_1_ID = UUID.nameUUIDFromBytes(USER_GROUP_1.getBytes(StandardCharsets.UTF_8)).toString();
private static final Integer UNKNOWN_ACTION_ID = 0;
@ -727,6 +739,99 @@ public class StandardNiFiServiceFacadeTest {
assertEquals(groupId, result.getBulletins().get(0).getGroupId());
}
@Test
public void testSearchTenantsNullQuery() {
setupSearchTenants();
final TenantsEntity tenantsEntity = serviceFacade.searchTenants(null);
assertUserFound(tenantsEntity);
assertUserGroupFound(tenantsEntity);
}
@Test
public void testSearchTenantsMatchedQuery() {
setupSearchTenants();
final TenantsEntity tenantsEntity = serviceFacade.searchTenants(USER_PREFIX);
assertUserFound(tenantsEntity);
assertUserGroupFound(tenantsEntity);
}
@Test
public void testSearchTenantsGroupMatchedQuery() {
setupSearchTenants();
final TenantsEntity tenantsEntity = serviceFacade.searchTenants(USER_GROUP_1);
assertUserGroupFound(tenantsEntity);
final Collection<TenantEntity> usersFound = tenantsEntity.getUsers();
assertTrue(usersFound.isEmpty());
}
@Test
public void testSearchTenantsNotMatchedQuery() {
setupSearchTenants();
final TenantsEntity tenantsEntity = serviceFacade.searchTenants(String.class.getSimpleName());
assertNotNull(tenantsEntity);
final Collection<TenantEntity> usersFound = tenantsEntity.getUsers();
assertTrue(usersFound.isEmpty());
final Collection<TenantEntity> userGroupsFound = tenantsEntity.getUserGroups();
assertTrue(userGroupsFound.isEmpty());
}
private void setupSearchTenants() {
final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build()));
SecurityContextHolder.getContext().setAuthentication(authentication);
authorizer = mock(Authorizer.class);
serviceFacade.setAuthorizer(authorizer);
final AuthorizableLookup authorizableLookup = mock(AuthorizableLookup.class);
serviceFacade.setAuthorizableLookup(authorizableLookup);
final Authorizable authorizable = mock(Authorizable.class);
when(authorizableLookup.getTenant()).thenReturn(authorizable);
final RevisionManager revisionManager = mock(RevisionManager.class);
serviceFacade.setRevisionManager(revisionManager);
final Revision revision = new Revision(1L, USER_1_ID, USER_1_ID);
when(revisionManager.getRevision(anyString())).thenReturn(revision);
final UserDAO userDAO = mock(UserDAO.class);
serviceFacade.setUserDAO(userDAO);
final UserGroupDAO userGroupDAO = mock(UserGroupDAO.class);
serviceFacade.setUserGroupDAO(userGroupDAO);
final User user = new User.Builder().identity(USER_1).identifier(USER_1_ID).build();
final Set<User> users = Collections.singleton(user);
when(userDAO.getUsers()).thenReturn(users);
final Group group = new Group.Builder().name(USER_GROUP_1).identifier(USER_GROUP_1_ID).build();
final Set<Group> groups = Collections.singleton(group);
when(userGroupDAO.getUserGroups()).thenReturn(groups);
}
private void assertUserFound(final TenantsEntity tenantsEntity) {
assertNotNull(tenantsEntity);
final Collection<TenantEntity> usersFound = tenantsEntity.getUsers();
assertFalse(usersFound.isEmpty());
final TenantEntity firstUserFound = usersFound.iterator().next();
assertEquals(USER_1_ID, firstUserFound.getId());
}
private void assertUserGroupFound(final TenantsEntity tenantsEntity) {
assertNotNull(tenantsEntity);
final Collection<TenantEntity> userGroupsFound = tenantsEntity.getUserGroups();
assertFalse(userGroupsFound.isEmpty());
final TenantEntity firstUserGroup = userGroupsFound.iterator().next();
assertEquals(USER_GROUP_1_ID, firstUserGroup.getId());
}
private static class MockTestBulletinRepository extends MockBulletinRepository {

View File

@ -152,6 +152,7 @@
// configure the user auto complete
$.widget('nf.userSearchAutocomplete', $.ui.autocomplete, {
delay: 500,
reset: function () {
this.term = null;
},