diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 19a20d6c75..c950f7a7e6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -97,6 +97,8 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.query.EntityDataSortOrder; +import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; @@ -169,6 +171,8 @@ import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.StringUtils.isNotEmpty; +import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; import static org.thingsboard.server.controller.ControllerConstants.INCORRECT_TENANT_ID; import static org.thingsboard.server.controller.UserController.YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -850,4 +854,17 @@ public abstract class BaseController { return deferredResult; } + protected EntityDataSortOrder createEntityDataSortOrder(String sortProperty, String sortOrder) { + if (isNotEmpty(sortProperty)) { + EntityDataSortOrder entityDataSortOrder = new EntityDataSortOrder(); + entityDataSortOrder.setKey(new EntityKey(ENTITY_FIELD, sortProperty)); + if (isNotEmpty(sortOrder)) { + entityDataSortOrder.setDirection(EntityDataSortOrder.Direction.valueOf(sortOrder)); + } + return entityDataSortOrder; + } else { + return null; + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index 3e202de938..e57bb73262 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -21,6 +21,7 @@ import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.http.HttpStatus; @@ -38,7 +39,9 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.UserEmailInfo; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; @@ -46,6 +49,11 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityTypeFilter; +import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserSettings; @@ -53,6 +61,7 @@ import org.thingsboard.server.common.data.security.event.UserCredentialsInvalida import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.service.query.EntityQueryService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; @@ -63,7 +72,10 @@ import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import java.util.Arrays; +import java.util.List; +import java.util.Map; +import static org.thingsboard.server.common.data.query.EntityKeyType.ENTITY_FIELD; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID; import static org.thingsboard.server.controller.ControllerConstants.CUSTOMER_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASHBOARD; @@ -106,6 +118,9 @@ public class UserController extends BaseController { private final ApplicationEventPublisher eventPublisher; private final TbUserService tbUserService; + @Autowired + private EntityQueryService entityQueryService; + @ApiOperation(value = "Get User (getUserById)", notes = "Fetch the User object based on the provided User Id. " + "If the user has the authority of 'SYS_ADMIN', the server does not perform additional checks. " + @@ -304,6 +319,44 @@ public class UserController extends BaseController { } } + @ApiOperation(value = "Find users by query (findUsersByQuery)", + notes = "Returns page of user data objects. Search is been executed by email, firstName and " + + "lastName fields. " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/users/info", method = RequestMethod.GET) + @ResponseBody + public PageData findUsersByQuery( + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = USER_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = USER_SORT_PROPERTY_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + SecurityUser securityUser = getCurrentUser(); + + EntityTypeFilter entityFilter = new EntityTypeFilter(); + entityFilter.setEntityType(EntityType.USER); + EntityDataPageLink pageLink = new EntityDataPageLink(pageSize, page, textSearch, createEntityDataSortOrder(sortProperty, sortOrder)); + List entityFields = Arrays.asList(new EntityKey(ENTITY_FIELD, "firstName"), + new EntityKey(ENTITY_FIELD, "lastName"), + new EntityKey(ENTITY_FIELD, "email")); + + EntityDataQuery query = new EntityDataQuery(entityFilter, pageLink, entityFields, null, null); + + return entityQueryService.findEntityDataByQuery(securityUser, query).mapData(entityData -> + { + Map fieldValues = entityData.getLatest().get(ENTITY_FIELD); + return new UserEmailInfo(UserId.fromString(entityData.getEntityId().getId().toString()), + fieldValues.get("email").getValue(), + fieldValues.get("firstName").getValue(), + fieldValues.get("lastName").getValue()); + }); + } + @ApiOperation(value = "Get Tenant Users (getTenantAdmins)", notes = "Returns a page of users owned by tenant. " + PAGE_DATA_PARAMETERS + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @@ -434,4 +487,5 @@ public class UserController extends BaseController { SecurityUser currentUser = getCurrentUser(); userSettingsService.deleteUserSettings(currentUser.getTenantId(), currentUser.getId(), Arrays.asList(paths.split(","))); } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 983e33ed7a..f289c8066f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.UserEmailInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -48,6 +49,7 @@ import org.thingsboard.server.service.mail.TestMailService; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; @@ -61,6 +63,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT; public abstract class BaseUserControllerTest extends AbstractControllerTest { private IdComparator idComparator = new IdComparator<>(); + private IdComparator userDataIdComparator = new IdComparator<>(); private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer()); @@ -84,21 +87,15 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testSaveUser() throws Exception { loginSysAdmin(); - String email = "tenant2@thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); - + User user = createTenantAdminUser(); + String email = user.getEmail(); Mockito.reset(tbClusterService, auditLogService); User savedUser = doPost("/api/user", user, User.class); Assert.assertNotNull(savedUser); Assert.assertNotNull(savedUser.getId()); Assert.assertTrue(savedUser.getCreatedTime() > 0); - Assert.assertEquals(user.getEmail(), savedUser.getEmail()); + Assert.assertEquals(email, savedUser.getEmail()); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); Assert.assertEquals(foundUser, savedUser); @@ -153,13 +150,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - String email = "tenant2@thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName(StringUtils.randomAlphabetic(300)); - user.setLastName("Downs"); + User user = createTenantAdminUser(StringUtils.randomAlphabetic(300), "Brown"); String msgError = msgErrorFieldLength("first name"); doPost("/api/user", user) .andExpect(status().isBadRequest()) @@ -186,12 +177,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testUpdateUserFromDifferentTenant() throws Exception { loginSysAdmin(); - User tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(tenantId); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); + User tenantAdmin = createTenantAdminUser(); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); loginDifferentTenant(); @@ -211,14 +197,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testResetPassword() throws Exception { loginSysAdmin(); - String email = "tenant2@thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); - + User user = createTenantAdminUser(); + String email = user.getEmail(); User savedUser = createUserAndLogin(user, "testPassword1"); resetTokens(); @@ -263,13 +243,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testFindUserById() throws Exception { loginSysAdmin(); - String email = "tenant2@thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); + User user = createTenantAdminUser(); User savedUser = doPost("/api/user", user, User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); @@ -283,15 +257,12 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - String email = TENANT_ADMIN_EMAIL; User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); + user.setEmail(TENANT_ADMIN_EMAIL); - String msgError = "User with email '" + email + "' already present in database"; + String msgError = "User with email '" + TENANT_ADMIN_EMAIL + "' already present in database"; doPost("/api/user", user) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); @@ -308,12 +279,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); String email = "tenant_thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); + User user = createTenantAdminUser(); user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); String msgError = "Invalid email address format '" + email + "'"; doPost("/api/user", user) @@ -374,13 +341,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testDeleteUser() throws Exception { loginSysAdmin(); - String email = "tenant2@thingsboard.org"; - User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); - user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); + User user = createTenantAdminUser(); User savedUser = doPost("/api/user", user, User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); @@ -562,20 +523,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testFindCustomerUsers() throws Exception { loginSysAdmin(); - User tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(tenantId); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - + User tenantAdmin = createTenantAdminUser(); createUserAndLogin(tenantAdmin, "testPassword1"); - Customer customer = new Customer(); - customer.setTitle("My customer"); - Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - - CustomerId customerId = savedCustomer.getId(); + CustomerId customerId = postCustomer(); List customerUsers = new ArrayList<>(); for (int i = 0; i < 56; i++) { @@ -612,47 +563,22 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testFindCustomerUsersByEmail() throws Exception { loginSysAdmin(); - User tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(tenantId); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - + User tenantAdmin = createTenantAdminUser(); createUserAndLogin(tenantAdmin, "testPassword1"); - Customer customer = new Customer(); - customer.setTitle("My customer"); - Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - - CustomerId customerId = savedCustomer.getId(); + CustomerId customerId = postCustomer(); String email1 = "testEmail1"; - List customerUsersEmail1 = new ArrayList<>(); - - for (int i = 0; i < 74; i++) { - User user = new User(); - user.setAuthority(Authority.CUSTOMER_USER); - user.setCustomerId(customerId); - String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); - String email = email1 + suffix + "@thingsboard.org"; - email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); - user.setEmail(email); - customerUsersEmail1.add(doPost("/api/user", user, User.class)); - } - String email2 = "testEmail2"; - List customerUsersEmail2 = new ArrayList<>(); - - for (int i = 0; i < 92; i++) { - User user = new User(); - user.setAuthority(Authority.CUSTOMER_USER); - user.setCustomerId(customerId); - String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); - String email = email2 + suffix + "@thingsboard.org"; - email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); - user.setEmail(email); - customerUsersEmail2.add(doPost("/api/user", user, User.class)); + List customerUsersEmail1 = new ArrayList<>(); + List customerUsersEmail2= new ArrayList<>(); + for (int i = 0; i < 45; i++) { + User customerUser = createCustomerUser( customerId); + customerUser.setEmail(email1 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org"); + customerUsersEmail1.add(doPost("/api/user", customerUser, User.class)); + + customerUser.setEmail(email2 + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org"); + customerUsersEmail2.add(doPost("/api/user", customerUser, User.class)); } List loadedCustomerUsersEmail1 = new ArrayList<>(); @@ -720,14 +646,18 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { @Test public void testDeleteUserWithDeleteRelationsOk() throws Exception { - UserId userId = createUser().getId(); + loginSysAdmin(); + User tenantAdminUser = createTenantAdminUser(); + UserId userId = doPost("/api/user", tenantAdminUser, User.class).getId(); testEntityDaoWithRelationsOk(tenantId, userId, "/api/user/" + userId); } @Ignore @Test public void testDeleteUserExceptionWithRelationsTransactional() throws Exception { - UserId userId = createUser().getId(); + loginSysAdmin(); + User tenantAdminUser = createTenantAdminUser("Joe", "Downs"); + UserId userId = doPost("/api/user", tenantAdminUser, User.class).getId(); testEntityDaoWithRelationsTransactionalException(userDao, tenantId, userId, "/api/user/" + userId); } @@ -858,15 +788,197 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Assert.assertEquals(expectedSettings, retrievedSettings); } - private User createUser() throws Exception { + @Test + public void checkCustomerUserDoNotSeeTenantUsersOtherTenantUsersOtherCustomerUsers() throws Exception { + loginSysAdmin(); + String searchText = "Joe"; + + loginDifferentTenant(); + CustomerId customerId1 = postCustomer(); + doPost("/api/user", createCustomerUser(searchText, "Ress", customerId1), User.class); + + loginSysAdmin(); + User tenantAdmin = createTenantAdminUser(searchText, "Brown"); + createUserAndLogin(tenantAdmin, "testPassword1"); + + CustomerId customerId2 = postCustomer(); + User user = createCustomerUser(searchText, "Downs", customerId2); + doPost("/api/user", user, User.class); + + CustomerId customerId3 = postCustomer(); + User user2 = createCustomerUser(customerId3); + createUserAndLogin(user2, "testPassword2"); + + PageLink pageLink = new PageLink(10, 0, searchText); + List usersInfo = getUsersInfo(pageLink); + + Assert.assertEquals(usersInfo.size(), 0); + + //clear users + loginDifferentTenant(); + doDelete("/api/customer/" + customerId1.getId().toString()) + .andExpect(status().isOk()); + loginUser(tenantAdmin.getEmail(), "testPassword1"); + doDelete("/api/customer/" + customerId2.getId().toString()) + .andExpect(status().isOk()); + doDelete("/api/customer/" + customerId3.getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void shouldFindCustomerUsersBySearchText() throws Exception { loginSysAdmin(); - String email = "tenant2@thingsboard.org"; + User tenantAdmin = createTenantAdminUser(); + createUserAndLogin(tenantAdmin, "testPassword1"); + + String searchText = "Philip"; + + CustomerId customerId = postCustomer(); + CustomerId customerId2 = postCustomer(); + + List customerUsersContainingWord = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + String suffix = StringUtils.randomAlphabetic((int) (5 + Math.random() * 10)); + + customerUsersContainingWord.add(doPost("/api/user", createCustomerUser(searchText + i, "Last" + i, customerId), User.class)); + customerUsersContainingWord.add(doPost("/api/user", createCustomerUser(null, null, searchText + suffix + "@thingsboard.org", customerId), User.class)); + doPost("/api/user", createCustomerUser(null, null, customerId), User.class); + + suffix = StringUtils.randomAlphabetic((int) (5 + Math.random() * 10)); + doPost("/api/user", createCustomerUser(searchText + i, "Last" + i, customerId2), User.class); + doPost("/api/user", createCustomerUser(null, null, searchText + suffix + "@thingsboard.org", customerId2), User.class); + } + + createUserAndLogin(createCustomerUser(customerId), "testPassword2"); + + // find users by search text + PageLink pageLink = new PageLink(10, 0, searchText); + List usersInfo = getUsersInfo(pageLink); + + List expectedUserInfos = customerUsersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), + customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), + customerUser.getLastName() == null ? "" : customerUser.getLastName())) + .sorted(userDataIdComparator).collect(Collectors.toList()); + usersInfo.sort(userDataIdComparator); + + Assert.assertEquals(expectedUserInfos, usersInfo); + + // find user by full name + pageLink = new PageLink(10, 0, searchText + "5"); + usersInfo = getUsersInfo(pageLink); + Assert.assertEquals(1, usersInfo.size()); + + //clear users + loginUser(tenantAdmin.getEmail(), "testPassword1"); + doDelete("/api/customer/" + customerId.getId().toString()) + .andExpect(status().isOk()); + doDelete("/api/customer/" + customerId2.getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void shouldFindTenantUsersBySearchText() throws Exception { + loginSysAdmin(); + + User tenantAdmin = createTenantAdminUser(); + createUserAndLogin(tenantAdmin, "testPassword1"); + CustomerId customerId = postCustomer(); + CustomerId customerId2 = postCustomer(); + + String searchText = "Brown"; + + List usersContainingWord = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + String suffix = StringUtils.randomAlphabetic((int) (5 + Math.random() * 10)); + usersContainingWord.add(doPost("/api/user", createCustomerUser("First" + i, searchText + i, customerId), User.class)); + usersContainingWord.add(doPost("/api/user", createCustomerUser(null, null, searchText + suffix + "@thingsboard.org", customerId), User.class)); + doPost("/api/user", createCustomerUser(null, null, customerId), User.class); + + suffix = StringUtils.randomAlphabetic((int) (5 + Math.random() * 10)); + usersContainingWord.add(doPost("/api/user", createCustomerUser("First" + i, searchText + i, customerId2), User.class)); + usersContainingWord.add(doPost("/api/user", createCustomerUser(null, null, searchText + suffix + "@thingsboard.org", customerId2), User.class)); + } + + loginDifferentTenant(); + CustomerId customerId3 = postCustomer(); + doPost("/api/user", createCustomerUser("Jane", searchText, customerId3), User.class); + + // find users by search text + loginUser(tenantAdmin.getEmail(), "testPassword1"); + PageLink pageLink = new PageLink(10, 0, searchText); + List usersInfo = getUsersInfo(pageLink); + + List expectedUserInfos = usersContainingWord.stream().map(customerUser -> new UserEmailInfo(customerUser.getId(), + customerUser.getEmail(), customerUser.getFirstName() == null ? "" : customerUser.getFirstName(), + customerUser.getLastName() == null ? "" : customerUser.getLastName())) + .sorted(userDataIdComparator).collect(Collectors.toList()); + usersInfo.sort(userDataIdComparator); + + Assert.assertEquals(expectedUserInfos, usersInfo); + + // find user by full last name + pageLink = new PageLink(10, 0, searchText + "3"); + usersInfo = getUsersInfo(pageLink); + Assert.assertEquals(2, usersInfo.size()); + + //clear users + doDelete("/api/customer/" + customerId.getId().toString()) + .andExpect(status().isOk()); + doDelete("/api/customer/" + customerId2.getId().toString()) + .andExpect(status().isOk()); + } + + private CustomerId postCustomer() { + Customer customer = new Customer(); + customer.setTitle(StringUtils.randomAlphabetic(9)); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + return savedCustomer.getId(); + } + + private static User createCustomerUser(CustomerId customerId) { + return createCustomerUser(null, null, customerId); + } + private static User createCustomerUser(String firstName, String lastName, CustomerId customerId) { + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + return createCustomerUser(firstName, lastName, "testMail" + suffix + "@thingsboard.org", customerId); + } + + private static User createCustomerUser(String firstName, String lastName, String email, CustomerId customerId) { User user = new User(); - user.setAuthority(Authority.TENANT_ADMIN); - user.setTenantId(tenantId); + user.setAuthority(Authority.CUSTOMER_USER); + user.setFirstName(firstName); + user.setLastName(lastName); + user.setCustomerId(customerId); user.setEmail(email); - user.setFirstName("Joe"); - user.setLastName("Downs"); - return doPost("/api/user", user, User.class); + return user; + } + + private User createTenantAdminUser() { + return createTenantAdminUser(null, null); + } + private User createTenantAdminUser(String firstName, String lastName) { + String suffix = StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + + User tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(tenantId); + tenantAdmin.setEmail("testEmail" + suffix + "@thingsbord.org"); + tenantAdmin.setFirstName(firstName); + tenantAdmin.setLastName(lastName); + return tenantAdmin; + } + + private List getUsersInfo(PageLink pageLink) throws Exception { + List loadedCustomerUsers = new ArrayList<>(); + PageData pageData = null; + do { + pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {}, pageLink); + loadedCustomerUsers.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + return loadedCustomerUsers; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java new file mode 100644 index 0000000000..f1eb95c805 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UserEmailInfo.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.common.data; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.UserId; + +@ApiModel +@Data +@AllArgsConstructor +public class UserEmailInfo implements HasId { + + @ApiModelProperty(position = 1, value = "User id") + private UserId id; + @ApiModelProperty(position = 2, value = "User email", example = "john@gmail.com") + private String email; + @ApiModelProperty(position = 3, value = "User first name", example = "John") + private String firstName; + @ApiModelProperty(position = 4, value = "User last name", example = "Brown") + private String lastName; + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java index f649083458..2aa3b7a837 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java @@ -41,7 +41,7 @@ import java.util.concurrent.ExecutionException; type = ComponentType.ENRICHMENT, name = "fetch device credentials", configClazz = TbFetchDeviceCredentialsNodeConfiguration.class, - nodeDescription = "Fetch device credentials for message originator", + nodeDescription = "Enrich the message body or metadata with the device credentials", nodeDetails = "Adds credentialsType and credentials properties to the message metadata if the " + "configuration parameter fetchToMetadata is set to true, otherwise, adds properties " + "to the message data. If originator type is not DEVICE or rule node failed to get device credentials " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java index 8eba395d11..7e42105e5d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.msg.TbMsg; @RuleNode(type = ComponentType.ENRICHMENT, name = "originator attributes", configClazz = TbGetAttributesNodeConfiguration.class, - nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Data or Metadata", + nodeDescription = "Enrich the message body or metadata with the originator attributes and/or timeseries data", nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message data/metadata " + "with specific prefix: cs/shared/ss. Latest telemetry value added into Message data/metadata without prefix. " + "To access those attributes in other nodes this template can be used " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java index 353342285b..fa4f3552bc 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerDetailsNode.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.msg.TbMsg; @RuleNode(type = ComponentType.ENRICHMENT, name = "customer details", configClazz = TbGetCustomerDetailsNodeConfiguration.class, - nodeDescription = "Adds fields from Customer details to the message body or metadata", + nodeDescription = "Enrich the message body or metadata with the corresponding customer details: title, address, email, phone, etc.", nodeDetails = "If checkbox: Add selected details to the message metadata is selected, existing fields will be added to the message metadata instead of message data.

" + "Note: only Device, Asset, and Entity View type are allowed.

" + "If the originator of the message is not assigned to Customer, or originator type is not supported - Message will be forwarded to Failure chain, otherwise, Success chain will be used.",