From ba6a83e57585a72e713115773b6d9a3c0391aefb Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 23 Mar 2023 21:09:42 +0200 Subject: [PATCH 1/6] Added getting users for assignment --- .../server/controller/UserController.java | 45 ++++++++++++++++++- .../server/dao/user/UserService.java | 2 + .../server/dao/sql/user/JpaUserDao.java | 13 ++++++ .../server/dao/sql/user/UserRepository.java | 10 ++++- .../thingsboard/server/dao/user/UserDao.java | 10 +++++ .../server/dao/user/UserServiceImpl.java | 9 ++++ ui-ngx/src/app/core/http/user.service.ts | 7 +++ .../alarm/alarm-assignee-panel.component.ts | 4 +- 8 files changed, 96 insertions(+), 4 deletions(-) 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 e57bb73262..577a25f65d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -42,8 +42,10 @@ 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.alarm.Alarm; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; @@ -58,9 +60,9 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; +import org.thingsboard.server.common.data.security.model.JwtPair; 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; @@ -70,12 +72,12 @@ import org.thingsboard.server.service.security.permission.Resource; 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.ALARM_ID_PARAM_DESCRIPTION; 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; @@ -440,6 +442,45 @@ public class UserController extends BaseController { } } + @ApiOperation(value = "Get usersForAssign (getUsersForAssign)", + notes = "Returns page of user data objects that can be assigned to provided alarmId. " + + "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/assign", params = {"alarmId", "pageSize", "page"}, method = RequestMethod.GET) + @ResponseBody + public PageData getAssignees( + @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @RequestParam String alarmId, + @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 { + try { + checkParameter("alarmId", alarmId); + AlarmId alarmEntityId = new AlarmId(toUUID(alarmId)); + Alarm alarm = checkAlarmId(alarmEntityId, Operation.READ); + SecurityUser currentUser = getCurrentUser(); + TenantId tenantId = currentUser.getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + PageData pageData; + if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { + pageData = userService.findUsersForAssignForTenant(tenantId, alarm.getCustomerId(), pageLink); + } else { + pageData = userService.findCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); + } + return pageData.mapData(user -> new UserEmailInfo(user.getId(), user.getEmail(), user.getFirstName(), user.getLastName())); + } catch (Exception e) { + throw handleException(e); + } + } + @ApiOperation(value = "Save user settings (saveUserSettings)", notes = "Save user settings represented in json format for authorized user. " ) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index bb40507772..fc4bf8c8d5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -64,6 +64,8 @@ public interface UserService extends EntityDaoService { PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findUsersForAssignForTenant(TenantId tenantId, CustomerId customerId, PageLink pageLink); + void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); void setUserCredentialsEnabled(TenantId tenantId, UserId userId, boolean enabled); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 473c145b5d..b667e1227d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -100,6 +100,19 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } + @Override + public PageData findUsersForAssignForTenant(UUID tenantId, UUID customerId, PageLink pageLink) { + return DaoUtil.toPageData( + userRepository + .findUsersByCustomerIdAndNullCustomerId( + tenantId, + customerId, + Objects.toString(pageLink.getTextSearch(), ""), + NULL_UUID, + DaoUtil.toPageable(pageLink))); + + } + @Override public Long countByTenantId(TenantId tenantId) { return userRepository.countByTenantId(tenantId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index 8a7ee7d581..13b3214dd0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -20,7 +20,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.sql.UserEntity; @@ -44,6 +43,15 @@ public interface UserRepository extends JpaRepository { @Param("authority") Authority authority, Pageable pageable); + @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + + "AND u.customerId IN (:customerId, :nullCustomerId) " + + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") + Page findUsersByCustomerIdAndNullCustomerId(@Param("tenantId") UUID tenantId, + @Param("customerId") UUID customerId, + @Param("searchText") String searchText, + @Param("nullCustomerId") UUID nullCustomerId, + Pageable pageable); + @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") Page findByTenantId(@Param("tenantId") UUID tenantId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index c2551c84d3..3bf2138cf5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -79,4 +79,14 @@ public interface UserDao extends Dao, TenantEntityDao { */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); + /** + * Find users for alarm assignment by tenantId, customerId and page link. + * + * @param tenantId the tenantId + * @param customerId the customerId + * @param pageLink the page link + * @return the list of user entities + */ + PageData findUsersForAssignForTenant(UUID tenantId, UUID customerId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index b99883c52f..08622bb776 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -265,6 +265,15 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic return userDao.findCustomerUsers(tenantId.getId(), customerId.getId(), pageLink); } + @Override + public PageData findUsersForAssignForTenant(TenantId tenantId, CustomerId customerId, PageLink pageLink) { + log.trace("Executing findUsersForAssignForTenant, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(customerId, "Incorrect customerId " + customerId); + validatePageLink(pageLink); + return userDao.findUsersForAssignForTenant(tenantId.getId(), customerId.getId(), pageLink); + } + @Override public void deleteCustomerUsers(TenantId tenantId, CustomerId customerId) { log.trace("Executing deleteCustomerUsers, customerId [{}]", customerId); diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts index 9a6b70ce29..48ca76a4a0 100644 --- a/ui-ngx/src/app/core/http/user.service.ts +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -51,6 +51,13 @@ export class UserService { defaultHttpOptionsFromConfig(config)); } + public getUsersForAssign(alarmId: string, pageLink: PageLink, + config?: RequestConfig): Observable> { + let url = `/api/users/assign${pageLink.toQuery()}`; + url += `&alarmId=` + alarmId; + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + } + public getUser(userId: string, config?: RequestConfig): Observable { return this.http.get(`/api/user/${userId}`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts index bd50beef31..121a09452c 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -44,6 +44,8 @@ import { AlarmService } from '@core/http/alarm.service'; import { OverlayRef } from '@angular/cdk/overlay'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { UtilsService } from '@core/services/utils.service'; +import {Authority} from "@shared/models/authority.enum"; +import {NULL_UUID} from "@shared/models/id/has-uuid"; export const ALARM_ASSIGNEE_PANEL_DATA = new InjectionToken('AlarmAssigneePanelData'); @@ -144,7 +146,7 @@ export class AlarmAssigneePanelComponent implements OnInit, AfterViewInit, OnDe property: 'email', direction: Direction.ASC }); - return this.userService.findUsersByQuery(pageLink, {ignoreLoading: true}) + return this.userService.getUsersForAssign(this.alarmId, pageLink, {ignoreLoading: true}) .pipe( catchError(() => of(emptyPageData())), map(pageData => { From d533bb14b8320cf8827dac78873032a6bcb73a85 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 23 Mar 2023 21:11:47 +0200 Subject: [PATCH 2/6] Added ability for customer users operate with other customer users --- .../security/permission/CustomerUserPermissions.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index 838cd7270b..1a8f87713e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -119,6 +119,15 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { return false; } + + if (!user.getCustomerId().equals(userEntity.getCustomerId())) { + return false; + } + + if (Operation.READ.equals(operation)) { + return true; + } + return user.getId().equals(userId); } From 32b1bf900dd426b6683d7c02602d6b5efa165080 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 24 Mar 2023 10:39:58 +0200 Subject: [PATCH 3/6] Refactoring and added method to get users for assignment to rest client --- .../server/controller/UserController.java | 10 +++++----- .../org/thingsboard/rest/client/RestClient.java | 14 ++++++++++++++ ui-ngx/src/app/core/http/user.service.ts | 5 ++--- .../alarm/alarm-assignee-panel.component.ts | 2 -- 4 files changed, 21 insertions(+), 10 deletions(-) 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 577a25f65d..dc35ce9b46 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -447,11 +447,11 @@ public class UserController extends BaseController { "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/assign", params = {"alarmId", "pageSize", "page"}, method = RequestMethod.GET) + @RequestMapping(value = "/users/assign/{alarmId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public PageData getAssignees( + public PageData getUsersForAssign( @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) - @RequestParam String alarmId, + @PathVariable("alarmId") String strAlarmId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @@ -463,8 +463,8 @@ public class UserController extends BaseController { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { try { - checkParameter("alarmId", alarmId); - AlarmId alarmEntityId = new AlarmId(toUUID(alarmId)); + checkParameter("alarmId", strAlarmId); + AlarmId alarmEntityId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmEntityId, Operation.READ); SecurityUser currentUser = getCurrentUser(); TenantId tenantId = currentUser.getTenantId(); diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index c2c0a6d794..61bd7fb72b 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -65,6 +65,7 @@ import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.UserEmailInfo; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; @@ -2565,6 +2566,19 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }, params).getBody(); } + public PageData getUsersForAssign(AlarmId alarmId, PageLink pageLink) { + Map params = new HashMap<>(); + params.put("alarmId", alarmId.getId().toString()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/users/assign/{alarmId}" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { restTemplate.postForLocation( baseURL + "/api/user/{userId}/userCredentialsEnabled?userCredentialsEnabled={userCredentialsEnabled}", diff --git a/ui-ngx/src/app/core/http/user.service.ts b/ui-ngx/src/app/core/http/user.service.ts index 48ca76a4a0..39a5a20e6e 100644 --- a/ui-ngx/src/app/core/http/user.service.ts +++ b/ui-ngx/src/app/core/http/user.service.ts @@ -53,9 +53,8 @@ export class UserService { public getUsersForAssign(alarmId: string, pageLink: PageLink, config?: RequestConfig): Observable> { - let url = `/api/users/assign${pageLink.toQuery()}`; - url += `&alarmId=` + alarmId; - return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + return this.http.get>(`/api/users/assign/${alarmId}${pageLink.toQuery()}`, + defaultHttpOptionsFromConfig(config)); } public getUser(userId: string, config?: RequestConfig): Observable { diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts index 121a09452c..82c7515ed9 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-assignee-panel.component.ts @@ -44,8 +44,6 @@ import { AlarmService } from '@core/http/alarm.service'; import { OverlayRef } from '@angular/cdk/overlay'; import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { UtilsService } from '@core/services/utils.service'; -import {Authority} from "@shared/models/authority.enum"; -import {NULL_UUID} from "@shared/models/id/has-uuid"; export const ALARM_ASSIGNEE_PANEL_DATA = new InjectionToken('AlarmAssigneePanelData'); From 01996334e2e8f262a3a3a13b5c1b13ec8ed964ff Mon Sep 17 00:00:00 2001 From: imbeacon Date: Mon, 27 Mar 2023 13:52:46 +0300 Subject: [PATCH 4/6] Refactoring, added tests --- .../server/controller/UserController.java | 8 +- .../server/controller/AbstractWebTest.java | 9 ++ .../controller/BaseUserControllerTest.java | 99 ++++++++++++++++++- .../server/dao/user/UserService.java | 2 +- .../server/dao/sql/user/JpaUserDao.java | 4 +- .../server/dao/sql/user/UserRepository.java | 12 +-- .../thingsboard/server/dao/user/UserDao.java | 2 +- .../server/dao/user/UserServiceImpl.java | 8 +- 8 files changed, 127 insertions(+), 17 deletions(-) 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 c3c09d3d63..27af6f35da 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -448,7 +448,7 @@ public class UserController extends BaseController { @RequestMapping(value = "/users/assign/{alarmId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getUsersForAssign( - @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) + @ApiParam(value = ALARM_ID_PARAM_DESCRIPTION, required = true) @PathVariable("alarmId") String strAlarmId, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, @@ -469,7 +469,11 @@ public class UserController extends BaseController { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageData pageData; if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { - pageData = userService.findUsersForAssignForTenant(tenantId, alarm.getCustomerId(), pageLink); + if (alarm.getCustomerId() == null) { + pageData = userService.findTenantAdmins(tenantId, pageLink); + } else { + pageData = userService.findTenantAndCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); + } } else { pageData = userService.findCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index b93e74c959..9bd6f56d17 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -798,6 +798,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } + public class EntityIdComparator implements Comparator { + + @Override + public int compare(D o1, D o2) { + return o1.getId().compareTo(o2.getId()); + } + + } + protected static ResultMatcher statusReason(Matcher matcher) { return jsonPath("$.message", matcher); } 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 7149f52630..f81acf7f37 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -31,10 +31,13 @@ import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; 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.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -53,7 +56,6 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -66,6 +68,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { private IdComparator idComparator = new IdComparator<>(); private IdComparator userDataIdComparator = new IdComparator<>(); + private EntityIdComparator userIdComparator = new EntityIdComparator<>(); + private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer()); @Autowired @@ -647,6 +651,89 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); } + @Test + public void testGetUsersForAssign() throws Exception { + loginTenantAdmin(); + + String email = "testEmail1"; + List expectedCustomerUserIds = new ArrayList<>(); + expectedCustomerUserIds.add(customerUserId); + for (int i = 0; i < 45; i++) { + User customerUser = createCustomerUser( customerId); + customerUser.setEmail(email + StringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)) + "@thingsboard.org"); + User user = doPost("/api/user", customerUser, User.class); + expectedCustomerUserIds.add(user.getId()); + } + List expectedTenantUserIds = new ArrayList<>(List.copyOf(expectedCustomerUserIds)); + expectedTenantUserIds.add(tenantAdminUserId); + + Device device = new Device(); + device.setName("testDevice"); + Device savedDevice = doPost("/api/device", device, Device.class); + + Alarm alarm = createTestAlarm(savedDevice); + + List loadedTenantUserIds = new ArrayList<>(); + PageLink pageLink = new PageLink(33, 0); + PageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/users/assign/" + alarm.getId().getId().toString() + "?", + new TypeReference<>() {}, pageLink); + loadedTenantUserIds.addAll(pageData.getData().stream().map(UserEmailInfo::getId) + .collect(Collectors.toList())); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Assert.assertEquals(1, loadedTenantUserIds.size()); + Assert.assertEquals(tenantAdminUserId, loadedTenantUserIds.get(0)); + + doDelete("/api/alarm/" + alarm.getId().getId().toString()); + + savedDevice.setCustomerId(customerId); + savedDevice = doPost("/api/customer/" + customerId.getId() + + "/device/" + savedDevice.getId().getId(), Device.class); + + alarm = createTestAlarm(savedDevice); + + List loadedUserIds = new ArrayList<>(); + pageLink = new PageLink(16, 0); + do { + pageData = doGetTypedWithPageLink("/api/users/assign/" + alarm.getId().getId().toString() + "?", + new TypeReference<>() {}, pageLink); + loadedUserIds.addAll(pageData.getData().stream().map(UserEmailInfo::getId) + .collect(Collectors.toList())); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + expectedTenantUserIds.sort(userIdComparator); + loadedUserIds.sort(userIdComparator); + + Assert.assertEquals(expectedTenantUserIds, loadedUserIds); + + loginCustomerUser(); + + loadedUserIds = new ArrayList<>(); + pageLink = new PageLink(16, 0); + do { + pageData = doGetTypedWithPageLink("/api/users/assign/" + alarm.getId().getId().toString() + "?", + new TypeReference<>() {}, pageLink); + loadedUserIds.addAll(pageData.getData().stream().map(UserEmailInfo::getId) + .collect(Collectors.toList())); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + expectedCustomerUserIds.sort(userIdComparator); + loadedUserIds.sort(userIdComparator); + + Assert.assertEquals(expectedCustomerUserIds, loadedUserIds); + } + @Test public void testDeleteUserWithDeleteRelationsOk() throws Exception { loginSysAdmin(); @@ -984,4 +1071,14 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { return loadedCustomerUsers; } + private Alarm createTestAlarm(Device device) { + Alarm alarm = new Alarm(); + alarm.setOriginator(device.getId()); + alarm.setCustomerId(device.getCustomerId()); + alarm.setSeverity(AlarmSeverity.MAJOR); + alarm.setType("testAlarm"); + alarm.setStartTs(System.currentTimeMillis()); + return doPost("/api/alarm", alarm, Alarm.class); + } + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index ba5f1722d2..7b11f1c6ff 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -77,7 +77,7 @@ public interface UserService extends EntityDaoService { PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); - PageData findUsersForAssignForTenant(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findTenantAndCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 8c9f41eef6..2bfab53b64 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -103,10 +103,10 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } @Override - public PageData findUsersForAssignForTenant(UUID tenantId, UUID customerId, PageLink pageLink) { + public PageData findTenantAndCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink) { return DaoUtil.toPageData( userRepository - .findUsersByCustomerIdAndNullCustomerId( + .findTenantAndCustomerUsers( tenantId, customerId, Objects.toString(pageLink.getTextSearch(), ""), diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index bdb9e829fa..9cd7029146 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -45,13 +45,13 @@ public interface UserRepository extends JpaRepository { Pageable pageable); @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + - "AND u.customerId IN (:customerId, :nullCustomerId) " + + "AND (:customerId IS NULL OR u.customerId IN (:customerId, :nullCustomerId)) " + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") - Page findUsersByCustomerIdAndNullCustomerId(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, - @Param("searchText") String searchText, - @Param("nullCustomerId") UUID nullCustomerId, - Pageable pageable); + Page findTenantAndCustomerUsers(@Param("tenantId") UUID tenantId, + @Param("customerId") UUID customerId, + @Param("searchText") String searchText, + @Param("nullCustomerId") UUID nullCustomerId, + Pageable pageable); @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index c20439b24d..2dee06db87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -90,7 +90,7 @@ public interface UserDao extends Dao, TenantEntityDao { * @param pageLink the page link * @return the list of user entities */ - PageData findUsersForAssignForTenant(UUID tenantId, UUID customerId, PageLink pageLink); + PageData findTenantAndCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); PageData findAll(PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index b273beb516..222298d124 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -294,12 +294,12 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override - public PageData findUsersForAssignForTenant(TenantId tenantId, CustomerId customerId, PageLink pageLink) { - log.trace("Executing findUsersForAssignForTenant, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); + public PageData findTenantAndCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink) { + log.trace("Executing findTenantAndCustomerUsers, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateId(customerId, "Incorrect customerId " + customerId); validatePageLink(pageLink); - return userDao.findUsersForAssignForTenant(tenantId.getId(), customerId.getId(), pageLink); + validateId(customerId, "Incorrect customerId " + customerId); + return userDao.findTenantAndCustomerUsers(tenantId.getId(), customerId.getId(), pageLink); } @Override From c99368d885a736deb4b34574060b6f7b61a16cd7 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 30 Mar 2023 09:52:04 +0300 Subject: [PATCH 5/6] Refactored --- .../server/controller/UserController.java | 13 ++++++++++++- .../thingsboard/server/dao/user/UserService.java | 2 +- .../thingsboard/server/dao/sql/user/JpaUserDao.java | 7 +++---- .../server/dao/sql/user/UserRepository.java | 5 ++--- .../org/thingsboard/server/dao/user/UserDao.java | 3 ++- .../server/dao/user/UserServiceImpl.java | 8 ++++---- 6 files changed, 24 insertions(+), 14 deletions(-) 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 27af6f35da..f7b68c5d48 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -60,6 +60,7 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserSettings; import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.service.query.EntityQueryService; @@ -71,7 +72,9 @@ import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -121,6 +124,9 @@ public class UserController extends BaseController { @Autowired private EntityQueryService entityQueryService; + @Autowired + private EntityService entityService; + @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. " + @@ -466,13 +472,18 @@ public class UserController extends BaseController { Alarm alarm = checkAlarmId(alarmEntityId, Operation.READ); SecurityUser currentUser = getCurrentUser(); TenantId tenantId = currentUser.getTenantId(); + CustomerId originatorCustomerId = entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator()).get(); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageData pageData; if (Authority.TENANT_ADMIN.equals(currentUser.getAuthority())) { if (alarm.getCustomerId() == null) { pageData = userService.findTenantAdmins(tenantId, pageLink); } else { - pageData = userService.findTenantAndCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); + ArrayList customerIds = new ArrayList<>(Collections.singletonList(new CustomerId(CustomerId.NULL_UUID))); + if (!CustomerId.NULL_UUID.equals(originatorCustomerId.getId())) { + customerIds.add(originatorCustomerId); + } + pageData = userService.findUsersByCustomerIds(tenantId, customerIds, pageLink); } } else { pageData = userService.findCustomerUsers(tenantId, alarm.getCustomerId(), pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 7b11f1c6ff..e1f16be1ed 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -77,7 +77,7 @@ public interface UserService extends EntityDaoService { PageData findCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); - PageData findTenantAndCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink); + PageData findUsersByCustomerIds(TenantId tenantId, List customerIds, PageLink pageLink); void deleteCustomerUsers(TenantId tenantId, CustomerId customerId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 2bfab53b64..04b919b484 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -20,6 +20,7 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -103,16 +104,14 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } @Override - public PageData findTenantAndCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink) { + public PageData findUsersByCustomerIds(UUID tenantId, List customerIds, PageLink pageLink) { return DaoUtil.toPageData( userRepository .findTenantAndCustomerUsers( tenantId, - customerId, + DaoUtil.toUUIDs(customerIds), Objects.toString(pageLink.getTextSearch(), ""), - NULL_UUID, DaoUtil.toPageable(pageLink))); - } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index 9cd7029146..2f6866d45b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -45,12 +45,11 @@ public interface UserRepository extends JpaRepository { Pageable pageable); @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + - "AND (:customerId IS NULL OR u.customerId IN (:customerId, :nullCustomerId)) " + + "AND u.customerId IN (:customerIds) " + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") Page findTenantAndCustomerUsers(@Param("tenantId") UUID tenantId, - @Param("customerId") UUID customerId, + @Param("customerIds") Collection customerIds, @Param("searchText") String searchText, - @Param("nullCustomerId") UUID nullCustomerId, Pageable pageable); @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index 2dee06db87..268e138b2f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.user; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; @@ -90,7 +91,7 @@ public interface UserDao extends Dao, TenantEntityDao { * @param pageLink the page link * @return the list of user entities */ - PageData findTenantAndCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); + PageData findUsersByCustomerIds(UUID tenantId, List customerIds, PageLink pageLink); PageData findAll(PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 222298d124..fbc48894d0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -294,12 +294,12 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } @Override - public PageData findTenantAndCustomerUsers(TenantId tenantId, CustomerId customerId, PageLink pageLink) { - log.trace("Executing findTenantAndCustomerUsers, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); + public PageData findUsersByCustomerIds(TenantId tenantId, List customerIds, PageLink pageLink) { + log.trace("Executing findTenantAndCustomerUsers, tenantId [{}], customerIds [{}], pageLink [{}]", tenantId, customerIds, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validatePageLink(pageLink); - validateId(customerId, "Incorrect customerId " + customerId); - return userDao.findTenantAndCustomerUsers(tenantId.getId(), customerId.getId(), pageLink); + customerIds.forEach(customerId -> {validateId(customerId, "Incorrect customerId " + customerId);}); + return userDao.findUsersByCustomerIds(tenantId.getId(), customerIds, pageLink); } @Override From 863b7a72ed306de78958445fda9598bf53d4da64 Mon Sep 17 00:00:00 2001 From: imbeacon Date: Thu, 6 Apr 2023 11:48:46 +0300 Subject: [PATCH 6/6] Merge --- .../thingsboard/server/controller/BaseUserControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 aea8a819a1..2d93e16f4f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -1078,7 +1078,6 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { return loadedCustomerUsers; } - private Alarm createTestAlarm(Device device) { Alarm alarm = new Alarm(); alarm.setOriginator(device.getId()); @@ -1087,6 +1086,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { alarm.setType("testAlarm"); alarm.setStartTs(System.currentTimeMillis()); return doPost("/api/alarm", alarm, Alarm.class); + } @Test public void testEmptyDashboardSettings() throws Exception {