From b57744f5d56afc5f77fb529d7c10d9888273615d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 19 Jul 2024 17:16:41 +0300 Subject: [PATCH] added test for Oauth2Controller, DomainController, MobileAppController --- .../server/controller/BaseController.java | 14 ++ .../server/controller/DomainController.java | 49 ++--- .../controller/MobileAppController.java | 51 ++--- .../server/controller/OAuth2Controller.java | 28 ++- .../dashboard/DefaultTbDashboardService.java | 1 - .../domain/DefaultTbDomainService.java | 23 ++- .../entitiy/domain/TbDomainService.java | 2 + .../mobile/DefaultTbMobileAppService.java | 21 +- .../entitiy/mobile/TbMobileAppService.java | 2 + .../DefaultTbOauth2ClientService.java | 5 +- .../controller/DomainControllerTest.java | 178 +++++++++++++++++ .../controller/MobileAppControllerTest.java | 180 ++++++++++++++++++ .../Oauth2ClientControllerTest.java | 118 ++++++++++++ .../server/common/data/domain/Domain.java | 2 + .../server/common/data/domain/DomainInfo.java | 4 + .../server/common/data/mobile/MobileApp.java | 5 + .../server/common/data/validation/Length.java | 2 + .../server/dao/domain/DomainServiceImpl.java | 12 +- .../dao/mobile/MobileAppServiceImpl.java | 12 +- .../dao/oauth2/OAuth2ClientServiceImpl.java | 2 +- .../server/dao/oauth2/OAuth2Utils.java | 10 - .../dao/service/StringLengthValidator.java | 6 +- .../validator/DomainDataValidator.java | 36 ---- .../validator/MobileAppDataValidator.java | 42 ---- .../server/dao/sql/domain/JpaDomainDao.java | 5 + .../dao/sql/mobile/JpaMobileAppDao.java | 6 + .../dao/sql/oauth2/JpaOAuth2ClientDao.java | 6 + .../server/dao/service/DomainServiceTest.java | 11 +- .../dao/service/OAuth2ClientServiceTest.java | 34 +++- .../thingsboard/rest/client/RestClient.java | 113 ++++++++++- 30 files changed, 757 insertions(+), 223 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java 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 a3d4243411..f45e687b8d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -167,6 +167,9 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -897,4 +900,15 @@ public abstract class BaseController { } } + protected List getOAuth2ClientIds(UUID[] ids) throws ThingsboardException { + List oauth2ClientIds = ids != null ? Arrays.asList(ids) : Collections.emptyList(); + List oAuth2ClientIds = new ArrayList<>(); + for (UUID id : oauth2ClientIds) { + OAuth2ClientId oauth2ClientId = new OAuth2ClientId(id); + checkOauth2ClientId(oauth2ClientId, Operation.READ); + oAuth2ClientIds.add(oauth2ClientId); + } + return oAuth2ClientIds; + } + } diff --git a/application/src/main/java/org/thingsboard/server/controller/DomainController.java b/application/src/main/java/org/thingsboard/server/controller/DomainController.java index 3b9001695d..33de93cdd7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DomainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DomainController.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; @@ -25,11 +26,11 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -41,9 +42,6 @@ import org.thingsboard.server.service.entitiy.domain.TbDomainService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.UUID; @@ -70,53 +68,30 @@ public class DomainController extends BaseController { @PostMapping(value = "/domain") public Domain saveDomain( @Parameter(description = "A JSON value representing the Domain.", required = true) - @RequestBody Domain domain, + @RequestBody @Valid Domain domain, @Parameter(description = "A list of oauth2 client registration ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) - @RequestParam(name = "oauth2ClientRegistrationIds", required = false) String[] ids) throws Exception { - List oauth2ClientIds = ids != null ? Arrays.asList(ids) : Collections.emptyList(); + @RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { domain.setTenantId(getCurrentUser().getTenantId()); checkEntity(domain.getId(), domain, Resource.DOMAIN); - List oAuth2ClientIds = new ArrayList<>(); - for (String id : oauth2ClientIds) { - OAuth2ClientId oauth2ClientId = new OAuth2ClientId(toUUID(id)); - checkOauth2ClientId(oauth2ClientId, Operation.READ); - oAuth2ClientIds.add(oauth2ClientId); - } - return tbDomainService.save(domain, oAuth2ClientIds, getCurrentUser()); + return tbDomainService.save(domain, getOAuth2ClientIds(ids), getCurrentUser()); } @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", notes = "Update oauth2 clients for the specified domain. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PostMapping(value = "/domain/{id}/oauth2Clients") + @PutMapping(value = "/domain/{id}/oauth2Clients") public void updateOauth2Clients(@PathVariable UUID id, - @RequestBody UUID[] oauth2ClientIds) throws ThingsboardException { + @RequestBody UUID[] clientIds) throws ThingsboardException { DomainId domainId = new DomainId(id); - Domain domain = null; - try { - domain = checkDomainId(domainId, Operation.WRITE); - List oAuth2ClientIds = new ArrayList<>(); - for (UUID outh2CLientId : oauth2ClientIds) { - OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(outh2CLientId); - checkEntityId(oAuth2ClientId, Operation.READ); - oAuth2ClientIds.add(oAuth2ClientId); - } - domainService.updateOauth2Clients(getTenantId(), domainId, oAuth2ClientIds); - logEntityActionService.logEntityAction(domain.getTenantId(), domain.getId(), domain, - ActionType.UPDATED, getCurrentUser(), oAuth2ClientIds.toString()); - } catch (Exception e) { - if (domain != null) { - logEntityActionService.logEntityAction(getTenantId(), domainId, domain, - ActionType.UPDATED, getCurrentUser(), e); - } - throw e; - } + Domain domain = checkDomainId(domainId, Operation.WRITE); + List oAuth2ClientIds = getOAuth2ClientIds(clientIds); + tbDomainService.updateOauth2Clients(domain, oAuth2ClientIds, getCurrentUser()); } - @ApiOperation(value = "Get Domain infos (getDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Get Domain infos (getTenantDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/domain/infos") - public List getDomainInfos() throws ThingsboardException { + public List getTenantDomainInfos() throws ThingsboardException { return domainService.findDomainInfosByTenantId(getTenantId()); } diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java index 06a80d71bc..0f39d8f0ea 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.access.prepost.PreAuthorize; @@ -25,11 +26,11 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.MobileAppId; import org.thingsboard.server.common.data.id.OAuth2ClientId; @@ -40,8 +41,8 @@ import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.mobile.TbMobileAppService; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -61,57 +62,37 @@ public class MobileAppController extends BaseController { @ApiOperation(value = "Save Or update Mobile app (saveMobileApp)", notes = "Create or update the Mobile app. When creating mobile app, platform generates Mobile App Id as " + UUID_WIKI_LINK + "The newly created Mobile App Id will be present in the response. " + - "Specify existing Mobile App Id to update the domain. " + + "Specify existing Mobile App Id to update the mobile app. " + "Referencing non-existing Mobile App Id will cause 'Not Found' error." + "\n\nMobile app package name is unique for entire platform setup.\n\n") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/mobileApp") public MobileApp saveMobileApp( - @Parameter(description = "A JSON value representing the Domain.", required = true) - @RequestBody MobileApp mobileApp, + @Parameter(description = "A JSON value representing the Mobile Application.", required = true) + @RequestBody @Valid MobileApp mobileApp, @Parameter(description = "A list of entity group ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) - @RequestParam(name = "oauth2ClientIds", required = false) UUID[] oauth2ClientIds) throws Exception { + @RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception { mobileApp.setTenantId(getCurrentUser().getTenantId()); - - List oAuth2Clients = new ArrayList<>(); - for (UUID id : oauth2ClientIds) { - OAuth2ClientId oauth2ClientId = new OAuth2ClientId(id); - checkOauth2ClientId(oauth2ClientId, Operation.READ); - oAuth2Clients.add(oauth2ClientId); - } - return tbMobileAppService.save(mobileApp, oAuth2Clients, getCurrentUser()); + checkEntity(mobileApp.getId(), mobileApp, Resource.MOBILE_APP); + return tbMobileAppService.save(mobileApp, getOAuth2ClientIds(ids), getCurrentUser()); } @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", notes = "Update oauth2 clients to the specified mobile app. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PostMapping(value = "/mobileApp/{id}/oauth2Clients") + @PutMapping(value = "/mobileApp/{id}/oauth2Clients") public void updateOauth2Clients(@PathVariable UUID id, - @RequestBody UUID[] oauth2ClientIds) throws ThingsboardException { + @RequestBody UUID[] clientIds) throws ThingsboardException { MobileAppId mobileAppId = new MobileAppId(id); - MobileApp mobileApp = null; - try { - mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); - List oAuth2ClientIds = new ArrayList<>(); - for (UUID outh2CLientId : oauth2ClientIds) { - OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(outh2CLientId); - checkEntityId(oAuth2ClientId, Operation.READ); - oAuth2ClientIds.add(oAuth2ClientId); - } - mobileAppService.updateOauth2Clients(getTenantId(), mobileAppId, oAuth2ClientIds); - } catch (Exception e) { - if (mobileApp != null) { - logEntityActionService.logEntityAction(getTenantId(), mobileAppId, mobileApp, - ActionType.UPDATED, getCurrentUser(), e); - } - throw e; - } + MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); + List oAuth2ClientIds = getOAuth2ClientIds(clientIds); + tbMobileAppService.updateOauth2Clients(mobileApp, oAuth2ClientIds, getCurrentUser()); } - @ApiOperation(value = "Get mobile app infos (getMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/mobileApp/infos") - public List getMobileAppInfos() throws ThingsboardException { + public List getTenantMobileAppInfos() throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); return mobileAppService.findMobileAppInfosByTenantId(tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 8739c492cf..7134bf1fd5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -18,26 +18,25 @@ package org.thingsboard.server.controller; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; @@ -69,16 +68,16 @@ public class OAuth2Controller extends BaseController { @ApiOperation(value = "Get OAuth2 clients (getOAuth2Clients)", notes = "Get the list of OAuth2 clients " + "to log in with, available for such domain scheme (HTTP or HTTPS) (if x-forwarded-proto request header is present - " + "the scheme is known from it) and domain name and port (port may be known from x-forwarded-port header)") - @PostMapping(value = "/noauth/oauth2Clients") + @PostMapping(value = "/noauth/oauth2/client") public List getOAuth2Clients(HttpServletRequest request, @Parameter(description = "Mobile application package name, to find OAuth2 clients " + - "where there is configured mobile application with such package name") - @RequestParam(required = false) String pkgName, + "where there is configured mobile application with such package name") + @RequestParam(required = false) String pkgName, @Parameter(description = "Platform type to search OAuth2 clients for which " + - "the usage with this platform type is allowed in the settings. " + - "If platform type is not one of allowable values - it will just be ignored", - schema = @Schema(allowableValues = {"WEB", "ANDROID", "IOS"})) - @RequestParam(required = false) String platform) throws ThingsboardException { + "the usage with this platform type is allowed in the settings. " + + "If platform type is not one of allowable values - it will just be ignored", + schema = @Schema(allowableValues = {"WEB", "ANDROID", "IOS"})) + @RequestParam(required = false) String platform) throws ThingsboardException { if (log.isDebugEnabled()) { log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); Enumeration headerNames = request.getHeaderNames(); @@ -104,7 +103,7 @@ public class OAuth2Controller extends BaseController { @ApiOperation(value = "Save OAuth2 Client Registration (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/oauth2/client") - public OAuth2Client saveOAuth2Client(@RequestBody OAuth2Client oAuth2Client) throws Exception { + public OAuth2Client saveOAuth2Client(@RequestBody @Valid OAuth2Client oAuth2Client) throws Exception { TenantId tenantId = getTenantId(); oAuth2Client.setTenantId(tenantId); checkEntity(oAuth2Client.getId(), oAuth2Client, Resource.OAUTH2_CLIENT); @@ -129,8 +128,7 @@ public class OAuth2Controller extends BaseController { @ApiOperation(value = "Delete oauth2 client (deleteAsset)", notes = "Deletes the asset and all the relations (from and to the asset). Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/client/{id}", method = RequestMethod.DELETE) - @ResponseStatus(value = HttpStatus.OK) + @DeleteMapping(value = "/oauth2/client/{id}") public void deleteOauth2Client(@PathVariable UUID id) throws Exception { OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(id); OAuth2Client oAuth2Client = checkOauth2ClientId(oAuth2ClientId, Operation.DELETE); @@ -142,7 +140,7 @@ public class OAuth2Controller extends BaseController { "further log in processing. This URL may be configured as 'security.oauth2.loginProcessingUrl' property in yml configuration file, or " + "as 'SECURITY_OAUTH2_LOGIN_PROCESSING_URL' env variable. By default it is '/login/oauth2/code/'" + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET) + @GetMapping(value = "/oauth2/loginProcessingUrl") public String getLoginProcessingUrl() { return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index 685eda0858..ee8b2d7e47 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.resource.ImageService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java index 1abb6215d0..755972f9fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java @@ -43,13 +43,27 @@ public class DefaultTbDomainService extends AbstractTbEntityService implements T TenantId tenantId = domain.getTenantId(); try { Domain savedDomain = checkNotNull(domainService.saveDomain(tenantId, domain)); - logEntityActionService.logEntityAction(tenantId, savedDomain.getId(), domain, actionType, user); if (!CollectionUtils.isEmpty(oAuth2Clients)) { domainService.updateOauth2Clients(domain.getTenantId(), savedDomain.getId(), oAuth2Clients); } + logEntityActionService.logEntityAction(tenantId, savedDomain.getId(), domain, actionType, user, oAuth2Clients); return savedDomain; } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), domain, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), domain, actionType, user, e, oAuth2Clients); + throw e; + } + } + + @Override + public void updateOauth2Clients(Domain domain, List oAuth2ClientIds, User user) { + ActionType actionType = ActionType.UPDATED; + TenantId tenantId = domain.getTenantId(); + DomainId domainId = domain.getId(); + try { + domainService.updateOauth2Clients(tenantId, domainId, oAuth2ClientIds); + logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, oAuth2ClientIds.toString()); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, e, oAuth2ClientIds.toString()); throw e; } } @@ -62,10 +76,9 @@ public class DefaultTbDomainService extends AbstractTbEntityService implements T DomainId domainId = domain.getId(); try { domainService.deleteDomainById(tenantId, domainId); - logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, domain.getName()); + logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user); } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), actionType, user, e, - domainId.toString()); + logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, e); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java index 0099c845a5..744005ff36 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java @@ -25,6 +25,8 @@ public interface TbDomainService { Domain save(Domain domain, List oAuth2Clients, User user) throws Exception; + void updateOauth2Clients(Domain domain, List oAuth2ClientIds, User user); + void delete(Domain domain, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java index 75243aefcd..a0731789be 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java @@ -43,10 +43,10 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement TenantId tenantId = mobileApp.getTenantId(); try { MobileApp savedMobileApp = checkNotNull(mobileAppService.saveMobileApp(tenantId, mobileApp)); - logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), mobileApp, actionType, user); if (!CollectionUtils.isEmpty(oauth2Clients)) { mobileAppService.updateOauth2Clients(tenantId, savedMobileApp.getId(), oauth2Clients); } + logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), mobileApp, actionType, user); return savedMobileApp; } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), mobileApp, actionType, user, e); @@ -54,6 +54,20 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement } } + @Override + public void updateOauth2Clients(MobileApp mobileApp, List oAuth2ClientIds, User user) { + ActionType actionType = ActionType.UPDATED; + TenantId tenantId = mobileApp.getTenantId(); + MobileAppId mobileAppId = mobileApp.getId(); + try { + mobileAppService.updateOauth2Clients(tenantId, mobileAppId, oAuth2ClientIds); + logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, oAuth2ClientIds.toString()); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, e, oAuth2ClientIds.toString()); + throw e; + } + } + @Override @Transactional public void delete(MobileApp mobileApp, User user) { @@ -62,10 +76,9 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement MobileAppId mobileAppId = mobileApp.getId(); try { mobileAppService.deleteMobileAppById(tenantId, mobileAppId); - logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, mobileApp.getPkgName()); + logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user); } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), actionType, user, e, - mobileAppId.toString()); + logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, e); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java index fdd5c8b353..bcc9cf7004 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java @@ -25,6 +25,8 @@ public interface TbMobileAppService { MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception; + void updateOauth2Clients(MobileApp mobileApp, List oAuth2ClientIds, User user); + void delete(MobileApp mobileApp, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java index 5c55edfc60..0a36c23369 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java @@ -53,10 +53,9 @@ public class DefaultTbOauth2ClientService extends AbstractTbEntityService implem OAuth2ClientId oAuth2ClientId = oAuth2Client.getId(); try { oAuth2ClientService.deleteOAuth2ClientById(tenantId, oAuth2ClientId); - logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user, oAuth2Client.getName()); + logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user); } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), actionType, user, e, - oAuth2ClientId.toString()); + logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user, e); throw e; } } diff --git a/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java new file mode 100644 index 0000000000..9fe161d936 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java @@ -0,0 +1,178 @@ +/** + * Copyright © 2016-2024 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@DaoSqlTest +public class DomainControllerTest extends AbstractControllerTest { + + @Before + public void setUp() throws Exception { + loginSysAdmin(); + } + + @After + public void tearDown() throws Exception { + List domains = doGetTyped("/api/domain/infos", new TypeReference>() { + }); + for (Domain domain : domains) { + doDelete("/api/domain/" + domain.getId().getId()) + .andExpect(status().isOk()); + } + + List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { + }); + for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) + .andExpect(status().isOk()); + } + } + + @Test + public void testSaveDomain() throws Exception { + List domainInfos = doGetTyped("/api/domain/infos", new TypeReference>() { + }); + assertThat(domainInfos).isEmpty(); + + Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true); + Domain savedDomain = doPost("/api/domain", domain, Domain.class); + + List domainInfos2 = doGetTyped("/api/domain/infos", new TypeReference>() { + }); + assertThat(domainInfos2).hasSize(1); + assertThat(domainInfos2.get(0)).isEqualTo(new DomainInfo(savedDomain, Collections.emptyList())); + + DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId()); + assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, Collections.emptyList())); + + doDelete("/api/domain/" + savedDomain.getId().getId()); + doGet("/api/domain/info/{id}", savedDomain.getId().getId()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveDomainWithoutName() throws Exception { + Domain domain = constructDomain(TenantId.SYS_TENANT_ID, null, true, true); + doPost("/api/domain", domain) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("name must not be blank"))); + } + + @Test + public void testUpdateDomainOauth2Clients() throws Exception { + Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true); + Domain savedDomain = doPost("/api/domain", domain, Domain.class); + + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + OAuth2Client oAuth2Client2 = validClientInfo(TenantId.SYS_TENANT_ID, "test facebook client"); + OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class); + + doPut("/api/domain/" + savedDomain.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId())); + + DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId()); + assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client), + new OAuth2ClientInfo(savedOAuth2Client2)))); + + doPut("/api/domain/" + savedDomain.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId())); + DomainInfo retrievedDomainInfo2 = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId()); + assertThat(retrievedDomainInfo2).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client2)))); + } + + @Test + public void testCreateDomainWithOauth2Clients() throws Exception { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true); + Domain savedDomain = doPost("/api/domain?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), domain, Domain.class); + + DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId()); + assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client)))); + } + + private Domain constructDomain(TenantId tenantId, String domainName, boolean oauth2Enabled, boolean propagateToEdge) { + Domain domain = new Domain(); + domain.setTenantId(tenantId); + domain.setName(domainName); + domain.setOauth2Enabled(oauth2Enabled); + domain.setPropagateToEdge(propagateToEdge); + return domain; + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title) { + return validClientInfo(tenantId, title, null); + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title, List platforms) { + OAuth2Client oAuth2Client = new OAuth2Client(); + oAuth2Client.setTenantId(tenantId); + oAuth2Client.setTitle(title); + oAuth2Client.setClientId(UUID.randomUUID().toString()); + oAuth2Client.setClientSecret(UUID.randomUUID().toString()); + oAuth2Client.setAuthorizationUri(UUID.randomUUID().toString()); + oAuth2Client.setAccessTokenUri(UUID.randomUUID().toString()); + oAuth2Client.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setPlatforms(platforms == null ? Collections.emptyList() : platforms); + oAuth2Client.setUserInfoUri(UUID.randomUUID().toString()); + oAuth2Client.setUserNameAttributeName(UUID.randomUUID().toString()); + oAuth2Client.setJwkSetUri(UUID.randomUUID().toString()); + oAuth2Client.setClientAuthenticationMethod(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonLabel(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonIcon(UUID.randomUUID().toString()); + oAuth2Client.setAdditionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .custom( + OAuth2CustomMapperConfig.builder() + .url(UUID.randomUUID().toString()) + .build() + ) + .build()); + return oAuth2Client; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java new file mode 100644 index 0000000000..3ba8bd29dd --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java @@ -0,0 +1,180 @@ +/** + * Copyright © 2016-2024 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.mobile.MobileAppInfo; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@DaoSqlTest +public class MobileAppControllerTest extends AbstractControllerTest { + + @Before + public void setUp() throws Exception { + loginSysAdmin(); + } + + @After + public void tearDown() throws Exception { + List mobileAppInfos = doGetTyped("/api/mobileApp/infos", new TypeReference>() { + }); + for (MobileApp mobileApp : mobileAppInfos) { + doDelete("/api/mobileApp/" + mobileApp.getId().getId()) + .andExpect(status().isOk()); + } + + List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { + }); + for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) + .andExpect(status().isOk()); + } + } + + @Test + public void testSaveMobileApp() throws Exception { + List MobileAppInfos = doGetTyped("/api/mobileApp/infos", new TypeReference>() { + }); + assertThat(MobileAppInfos).isEmpty(); + + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); + MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class); + + List MobileAppInfos2 = doGetTyped("/api/mobileApp/infos", new TypeReference>() { + }); + assertThat(MobileAppInfos2).hasSize(1); + assertThat(MobileAppInfos2.get(0)).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList())); + + MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); + assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList())); + + doDelete("/api/mobileApp/" + savedMobileApp.getId().getId()); + doGet("/api/mobileApp/info/{id}", savedMobileApp.getId().getId()) + .andExpect(status().isNotFound()); + } + + @Test + public void testSaveMobileAppWithShortAppSecret() throws Exception { + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce", true); + mobileApp.setAppSecret("short"); + doPost("/api/mobileApp", mobileApp) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("appSecret must be at least 16 characters"))); + } + + @Test + public void testUpdateMobileAppOauth2Clients() throws Exception { + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); + MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class); + + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + OAuth2Client oAuth2Client2 = validClientInfo(TenantId.SYS_TENANT_ID, "test facebook client"); + OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class); + + doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId())); + + MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); + assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client), + new OAuth2ClientInfo(savedOAuth2Client2)))); + + doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId())); + MobileAppInfo retrievedMobileAppInfo2 = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); + assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client2)))); + } + + @Test + public void testCreateMobileAppWithOauth2Clients() throws Exception { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true); + MobileApp savedMobileApp = doPost("/api/mobileApp?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), mobileApp, MobileApp.class); + + MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId()); + assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client)))); + } + + private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, boolean oauth2Enabled) { + MobileApp MobileApp = new MobileApp(); + MobileApp.setTenantId(tenantId); + MobileApp.setPkgName(mobileAppName); + MobileApp.setAppSecret(StringUtils.randomAlphanumeric(24)); + MobileApp.setOauth2Enabled(oauth2Enabled); + return MobileApp; + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title) { + return validClientInfo(tenantId, title, null); + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title, List platforms) { + OAuth2Client oAuth2Client = new OAuth2Client(); + oAuth2Client.setTenantId(tenantId); + oAuth2Client.setTitle(title); + oAuth2Client.setClientId(UUID.randomUUID().toString()); + oAuth2Client.setClientSecret(UUID.randomUUID().toString()); + oAuth2Client.setAuthorizationUri(UUID.randomUUID().toString()); + oAuth2Client.setAccessTokenUri(UUID.randomUUID().toString()); + oAuth2Client.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setPlatforms(platforms == null ? Collections.emptyList() : platforms); + oAuth2Client.setUserInfoUri(UUID.randomUUID().toString()); + oAuth2Client.setUserNameAttributeName(UUID.randomUUID().toString()); + oAuth2Client.setJwkSetUri(UUID.randomUUID().toString()); + oAuth2Client.setClientAuthenticationMethod(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonLabel(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonIcon(UUID.randomUUID().toString()); + oAuth2Client.setAdditionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .custom( + OAuth2CustomMapperConfig.builder() + .url(UUID.randomUUID().toString()) + .build() + ) + .build()); + return oAuth2Client; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java new file mode 100644 index 0000000000..8ab7233286 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2024 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +@DaoSqlTest +public class Oauth2ClientControllerTest extends AbstractControllerTest { + + @Before + public void setUp() throws Exception { + loginSysAdmin(); + } + + @After + public void tearDown() throws Exception { + List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { + }); + for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) + .andExpect(status().isOk()); + } + } + + @Test + public void testSaveOauth2Client() throws Exception { + loginSysAdmin(); + List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { + }); + assertThat(oAuth2ClientInfos).isEmpty(); + + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + List oAuth2ClientInfos2 = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { + }); + assertThat(oAuth2ClientInfos2).hasSize(1); + assertThat(oAuth2ClientInfos2.get(0)).isEqualTo(new OAuth2ClientInfo(savedOAuth2Client)); + + OAuth2Client retrievedOAuth2ClientInfo = doGet("/api/oauth2/client/{id}", OAuth2Client.class, savedOAuth2Client.getId().getId()); + assertThat(retrievedOAuth2ClientInfo).isEqualTo(savedOAuth2Client); + + doDelete("/api/oauth2/client/" + savedOAuth2Client.getId().getId()); + doGet("/api/oauth2/client/{id}", savedOAuth2Client.getId().getId()) + .andExpect(status().isNotFound()); + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title) { + return validClientInfo(tenantId, title, null); + } + + protected OAuth2Client validClientInfo(TenantId tenantId, String title, List platforms) { + OAuth2Client oAuth2Client = new OAuth2Client(); + oAuth2Client.setTenantId(tenantId); + oAuth2Client.setTitle(title); + oAuth2Client.setClientId(UUID.randomUUID().toString()); + oAuth2Client.setClientSecret(UUID.randomUUID().toString()); + oAuth2Client.setAuthorizationUri(UUID.randomUUID().toString()); + oAuth2Client.setAccessTokenUri(UUID.randomUUID().toString()); + oAuth2Client.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setPlatforms(platforms == null ? Collections.emptyList() : platforms); + oAuth2Client.setUserInfoUri(UUID.randomUUID().toString()); + oAuth2Client.setUserNameAttributeName(UUID.randomUUID().toString()); + oAuth2Client.setJwkSetUri(UUID.randomUUID().toString()); + oAuth2Client.setClientAuthenticationMethod(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonLabel(UUID.randomUUID().toString()); + oAuth2Client.setLoginButtonIcon(UUID.randomUUID().toString()); + oAuth2Client.setAdditionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Client.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(true) + .activateUser(true) + .type(MapperType.CUSTOM) + .custom( + OAuth2CustomMapperConfig.builder() + .url(UUID.randomUUID().toString()) + .build() + ) + .build()); + return oAuth2Client; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java b/common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java index ed154e656f..6b1853c890 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.domain; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -35,6 +36,7 @@ public class Domain extends BaseData implements HasTenantId, HasName { @Schema(description = "JSON object with Tenant Id") private TenantId tenantId; @Schema(description = "Domain name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String name; @Schema(description = "Whether OAuth2 settings are enabled or not") private boolean oauth2Enabled; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java index 98925cfeb0..83ea9998df 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java @@ -16,8 +16,10 @@ package org.thingsboard.server.common.data.domain; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.oauth2.HasOauth2Clients; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; @@ -25,6 +27,8 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) @Data +@NoArgsConstructor +@AllArgsConstructor @Schema public class DomainInfo extends Domain implements HasOauth2Clients { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java index 576c0920c9..ddbb3fcf58 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.mobile; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.MobileAppId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.validation.Length; @EqualsAndHashCode(callSuper = true) @Data @@ -36,8 +38,11 @@ public class MobileApp extends BaseData implements HasTenantId, Has @Schema(description = "JSON object with Tenant Id") private TenantId tenantId; @Schema(description = "Application package name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty private String pkgName; @Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty + @Length(min = 16, message = "must be at least 16 characters") private String appSecret; @Schema(description = "Whether OAuth2 settings are enabled or not") private boolean oauth2Enabled; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java index 3437530387..f0018cbe9b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java @@ -32,6 +32,8 @@ public @interface Length { int max() default 255; + int min() default 0; + Class[] groups() default {}; Class[] payload() default {}; diff --git a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java index 9405c89cbb..bb1c89b012 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java @@ -30,13 +30,12 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; -import org.thingsboard.server.dao.oauth2.OAuth2Utils; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; import java.util.ArrayList; @@ -58,13 +57,10 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe private OAuth2ClientDao oauth2ClientDao; @Autowired private DomainDao domainDao; - @Autowired - private DataValidator domainValidator; @Override public Domain saveDomain(TenantId tenantId, Domain domain) { log.trace("Executing saveDomain [{}]", domain); - domainValidator.validate(domain, Domain::getTenantId); try { Domain savedDomain = domainDao.save(tenantId, domain); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(savedDomain).build()); @@ -109,7 +105,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe @Override public void deleteDomainById(TenantId tenantId, DomainId domainId) { - log.trace("Executing deleteDomain [{}]", domainId.getId()); + log.trace("Executing deleteDomainById [{}]", domainId.getId()); domainDao.removeById(tenantId, domainId.getId()); eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(domainId).build()); } @@ -127,7 +123,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe List domainInfos = new ArrayList<>(); domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { domainInfos.add(new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); }); return domainInfos; @@ -141,7 +137,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe return null; } return new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2ClientInfo::new) .collect(Collectors.toList())); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java index 79b7c4a095..6dc5d1c1f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java @@ -30,13 +30,12 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.mobile.MobileAppInfo; import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; -import org.thingsboard.server.dao.oauth2.OAuth2Utils; -import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; import java.util.ArrayList; @@ -58,13 +57,10 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil private OAuth2ClientDao oauth2ClientDao; @Autowired private MobileAppDao mobileAppDao; - @Autowired - private DataValidator mobileAppValidator; @Override public MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp) { log.trace("Executing saveMobileApp [{}]", mobileApp); - mobileAppValidator.validate(mobileApp, MobileApp::getTenantId); try { MobileApp savedMobileApp = mobileAppDao.save(tenantId, mobileApp); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(savedMobileApp).build()); @@ -84,7 +80,7 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil log.trace("Executing deleteMobileAppById [{}]", mobileAppId.getId()); mobileAppDao.removeById(tenantId, mobileAppId.getId()); eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(mobileAppId).build()); - } + } @Override public MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId) { @@ -99,7 +95,7 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil List mobileAppInfos = new ArrayList<>(); mobileApps.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); }); return mobileAppInfos; @@ -113,7 +109,7 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil return null; } return new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2ClientInfo::new) .collect(Collectors.toList())); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java index 75e63ec48c..6cf944804d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java @@ -129,7 +129,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA log.trace("Executing findOAuth2ClientInfosByTenantId tenantId=[{}]", tenantId); return oauth2ClientDao.findByTenantId(tenantId.getId()) .stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2ClientInfo::new) .collect(Collectors.toList()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 22a100305a..9a4702dac5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; import org.thingsboard.server.common.data.oauth2.OAuth2Client; @@ -30,13 +29,4 @@ public class OAuth2Utils { return client; } - public static OAuth2ClientInfo toClientInfo(OAuth2Client oAuth2Client) { - OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setId(oAuth2Client.getId()); - client.setCreatedTime(oAuth2Client.getCreatedTime()); - client.setTitle(oAuth2Client.getTitle()); - client.setPlatforms(oAuth2Client.getPlatforms()); - return client; - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java index 252af5458a..2373612809 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java @@ -26,6 +26,7 @@ import jakarta.validation.ConstraintValidatorContext; @Slf4j public class StringLengthValidator implements ConstraintValidator { private int max; + private int min; @Override public boolean isValid(Object value, ConstraintValidatorContext context) { @@ -35,14 +36,15 @@ public class StringLengthValidator implements ConstraintValidator= min && stringValue.length() <= max; } @Override public void initialize(Length constraintAnnotation) { this.max = constraintAnnotation.max(); + this.min = constraintAnnotation.min(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java deleted file mode 100644 index 644e35f14a..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java +++ /dev/null @@ -1,36 +0,0 @@ -/** - * Copyright © 2016-2024 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.dao.service.validator; - -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.domain.Domain; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.DataValidator; - -@Component -@AllArgsConstructor -public class DomainDataValidator extends DataValidator { - - @Override - protected void validateDataImpl(TenantId tenantId, Domain domain) { - if (StringUtils.isEmpty(domain.getName())) { - throw new DataValidationException("Domain name should be specified!"); - } - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java deleted file mode 100644 index 89082bc02b..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright © 2016-2024 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.dao.service.validator; - -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.service.DataValidator; - -@Component -@AllArgsConstructor -public class MobileAppDataValidator extends DataValidator { - - @Override - protected void validateDataImpl(TenantId tenantId, MobileApp mobileApp) { - if (StringUtils.isEmpty(mobileApp.getPkgName())) { - throw new DataValidationException("Package should be specified!"); - } - if (StringUtils.isEmpty(mobileApp.getAppSecret())) { - throw new DataValidationException("Application secret should be specified!"); - } - if (mobileApp.getAppSecret().length() < 16) { - throw new DataValidationException("Application secret should be at least 16 characters!"); - } - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java index 55c7be8e56..ec9f9f9ca9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.domain; import lombok.RequiredArgsConstructor; 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.domain.Domain; import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; @@ -82,5 +83,9 @@ public class JpaDomainDao extends JpaAbstractDao implement domainRepository.deleteByTenantId(tenantId.getId()); } + @Override + public EntityType getEntityType() { + return EntityType.DOMAIN; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java index 850243a02d..98442974f8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.mobile; import lombok.RequiredArgsConstructor; 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.id.MobileAppId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; @@ -77,5 +78,10 @@ public class JpaMobileAppDao extends JpaAbstractDao repository.deleteByTenantId(tenantId.getId()); } + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java index d8ca155153..733949c212 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; 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.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; @@ -83,4 +84,9 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao domains = new ArrayList<>(); for (int i = 0; i < 5; i++) { - Domain oAuth2Client = validDomain(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true, false); + Domain oAuth2Client = constructDomain(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true, false); Domain savedOauth2Client = domainService.saveDomain(SYSTEM_TENANT_ID, oAuth2Client); domains.add(savedOauth2Client); } @@ -102,7 +102,7 @@ public class DomainServiceTest extends AbstractServiceTest { OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); List infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); - Domain domain = validDomain(TenantId.SYS_TENANT_ID, "test.domain.com", true, true); + Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "test.domain.com", true, true); Domain savedDomain = domainService.saveDomain(SYSTEM_TENANT_ID, domain); domainService.updateOauth2Clients(TenantId.SYS_TENANT_ID, savedDomain.getId(), List.of(savedOauth2Client.getId())); @@ -116,7 +116,7 @@ public class DomainServiceTest extends AbstractServiceTest { assertThat(oauth2LoginInfo).containsOnly(new OAuth2ClientLoginInfo(savedOauth2Client.getName(), savedOauth2Client.getLoginButtonIcon(), String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, savedOauth2Client.getUuidId().toString()))); } - private Domain validDomain(TenantId tenantId, String domainName, boolean oauth2Enabled, boolean propagateToEdge) { + private Domain constructDomain(TenantId tenantId, String domainName, boolean oauth2Enabled, boolean propagateToEdge) { Domain domain = new Domain(); domain.setTenantId(tenantId); domain.setName(domainName); @@ -125,7 +125,4 @@ public class DomainServiceTest extends AbstractServiceTest { return domain; } - - - } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java index 80bc79e39b..ae77f74e54 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java @@ -22,15 +22,16 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; -import org.thingsboard.server.dao.oauth2.OAuth2Utils; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @DaoSqlTest public class OAuth2ClientServiceTest extends AbstractServiceTest { @@ -58,6 +59,35 @@ public class OAuth2ClientServiceTest extends AbstractServiceTest { assertThat(retrievedOauth2Client2).isEqualTo(updatedOauth2Client); } + @Test + public void testSaveOauth2ClientWithoutMapper() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client", List.of(PlatformType.ANDROID)); + oAuth2Client.setMapperConfig(null); + + assertThatThrownBy(() -> { + oAuth2ClientService.saveOAuth2Client(TenantId.SYS_TENANT_ID, oAuth2Client); + }).hasMessageContaining("mapperConfig must not be null"); + } + + @Test + public void testSaveOauth2ClientWithoutCustomConfig() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client", List.of(PlatformType.ANDROID)); + oAuth2Client.getMapperConfig().setCustom(null); + + assertThatThrownBy(() -> { + oAuth2ClientService.saveOAuth2Client(TenantId.SYS_TENANT_ID, oAuth2Client); + }).hasMessageContaining("Custom config should be specified!"); + } + + @Test + public void testSaveOauth2ClientWithoutCustomUrl() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client", List.of(PlatformType.ANDROID)); + oAuth2Client.getMapperConfig().setCustom(OAuth2CustomMapperConfig.builder().build()); + assertThatThrownBy(() -> { + oAuth2ClientService.saveOAuth2Client(TenantId.SYS_TENANT_ID, oAuth2Client); + }).hasMessageContaining("Custom mapper URL should be specified!"); + } + @Test public void testGetTenantOAuth2Clients() { List oAuth2Clients = new ArrayList<>(); @@ -70,7 +100,7 @@ public class OAuth2ClientServiceTest extends AbstractServiceTest { assertThat(retrieved).containsOnlyOnceElementsOf(oAuth2Clients); List retrievedInfos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); - List oAuth2ClientInfos = oAuth2Clients.stream().map(OAuth2Utils::toClientInfo).collect(Collectors.toList()); + List oAuth2ClientInfos = oAuth2Clients.stream().map(OAuth2ClientInfo::new).collect(Collectors.toList()); assertThat(retrievedInfos).containsOnlyOnceElementsOf(oAuth2ClientInfos); } 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 c28da9332e..078b2ae02f 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 @@ -86,6 +86,8 @@ import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeInfo; @@ -100,9 +102,12 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; @@ -117,6 +122,10 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.mobile.MobileAppInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.PlatformType; @@ -2070,13 +2079,103 @@ public class RestClient implements Closeable { }, params).getBody(); } -// public OAuth2Info getCurrentOAuth2Info() { -// return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2Info.class).getBody(); -// } -// -// public OAuth2Info saveOAuth2Info(OAuth2Info oauth2Info) { -// return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Info, OAuth2Info.class).getBody(); -// } + public List getTenantOAuth2Clients() { + return restTemplate.exchange( + baseURL + "/api/oauth2/client/infos", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getOauth2ClientById(OAuth2ClientId oAuth2ClientId) { + try { + ResponseEntity oauth2Client = restTemplate.getForEntity(baseURL + "/api/oauth2/client/{id}", OAuth2Client.class, oAuth2ClientId.getId()); + return Optional.ofNullable(oauth2Client.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public OAuth2Client saveOAuth2Client(OAuth2Client oAuth2Client) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/client", oAuth2Client, OAuth2Client.class).getBody(); + } + + public void deleteOauth2CLient(OAuth2ClientId oAuth2ClientId) { + restTemplate.delete(baseURL + "/api/oauth2/client/{id}", oAuth2ClientId.getId()); + } + + public List getTenantDomainInfos() { + return restTemplate.exchange( + baseURL + "/api/domain/infos", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getDomainInfoById(DomainId domainId) { + try { + ResponseEntity domainInfo = restTemplate.getForEntity(baseURL + "/api/domain/info/{id}", DomainInfo.class, domainId.getId()); + return Optional.ofNullable(domainInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Domain saveDomain(Domain domain) { + return restTemplate.postForEntity(baseURL + "/api/domain", domain, Domain.class).getBody(); + } + + public void deleteDomain(DomainId domainId) { + restTemplate.delete(baseURL + "/api/domain/{id}", domainId.getId()); + } + + public void updateDomainOauth2Clients(DomainId domainId, UUID[] oauth2ClientIds) { + restTemplate.postForLocation(baseURL + "/api/domain/{id}/oauth2Clients", oauth2ClientIds, domainId.getId()); + } + + public List getTenantMobileAppInfos() { + return restTemplate.exchange( + baseURL + "/api/mobileApp/infos", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getMobileAppInfoById(MobileAppId mobileAppId) { + try { + ResponseEntity mobileAppInfo = restTemplate.getForEntity(baseURL + "/api/mobileApp/info/{id}", MobileAppInfo.class, mobileAppId.getId()); + return Optional.ofNullable(mobileAppInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public MobileApp saveMobileApp(MobileApp mobileApp) { + return restTemplate.postForEntity(baseURL + "/api/mobileApp", mobileApp, MobileApp.class).getBody(); + } + + public void deleteMobileApp(MobileAppId mobileAppId) { + restTemplate.delete(baseURL + "/api/mobileApp/{id}", mobileAppId.getId()); + } + + public void updateMobileAppOauth2Clients(MobileAppId mobileAppId, UUID[] oauth2ClientIds) { + restTemplate.postForLocation(baseURL + "/api/mobileApp/{id}/oauth2Clients", oauth2ClientIds, mobileAppId.getId()); + } public String getLoginProcessingUrl() { return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody();