From f274c274e9dbc87bbf545dc466c4d2536c1c85a2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 18 Jul 2024 12:49:48 +0300 Subject: [PATCH 01/29] oauth2 configuration breakdown: initial implementation --- .../main/data/upgrade/3.7.0/schema_update.sql | 83 +++ ...tomOAuth2AuthorizationRequestResolver.java | 6 +- .../server/controller/BaseController.java | 39 +- .../server/controller/DomainController.java | 142 ++++ .../controller/MobileAppController.java | 142 ++++ .../server/controller/OAuth2Controller.java | 73 +- .../edge/DefaultEdgeNotificationService.java | 2 +- .../service/edge/EdgeContextComponent.java | 4 +- .../edge/EdgeEventSourcingListener.java | 8 +- .../service/edge/rpc/EdgeGrpcSession.java | 4 +- .../service/edge/rpc/EdgeSyncCursor.java | 2 +- .../oauth2/OAuth2MsgConstructor.java | 6 +- .../rpc/fetch/OAuth2EdgeEventFetcher.java | 12 +- .../edge/rpc/processor/BaseEdgeProcessor.java | 4 +- .../processor/oauth2/OAuth2EdgeProcessor.java | 14 +- .../domain/DefaultTbDomainService.java | 77 ++ .../entitiy/domain/TbDomainService.java | 15 +- .../mobile/DefaultTbMobileAppService.java | 74 ++ .../entitiy/mobile/TbMobileAppService.java | 14 +- .../DefaultTbOauth2ClientService.java | 63 ++ .../oauth2client/TbOauth2ClientService.java | 31 + .../DefaultSystemDataLoaderService.java | 8 +- .../Oauth2AuthenticationSuccessHandler.java | 11 +- .../service/security/permission/Resource.java | 4 +- .../permission/SysAdminPermissions.java | 4 +- .../system/DefaultSystemInfoService.java | 6 +- .../server/controller/HomePageApiTest.java | 84 +-- .../server/edge/AbstractEdgeTest.java | 14 +- .../server/edge/OAuth2EdgeTest.java | 74 +- .../rpc/processor/BaseEdgeProcessorTest.java | 4 +- .../server/dao/domain/DomainService.java | 44 ++ .../server/dao/mobile/MobileAppService.java | 41 ++ ...2Service.java => OAuth2ClientService.java} | 21 +- .../server/common/data/EntityType.java | 5 +- .../server/common/data/audit/ActionType.java | 3 +- .../server/common/data/domain/Domain.java | 52 ++ .../DomainInfo.java} | 24 +- .../data/domain/DomainOauth2Registration.java | 20 +- .../common/data/edge/EdgeEventType.java | 2 +- .../id/{OAuth2DomainId.java => DomainId.java} | 14 +- .../common/data/id/EntityIdFactory.java | 6 + .../{OAuth2MobileId.java => MobileAppId.java} | 14 +- .../common/data/id/OAuth2RegistrationId.java | 8 +- .../server/common/data/mobile/MobileApp.java | 59 ++ .../MobileAppInfo.java} | 25 +- .../mobile/MobileAppOauth2Registration.java | 32 + .../data/oauth2/HasOauth2Registrations.java | 12 +- .../common/data/oauth2/OAuth2Domain.java | 42 -- .../common/data/oauth2/OAuth2DomainInfo.java | 38 - .../common/data/oauth2/OAuth2Mobile.java | 42 -- .../common/data/oauth2/OAuth2ParamsInfo.java | 46 -- .../data/oauth2/OAuth2Registration.java | 29 +- .../data/oauth2/OAuth2RegistrationInfo.java | 58 +- .../server/dao/domain/DomainDao.java | 39 + .../server/dao/domain/DomainServiceImpl.java | 167 +++++ .../server/dao/mobile/MobileAppDao.java | 36 + .../dao/mobile/MobileAppServiceImpl.java | 163 +++++ .../server/dao/model/ModelConstants.java | 39 +- .../server/dao/model/sql/DomainEntity.java | 81 +++ .../DomainOauth2RegistrationCompositeKey.java | 37 + .../sql/DomainOauth2RegistrationEntity.java | 66 ++ ...MobileEntity.java => MobileAppEntity.java} | 46 +- ...bileAppOauth2RegistrationCompositeKey.java | 37 + .../MobileAppOauth2RegistrationEntity.java | 67 ++ .../dao/model/sql/OAuth2DomainEntity.java | 76 -- .../dao/model/sql/OAuth2ParamsEntity.java | 70 -- .../model/sql/OAuth2RegistrationEntity.java | 16 +- .../sql/OAuth2RegistrationInfoEntity.java | 61 ++ .../HybridClientRegistrationRepository.java | 6 +- .../dao/oauth2/OAuth2ClientServiceImpl.java | 145 ++++ .../dao/oauth2/OAuth2RegistrationDao.java | 14 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 295 -------- .../server/dao/oauth2/OAuth2Utils.java | 99 --- .../validator/DomainDataValidator.java | 36 + .../validator/MobileAppDataValidator.java | 43 ++ .../Oauth2RegistrationDataValidator.java | 114 +++ .../DomainOauth2RegistrationRepository.java | 34 + .../dao/sql/domain/DomainRepository.java | 39 + .../server/dao/sql/domain/JpaDomainDao.java | 83 +++ .../dao/sql/mobile/JpaMobileAppDao.java | 77 ++ ...MobileAppOauth2RegistrationRepository.java | 32 + .../MobileAppRepository.java} | 19 +- .../dao/sql/oauth2/JpaOAuth2DomainDao.java | 54 -- .../dao/sql/oauth2/JpaOAuth2MobileDao.java | 54 -- .../dao/sql/oauth2/JpaOAuth2ParamsDao.java | 49 -- .../sql/oauth2/JpaOAuth2RegistrationDao.java | 31 +- .../oauth2/OAuth2RegistrationRepository.java | 69 +- .../server/dao/tenant/TenantServiceImpl.java | 2 +- .../main/resources/sql/schema-entities.sql | 49 +- .../dao/service/OAuth2ClientServiceTest.java | 641 +++++++++++++++++ .../server/dao/service/OAuth2ServiceTest.java | 668 ------------------ .../thingsboard/rest/client/RestClient.java | 15 +- 92 files changed, 3414 insertions(+), 1916 deletions(-) create mode 100644 application/src/main/data/upgrade/3.7.0/schema_update.sql create mode 100644 application/src/main/java/org/thingsboard/server/controller/DomainController.java create mode 100644 application/src/main/java/org/thingsboard/server/controller/MobileAppController.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java rename dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java => application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java (61%) create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java rename dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java => application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java (60%) create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java rename common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/{OAuth2Service.java => OAuth2ClientService.java} (50%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java rename common/data/src/main/java/org/thingsboard/server/common/data/{oauth2/OAuth2MobileInfo.java => domain/DomainInfo.java} (56%) rename dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java => common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java (59%) rename common/data/src/main/java/org/thingsboard/server/common/data/id/{OAuth2DomainId.java => DomainId.java} (68%) rename common/data/src/main/java/org/thingsboard/server/common/data/id/{OAuth2MobileId.java => MobileAppId.java} (68%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java rename common/data/src/main/java/org/thingsboard/server/common/data/{oauth2/OAuth2Info.java => mobile/MobileAppInfo.java} (58%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java rename dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java => common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java (68%) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{OAuth2MobileEntity.java => MobileAppEntity.java} (56%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java rename dao/src/main/java/org/thingsboard/server/dao/sql/{oauth2/OAuth2ParamsRepository.java => mobile/MobileAppRepository.java} (50%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java diff --git a/application/src/main/data/upgrade/3.7.0/schema_update.sql b/application/src/main/data/upgrade/3.7.0/schema_update.sql new file mode 100644 index 0000000000..caa98e6470 --- /dev/null +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -0,0 +1,83 @@ +-- +-- 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. +-- + +-- OAUTH2 UPDATE START + +ALTER TABLE IF EXISTS oauth2_mobile RENAME TO mobile_app; +ALTER TABLE IF EXISTS oauth2_domain RENAME TO domain; + +ALTER TABLE domain ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, + ADD COLUMN IF NOT EXISTS propagate_to_edge boolean, + ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080', + DROP COLUMN IF EXISTS domain_scheme; +ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, + ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; +ALTER TABLE oauth2_registration ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; +ALTER TABLE oauth2_registration ADD COLUMN IF NOT EXISTS title varchar(100); + +CREATE TABLE IF NOT EXISTS domain_oauth2_registration ( + domain_id uuid NOT NULL, + oauth2_registration_id uuid NOT NULL, + CONSTRAINT fk_domain FOREIGN KEY (domain_id) REFERENCES domain(id) ON DELETE CASCADE, + CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS mobile_app_oauth2_registration ( + mobile_app_id uuid NOT NULL, + oauth2_registration_id uuid NOT NULL, + CONSTRAINT fk_domain FOREIGN KEY (mobile_app_id) REFERENCES mobile_app(id) ON DELETE CASCADE, + CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE +); + +DO +$$ + BEGIN + IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'oauth2_params') THEN + -- delete duplicated domains + DELETE FROM domain d1 USING domain d2 WHERE d1.created_time < d2.created_time AND d1.domain_name = d2.domain_name; + + UPDATE domain SET oauth2_enabled = p.enabled, + propagate_to_edge = p.edge_enabled + FROM oauth2_params p WHERE p.id = domain.oauth2_params_id; + + UPDATE mobile_app SET oauth2_enabled = p.enabled + FROM oauth2_params p WHERE p.id = mobile_app.oauth2_params_id; + + INSERT INTO domain_oauth2_registration(domain_id, oauth2_registration_id) + (SELECT d.id, r.id FROM domain d LEFT JOIN oauth2_registration r on d.oauth2_params_id = r.oauth2_params_id + WHERE r.platforms IS NULL OR r.platforms IN ('','WEB')); + + INSERT INTO mobile_app_oauth2_registration(mobile_app_id, oauth2_registration_id) + (SELECT m.id, r.id FROM mobile_app m LEFT JOIN oauth2_registration r on m.oauth2_params_id = r.oauth2_params_id + WHERE r.platforms IS NULL OR r.platforms IN ('','ANDROID','IOS')); + + ALTER TABLE mobile_app RENAME CONSTRAINT oauth2_mobile_pkey TO mobile_app_pkey; + ALTER TABLE domain RENAME CONSTRAINT oauth2_domain_pkey TO domain_pkey; + UPDATE oauth2_registration SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL; + + ALTER TABLE domain DROP COLUMN oauth2_params_id; + ALTER TABLE mobile_app DROP COLUMN oauth2_params_id; + ALTER TABLE oauth2_registration DROP COLUMN oauth2_params_id; + + ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name); + ALTER TABLE domain ADD CONSTRAINT domain_unq_key UNIQUE (domain_name); + + DROP TABLE IF EXISTS oauth2_params; + END IF; + END +$$; + +-- OAUTH2 UPDATE END \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index 1784c6fa1b..2dd5027a2a 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -38,7 +38,7 @@ import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames; import org.thingsboard.server.service.security.model.token.OAuth2AppTokenFactory; @@ -70,7 +70,7 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza private ClientRegistrationRepository clientRegistrationRepository; @Autowired - private OAuth2Service oAuth2Service; + private OAuth2ClientService oAuth2ClientService; @Autowired private OAuth2AppTokenFactory oAuth2AppTokenFactory; @@ -131,7 +131,7 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza if (StringUtils.isEmpty(appToken)) { throw new IllegalArgumentException("Invalid application token."); } else { - String appSecret = this.oAuth2Service.findAppSecret(UUID.fromString(registrationId), appPackage); + String appSecret = this.oAuth2ClientService.findAppSecret(UUID.fromString(registrationId), appPackage); if (StringUtils.isEmpty(appSecret)) { throw new IllegalArgumentException("Invalid package: " + appPackage + ". No application secret found for Client Registration with given application package."); } 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 0942329f40..1dbee4abce 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -62,6 +62,7 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -74,11 +75,14 @@ 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.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RpcId; @@ -91,6 +95,8 @@ import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -117,13 +123,15 @@ import org.thingsboard.server.dao.device.ClaimDevicesService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.mobile.MobileAppService; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -233,7 +241,13 @@ public abstract class BaseController { protected DashboardService dashboardService; @Autowired - protected OAuth2Service oAuth2Service; + protected OAuth2ClientService oAuth2ClientService; + + @Autowired + protected DomainService domainService; + + @Autowired + protected MobileAppService mobileAppService; @Autowired protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; @@ -600,6 +614,15 @@ public abstract class BaseController { case QUEUE: checkQueueId(new QueueId(entityId.getId()), operation); return; + case OAUTH2_CLIENT: + checkOauth2ClientId(new OAuth2RegistrationId(entityId.getId()), operation); + return; + case DOMAIN: + checkDomainId(new DomainId(entityId.getId()), operation); + return; + case MOBILE_APP: + checkMobileAppId(new MobileAppId(entityId.getId()), operation); + return; default: checkEntityId(entityId, entitiesService::findEntityByTenantIdAndId, operation); } @@ -776,6 +799,18 @@ public abstract class BaseController { return queue; } + OAuth2Registration checkOauth2ClientId(OAuth2RegistrationId oAuth2RegistrationId, Operation operation) throws ThingsboardException { + return checkEntityId(oAuth2RegistrationId, oAuth2ClientService::findOAuth2ClientById, operation); + } + + Domain checkDomainId(DomainId domainId, Operation operation) throws ThingsboardException { + return checkEntityId(domainId, domainService::findDomainById, operation); + } + + MobileApp checkMobileAppId(MobileAppId mobileAppId, Operation operation) throws ThingsboardException { + return checkEntityId(mobileAppId, mobileAppService::findMobileAppById, operation); + } + protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } diff --git a/application/src/main/java/org/thingsboard/server/controller/DomainController.java b/application/src/main/java/org/thingsboard/server/controller/DomainController.java new file mode 100644 index 0000000000..c17fbfd709 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/DomainController.java @@ -0,0 +1,142 @@ +/** + * 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 io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.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; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.config.annotations.ApiOperation; +import org.thingsboard.server.queue.util.TbCoreComponent; +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; + +import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class DomainController extends BaseController { + + private final TbDomainService tbDomainService; + + @ApiOperation(value = "Save or Update Domain (saveDomain)", + notes = "Create or update the Domain. When creating domain, platform generates Domain Id as " + UUID_WIKI_LINK + + "The newly created Domain Id will be present in the response. " + + "Specify existing Domain Id to update the domain. " + + "Referencing non-existing Domain Id will cause 'Not Found' error." + + "\n\nDomain name is unique for entire platform setup.\n\n") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @PostMapping(value = "/domain") + public Domain saveDomain( + @Parameter(description = "A JSON value representing the Domain.", required = true) + @RequestBody 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(); + domain.setTenantId(getCurrentUser().getTenantId()); + checkEntity(domain.getId(), domain, Resource.DOMAIN); + List oAuth2ClientIds = new ArrayList<>(); + for (String id : oauth2ClientIds) { + OAuth2RegistrationId oauth2ClientId = new OAuth2RegistrationId(toUUID(id)); + checkOauth2ClientId(oauth2ClientId, Operation.READ); + oAuth2ClientIds.add(oauth2ClientId); + } + return tbDomainService.save(domain, oAuth2ClientIds, getCurrentUser()); + } + + @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", + notes = "Update oauth2 clients for the specified domain. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @PostMapping(value = "/domain/{id}/oauth2Clients") + public void updateOauth2Clients(@PathVariable UUID id, + @RequestBody UUID[] oauth2ClientIds) throws ThingsboardException { + DomainId domainId = new DomainId(id); + Domain domain = null; + try { + domain = checkDomainId(domainId, Operation.WRITE); + List oAuth2ClientIds = new ArrayList<>(); + for (UUID outh2CLientId : oauth2ClientIds) { + OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(outh2CLientId); + checkEntityId(oAuth2RegistrationId, Operation.READ); + oAuth2ClientIds.add(oAuth2RegistrationId); + } + domainService.updateOauth2Clients(getTenantId(), domainId, oAuth2ClientIds); + logEntityActionService.logEntityAction(domain.getTenantId(), domain.getId(), domain, + UPDATED_OAUTH2_CLIENTS, getCurrentUser(), oAuth2ClientIds.toString()); + } catch (Exception e) { + if (domain != null) { + logEntityActionService.logEntityAction(getTenantId(), domainId, domain, + ActionType.UPDATED_OAUTH2_CLIENTS, getCurrentUser(), e); + } + throw e; + } + } + + @ApiOperation(value = "Get Domain infos (getDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/domain/infos") + public List getDomainInfos() throws ThingsboardException { + return domainService.findDomainInfosByTenantId(getTenantId()); + } + + @ApiOperation(value = "Get Domain info by Id (getDomainInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/domain/info/{id}") + public DomainInfo getDomainInfoById(@PathVariable UUID id) throws ThingsboardException { + DomainId domainId = new DomainId(id); + return checkEntityId(domainId, domainService::findDomainInfoById, Operation.READ); + } + + @ApiOperation(value = "Delete Domain by ID (deleteDomain)", + notes = "Deletes Domain by ID. Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @DeleteMapping(value = "/domain/{id}") + public void deleteDomain(@PathVariable UUID id) throws Exception { + DomainId domainId = new DomainId(id); + checkDomainId(domainId, Operation.DELETE); + domainService.deleteDomainById(getTenantId(), domainId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java new file mode 100644 index 0000000000..f245ecfdd1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -0,0 +1,142 @@ +/** + * 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 io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.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.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +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.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 java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; + +@RestController +@TbCoreComponent +@RequestMapping("/api") +@RequiredArgsConstructor +@Slf4j +public class MobileAppController extends BaseController { + + private final TbMobileAppService tbMobileAppService; + + @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. " + + "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 list of entity group ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) + @RequestParam(name = "oauth2RegistrationIds", required = false) UUID[] oauth2RegistrationIds) throws Exception { + mobileApp.setTenantId(getCurrentUser().getTenantId()); + + List oAuth2Registrations = new ArrayList<>(); + for (UUID id : oauth2RegistrationIds) { + OAuth2RegistrationId oauth2ClientId = new OAuth2RegistrationId(id); + checkOauth2ClientId(oauth2ClientId, Operation.READ); + oAuth2Registrations.add(oauth2ClientId); + } + return tbMobileAppService.save(mobileApp, oAuth2Registrations, getCurrentUser()); + } + + @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", + notes = "Update oauth2 clients to the specified mobile app. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @PostMapping(value = "/mobileApp/{id}/updateOauth2Clients") + public void updateOauth2Clients(@PathVariable UUID id, + @RequestBody UUID[] oauth2ClientIds) throws ThingsboardException { + MobileAppId mobileAppId = new MobileAppId(id); + MobileApp mobileApp = null; + try { + mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); + List oAuth2ClientIds = new ArrayList<>(); + for (UUID outh2CLientId : oauth2ClientIds) { + OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(outh2CLientId); + checkEntityId(oAuth2RegistrationId, Operation.READ); + oAuth2ClientIds.add(oAuth2RegistrationId); + } + mobileAppService.updateOauth2Clients(getTenantId(), mobileAppId, oAuth2ClientIds); + logEntityActionService.logEntityAction(getTenantId(), mobileAppId, mobileApp, + UPDATED_OAUTH2_CLIENTS, getCurrentUser(), oAuth2ClientIds.toString()); + } catch (Exception e) { + if (mobileApp != null) { + logEntityActionService.logEntityAction(getTenantId(), mobileAppId, mobileApp, + ActionType.UPDATED_OAUTH2_CLIENTS, getCurrentUser(), e); + } + throw e; + } + } + + @ApiOperation(value = "Get mobile app infos (getMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/mobileApp/infos") + public List getMobileAppInfos() throws ThingsboardException { + TenantId tenantId = getCurrentUser().getTenantId(); + return mobileAppService.findMobileAppInfosByTenantId(tenantId); + } + + @ApiOperation(value = "Get mobile info by id (getMobileAppInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/mobileApp/info/{id}") + public MobileAppInfo getMobileAppInfoById(@PathVariable UUID id) throws ThingsboardException { + MobileAppId mobileAppId = new MobileAppId(id); + return checkEntityId(mobileAppId, mobileAppService::findMobileAppInfoById, Operation.READ); + } + + @ApiOperation(value = "Delete Mobile App by ID (deleteMobileApp)", + notes = "Deletes Mobile App by ID. Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @DeleteMapping(value = "/mobileApp/{id}") + public void deleteMobileApp(@PathVariable UUID id) throws Exception { + MobileAppId mobileAppId = new MobileAppId(id); + checkMobileAppId(mobileAppId, Operation.DELETE); + mobileAppService.deleteMobileAppById(getTenantId(), mobileAppId); + } + +} 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 325cc3b025..1b64e7eb60 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -18,49 +18,59 @@ 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 lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; +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.ResponseBody; 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.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.oauth2client.TbOauth2ClientService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; import java.util.Enumeration; import java.util.List; +import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; @RestController @TbCoreComponent @RequestMapping("/api") +@RequiredArgsConstructor @Slf4j public class OAuth2Controller extends BaseController { - @Autowired - private OAuth2Configuration oAuth2Configuration; + private final OAuth2Configuration oAuth2Configuration; + + private final TbOauth2ClientService tbOauth2ClientService; @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)") - @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) - @ResponseBody + @PostMapping(value = "/noauth/oauth2Clients") 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") @@ -85,26 +95,47 @@ public class OAuth2Controller extends BaseController { } catch (Exception e) { } } - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType); + if (StringUtils.isNotEmpty(pkgName)) { + return oAuth2ClientService.getMobileOAuth2Clients(pkgName, platformType); + } else { + return oAuth2ClientService.getWebOAuth2Clients(MiscUtils.getDomainNameAndPort(request), platformType); + } } - @ApiOperation(value = "Get current OAuth2 settings (getCurrentOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Save OAuth2 Client Registration (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") - @ResponseBody - public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); - return oAuth2Service.findOAuth2Info(); + @PostMapping(value = "/oauth2/client") + public OAuth2Registration saveOAuth2Client(@RequestBody OAuth2Registration oAuth2Registration) throws Exception { + TenantId tenantId = getTenantId(); + oAuth2Registration.setTenantId(tenantId); + checkEntity(oAuth2Registration.getId(), oAuth2Registration, Resource.OAUTH2_CLIENT); + return tbOauth2ClientService.save(oAuth2Registration, getCurrentUser()); } - @ApiOperation(value = "Save OAuth2 settings (saveOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Get OAuth2 Client Registration infos (findTenantOAuth2ClientInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) + @GetMapping(value = "/oauth2/client/infos") + public List findTenantOAuth2ClientInfos() throws ThingsboardException { + return oAuth2ClientService.findOauth2ClientInfosByTenantId(getTenantId()); + } + + @ApiOperation(value = "Get OAuth2 Client Registration by id (getOAuth2ClientById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/oauth2/client/{id}") + public OAuth2Registration getOAuth2ClientById(@PathVariable UUID id) throws ThingsboardException { + OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(id); + return checkEntityId(oAuth2RegistrationId, oAuth2ClientService::findOAuth2ClientById, Operation.READ); + } + + @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) - public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); - oAuth2Service.saveOAuth2Info(oauth2Info); - return oAuth2Service.findOAuth2Info(); + public void deleteOauth2Client(@PathVariable UUID id) throws Exception { + OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(id); + OAuth2Registration oAuth2Registration = checkOauth2ClientId(oAuth2RegistrationId, Operation.DELETE); + tbOauth2ClientService.delete(oAuth2Registration, getCurrentUser()); } @ApiOperation(value = "Get OAuth2 log in processing URL (getLoginProcessingUrl)", notes = "Returns the URL enclosed in " + @@ -113,9 +144,7 @@ public class OAuth2Controller extends BaseController { "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) - @ResponseBody - public String getLoginProcessingUrl() throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); + public String getLoginProcessingUrl() { return "\"" + oAuth2Configuration.getLoginProcessingUrl() + "\""; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index f29255e414..d7b6db7f21 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -201,7 +201,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE -> notificationEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); case TB_RESOURCE -> resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); - case OAUTH2 -> oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg); + case OAUTH2_CLIENT -> oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg); default -> log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type); } } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 489fc6b6b8..8e609ed545 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -35,7 +35,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.resource.ResourceService; @@ -167,7 +167,7 @@ public class EdgeContextComponent { private NotificationTemplateService notificationTemplateService; @Autowired - private OAuth2Service oAuth2Service; + private OAuth2ClientService oAuth2ClientService; @Autowired private RateLimitService rateLimitService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 651339e3a4..2a11c7df29 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; @@ -227,8 +227,8 @@ public class EdgeEventSourcingListener { private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) { if (entity instanceof AlarmComment) { return EdgeEventType.ALARM_COMMENT; - } else if (entity instanceof OAuth2Info) { - return EdgeEventType.OAUTH2; + } else if (entity instanceof OAuth2Registration) { + return EdgeEventType.OAUTH2_CLIENT; } return null; } @@ -236,7 +236,7 @@ public class EdgeEventSourcingListener { private String getBodyMsgForEntityEvent(Object entity) { if (entity instanceof AlarmComment) { return JacksonUtil.toString(entity); - } else if (entity instanceof OAuth2Info) { + } else if (entity instanceof OAuth2Registration) { return JacksonUtil.toString(entity); } return null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 55e8e051ab..68a08920d9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -689,8 +689,8 @@ public final class EdgeGrpcSession implements Closeable { return ctx.getNotificationEdgeProcessor().convertNotificationTargetToDownlink(edgeEvent); case NOTIFICATION_TEMPLATE: return ctx.getNotificationEdgeProcessor().convertNotificationTemplateToDownlink(edgeEvent); - case OAUTH2: - return ctx.getOAuth2EdgeProcessor().convertOAuth2EventToDownlink(edgeEvent); + case OAUTH2_CLIENT: + return ctx.getOAuth2EdgeProcessor().convertOAuth2ProviderEventToDownlink(edgeEvent); default: log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent); return null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index 6321991586..436fa37321 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -86,7 +86,7 @@ public class EdgeSyncCursor { fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService())); - fetchers.add(new OAuth2EdgeEventFetcher(ctx.getOAuth2Service())); + fetchers.add(new OAuth2EdgeEventFetcher(ctx.getOAuth2ClientService())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java index c628b042a2..8c734da83c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.edge.rpc.constructor.oauth2; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -25,8 +25,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class OAuth2MsgConstructor { - public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Info oAuth2Info) { - return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Info)).build(); + public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Registration oAuth2Registration) { + return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Registration)).build(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java index 4412f91183..e491f8bf2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java @@ -24,10 +24,10 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import java.util.ArrayList; import java.util.List; @@ -36,7 +36,7 @@ import java.util.List; @Slf4j public class OAuth2EdgeEventFetcher implements EdgeEventFetcher { - private final OAuth2Service oAuth2Service; + private final OAuth2ClientService oAuth2ClientService; @Override public PageLink getPageLink(int pageSize) { @@ -46,9 +46,9 @@ public class OAuth2EdgeEventFetcher implements EdgeEventFetcher { @Override public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { List result = new ArrayList<>(); - OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info(); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2, - EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info))); + List oauth2Registrations = oAuth2ClientService.findOauth2ClientsByTenantId(TenantId.SYS_TENANT_ID); + result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2_CLIENT, + EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oauth2Registrations))); // returns PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index f46c07ab5a..28a3628f7f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -76,7 +76,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -240,7 +240,7 @@ public abstract class BaseEdgeProcessor { protected NotificationTemplateService notificationTemplateService; @Autowired - protected OAuth2Service oAuth2Service; + protected OAuth2ClientService oAuth2ClientService; @Autowired @Lazy diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java index ce0600b491..a6bcb663a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; @@ -37,21 +37,21 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class OAuth2EdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg convertOAuth2EventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertOAuth2ProviderEventToDownlink(EdgeEvent edgeEvent) { DownlinkMsg downlinkMsg = null; - OAuth2Info oAuth2Info = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Info.class); - if (oAuth2Info != null) { - OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Info); + OAuth2Registration oAuth2Registration = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Registration.class); + if (oAuth2Registration != null) { + OAuth2UpdateMsg oAuth2ProviderUpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Registration); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addOAuth2UpdateMsg(oAuth2UpdateMsg) + .addOAuth2UpdateMsg(oAuth2ProviderUpdateMsg) .build(); } return downlinkMsg; } public ListenableFuture processOAuth2Notification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - OAuth2Info oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Info.class); + OAuth2Registration oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Registration.class); if (oAuth2Info == null) { return Futures.immediateFuture(null); } 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 new file mode 100644 index 0000000000..882a444aed --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java @@ -0,0 +1,77 @@ +/** + * 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.service.entitiy.domain; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.domain.DomainService; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; + +import java.util.List; + +import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; + +@Service +@AllArgsConstructor +public class DefaultTbDomainService extends AbstractTbEntityService implements TbDomainService { + + private final DomainService domainService; + + @Override + public Domain save(Domain domain, List oAuth2Clients, User user) throws Exception { + ActionType actionType = domain.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + 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(domain.getTenantId(), savedDomain.getId(), savedDomain, + UPDATED_OAUTH2_CLIENTS, user, oAuth2Clients.toString()); + } + return savedDomain; + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), domain, actionType, user, e); + throw e; + } + } + + @Override + @Transactional + public void delete(Domain domain, User user) { + ActionType actionType = ActionType.DELETED; + TenantId tenantId = domain.getTenantId(); + DomainId domainId = domain.getId(); + try { + domainService.deleteDomainById(tenantId, domainId); + logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, domain.getName()); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), actionType, user, e, + domainId.toString()); + throw e; + } + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java similarity index 61% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java index eec1bc5f50..4170558e52 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java @@ -13,17 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.service.entitiy.domain; -import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sql.OAuth2DomainEntity; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; import java.util.List; -import java.util.UUID; -public interface OAuth2DomainRepository extends JpaRepository { +public interface TbDomainService { - List findByOauth2ParamsId(UUID oauth2ParamsId); + Domain save(Domain domain, List oAuth2Clients, User user) throws Exception; -} + 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 new file mode 100644 index 0000000000..6865a837c0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java @@ -0,0 +1,74 @@ +/** + * 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.service.entitiy.mobile; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.dao.mobile.MobileAppService; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; + +import java.util.List; + +@Service +@AllArgsConstructor +public class DefaultTbMobileAppService extends AbstractTbEntityService implements TbMobileAppService { + + private final MobileAppService mobileAppService; + + @Override + public MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception { + ActionType actionType = mobileApp.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + 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(), savedMobileApp, + ActionType.UPDATED_OAUTH2_CLIENTS, user, oauth2Clients.toString()); + } + return savedMobileApp; + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), mobileApp, actionType, user, e); + throw e; + } + } + + @Override + @Transactional + public void delete(MobileApp mobileApp, User user) { + ActionType actionType = ActionType.DELETED; + TenantId tenantId = mobileApp.getTenantId(); + MobileAppId mobileAppId = mobileApp.getId(); + try { + mobileAppService.deleteMobileAppById(tenantId, mobileAppId); + logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, mobileApp.getPkgName()); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), actionType, user, e, + mobileAppId.toString()); + throw e; + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java similarity index 60% rename from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java index ab1b981dc2..ebcdfc3cd0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.oauth2; +package org.thingsboard.server.service.entitiy.mobile; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; -import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.mobile.MobileApp; import java.util.List; -import java.util.UUID; -public interface OAuth2MobileDao extends Dao { +public interface TbMobileAppService { - List findByOAuth2ParamsId(UUID oauth2ParamsId); + MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception; + + 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 new file mode 100644 index 0000000000..e24f173c6a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java @@ -0,0 +1,63 @@ +/** + * 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.service.entitiy.oauth2client; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; + +@Service +@AllArgsConstructor +public class DefaultTbOauth2ClientService extends AbstractTbEntityService implements TbOauth2ClientService { + + private final OAuth2ClientService oAuth2ClientService; + + @Override + public OAuth2Registration save(OAuth2Registration oAuth2Registration, User user) throws Exception { + ActionType actionType = oAuth2Registration.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = oAuth2Registration.getTenantId(); + try { + OAuth2Registration savedRegistration = checkNotNull(oAuth2ClientService.saveOAuth2Client(tenantId, oAuth2Registration)); + logEntityActionService.logEntityAction(tenantId, savedRegistration.getId(), oAuth2Registration, actionType, user); + return savedRegistration; + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), oAuth2Registration, actionType, user, e); + throw e; + } + } + + @Override + public void delete(OAuth2Registration oAuth2Registration, User user) { + ActionType actionType = ActionType.DELETED; + TenantId tenantId = oAuth2Registration.getTenantId(); + OAuth2RegistrationId oAuth2RegistrationId = oAuth2Registration.getId(); + try { + oAuth2ClientService.deleteById(tenantId, oAuth2RegistrationId); + logEntityActionService.logEntityAction(tenantId, oAuth2RegistrationId, oAuth2Registration, actionType, user, oAuth2Registration.getName()); + } catch (Exception e) { + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), actionType, user, e, + oAuth2RegistrationId.toString()); + throw e; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java new file mode 100644 index 0000000000..de6d45a6c0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java @@ -0,0 +1,31 @@ +/** + * 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.service.entitiy.oauth2client; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; + +import java.util.List; + +public interface TbOauth2ClientService { + + OAuth2Registration save(OAuth2Registration oAuth2Registration, User user) throws Exception; + + void delete(OAuth2Registration oAuth2Registration, User user); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 7588726a73..3168e22f6f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -69,7 +69,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; +import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; @@ -100,7 +100,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.notification.NotificationSettingsService; import org.thingsboard.server.dao.notification.NotificationTargetService; -import org.thingsboard.server.dao.oauth2.OAuth2MobileDao; +import org.thingsboard.server.dao.mobile.MobileAppDao; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; @@ -149,7 +149,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { private final DeviceConnectivityConfiguration connectivityConfiguration; private final QueueService queueService; private final JwtSettingsService jwtSettingsService; - private final OAuth2MobileDao oAuth2MobileDao; + private final MobileAppDao oAuth2MobileDao; private final NotificationSettingsService notificationSettingsService; private final NotificationTargetService notificationTargetService; @@ -308,7 +308,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { jwtSettingsService.saveJwtSettings(jwtSettings); } - List mobiles = oAuth2MobileDao.find(TenantId.SYS_TENANT_ID); + List mobiles = oAuth2MobileDao.findByTenantId(TenantId.SYS_TENANT_ID); if (CollectionUtils.isNotEmpty(mobiles)) { mobiles.stream() .filter(config -> !validateKeyLength(config.getAppSecret())) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 09ebb75e29..b6e0d39ec6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -28,10 +28,11 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.security.model.JwtPair; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.SecurityUser; @@ -56,7 +57,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final JwtTokenFactory tokenFactory; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; - private final OAuth2Service oAuth2Service; + private final OAuth2ClientService oAuth2ClientService; private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; private final SystemSecurityService systemSecurityService; @@ -64,13 +65,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Service oAuth2Service, + final OAuth2ClientService oAuth2ClientService, final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository, final SystemSecurityService systemSecurityService) { this.tokenFactory = tokenFactory; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; - this.oAuth2Service = oAuth2Service; + this.oAuth2ClientService = oAuth2ClientService; this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService; this.httpCookieOAuth2AuthorizationRequestRepository = httpCookieOAuth2AuthorizationRequestRepository; this.systemSecurityService = systemSecurityService; @@ -96,7 +97,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); + OAuth2Registration registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2RegistrationId(UUID.fromString(token.getAuthorizedClientRegistrationId()))); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index d43663fb97..16a3c4be59 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -33,7 +33,9 @@ public enum Resource { USER(EntityType.USER), WIDGETS_BUNDLE(EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(EntityType.WIDGET_TYPE), - OAUTH2_CONFIGURATION_INFO(), + OAUTH2_CLIENT(EntityType.OAUTH2_CLIENT), + DOMAIN(EntityType.DOMAIN), + MOBILE_APP(EntityType.MOBILE_APP), OAUTH2_CONFIGURATION_TEMPLATE(), TENANT_PROFILE(EntityType.TENANT_PROFILE), DEVICE_PROFILE(EntityType.DEVICE_PROFILE), diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java index d906938e47..4790a07965 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java @@ -35,7 +35,9 @@ public class SysAdminPermissions extends AbstractPermissions { put(Resource.USER, userPermissionChecker); put(Resource.WIDGETS_BUNDLE, systemEntityPermissionChecker); put(Resource.WIDGET_TYPE, systemEntityPermissionChecker); - put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker); + put(Resource.OAUTH2_CLIENT, PermissionChecker.allowAllPermissionChecker); + put(Resource.MOBILE_APP, PermissionChecker.allowAllPermissionChecker); + put(Resource.DOMAIN, PermissionChecker.allowAllPermissionChecker); put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker); put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker); put(Resource.TB_RESOURCE, systemEntityPermissionChecker); diff --git a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java index cd95e9504d..ab61140f30 100644 --- a/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java @@ -38,7 +38,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.stats.TbApiUsageStateClient; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; import org.thingsboard.server.queue.discovery.DiscoveryService; @@ -89,7 +89,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener oAuth2UpdateMsgOpt = edgeImitator.findMessageByType(OAuth2UpdateMsg.class); Assert.assertTrue(oAuth2UpdateMsgOpt.isPresent()); - OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2UpdateMsgOpt.get(); - OAuth2Info oAuth2Info = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true); - Assert.assertNotNull(oAuth2Info); - OAuth2Info auth2Info = doGet("/api/oauth2/config", OAuth2Info.class); + OAuth2UpdateMsg oAuth2ProviderUpdateMsg = oAuth2UpdateMsgOpt.get(); + OAuth2Registration oAuth2Registration = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); + Assert.assertNotNull(oAuth2Registration); + OAuth2Registration auth2Info = doGet("/api/oauth2/config", OAuth2Registration.class); Assert.assertNotNull(auth2Info); - Assert.assertEquals(oAuth2Info, auth2Info); - testAutoGeneratedCodeByProtobuf(oAuth2UpdateMsg); + Assert.assertEquals(oAuth2Registration, auth2Info); + testAutoGeneratedCodeByProtobuf(oAuth2ProviderUpdateMsg); } private void validateSyncCompleted() { diff --git a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java index c25410b562..f3e5e30457 100644 --- a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java @@ -15,19 +15,14 @@ */ package org.thingsboard.server.edge; -import com.google.common.collect.Lists; import com.google.protobuf.AbstractMessage; import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; -import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; @@ -45,59 +40,47 @@ public class OAuth2EdgeTest extends AbstractEdgeTest { // enable oauth edgeImitator.allowIgnoredTypes(); edgeImitator.expectMessageAmount(1); - OAuth2Info oAuth2Info = createDefaultOAuth2Info(); - oAuth2Info = doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class); + OAuth2Registration oAuth2Registration = createDefaultOAuth2Info(); + oAuth2Registration = doPost("/api/oauth2/config", oAuth2Registration, OAuth2Registration.class); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg); - OAuth2UpdateMsg oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage; - OAuth2Info result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true); - Assert.assertEquals(oAuth2Info, result); + OAuth2UpdateMsg oAuth2ProviderUpdateMsg = (OAuth2UpdateMsg) latestMessage; + OAuth2Registration result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); + Assert.assertEquals(oAuth2Registration, result); // disable oauth support edgeImitator.expectMessageAmount(1); - oAuth2Info.setEnabled(false); - oAuth2Info.setEdgeEnabled(false); - doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class); + doPost("/api/oauth2/config", oAuth2Registration, OAuth2Registration.class); Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg); - oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage; - result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true); - Assert.assertEquals(oAuth2Info, result); + oAuth2ProviderUpdateMsg = (OAuth2UpdateMsg) latestMessage; + result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); + Assert.assertEquals(oAuth2Registration, result); edgeImitator.ignoreType(OAuth2UpdateMsg.class); loginTenantAdmin(); } - private OAuth2Info createDefaultOAuth2Info() { - return new OAuth2Info(true, true, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo() - )) - .build() - )); + private OAuth2Registration createDefaultOAuth2Info() { + return validRegistrationInfo(); } - private OAuth2RegistrationInfo validRegistrationInfo() { - return OAuth2RegistrationInfo.builder() - .clientId(UUID.randomUUID().toString()) - .clientSecret(UUID.randomUUID().toString()) - .authorizationUri(UUID.randomUUID().toString()) - .accessTokenUri(UUID.randomUUID().toString()) - .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) - .platforms(Collections.emptyList()) - .userInfoUri(UUID.randomUUID().toString()) - .userNameAttributeName(UUID.randomUUID().toString()) - .jwkSetUri(UUID.randomUUID().toString()) - .clientAuthenticationMethod(UUID.randomUUID().toString()) - .loginButtonLabel(UUID.randomUUID().toString()) - .mapperConfig( + private OAuth2Registration validRegistrationInfo() { + OAuth2Registration oAuth2Registration = new OAuth2Registration(); + oAuth2Registration.setClientId(UUID.randomUUID().toString()); + oAuth2Registration.setClientSecret(UUID.randomUUID().toString()); + oAuth2Registration.setAuthorizationUri(UUID.randomUUID().toString()); + oAuth2Registration.setAccessTokenUri(UUID.randomUUID().toString()); + oAuth2Registration.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); + oAuth2Registration.setPlatforms(Collections.emptyList()); + oAuth2Registration.setUserInfoUri(UUID.randomUUID().toString()); + oAuth2Registration.setUserNameAttributeName(UUID.randomUUID().toString()); + oAuth2Registration.setJwkSetUri(UUID.randomUUID().toString()); + oAuth2Registration.setClientAuthenticationMethod(UUID.randomUUID().toString()); + oAuth2Registration.setLoginButtonLabel(UUID.randomUUID().toString()); + oAuth2Registration.setMapperConfig( OAuth2MapperConfig.builder() .type(MapperType.CUSTOM) .custom( @@ -105,9 +88,8 @@ public class OAuth2EdgeTest extends AbstractEdgeTest { .url(UUID.randomUUID().toString()) .build() ) - .build() - ) - .build(); + .build()); + return oAuth2Registration; } } diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java index 9e4fd65ab4..ac21141b75 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java @@ -49,7 +49,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.notification.NotificationRuleService; import org.thingsboard.server.dao.notification.NotificationTargetService; import org.thingsboard.server.dao.notification.NotificationTemplateService; -import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; @@ -257,7 +257,7 @@ public abstract class BaseEdgeProcessorTest { protected ResourceService resourceService; @MockBean - protected OAuth2Service oAuth2Service; + protected OAuth2ClientService oAuth2ClientService; @MockBean @Lazy diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java new file mode 100644 index 0000000000..84ab33fbce --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java @@ -0,0 +1,44 @@ +/** + * 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.domain; + +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainInfo; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.entity.EntityDaoService; + +import java.util.List; +import java.util.UUID; + +public interface DomainService extends EntityDaoService { + + Domain saveDomain(TenantId tenantId, Domain domain); + + void deleteDomainById(TenantId tenantId, DomainId domainId); + + Domain findDomainById(TenantId tenantId, DomainId domainId); + + List findDomainInfosByTenantId(TenantId tenantId); + + DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId); + + boolean isOauth2Enabled(TenantId tenantId); + + void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java new file mode 100644 index 0000000000..da26f2f41a --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java @@ -0,0 +1,41 @@ +/** + * 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.mobile; + +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +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.dao.entity.EntityDaoService; + +import java.util.List; + +public interface MobileAppService extends EntityDaoService { + + MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp); + + void deleteMobileAppById(TenantId tenantId, MobileAppId mobileAppId); + + MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId); + + List findMobileAppInfosByTenantId(TenantId tenantId); + + MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId); + + void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds); + +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java similarity index 50% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java index 11129002e7..dd9921d5d1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java @@ -15,25 +15,32 @@ */ package org.thingsboard.server.dao.oauth2; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; import java.util.UUID; -public interface OAuth2Service { +public interface OAuth2ClientService extends EntityDaoService { - List getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType); + List getWebOAuth2Clients(String domainName, PlatformType platformType); - void saveOAuth2Info(OAuth2Info oauth2Info); + List getMobileOAuth2Clients(String pkgName, PlatformType platformType); - OAuth2Info findOAuth2Info(); + List findOauth2ClientInfosByTenantId(TenantId tenantId); - OAuth2Registration findRegistration(UUID id); + List findOauth2ClientsByTenantId(TenantId tenantId); - List findAllRegistrations(); + OAuth2Registration saveOAuth2Client(TenantId tenantId, OAuth2Registration oAuth2Registration); + + OAuth2Registration findOAuth2ClientById(TenantId tenantId, OAuth2RegistrationId providerId); String findAppSecret(UUID registrationId, String pkgName); + + void deleteById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 6748aebb50..b5ce79e20f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -57,7 +57,10 @@ public enum EntityType { NOTIFICATION_REQUEST(31), NOTIFICATION(32), NOTIFICATION_RULE(33), - QUEUE_STATS(34); + QUEUE_STATS(34), + OAUTH2_CLIENT(35), + DOMAIN(36), + MOBILE_APP(37); @Getter private final int protoNumber; // Corresponds to EntityTypeProto diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index d3c0889d6b..edcc216d1e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -57,7 +57,8 @@ public enum ActionType { ADDED_COMMENT(false, TbMsgType.COMMENT_CREATED), UPDATED_COMMENT(false, TbMsgType.COMMENT_UPDATED), DELETED_COMMENT(false, null), - SMS_SENT(false, null); + SMS_SENT(false, null), + UPDATED_OAUTH2_CLIENTS(false, null); @Getter private final boolean isRead; 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 new file mode 100644 index 0000000000..ed154e656f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java @@ -0,0 +1,52 @@ +/** + * 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.common.data.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.TenantId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +@NoArgsConstructor +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) + private String name; + @Schema(description = "Whether OAuth2 settings are enabled or not") + private boolean oauth2Enabled; + @Schema(description = "Whether OAuth2 settings are enabled on Edge or not") + private boolean propagateToEdge; + + public Domain(Domain domain) { + super(domain); + this.tenantId = domain.tenantId; + this.name = domain.name; + this.oauth2Enabled = domain.oauth2Enabled; + this.propagateToEdge = domain.propagateToEdge; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java similarity index 56% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java index 053762bdf0..edca39a828 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java @@ -13,26 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.domain; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import lombok.ToString; +import org.thingsboard.server.common.data.oauth2.HasOauth2Registrations; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; -@EqualsAndHashCode +import java.util.List; + +@EqualsAndHashCode(callSuper = true) @Data @ToString -@NoArgsConstructor @AllArgsConstructor @Builder @Schema -public class OAuth2MobileInfo { - @Schema(description = "Application package name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String pkgName; - @Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED) - private String appSecret; +public class DomainInfo extends Domain implements HasOauth2Registrations { + + @Schema(description = "List of available oauth2 client registration") + private List oauth2RegistrationInfos; + + public DomainInfo(Domain domain, List oauth2RegistrationInfos) { + super(domain); + this.oauth2RegistrationInfos = oauth2RegistrationInfos; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java similarity index 59% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java rename to common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java index 2456494a74..13b3b15093 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java @@ -13,16 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.common.data.domain; -import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sql.OAuth2MobileEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; -import java.util.List; -import java.util.UUID; +@Data +@NoArgsConstructor +@AllArgsConstructor +public class DomainOauth2Registration { -public interface OAuth2MobileRepository extends JpaRepository { - - List findByOauth2ParamsId(UUID oauth2ParamsId); + private DomainId domainId; + private OAuth2RegistrationId oAuth2RegistrationId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java index 44a3ed8efe..141966c446 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java @@ -45,7 +45,7 @@ public enum EdgeEventType { NOTIFICATION_TARGET (true, EntityType.NOTIFICATION_TARGET), NOTIFICATION_TEMPLATE (true, EntityType.NOTIFICATION_TEMPLATE), TB_RESOURCE(true, EntityType.TB_RESOURCE), - OAUTH2(true, null); + OAUTH2_CLIENT(true, EntityType.OAUTH2_CLIENT); private final boolean allEdgesRelated; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java similarity index 68% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java index 14899d9dc1..c3d4aab159 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java @@ -17,17 +17,23 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2DomainId extends UUIDBased { +public class DomainId extends UUIDBased implements EntityId { @JsonCreator - public OAuth2DomainId(@JsonProperty("id") UUID id) { + public DomainId(@JsonProperty("id") UUID id) { super(id); } - public static OAuth2DomainId fromString(String oauth2DomainId) { - return new OAuth2DomainId(UUID.fromString(oauth2DomainId)); + public static DomainId fromString(String oauth2DomainId) { + return new DomainId(UUID.fromString(oauth2DomainId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.DOMAIN; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index ad2df69349..48e841fb46 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -105,6 +105,12 @@ public class EntityIdFactory { return new NotificationId(uuid); case QUEUE_STATS: return new QueueStatsId(uuid); + case OAUTH2_CLIENT: + return new OAuth2RegistrationId(uuid); + case MOBILE_APP: + return new MobileAppId(uuid); + case DOMAIN: + return new DomainId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java similarity index 68% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java index 6b280dfc60..114e4351f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java @@ -17,17 +17,23 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2MobileId extends UUIDBased { +public class MobileAppId extends UUIDBased implements EntityId{ @JsonCreator - public OAuth2MobileId(@JsonProperty("id") UUID id) { + public MobileAppId(@JsonProperty("id") UUID id) { super(id); } - public static OAuth2MobileId fromString(String oauth2MobileId) { - return new OAuth2MobileId(UUID.fromString(oauth2MobileId)); + public static MobileAppId fromString(String mobileAppId) { + return new MobileAppId(UUID.fromString(mobileAppId)); + } + + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java index 28156417db..5b363d6cfd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java @@ -17,10 +17,11 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2RegistrationId extends UUIDBased { +public class OAuth2RegistrationId extends UUIDBased implements EntityId { @JsonCreator public OAuth2RegistrationId(@JsonProperty("id") UUID id) { @@ -30,4 +31,9 @@ public class OAuth2RegistrationId extends UUIDBased { public static OAuth2RegistrationId fromString(String oauth2RegistrationId) { return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId)); } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT; + } } 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 new file mode 100644 index 0000000000..576c0920c9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java @@ -0,0 +1,59 @@ +/** + * 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.common.data.mobile; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +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; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +@NoArgsConstructor +public class MobileApp extends BaseData implements HasTenantId, HasName { + + @Schema(description = "JSON object with Tenant Id") + private TenantId tenantId; + @Schema(description = "Application package name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + private String pkgName; + @Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED) + private String appSecret; + @Schema(description = "Whether OAuth2 settings are enabled or not") + private boolean oauth2Enabled; + + public MobileApp(MobileApp mobile) { + super(mobile); + this.tenantId = mobile.tenantId; + this.pkgName = mobile.pkgName; + this.appSecret = mobile.appSecret; + this.oauth2Enabled = mobile.oauth2Enabled; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Schema(description = "Mobile app package name", example = "my.mobile.app", accessMode = Schema.AccessMode.READ_ONLY) + public String getName() { + return pkgName; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java similarity index 58% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java index 0a2ace050c..519fd7bf23 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.mobile; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; @@ -22,21 +22,26 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; +import org.thingsboard.server.common.data.oauth2.HasOauth2Registrations; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import java.util.List; -@EqualsAndHashCode +@EqualsAndHashCode(callSuper = true) @Data @ToString -@Builder(toBuilder = true) @NoArgsConstructor @AllArgsConstructor +@Builder @Schema -public class OAuth2Info { - @Schema(description = "Whether OAuth2 settings are enabled or not") - private boolean enabled; - @Schema(description = "Whether OAuth2 settings are enabled on Edge or not") - private boolean edgeEnabled; - @Schema(description = "List of configured OAuth2 clients. Cannot contain null values", requiredMode = Schema.RequiredMode.REQUIRED) - private List oauth2ParamsInfos; +public class MobileAppInfo extends MobileApp implements HasOauth2Registrations { + + @Schema(description = "List of available oauth2 client registrations") + private List oauth2RegistrationInfos; + + public MobileAppInfo(MobileApp mobileApp, List oauth2RegistrationInfos) { + super(mobileApp); + this.oauth2RegistrationInfos = oauth2RegistrationInfos; + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java new file mode 100644 index 0000000000..b309aafe32 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java @@ -0,0 +1,32 @@ +/** + * 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.common.data.mobile; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class MobileAppOauth2Registration { + + private MobileAppId mobileAppId; + private OAuth2RegistrationId oAuth2RegistrationId; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java similarity index 68% rename from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java index 52bde2cd8c..51524f458d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.oauth2; +package org.thingsboard.server.common.data.oauth2; -import org.thingsboard.server.common.data.oauth2.OAuth2Domain; -import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.common.data.EntityInfo; + +import java.io.Serializable; import java.util.List; -import java.util.UUID; -public interface OAuth2DomainDao extends Dao { +public interface HasOauth2Registrations extends Serializable { - List findByOAuth2ParamsId(UUID oauth2ParamsId); + List getOauth2RegistrationInfos(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java deleted file mode 100644 index 6fa0475361..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.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.common.data.oauth2; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2DomainId; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; - -@EqualsAndHashCode(callSuper = true) -@Data -@ToString -@NoArgsConstructor -public class OAuth2Domain extends BaseData { - - private OAuth2ParamsId oauth2ParamsId; - private String domainName; - private SchemeType domainScheme; - - public OAuth2Domain(OAuth2Domain domain) { - super(domain); - this.oauth2ParamsId = domain.oauth2ParamsId; - this.domainName = domain.domainName; - this.domainScheme = domain.domainScheme; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java deleted file mode 100644 index dc70213e95..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java +++ /dev/null @@ -1,38 +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.common.data.oauth2; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; - -@EqualsAndHashCode -@Data -@ToString -@NoArgsConstructor -@AllArgsConstructor -@Builder -@Schema -public class OAuth2DomainInfo { - @Schema(description = "Domain scheme. Mixed scheme means than both HTTP and HTTPS are going to be used", requiredMode = Schema.RequiredMode.REQUIRED) - private SchemeType scheme; - @Schema(description = "Domain name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String name; -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java deleted file mode 100644 index fd9bbe527f..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.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.common.data.oauth2; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2MobileId; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; - -@EqualsAndHashCode(callSuper = true) -@Data -@ToString -@NoArgsConstructor -public class OAuth2Mobile extends BaseData { - - private OAuth2ParamsId oauth2ParamsId; - private String pkgName; - private String appSecret; - - public OAuth2Mobile(OAuth2Mobile mobile) { - super(mobile); - this.oauth2ParamsId = mobile.oauth2ParamsId; - this.pkgName = mobile.pkgName; - this.appSecret = mobile.appSecret; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java deleted file mode 100644 index c1b6124b8f..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java +++ /dev/null @@ -1,46 +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.common.data.oauth2; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; - -import java.util.List; - -@EqualsAndHashCode -@Data -@ToString -@Builder(toBuilder = true) -@NoArgsConstructor -@AllArgsConstructor -@Schema -public class OAuth2ParamsInfo { - - @Schema(description = "List of configured domains where OAuth2 platform will redirect a user after successful " + - "authentication. Cannot be empty. There have to be only one domain with specific name with scheme type 'MIXED'. " + - "Configured domains with the same name must have different scheme types", requiredMode = Schema.RequiredMode.REQUIRED) - private List domainInfos; - @Schema(description = "Mobile applications settings. Application package name must be unique within the list", requiredMode = Schema.RequiredMode.REQUIRED) - private List mobileInfos; - @Schema(description = "List of OAuth2 provider settings. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private List clientRegistrations; - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java index 76c1953387..5a4ec293be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java @@ -16,14 +16,17 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import java.util.List; @@ -31,26 +34,44 @@ import java.util.List; @Data @ToString(exclude = {"clientSecret"}) @NoArgsConstructor -public class OAuth2Registration extends BaseDataWithAdditionalInfo implements HasName { +public class OAuth2Registration extends BaseDataWithAdditionalInfo implements HasName, HasTenantId { - private OAuth2ParamsId oauth2ParamsId; + @Schema(description = "JSON object with Tenant Id") + private TenantId tenantId; + @Schema(description = "Oauth2 client title") + private String title; + @Schema(description = "Config for mapping OAuth2 log in response to platform entities", requiredMode = Schema.RequiredMode.REQUIRED) private OAuth2MapperConfig mapperConfig; + @Schema(description = "OAuth2 client ID. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String clientId; + @Schema(description = "OAuth2 client secret. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String clientSecret; + @Schema(description = "Authorization URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String authorizationUri; + @Schema(description = "Access token URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String accessTokenUri; + @Schema(description = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private List scope; + @Schema(description = "User info URI of the OAuth2 provider") private String userInfoUri; + @Schema(description = "Name of the username attribute in OAuth2 provider response. Cannot be empty") private String userNameAttributeName; + @Schema(description = "JSON Web Key URI of the OAuth2 provider") private String jwkSetUri; + @Schema(description = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String clientAuthenticationMethod; + @Schema(description = "OAuth2 provider label. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) private String loginButtonLabel; + @Schema(description = "Log in button icon for OAuth2 provider") private String loginButtonIcon; + @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") private List platforms; + @Schema(description = "Additional info of OAuth2 client (e.g. providerName)", requiredMode = Schema.RequiredMode.REQUIRED) + private JsonNode additionalInfo; public OAuth2Registration(OAuth2Registration registration) { super(registration); - this.oauth2ParamsId = registration.oauth2ParamsId; + this.tenantId = registration.tenantId; this.mapperConfig = registration.mapperConfig; this.clientId = registration.clientId; this.clientSecret = registration.clientSecret; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java index 36bae12b23..4e360993c1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java @@ -15,51 +15,35 @@ */ package org.thingsboard.server.common.data.oauth2; -import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; import java.util.List; -@EqualsAndHashCode @Data -@ToString(exclude = {"clientSecret"}) -@NoArgsConstructor -@AllArgsConstructor -@Builder @Schema -public class OAuth2RegistrationInfo { - @Schema(description = "Config for mapping OAuth2 log in response to platform entities", requiredMode = Schema.RequiredMode.REQUIRED) - private OAuth2MapperConfig mapperConfig; - @Schema(description = "OAuth2 client ID. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String clientId; - @Schema(description = "OAuth2 client secret. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String clientSecret; - @Schema(description = "Authorization URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String authorizationUri; - @Schema(description = "Access token URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String accessTokenUri; - @Schema(description = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private List scope; - @Schema(description = "User info URI of the OAuth2 provider") - private String userInfoUri; - @Schema(description = "Name of the username attribute in OAuth2 provider response. Cannot be empty") - private String userNameAttributeName; - @Schema(description = "JSON Web Key URI of the OAuth2 provider") - private String jwkSetUri; - @Schema(description = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String clientAuthenticationMethod; - @Schema(description = "OAuth2 provider label. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - private String loginButtonLabel; - @Schema(description = "Log in button icon for OAuth2 provider") - private String loginButtonIcon; +@EqualsAndHashCode(callSuper = true) +public class OAuth2RegistrationInfo extends BaseData { + @Schema(description = "Oauth2 client registration title (e.g. Google)") + private String title; @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") private List platforms; - @Schema(description = "Additional info of OAuth2 client (e.g. providerName)", requiredMode = Schema.RequiredMode.REQUIRED) - private JsonNode additionalInfo; + + public OAuth2RegistrationInfo() { + super(); + } + + public OAuth2RegistrationInfo(OAuth2RegistrationId id) { + super(id); + } + + public OAuth2RegistrationInfo(OAuth2RegistrationId id, String title, List platforms) { + super(id); + this.title = title; + this.platforms = platforms; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java new file mode 100644 index 0000000000..898598b38f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java @@ -0,0 +1,39 @@ +/** + * 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.domain; + +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.Dao; + +import java.util.List; + +public interface DomainDao extends Dao { + + List findByTenantId(TenantId tenantId); + + int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean oauth2Enabled); + + List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId); + + void saveOauth2Clients(DomainOauth2Registration domainOauth2Registration); + + void removeOauth2Clients(DomainId domainId, OAuth2RegistrationId oAuth2RegistrationId); + +} 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 new file mode 100644 index 0000000000..e260d5b154 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java @@ -0,0 +1,167 @@ +/** + * 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.domain; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainInfo; +import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +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.OAuth2RegistrationDao; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.Validator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +import static org.thingsboard.server.dao.service.Validator.validateIds; + +@Slf4j +@Service +public class DomainServiceImpl extends AbstractEntityService implements DomainService { + + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + public static final String INCORRECT_DOMAIN_ID = "Incorrect domainId "; + + @Autowired + private OAuth2RegistrationDao oauth2RegistrationDao; + @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()); + return savedDomain; + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("domain_unq_key")) { + throw new DataValidationException("Domain with such name and scheme already exists!"); + } else { + throw t; + } + } + } + + @Override + public void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds) { + log.trace("Executing addOauth2Clients, domainId [{}], oAuth2ClientIds [{}]", domainId, oAuth2ClientIds); + Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + Validator.validateId(domainId, id -> INCORRECT_DOMAIN_ID + id); + Validator.checkNotNull(oAuth2ClientIds, "Incorrect oAuth2ClientIds " + oAuth2ClientIds); + if (!oAuth2ClientIds.isEmpty()) { + validateIds(oAuth2ClientIds, ids -> "Incorrect oAuth2ClientIds " + ids); + } + List oauth2Clients = new ArrayList<>(); + for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) { + oauth2Clients.add(new DomainOauth2Registration(domainId, oAuth2RegistrationId)); + } + List existingClients = domainDao.findOauth2ClientsByDomainId(tenantId, domainId); + List toRemove = existingClients.stream() + .map(DomainOauth2Registration::getOAuth2RegistrationId) + .filter(clientId -> oAuth2ClientIds.stream().noneMatch(oauth2ClientId -> + oauth2ClientId.equals(clientId))).toList(); + for (OAuth2RegistrationId clientId : toRemove) { + domainDao.removeOauth2Clients(domainId, clientId); + } + for (DomainOauth2Registration domainOauth2Registration : oauth2Clients) { + domainDao.saveOauth2Clients(domainOauth2Registration); + } + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) + .entityId(domainId).created(false).build()); + } + + @Override + public void deleteDomainById(TenantId tenantId, DomainId domainId) { + log.trace("Executing deleteDomain [{}]", domainId.getId()); + domainDao.removeById(tenantId, domainId.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(domainId).build()); + } + + @Override + public Domain findDomainById(TenantId tenantId, DomainId domainId) { + log.trace("Executing findDomainInfo [{}] [{}]", tenantId, domainId); + return domainDao.findById(tenantId, domainId.getId()); + } + + @Override + public List findDomainInfosByTenantId(TenantId tenantId) { + log.trace("Executing findDomainInfo [{}]", tenantId); + List domains = domainDao.findByTenantId(tenantId); + List domainInfos = new ArrayList<>(); + domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { + domainInfos.add(new DomainInfo(domain, oauth2RegistrationDao.findInfosByDomainId(domain.getUuidId()))); + }); + return domainInfos; + } + + @Override + public DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId) { + log.trace("Executing findDomainInfoById [{}] [{}]", tenantId, domainId); + Domain domain = domainDao.findById(tenantId, domainId.getId()); + if (domain == null) { + return null; + } + return new DomainInfo(domain, oauth2RegistrationDao.findInfosByDomainId(domain.getUuidId())); + } + + @Override + public boolean isOauth2Enabled(TenantId tenantId) { + log.trace("Executing isOauth2Enabled [{}] ", tenantId); + return domainDao.countDomainByTenantIdAndOauth2Enabled(tenantId, true) > 0; + } + + @Override + public Optional> findEntity(TenantId tenantId, EntityId entityId) { + return Optional.ofNullable(findDomainById(tenantId, new DomainId(entityId.getId()))); + } + + @Override + public EntityType getEntityType() { + return EntityType.DOMAIN; + } + + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { + Domain domain = domainDao.findById(tenantId, id.getId()); + if (domain == null) { + return; + } + deleteDomainById(tenantId, domain.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java new file mode 100644 index 0000000000..caa77b5f7a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java @@ -0,0 +1,36 @@ +/** + * 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.mobile; + +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.dao.Dao; + +import java.util.List; + +public interface MobileAppDao extends Dao { + + List findByTenantId(TenantId tenantId); + + List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId); + + void saveOauth2Clients(MobileAppOauth2Registration mobileAppOauth2Registration); + + void removeOauth2Clients(MobileAppId mobileAppId, OAuth2RegistrationId oAuth2RegistrationId); +} 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 new file mode 100644 index 0000000000..9f049518e5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java @@ -0,0 +1,163 @@ +/** + * 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.mobile; + +import lombok.extern.slf4j.Slf4j; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +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.MobileAppOauth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +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.OAuth2RegistrationDao; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.Validator; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import static org.thingsboard.server.dao.service.Validator.validateIds; + +@Slf4j +@Service +public class MobileAppServiceImpl extends AbstractEntityService implements MobileAppService { + + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + public static final String INCORRECT_MOBILE_APP_ID = "Incorrect mobileApppId "; + + @Autowired + private OAuth2RegistrationDao oauth2RegistrationDao; + @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()); + return savedMobileApp; + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("mobile_app_unq_key")) { + throw new DataValidationException("Mobile app with such package already exists!"); + } else { + throw t; + } + } + } + + @Override + public void deleteMobileAppById(TenantId tenantId, MobileAppId mobileAppId) { + 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) { + log.trace("Executing findMobileAppById [{}] [{}]", tenantId, mobileAppId); + return mobileAppDao.findById(tenantId, mobileAppId.getId()); + } + + @Override + public List findMobileAppInfosByTenantId(TenantId tenantId) { + log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); + List mobileApps = mobileAppDao.findByTenantId(tenantId); + List mobileAppInfos = new ArrayList<>(); + mobileApps.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { + mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2RegistrationDao.findInfosByMobileAppId(mobileApp.getUuidId()))); + }); + return mobileAppInfos; + } + + @Override + public MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId) { + log.trace("Executing findMobileAppInfoById [{}] [{}]", tenantId, mobileAppId); + MobileApp mobileApp = mobileAppDao.findById(tenantId, mobileAppId.getId()); + if (mobileApp == null) { + return null; + } + return new MobileAppInfo(mobileApp, oauth2RegistrationDao.findInfosByMobileAppId(mobileApp.getUuidId())); + } + + @Override + public void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds) { + log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppId, oAuth2ClientIds); + Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + Validator.validateId(mobileAppId, id -> INCORRECT_MOBILE_APP_ID + id); + Validator.checkNotNull(oAuth2ClientIds, "Incorrect oAuth2ClientIds " + oAuth2ClientIds); + if (!oAuth2ClientIds.isEmpty()) { + validateIds(oAuth2ClientIds, ids -> "Incorrect oAuth2ClientIds " + ids); + } + List oauth2Clients = new ArrayList<>(); + for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) { + oauth2Clients.add(new MobileAppOauth2Registration(mobileAppId, oAuth2RegistrationId)); + } + List existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId); + List toRemove = existingClients.stream() + .map(MobileAppOauth2Registration::getOAuth2RegistrationId) + .filter(clientId -> oAuth2ClientIds.stream().noneMatch(oauth2ClientId -> + oauth2ClientId.equals(clientId))).toList(); + for (OAuth2RegistrationId clientId : toRemove) { + mobileAppDao.removeOauth2Clients(mobileAppId, clientId); + } + for (MobileAppOauth2Registration mobileAppOauth2Registration : oauth2Clients) { + mobileAppDao.saveOauth2Clients(mobileAppOauth2Registration); + } + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) + .entityId(mobileAppId).created(false).build()); + } + + @Override + public Optional> findEntity(TenantId tenantId, EntityId entityId) { + return Optional.ofNullable(findMobileAppById(tenantId, new MobileAppId(entityId.getId()))); + } + + @Override + public EntityType getEntityType() { + return EntityType.MOBILE_APP; + } + + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { + MobileApp mobileApp = mobileAppDao.findById(tenantId, id.getId()); + if (mobileApp == null) { + return; + } + deleteMobileAppById(tenantId, mobileApp.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index fb77ad6987..0b540066a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -424,24 +424,37 @@ public class ModelConstants { public static final String RULE_NODE_STATE_DATA_PROPERTY = "state_data"; /** - * OAuth2 client registration constants. + * Domain constants. */ - public static final String OAUTH2_PARAMS_TABLE_NAME = "oauth2_params"; - public static final String OAUTH2_PARAMS_ENABLED_PROPERTY = "enabled"; - public static final String OAUTH2_PARAMS_EDGE_ENABLED_PROPERTY = "edge_enabled"; - public static final String OAUTH2_PARAMS_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + public static final String DOMAIN_TABLE_NAME = "domain"; + public static final String DOMAIN_DOMAIN_NAME_PROPERTY = "domain_name"; + public static final String DOMAIN_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled"; + public static final String DOMAIN_PROPAGATE_TO_EDGE_PROPERTY = "propagate_to_edge"; - public static final String OAUTH2_REGISTRATION_TABLE_NAME = "oauth2_registration"; - public static final String OAUTH2_DOMAIN_TABLE_NAME = "oauth2_domain"; - public static final String OAUTH2_MOBILE_TABLE_NAME = "oauth2_mobile"; - public static final String OAUTH2_PARAMS_ID_PROPERTY = "oauth2_params_id"; - public static final String OAUTH2_PKG_NAME_PROPERTY = "pkg_name"; - public static final String OAUTH2_APP_SECRET_PROPERTY = "app_secret"; + public static final String DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME = "domain_oauth2_registration"; + public static final String DOMAIN_OAUTH2_PROVIDER_PROVIDER_ID_PROPERTY = "oauth2_registration_id"; + public static final String DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY = "domain_id"; + + /** + * Mobile application constants. + */ + public static final String MOBILE_APP_TABLE_NAME = "mobile_app"; + public static final String MOBILE_APP_PKG_NAME_PROPERTY = "pkg_name"; + public static final String MOBILE_APP_APP_SECRET_PROPERTY = "app_secret"; + public static final String MOBILE_APP_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled"; + + public static final String MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME = "mobile_app_oauth2_registration"; + public static final String MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY = "oauth2_registration_id"; + public static final String MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY = "mobile_app_id"; + + /** + * OAuth2 client registration constants. + */ + public static final String OAUTH2_REGISTRATION_TABLE_NAME = "oauth2_registration"; public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_TABLE_NAME = "oauth2_client_registration_template"; public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; - public static final String OAUTH2_DOMAIN_NAME_PROPERTY = "domain_name"; - public static final String OAUTH2_DOMAIN_SCHEME_PROPERTY = "domain_scheme"; + public static final String OAUTH2_CLIENT_TITLE_PROPERTY = "title"; public static final String OAUTH2_CLIENT_ID_PROPERTY = "client_id"; public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java new file mode 100644 index 0000000000..6138e8f114 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java @@ -0,0 +1,81 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.DOMAIN_TABLE_NAME) +public class DomainEntity extends BaseSqlEntity { + + @Column(name = TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = ModelConstants.DOMAIN_DOMAIN_NAME_PROPERTY) + private String name; + + @Column(name = ModelConstants.DOMAIN_OAUTH2_ENABLED_PROPERTY) + private Boolean oauth2Enabled; + + @Column(name = ModelConstants.DOMAIN_PROPAGATE_TO_EDGE_PROPERTY) + private Boolean propagateToEdge; + + public DomainEntity(Domain domain) { + if (domain.getId() != null) { + this.setUuid(domain.getId().getId()); + } + if (domain.getTenantId() != null) { + this.tenantId = domain.getTenantId().getId(); + } + this.setCreatedTime(domain.getCreatedTime()); + this.name = domain.getName(); + this.oauth2Enabled = domain.isOauth2Enabled(); + this.propagateToEdge = domain.isPropagateToEdge(); + } + + public DomainEntity() { + super(); + } + + @Override + public Domain toData() { + Domain domain = new Domain(); + domain.setId(new DomainId(id)); + if (tenantId != null) { + domain.setTenantId(TenantId.fromUUID(tenantId)); + } + domain.setCreatedTime(createdTime); + domain.setName(name); + domain.setOauth2Enabled(oauth2Enabled); + domain.setPropagateToEdge(propagateToEdge); + return domain; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java new file mode 100644 index 0000000000..33b6047808 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java @@ -0,0 +1,37 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Transient; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class DomainOauth2RegistrationCompositeKey implements Serializable { + + @Transient + private static final long serialVersionUID = -245388185894468455L; + + private UUID domainId; + private UUID oauth2RegistrationId; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java new file mode 100644 index 0000000000..0699c3e7a6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java @@ -0,0 +1,66 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; +import lombok.Data; +import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.ToData; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME; + +@Data +@Entity +@Table(name = DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME) +@IdClass(DomainOauth2RegistrationCompositeKey.class) +public final class DomainOauth2RegistrationEntity implements ToData { + + @Id + @Column(name = DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY, columnDefinition = "uuid") + private UUID domainId; + + @Id + @Column(name = ModelConstants.DOMAIN_OAUTH2_PROVIDER_PROVIDER_ID_PROPERTY, columnDefinition = "uuid") + private UUID oauth2RegistrationId; + + + public DomainOauth2RegistrationEntity() { + super(); + } + + public DomainOauth2RegistrationEntity(DomainOauth2Registration domainOauth2Registration) { + domainId = domainOauth2Registration.getDomainId().getId(); + oauth2RegistrationId = domainOauth2Registration.getOAuth2RegistrationId().getId(); + } + + @Override + public DomainOauth2Registration toData() { + DomainOauth2Registration result = new DomainOauth2Registration(); + result.setDomainId(new DomainId(domainId)); + result.setOAuth2RegistrationId(new OAuth2RegistrationId(oauth2RegistrationId)); + return result; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java similarity index 56% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java index 78ec508ad0..e61a32e2b3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java @@ -17,56 +17,66 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.id.OAuth2MobileId; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.Table; + import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN; + @Data @EqualsAndHashCode(callSuper = true) @Entity -@Table(name = ModelConstants.OAUTH2_MOBILE_TABLE_NAME) -public class OAuth2MobileEntity extends BaseSqlEntity { +@Table(name = ModelConstants.MOBILE_APP_TABLE_NAME) +public class MobileAppEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) - private UUID oauth2ParamsId; + @Column(name = TENANT_ID_COLUMN) + private UUID tenantId; - @Column(name = ModelConstants.OAUTH2_PKG_NAME_PROPERTY) + @Column(name = ModelConstants.MOBILE_APP_PKG_NAME_PROPERTY) private String pkgName; - @Column(name = ModelConstants.OAUTH2_APP_SECRET_PROPERTY) + @Column(name = ModelConstants.MOBILE_APP_APP_SECRET_PROPERTY) private String appSecret; - public OAuth2MobileEntity() { + @Column(name = ModelConstants.MOBILE_APP_OAUTH2_ENABLED_PROPERTY) + private Boolean oauth2Enabled; + + public MobileAppEntity() { super(); } - public OAuth2MobileEntity(OAuth2Mobile mobile) { + public MobileAppEntity(MobileApp mobile) { if (mobile.getId() != null) { this.setUuid(mobile.getId().getId()); } - this.setCreatedTime(mobile.getCreatedTime()); - if (mobile.getOauth2ParamsId() != null) { - this.oauth2ParamsId = mobile.getOauth2ParamsId().getId(); + if (mobile.getTenantId() != null) { + this.tenantId = mobile.getTenantId().getId(); } + this.setCreatedTime(mobile.getCreatedTime()); this.pkgName = mobile.getPkgName(); this.appSecret = mobile.getAppSecret(); + this.oauth2Enabled = mobile.isOauth2Enabled(); } @Override - public OAuth2Mobile toData() { - OAuth2Mobile mobile = new OAuth2Mobile(); - mobile.setId(new OAuth2MobileId(id)); + public MobileApp toData() { + MobileApp mobile = new MobileApp(); + mobile.setId(new MobileAppId(id)); + if (tenantId != null) { + mobile.setTenantId(TenantId.fromUUID(tenantId)); + } mobile.setCreatedTime(createdTime); - mobile.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); mobile.setPkgName(pkgName); mobile.setAppSecret(appSecret); + mobile.setOauth2Enabled(oauth2Enabled); return mobile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java new file mode 100644 index 0000000000..feccbe8aaa --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java @@ -0,0 +1,37 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Transient; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; +import java.util.UUID; + +@NoArgsConstructor +@AllArgsConstructor +@Data +public class MobileAppOauth2RegistrationCompositeKey implements Serializable { + + @Transient + private static final long serialVersionUID = -245388185894468455L; + + private UUID mobileAppId; + private UUID oauth2RegistrationId; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java new file mode 100644 index 0000000000..c68690e167 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java @@ -0,0 +1,67 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.IdClass; +import jakarta.persistence.Table; +import lombok.Data; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.dao.model.ToData; + +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME; + +@Data +@Entity +@Table(name = MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME) +@IdClass(MobileAppOauth2RegistrationCompositeKey.class) +public final class MobileAppOauth2RegistrationEntity implements ToData { + + @Id + @Column(name = MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY, columnDefinition = "uuid") + private UUID mobileAppId; + + @Id + @Column(name = MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY, columnDefinition = "uuid") + private UUID oauth2RegistrationId; + + + public MobileAppOauth2RegistrationEntity() { + super(); + } + + public MobileAppOauth2RegistrationEntity(MobileAppOauth2Registration domainOauth2Provider) { + mobileAppId = domainOauth2Provider.getMobileAppId().getId(); + oauth2RegistrationId = domainOauth2Provider.getOAuth2RegistrationId().getId(); + } + + + @Override + public MobileAppOauth2Registration toData() { + MobileAppOauth2Registration result = new MobileAppOauth2Registration(); + result.setMobileAppId(new MobileAppId(mobileAppId)); + result.setOAuth2RegistrationId(new OAuth2RegistrationId(oauth2RegistrationId)); + return result; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java deleted file mode 100644 index 7841a11378..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java +++ /dev/null @@ -1,76 +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.model.sql; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.id.OAuth2DomainId; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; -import org.thingsboard.server.common.data.oauth2.OAuth2Domain; -import org.thingsboard.server.common.data.oauth2.SchemeType; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.Table; -import java.util.UUID; - -@Data -@EqualsAndHashCode(callSuper = true) -@Entity -@Table(name = ModelConstants.OAUTH2_DOMAIN_TABLE_NAME) -public class OAuth2DomainEntity extends BaseSqlEntity { - - @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) - private UUID oauth2ParamsId; - - @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) - private String domainName; - - @Enumerated(EnumType.STRING) - @Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY) - private SchemeType domainScheme; - - public OAuth2DomainEntity() { - super(); - } - - public OAuth2DomainEntity(OAuth2Domain domain) { - if (domain.getId() != null) { - this.setUuid(domain.getId().getId()); - } - this.setCreatedTime(domain.getCreatedTime()); - if (domain.getOauth2ParamsId() != null) { - this.oauth2ParamsId = domain.getOauth2ParamsId().getId(); - } - this.domainName = domain.getDomainName(); - this.domainScheme = domain.getDomainScheme(); - } - - @Override - public OAuth2Domain toData() { - OAuth2Domain domain = new OAuth2Domain(); - domain.setId(new OAuth2DomainId(id)); - domain.setCreatedTime(createdTime); - domain.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); - domain.setDomainName(domainName); - domain.setDomainScheme(domainScheme); - return domain; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java deleted file mode 100644 index 4983172d3e..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java +++ /dev/null @@ -1,70 +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.model.sql; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Params; -import org.thingsboard.server.dao.model.BaseSqlEntity; -import org.thingsboard.server.dao.model.ModelConstants; - -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Table; -import java.util.UUID; - -@Data -@EqualsAndHashCode(callSuper = true) -@Entity -@Table(name = ModelConstants.OAUTH2_PARAMS_TABLE_NAME) -@NoArgsConstructor -public class OAuth2ParamsEntity extends BaseSqlEntity { - - @Column(name = ModelConstants.OAUTH2_PARAMS_ENABLED_PROPERTY) - private Boolean enabled; - - @Column(name = ModelConstants.OAUTH2_PARAMS_EDGE_ENABLED_PROPERTY) - private Boolean edgeEnabled; - - @Column(name = ModelConstants.OAUTH2_PARAMS_TENANT_ID_PROPERTY) - private UUID tenantId; - - public OAuth2ParamsEntity(OAuth2Params oauth2Params) { - if (oauth2Params.getId() != null) { - this.setUuid(oauth2Params.getUuidId()); - } - this.setCreatedTime(oauth2Params.getCreatedTime()); - this.enabled = oauth2Params.isEnabled(); - this.edgeEnabled = oauth2Params.isEdgeEnabled(); - if (oauth2Params.getTenantId() != null) { - this.tenantId = oauth2Params.getTenantId().getId(); - } - } - - @Override - public OAuth2Params toData() { - OAuth2Params oauth2Params = new OAuth2Params(); - oauth2Params.setId(new OAuth2ParamsId(id)); - oauth2Params.setCreatedTime(createdTime); - oauth2Params.setTenantId(TenantId.fromUUID(tenantId)); - oauth2Params.setEnabled(enabled); - oauth2Params.setEdgeEnabled(edgeEnabled); - return oauth2Params; - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java index 45ef7466f9..9ba8bd0ae1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java @@ -25,8 +25,8 @@ import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; @@ -49,8 +49,10 @@ import java.util.stream.Collectors; @Table(name = ModelConstants.OAUTH2_REGISTRATION_TABLE_NAME) public class OAuth2RegistrationEntity extends BaseSqlEntity { - @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) - private UUID oauth2ParamsId; + @Column(name = ModelConstants.TENANT_ID_COLUMN) + private UUID tenantId; + @Column(name = ModelConstants.OAUTH2_CLIENT_TITLE_PROPERTY) + private String title; @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) private String clientId; @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) @@ -121,9 +123,10 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity this.setUuid(registration.getId().getId()); } this.setCreatedTime(registration.getCreatedTime()); - if (registration.getOauth2ParamsId() != null) { - this.oauth2ParamsId = registration.getOauth2ParamsId().getId(); + if (registration.getTenantId() != null) { + this.tenantId = registration.getTenantId().getId(); } + this.title = registration.getTitle(); this.clientId = registration.getClientId(); this.clientSecret = registration.getClientSecret(); this.authorizationUri = registration.getAuthorizationUri(); @@ -168,7 +171,8 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity OAuth2Registration registration = new OAuth2Registration(); registration.setId(new OAuth2RegistrationId(id)); registration.setCreatedTime(createdTime); - registration.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); + registration.setTenantId(new TenantId(tenantId)); + registration.setTitle(title); registration.setAdditionalInfo(additionalInfo); registration.setMapperConfig( OAuth2MapperConfig.builder() diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java new file mode 100644 index 0000000000..855dcb3446 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java @@ -0,0 +1,61 @@ +/** + * 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.model.sql; + +import jakarta.persistence.Entity; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.dao.model.BaseSqlEntity; + +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.stream.Collectors; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +public class OAuth2RegistrationInfoEntity extends BaseSqlEntity { + + private String platforms; + private String title; + + public OAuth2RegistrationInfoEntity() { + super(); + } + + public OAuth2RegistrationInfoEntity(UUID id, long createdTime, String platforms, String title) { + this.id = id; + this.createdTime = createdTime; + this.platforms = platforms; + this.title = title; + } + + @Override + public OAuth2RegistrationInfo toData() { + OAuth2RegistrationInfo oAuth2RegistrationInfo = new OAuth2RegistrationInfo(); + oAuth2RegistrationInfo.setId(new OAuth2RegistrationId(id)); + oAuth2RegistrationInfo.setCreatedTime(createdTime); + oAuth2RegistrationInfo.setTitle(title); + oAuth2RegistrationInfo.setPlatforms(StringUtils.isNotEmpty(platforms) ? Arrays.stream(platforms.split(",")) + .map(str -> PlatformType.valueOf(str)).collect(Collectors.toList()) : Collections.emptyList()); + return oAuth2RegistrationInfo; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 004c89bd54..4ac2de4297 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -21,6 +21,8 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import java.util.UUID; @@ -30,11 +32,11 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}"; @Autowired - private OAuth2Service oAuth2Service; + private OAuth2ClientService oAuth2ClientService; @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(registrationId)); + OAuth2Registration registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2RegistrationId(UUID.fromString(registrationId))); return registration == null ? null : toSpringClientRegistration(registration); } 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 new file mode 100644 index 0000000000..2e2a1fa5c0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java @@ -0,0 +1,145 @@ +/** + * 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.oauth2; + +import jakarta.transaction.Transactional; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.oauth2.PlatformType; +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.service.DataValidator; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateString; + +@Slf4j +@Service +public class OAuth2ClientServiceImpl extends AbstractEntityService implements OAuth2ClientService { + + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; + public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; + + @Autowired + private OAuth2RegistrationDao oauth2RegistrationDao; + @Autowired + private DataValidator oAuth2RegistrationDataValidator; + + @Override + public List getWebOAuth2Clients(String domainName, PlatformType platformType) { + log.trace("Executing getOAuth2Clients [{}] ", domainName); + validateString(domainName, dn -> INCORRECT_DOMAIN_NAME + dn); + return oauth2RegistrationDao.findEnabledByDomainNameAndPlatformType(domainName, platformType) + .stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()); + } + + @Override + public List getMobileOAuth2Clients(String pkgName, PlatformType platformType) { + log.trace("Executing getOAuth2Clients pkgName=[{}] platformType=[{}]",pkgName, platformType); + return oauth2RegistrationDao.findEnabledByPckNameAndPlatformType(pkgName, platformType) + .stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()); + } + + @Override + @Transactional + public OAuth2Registration saveOAuth2Client(TenantId tenantId, OAuth2Registration oAuth2Registration) { + log.trace("Executing saveOAuth2Client [{}]", oAuth2Registration); + oAuth2RegistrationDataValidator.validate(oAuth2Registration, OAuth2Registration::getTenantId); + OAuth2Registration savedOauth2Registration = oauth2RegistrationDao.save(tenantId, oAuth2Registration); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entity(oAuth2Registration).build()); + return savedOauth2Registration; + } + + @Override + public OAuth2Registration findOAuth2ClientById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId) { + log.trace("Executing findOAuth2ClientById [{}]", oAuth2RegistrationId); + validateId(oAuth2RegistrationId, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); + return oauth2RegistrationDao.findById(tenantId, oAuth2RegistrationId.getId()); + } + + @Override + public List findOauth2ClientInfosByTenantId(TenantId tenantId) { + log.trace("Executing findOauth2ClientInfosByTenantId"); + return oauth2RegistrationDao.findInfosByTenantId(tenantId.getId()); + } + + @Override + public List findOauth2ClientsByTenantId(TenantId tenantId) { + log.trace("Executing findOauth2ClientsByTenantId [{}]", tenantId); + return oauth2RegistrationDao.findByTenantId(tenantId.getId()); + } + + @Override + public String findAppSecret(UUID id, String pkgName) { + log.trace("Executing findAppSecret [{}][{}]", id, pkgName); + validateId(id, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); + validateString(pkgName, "Incorrect package name"); + return oauth2RegistrationDao.findAppSecret(id, pkgName); + } + + @Override + @Transactional + public void deleteById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId) { + log.trace("[{}][{}] Executing deleteById [{}]", tenantId, oAuth2RegistrationId); + oauth2RegistrationDao.removeById(tenantId, oAuth2RegistrationId.getId()); + eventPublisher.publishEvent(DeleteEntityEvent.builder() + .tenantId(tenantId) + .entityId(oAuth2RegistrationId) + .build()); + + } + + @Override + public Optional> findEntity(TenantId tenantId, EntityId entityId) { + return Optional.ofNullable(findOAuth2ClientById(tenantId, new OAuth2RegistrationId(entityId.getId()))); + } + + @Override + @Transactional + public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { + OAuth2Registration oAuth2Registration = oauth2RegistrationDao.findById(tenantId, id.getId()); + if (oAuth2Registration == null) { + return; + } + deleteById(tenantId, oAuth2Registration.getId()); + } + + @Override + public EntityType getEntityType() { + return EntityType.OAUTH2_CLIENT; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java index 96a91f7d3b..a0dcd22801 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java @@ -16,8 +16,8 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; -import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -25,9 +25,17 @@ import java.util.UUID; public interface OAuth2RegistrationDao extends Dao { - List findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List domainSchemes, String domainName, String pkgName, PlatformType platformType); + List findInfosByTenantId(UUID tenantId); - List findByOAuth2ParamsId(UUID oauth2ParamsId); + List findByTenantId(UUID tenantId); + + List findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType); + + List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType); + + List findInfosByDomainId(UUID domainId); + + List findInfosByMobileAppId(UUID domainId); String findAppSecret(UUID id, String pkgName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java deleted file mode 100644 index 5bb7ae163e..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ /dev/null @@ -1,295 +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.oauth2; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.CollectionUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.MapperType; -import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Domain; -import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; -import org.thingsboard.server.common.data.oauth2.OAuth2MobileInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Params; -import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; -import org.thingsboard.server.common.data.oauth2.PlatformType; -import org.thingsboard.server.common.data.oauth2.SchemeType; -import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; -import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; - -import jakarta.transaction.Transactional; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validateString; - -@Slf4j -@Service -public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Service { - - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - public static final String INCORRECT_CLIENT_REGISTRATION_ID = "Incorrect clientRegistrationId "; - public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; - public static final String INCORRECT_DOMAIN_SCHEME = "Incorrect domainScheme "; - - @Autowired - private OAuth2ParamsDao oauth2ParamsDao; - @Autowired - private OAuth2RegistrationDao oauth2RegistrationDao; - @Autowired - private OAuth2DomainDao oauth2DomainDao; - @Autowired - private OAuth2MobileDao oauth2MobileDao; - - @Override - public List getOAuth2Clients(String domainSchemeStr, String domainName, String pkgName, PlatformType platformType) { - log.trace("Executing getOAuth2Clients [{}://{}] pkgName=[{}] platformType=[{}]", domainSchemeStr, domainName, pkgName, platformType); - if (domainSchemeStr == null) { - throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); - } - SchemeType domainScheme; - try { - domainScheme = SchemeType.valueOf(domainSchemeStr.toUpperCase()); - } catch (IllegalArgumentException e){ - throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); - } - validateString(domainName, dn -> INCORRECT_DOMAIN_NAME + dn); - return oauth2RegistrationDao.findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType( - Arrays.asList(domainScheme, SchemeType.MIXED), domainName, pkgName, platformType) - .stream() - .map(OAuth2Utils::toClientInfo) - .collect(Collectors.toList()); - } - - @Override - @Transactional - public void saveOAuth2Info(OAuth2Info oauth2Info) { - log.trace("Executing saveOAuth2Info [{}]", oauth2Info); - oauth2InfoValidator.accept(oauth2Info); - oauth2ParamsDao.deleteAll(); - oauth2Info.getOauth2ParamsInfos().forEach(oauth2ParamsInfo -> { - OAuth2Params oauth2Params = OAuth2Utils.infoToOAuth2Params(oauth2Info); - OAuth2Params savedOauth2Params = oauth2ParamsDao.save(TenantId.SYS_TENANT_ID, oauth2Params); - oauth2ParamsInfo.getClientRegistrations().forEach(registrationInfo -> { - OAuth2Registration registration = OAuth2Utils.toOAuth2Registration(savedOauth2Params.getId(), registrationInfo); - oauth2RegistrationDao.save(TenantId.SYS_TENANT_ID, registration); - }); - oauth2ParamsInfo.getDomainInfos().forEach(domainInfo -> { - OAuth2Domain domain = OAuth2Utils.toOAuth2Domain(savedOauth2Params.getId(), domainInfo); - oauth2DomainDao.save(TenantId.SYS_TENANT_ID, domain); - }); - if (oauth2ParamsInfo.getMobileInfos() != null) { - oauth2ParamsInfo.getMobileInfos().forEach(mobileInfo -> { - OAuth2Mobile mobile = OAuth2Utils.toOAuth2Mobile(savedOauth2Params.getId(), mobileInfo); - oauth2MobileDao.save(TenantId.SYS_TENANT_ID, mobile); - }); - } - }); - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entity(oauth2Info).build()); - } - - @Override - public OAuth2Info findOAuth2Info() { - log.trace("Executing findOAuth2Info"); - OAuth2Info oauth2Info = new OAuth2Info(); - List oauth2ParamsList = oauth2ParamsDao.find(TenantId.SYS_TENANT_ID); - oauth2Info.setEnabled(oauth2ParamsList.stream().anyMatch(OAuth2Params::isEnabled)); - oauth2Info.setEdgeEnabled(oauth2ParamsList.stream().anyMatch(OAuth2Params::isEdgeEnabled)); - List oauth2ParamsInfos = new ArrayList<>(); - oauth2Info.setOauth2ParamsInfos(oauth2ParamsInfos); - oauth2ParamsList.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(oauth2Params -> { - List registrations = oauth2RegistrationDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); - List domains = oauth2DomainDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); - List mobiles = oauth2MobileDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); - oauth2ParamsInfos.add(OAuth2Utils.toOAuth2ParamsInfo(registrations, domains, mobiles)); - }); - return oauth2Info; - } - - @Override - public OAuth2Registration findRegistration(UUID id) { - log.trace("Executing findRegistration [{}]", id); - validateId(id, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); - return oauth2RegistrationDao.findById(null, id); - } - - @Override - public String findAppSecret(UUID id, String pkgName) { - log.trace("Executing findAppSecret [{}][{}]", id, pkgName); - validateId(id, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); - validateString(pkgName, "Incorrect package name"); - return oauth2RegistrationDao.findAppSecret(id, pkgName); - } - - - @Override - public List findAllRegistrations() { - log.trace("Executing findAllRegistrations"); - return oauth2RegistrationDao.find(TenantId.SYS_TENANT_ID); - } - - private final Consumer oauth2InfoValidator = oauth2Info -> { - if (oauth2Info == null - || oauth2Info.getOauth2ParamsInfos() == null) { - throw new DataValidationException("OAuth2 param infos should be specified!"); - } - for (OAuth2ParamsInfo oauth2Params : oauth2Info.getOauth2ParamsInfos()) { - if (oauth2Params.getDomainInfos() == null - || oauth2Params.getDomainInfos().isEmpty()) { - throw new DataValidationException("List of domain configuration should be specified!"); - } - for (OAuth2DomainInfo domainInfo : oauth2Params.getDomainInfos()) { - if (StringUtils.isEmpty(domainInfo.getName())) { - throw new DataValidationException("Domain name should be specified!"); - } - if (domainInfo.getScheme() == null) { - throw new DataValidationException("Domain scheme should be specified!"); - } - } - oauth2Params.getDomainInfos().stream() - .collect(Collectors.groupingBy(OAuth2DomainInfo::getName)) - .forEach((domainName, domainInfos) -> { - if (domainInfos.size() > 1 && domainInfos.stream().anyMatch(domainInfo -> domainInfo.getScheme() == SchemeType.MIXED)) { - throw new DataValidationException("MIXED scheme type shouldn't be combined with another scheme type!"); - } - domainInfos.stream() - .collect(Collectors.groupingBy(OAuth2DomainInfo::getScheme)) - .forEach((schemeType, domainInfosBySchemeType) -> { - if (domainInfosBySchemeType.size() > 1) { - throw new DataValidationException("Domain name and protocol must be unique within OAuth2 parameters!"); - } - }); - }); - if (oauth2Params.getMobileInfos() != null) { - for (OAuth2MobileInfo mobileInfo : oauth2Params.getMobileInfos()) { - if (StringUtils.isEmpty(mobileInfo.getPkgName())) { - throw new DataValidationException("Package should be specified!"); - } - if (StringUtils.isEmpty(mobileInfo.getAppSecret())) { - throw new DataValidationException("Application secret should be specified!"); - } - if (mobileInfo.getAppSecret().length() < 16) { - throw new DataValidationException("Application secret should be at least 16 characters!"); - } - } - oauth2Params.getMobileInfos().stream() - .collect(Collectors.groupingBy(OAuth2MobileInfo::getPkgName)) - .forEach((pkgName, mobileInfos) -> { - if (mobileInfos.size() > 1) { - throw new DataValidationException("Mobile app package name must be unique within OAuth2 parameters!"); - } - }); - } - if (oauth2Params.getClientRegistrations() == null || oauth2Params.getClientRegistrations().isEmpty()) { - throw new DataValidationException("Client registrations should be specified!"); - } - for (OAuth2RegistrationInfo clientRegistration : oauth2Params.getClientRegistrations()) { - if (StringUtils.isEmpty(clientRegistration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { - throw new DataValidationException("Client secret should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { - throw new DataValidationException("Authorization uri should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { - throw new DataValidationException("Token uri should be specified!"); - } - if (CollectionUtils.isEmpty(clientRegistration.getScope())) { - throw new DataValidationException("Scope should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { - throw new DataValidationException("User name attribute name should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { - throw new DataValidationException("Client authentication method should be specified!"); - } - if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { - throw new DataValidationException("Login button label should be specified!"); - } - OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); - if (mapperConfig == null) { - throw new DataValidationException("Mapper config should be specified!"); - } - if (mapperConfig.getType() == null) { - throw new DataValidationException("Mapper config type should be specified!"); - } - if (mapperConfig.getType() == MapperType.BASIC) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } - } - if (mapperConfig.getType() == MapperType.GITHUB) { - OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); - if (basicConfig == null) { - throw new DataValidationException("Basic config should be specified!"); - } - if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { - throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); - } - if (basicConfig.getTenantNameStrategy() == null) { - throw new DataValidationException("Tenant name strategy should be specified!"); - } - if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM - && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { - throw new DataValidationException("Tenant name pattern should be specified!"); - } - } - if (mapperConfig.getType() == MapperType.CUSTOM) { - OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); - if (customConfig == null) { - throw new DataValidationException("Custom config should be specified!"); - } - if (StringUtils.isEmpty(customConfig.getUrl())) { - throw new DataValidationException("Custom mapper URL should be specified!"); - } - } - } - } - }; -} 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 8d30fc905d..c3fc12748b 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,23 +15,8 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2ParamsId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Domain; -import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; -import org.thingsboard.server.common.data.oauth2.OAuth2MobileInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Params; -import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; - -import java.util.Comparator; -import java.util.List; -import java.util.stream.Collectors; public class OAuth2Utils { public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; @@ -44,88 +29,4 @@ public class OAuth2Utils { return client; } - public static OAuth2ParamsInfo toOAuth2ParamsInfo(List registrations, List domains, List mobiles) { - OAuth2ParamsInfo oauth2ParamsInfo = new OAuth2ParamsInfo(); - oauth2ParamsInfo.setClientRegistrations(registrations.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2RegistrationInfo).collect(Collectors.toList())); - oauth2ParamsInfo.setDomainInfos(domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2DomainInfo).collect(Collectors.toList())); - oauth2ParamsInfo.setMobileInfos(mobiles.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2MobileInfo).collect(Collectors.toList())); - return oauth2ParamsInfo; - } - - public static OAuth2RegistrationInfo toOAuth2RegistrationInfo(OAuth2Registration registration) { - return OAuth2RegistrationInfo.builder() - .mapperConfig(registration.getMapperConfig()) - .clientId(registration.getClientId()) - .clientSecret(registration.getClientSecret()) - .authorizationUri(registration.getAuthorizationUri()) - .accessTokenUri(registration.getAccessTokenUri()) - .scope(registration.getScope()) - .platforms(registration.getPlatforms()) - .userInfoUri(registration.getUserInfoUri()) - .userNameAttributeName(registration.getUserNameAttributeName()) - .jwkSetUri(registration.getJwkSetUri()) - .clientAuthenticationMethod(registration.getClientAuthenticationMethod()) - .loginButtonLabel(registration.getLoginButtonLabel()) - .loginButtonIcon(registration.getLoginButtonIcon()) - .additionalInfo(registration.getAdditionalInfo()) - .build(); - } - - public static OAuth2DomainInfo toOAuth2DomainInfo(OAuth2Domain domain) { - return OAuth2DomainInfo.builder() - .name(domain.getDomainName()) - .scheme(domain.getDomainScheme()) - .build(); - } - - public static OAuth2MobileInfo toOAuth2MobileInfo(OAuth2Mobile mobile) { - return OAuth2MobileInfo.builder() - .pkgName(mobile.getPkgName()) - .appSecret(mobile.getAppSecret()) - .build(); - } - - public static OAuth2Params infoToOAuth2Params(OAuth2Info oauth2Info) { - OAuth2Params oauth2Params = new OAuth2Params(); - oauth2Params.setEnabled(oauth2Info.isEnabled()); - oauth2Params.setEdgeEnabled(oauth2Info.isEdgeEnabled()); - oauth2Params.setTenantId(TenantId.SYS_TENANT_ID); - return oauth2Params; - } - - public static OAuth2Registration toOAuth2Registration(OAuth2ParamsId oauth2ParamsId, OAuth2RegistrationInfo registrationInfo) { - OAuth2Registration registration = new OAuth2Registration(); - registration.setOauth2ParamsId(oauth2ParamsId); - registration.setMapperConfig(registrationInfo.getMapperConfig()); - registration.setClientId(registrationInfo.getClientId()); - registration.setClientSecret(registrationInfo.getClientSecret()); - registration.setAuthorizationUri(registrationInfo.getAuthorizationUri()); - registration.setAccessTokenUri(registrationInfo.getAccessTokenUri()); - registration.setScope(registrationInfo.getScope()); - registration.setPlatforms(registrationInfo.getPlatforms()); - registration.setUserInfoUri(registrationInfo.getUserInfoUri()); - registration.setUserNameAttributeName(registrationInfo.getUserNameAttributeName()); - registration.setJwkSetUri(registrationInfo.getJwkSetUri()); - registration.setClientAuthenticationMethod(registrationInfo.getClientAuthenticationMethod()); - registration.setLoginButtonLabel(registrationInfo.getLoginButtonLabel()); - registration.setLoginButtonIcon(registrationInfo.getLoginButtonIcon()); - registration.setAdditionalInfo(registrationInfo.getAdditionalInfo()); - return registration; - } - - public static OAuth2Domain toOAuth2Domain(OAuth2ParamsId oauth2ParamsId, OAuth2DomainInfo domainInfo) { - OAuth2Domain domain = new OAuth2Domain(); - domain.setOauth2ParamsId(oauth2ParamsId); - domain.setDomainName(domainInfo.getName()); - domain.setDomainScheme(domainInfo.getScheme()); - return domain; - } - - public static OAuth2Mobile toOAuth2Mobile(OAuth2ParamsId oauth2ParamsId, OAuth2MobileInfo mobileInfo) { - OAuth2Mobile mobile = new OAuth2Mobile(); - mobile.setOauth2ParamsId(oauth2ParamsId); - mobile.setPkgName(mobileInfo.getPkgName()); - mobile.setAppSecret(mobileInfo.getAppSecret()); - return mobile; - } } 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 new file mode 100644 index 0000000000..644e35f14a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java @@ -0,0 +1,36 @@ +/** + * 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 new file mode 100644 index 0000000000..a96f7a0ac6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java @@ -0,0 +1,43 @@ +/** + * 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.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/service/validator/Oauth2RegistrationDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java new file mode 100644 index 0000000000..e5efbd13e7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java @@ -0,0 +1,114 @@ +/** + * 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.apache.commons.collections4.CollectionUtils; +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.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +@AllArgsConstructor +public class Oauth2RegistrationDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, OAuth2Registration oAuth2Registration) { + if (StringUtils.isEmpty(oAuth2Registration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getAccessTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (CollectionUtils.isEmpty(oAuth2Registration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getUserNameAttributeName())) { + throw new DataValidationException("User name attribute name should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(oAuth2Registration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = oAuth2Registration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.GITHUB) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java new file mode 100644 index 0000000000..dd15bcacab --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java @@ -0,0 +1,34 @@ +/** + * 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.sql.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationCompositeKey; +import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; + +import java.util.List; +import java.util.UUID; + +public interface DomainOauth2RegistrationRepository extends JpaRepository { + + List findAllByDomainId(@Param("domainId") UUID domainId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java new file mode 100644 index 0000000000..1de8f939dd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java @@ -0,0 +1,39 @@ +/** + * 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.sql.domain; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.DomainEntity; + +import java.util.List; +import java.util.UUID; + +public interface DomainRepository extends JpaRepository { + + List findByTenantId(@Param("tenantId") UUID tenantId); + + @Transactional + @Modifying + @Query("DELETE FROM MobileAppEntity r WHERE r.tenantId = :tenantId") + void deleteByTenantId(@Param("tenantId") UUID tenantId); + + int countByTenantIdAndOauth2Enabled(@Param("tenantId") UUID tenantId, @Param("oauth2Enabled") boolean oauth2Enabled); + +} 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 new file mode 100644 index 0000000000..f4e10bda7a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java @@ -0,0 +1,83 @@ +/** + * 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.sql.domain; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.domain.Domain; +import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.id.DomainId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.domain.DomainDao; +import org.thingsboard.server.dao.model.sql.DomainEntity; +import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationCompositeKey; +import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.WidgetsBundleWidgetEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +@SqlDao +public class JpaDomainDao extends JpaAbstractDao implements DomainDao { + + private final DomainRepository domainRepository; + private final DomainOauth2RegistrationRepository domainOauth2RegistrationRepository; + + @Override + protected Class getEntityClass() { + return DomainEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return domainRepository; + } + + @Override + public List findByTenantId(TenantId tenantId) { + return DaoUtil.convertDataList(domainRepository.findByTenantId(tenantId.getId())); + } + + @Override + public int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean enabled) { + return domainRepository.countByTenantIdAndOauth2Enabled(tenantId.getId(), enabled); + } + + @Override + public List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId) { + return DaoUtil.convertDataList(domainOauth2RegistrationRepository.findAllByDomainId(domainId.getId())); + } + + @Override + public void saveOauth2Clients(DomainOauth2Registration domainOauth2Registration) { + domainOauth2RegistrationRepository.save(new DomainOauth2RegistrationEntity(domainOauth2Registration)); + } + + @Override + public void removeOauth2Clients(DomainId domainId, OAuth2RegistrationId oAuth2RegistrationId) { + domainOauth2RegistrationRepository.deleteById(new DomainOauth2RegistrationCompositeKey(domainId.getId(), oAuth2RegistrationId.getId())); + } + +} + 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 new file mode 100644 index 0000000000..96e40a742f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java @@ -0,0 +1,77 @@ +/** + * 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.sql.mobile; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.MobileAppId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.mobile.MobileApp; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.mobile.MobileAppDao; +import org.thingsboard.server.dao.model.sql.MobileAppEntity; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationCompositeKey; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.util.SqlDao; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +@SqlDao +public class JpaMobileAppDao extends JpaAbstractDao implements MobileAppDao { + + private final MobileAppRepository repository; + private final MobileAppOauth2RegistrationRepository mobileOauth2ProviderRepository; + + @Override + protected Class getEntityClass() { + return MobileAppEntity.class; + } + + @Override + protected JpaRepository getRepository() { + return repository; + } + + @Override + public List findByTenantId(TenantId tenantId) { + return DaoUtil.convertDataList(repository.findByTenantId(tenantId.getId())); + } + + @Override + public List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId) { + return DaoUtil.convertDataList(mobileOauth2ProviderRepository.findAllByMobileAppId(mobileAppId.getId())); + } + + @Override + public void saveOauth2Clients(MobileAppOauth2Registration mobileAppOauth2Registration) { + mobileOauth2ProviderRepository.save(new MobileAppOauth2RegistrationEntity(mobileAppOauth2Registration)); + } + + @Override + public void removeOauth2Clients(MobileAppId mobileAppId, OAuth2RegistrationId oAuth2RegistrationId) { + mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2RegistrationCompositeKey(mobileAppId.getId(), oAuth2RegistrationId.getId())); + + } + +} + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java new file mode 100644 index 0000000000..c00f65d18f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java @@ -0,0 +1,32 @@ +/** + * 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.sql.mobile; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationCompositeKey; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationEntity; + +import java.util.List; +import java.util.UUID; + +public interface MobileAppOauth2RegistrationRepository extends JpaRepository { + + List findAllByMobileAppId(@Param("mobileAppId") UUID mobileAppId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java similarity index 50% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java index ed637b269b..a74a32b7d6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java @@ -13,12 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.dao.sql.mobile; import org.springframework.data.jpa.repository.JpaRepository; -import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.dao.model.sql.MobileAppEntity; +import java.util.List; import java.util.UUID; -public interface OAuth2ParamsRepository extends JpaRepository { +public interface MobileAppRepository extends JpaRepository { + + List findByTenantId(@Param("tenantId") UUID tenantId); + + @Transactional + @Modifying + @Query("DELETE FROM MobileAppEntity r WHERE r.tenantId = :tenantId") + void deleteByTenantId(@Param("tenantId") UUID tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java deleted file mode 100644 index 2eac50ba02..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java +++ /dev/null @@ -1,54 +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.sql.oauth2; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2Domain; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.OAuth2DomainEntity; -import org.thingsboard.server.dao.oauth2.OAuth2DomainDao; -import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.List; -import java.util.UUID; - -@Component -@RequiredArgsConstructor -@SqlDao -public class JpaOAuth2DomainDao extends JpaAbstractDao implements OAuth2DomainDao { - - private final OAuth2DomainRepository repository; - - @Override - protected Class getEntityClass() { - return OAuth2DomainEntity.class; - } - - @Override - protected JpaRepository getRepository() { - return repository; - } - - @Override - public List findByOAuth2ParamsId(UUID oauth2ParamsId) { - return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); - } - -} - diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java deleted file mode 100644 index c5c8a50636..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java +++ /dev/null @@ -1,54 +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.sql.oauth2; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; -import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.OAuth2MobileEntity; -import org.thingsboard.server.dao.oauth2.OAuth2MobileDao; -import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.List; -import java.util.UUID; - -@Component -@RequiredArgsConstructor -@SqlDao -public class JpaOAuth2MobileDao extends JpaAbstractDao implements OAuth2MobileDao { - - private final OAuth2MobileRepository repository; - - @Override - protected Class getEntityClass() { - return OAuth2MobileEntity.class; - } - - @Override - protected JpaRepository getRepository() { - return repository; - } - - @Override - public List findByOAuth2ParamsId(UUID oauth2ParamsId) { - return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); - } - -} - diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java deleted file mode 100644 index e4de6c2ea8..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java +++ /dev/null @@ -1,49 +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.sql.oauth2; - -import lombok.RequiredArgsConstructor; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2Params; -import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; -import org.thingsboard.server.dao.oauth2.OAuth2ParamsDao; -import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.util.SqlDao; - -import java.util.UUID; - -@Component -@RequiredArgsConstructor -@SqlDao -public class JpaOAuth2ParamsDao extends JpaAbstractDao implements OAuth2ParamsDao { - private final OAuth2ParamsRepository repository; - - @Override - protected Class getEntityClass() { - return OAuth2ParamsEntity.class; - } - - @Override - protected JpaRepository getRepository() { - return repository; - } - - @Override - public void deleteAll() { - repository.deleteAll(); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java index 09060d5106..a4e94eb6d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java @@ -19,8 +19,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; -import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; import org.thingsboard.server.dao.oauth2.OAuth2RegistrationDao; @@ -48,14 +48,35 @@ public class JpaOAuth2RegistrationDao extends JpaAbstractDao findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List domainSchemes, String domainName, String pkgName, PlatformType platformType) { - return DaoUtil.convertDataList(repository.findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(domainSchemes, domainName, pkgName, + public List findInfosByTenantId(UUID tenantId) { + return DaoUtil.convertDataList(repository.findInfosByTenantId(tenantId)); + } + + @Override + public List findByTenantId(UUID tenantId) { + return DaoUtil.convertDataList(repository.findByTenantId(tenantId)); + } + + @Override + public List findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType) { + return DaoUtil.convertDataList(repository.findEnabledByDomainNameAndPlatformType(domainName, platformType != null ? "%" + platformType.name() + "%" : null)); } @Override - public List findByOAuth2ParamsId(UUID oauth2ParamsId) { - return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); + public List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType) { + return DaoUtil.convertDataList(repository.findEnabledByPkgNameAndPlatformType(pkgName, + platformType != null ? "%" + platformType.name() + "%" : null)); + } + + @Override + public List findInfosByDomainId(UUID oauth2ParamsId) { + return DaoUtil.convertDataList(repository.findInfosByDomainId(oauth2ParamsId)); + } + + @Override + public List findInfosByMobileAppId(UUID mobileAppId) { + return DaoUtil.convertDataList(repository.findInfosByMobileAppId(mobileAppId)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java index a8315eb654..16fbe74aed 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java @@ -18,36 +18,59 @@ package org.thingsboard.server.dao.sql.oauth2; 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.oauth2.SchemeType; import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity; import java.util.List; import java.util.UUID; public interface OAuth2RegistrationRepository extends JpaRepository { - @Query("SELECT reg " + - "FROM OAuth2RegistrationEntity reg " + - "LEFT JOIN OAuth2ParamsEntity params on reg.oauth2ParamsId = params.id " + - "LEFT JOIN OAuth2DomainEntity domain on reg.oauth2ParamsId = domain.oauth2ParamsId " + - "WHERE params.enabled = true " + - "AND domain.domainName = :domainName " + - "AND domain.domainScheme IN (:domainSchemes) " + - "AND (:pkgName IS NULL OR EXISTS (SELECT mobile FROM OAuth2MobileEntity mobile WHERE mobile.oauth2ParamsId = reg.oauth2ParamsId AND mobile.pkgName = :pkgName)) " + - "AND (:platformFilter IS NULL OR reg.platforms IS NULL OR reg.platforms = '' OR reg.platforms LIKE :platformFilter)") - List findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(@Param("domainSchemes") List domainSchemes, - @Param("domainName") String domainName, - @Param("pkgName") String pkgName, - @Param("platformFilter") String platformFilter); - - List findByOauth2ParamsId(UUID oauth2ParamsId); - - @Query("SELECT mobile.appSecret " + - "FROM OAuth2MobileEntity mobile " + - "LEFT JOIN OAuth2RegistrationEntity reg on mobile.oauth2ParamsId = reg.oauth2ParamsId " + - "WHERE reg.id = :registrationId " + - "AND mobile.pkgName = :pkgName") + List findByTenantId(@Param("tenantId") UUID tenantId); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + + "FROM OAuth2RegistrationEntity r " + + "WHERE r.tenantId = :tenantId") + List findInfosByTenantId(@Param("tenantId") UUID tenantId); + + @Query(value = "SELECT r " + + "FROM oauth2_registration r " + + "LEFT JOIN domain_oauth2_registration dr on dr.oauth2_registration_id = r.id " + + "LEFT JOIN domain d on dr.domain_id = d.id " + + "WHERE d.oauth2_enabled = true " + + "AND d.domain_name = :domainName " + + "AND (:platformFilter IS NULL OR r.platforms IS NULL OR r.platforms = '' OR r.platforms LIKE :platformFilter)", nativeQuery = true) + List findEnabledByDomainNameAndPlatformType(@Param("domainName") String domainName, + @Param("platformFilter") String platformFilter); + + @Query(value = "SELECT r " + + "FROM oauth2_registration r " + + "LEFT JOIN mobile_app_oauth2_registration mr on mr.oauth2_registration_id = r.id " + + "LEFT JOIN mobile_app m on mr.mobile_app_id = m.id " + + "WHERE m.oauth2_enabled = true " + + "AND m.pck_name = :pkgName " + + "AND (:platformFilter IS NULL OR r.platforms IS NULL OR r.platforms = '' OR r.platforms LIKE :platformFilter)", nativeQuery = true) + List findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName, + @Param("platformFilter") String platformFilter); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + + "FROM OAuth2RegistrationEntity r " + + "LEFT JOIN DomainOauth2RegistrationEntity dr on dr.oauth2RegistrationId = r.id " + + "WHERE dr.domainId = :domainId ") + List findInfosByDomainId(UUID domainId); + + @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + + "FROM OAuth2RegistrationEntity r " + + "LEFT JOIN MobileAppOauth2RegistrationEntity mr on mr.oauth2RegistrationId = r.id " + + "WHERE mr.mobileAppId = :mobileAppId ") + List findInfosByMobileAppId(UUID mobileAppId); + + @Query("SELECT m.appSecret " + + "FROM MobileAppEntity m " + + "LEFT JOIN MobileAppOauth2RegistrationEntity mp on m.id = mp.mobileAppId " + + "LEFT JOIN OAuth2RegistrationEntity p on mp.oauth2RegistrationId = p.id " + + "WHERE p.id = :registrationId " + + "AND m.pkgName = :pkgName") String findAppSecret(@Param("registrationId") UUID id, @Param("pkgName") String pkgName); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index e86abdbb8b..75d04757ad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -177,7 +177,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService { +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// }); +// } +// +// @Test +// public void testSaveHttpsAndMixedDomainsTogether() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build() +// )); +// Assertions.assertThrows(DataValidationException.class, () -> { +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// }); +// } +// +// @Test +// public void testCreateAndFindParams() { +// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// // TODO ask if it's safe to check equality on AdditionalProperties +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// } +// +// @Test +// public void testDisableParams() { +// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); +// oAuth2Info.setEnabled(true); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// oAuth2Info.setEnabled(false); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundDisabledOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertEquals(oAuth2Info, foundDisabledOAuth2Info); +// } +// +// @Test +// public void testClearDomainParams() { +// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); +// OAuth2Info foundAfterClearClientsParams = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundAfterClearClientsParams); +// Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); +// } +// +// @Test +// public void testUpdateClientsParams() { +// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// OAuth2Info newOAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo() +// )) +// .build() +// )); +// oAuth2Service.saveOAuth2Info(newOAuth2Info); +// OAuth2Info foundAfterUpdateOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundAfterUpdateOAuth2Info); +// Assert.assertEquals(newOAuth2Info, foundAfterUpdateOAuth2Info); +// } +// +// @Test +// public void testGetOAuth2Clients() { +// List firstGroup = Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// ); +// List secondGroup = Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// ); +// List thirdGroup = Lists.newArrayList( +// validRegistrationInfo() +// ); +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(firstGroup) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(secondGroup) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), +// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(thirdGroup) +// .build() +// )); +// +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// List firstGroupClientInfos = firstGroup.stream() +// .map(registrationInfo -> new OAuth2ClientInfo( +// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) +// .collect(Collectors.toList()); +// List secondGroupClientInfos = secondGroup.stream() +// .map(registrationInfo -> new OAuth2ClientInfo( +// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) +// .collect(Collectors.toList()); +// List thirdGroupClientInfos = thirdGroup.stream() +// .map(registrationInfo -> new OAuth2ClientInfo( +// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) +// .collect(Collectors.toList()); +// +// List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null); +// Assert.assertTrue(nonExistentDomainClients.isEmpty()); +// +// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); +// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); +// firstGroupClientInfos.forEach(firstGroupClientInfo -> { +// Assert.assertTrue( +// firstDomainHttpClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(firstGroupClientInfo.getName())) +// ); +// }); +// +// List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); +// Assert.assertTrue(firstDomainHttpsClients.isEmpty()); +// +// List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null, null); +// Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); +// secondGroupClientInfos.forEach(secondGroupClientInfo -> { +// Assert.assertTrue( +// fourthDomainHttpClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(secondGroupClientInfo.getName())) +// ); +// }); +// List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null, null); +// Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); +// secondGroupClientInfos.forEach(secondGroupClientInfo -> { +// Assert.assertTrue( +// fourthDomainHttpsClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(secondGroupClientInfo.getName())) +// ); +// }); +// +// List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); +// Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); +// firstGroupClientInfos.forEach(firstGroupClientInfo -> { +// Assert.assertTrue( +// secondDomainHttpClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(firstGroupClientInfo.getName())) +// ); +// }); +// secondGroupClientInfos.forEach(secondGroupClientInfo -> { +// Assert.assertTrue( +// secondDomainHttpClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(secondGroupClientInfo.getName())) +// ); +// }); +// +// List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null, null); +// Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); +// firstGroupClientInfos.forEach(firstGroupClientInfo -> { +// Assert.assertTrue( +// secondDomainHttpsClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(firstGroupClientInfo.getName())) +// ); +// }); +// thirdGroupClientInfos.forEach(thirdGroupClientInfo -> { +// Assert.assertTrue( +// secondDomainHttpsClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(thirdGroupClientInfo.getName())) +// ); +// }); +// } +// +// @Test +// public void testGetOAuth2ClientsForHttpAndHttps() { +// List firstGroup = Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// ); +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(firstGroup) +// .build() +// )); +// +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertNotNull(foundOAuth2Info); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// List firstGroupClientInfos = firstGroup.stream() +// .map(registrationInfo -> new OAuth2ClientInfo( +// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) +// .collect(Collectors.toList()); +// +// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); +// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); +// firstGroupClientInfos.forEach(firstGroupClientInfo -> { +// Assert.assertTrue( +// firstDomainHttpClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(firstGroupClientInfo.getName())) +// ); +// }); +// +// List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); +// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); +// firstGroupClientInfos.forEach(firstGroupClientInfo -> { +// Assert.assertTrue( +// firstDomainHttpsClients.stream().anyMatch(clientInfo -> +// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) +// && clientInfo.getName().equals(firstGroupClientInfo.getName())) +// ); +// }); +// } +// +// @Test +// public void testGetDisabledOAuth2Clients() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build() +// )); +// +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// +// List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); +// Assert.assertEquals(5, secondDomainHttpClients.size()); +// +// oAuth2Info.setEnabled(false); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// +// List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); +// Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); +// } +// +// @Test +// public void testFindAllRegistrations() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), +// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo() +// )) +// .build() +// )); +// +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// List foundRegistrations = oAuth2Service.findOauth2ProvidersByTenantId(); +// Assert.assertEquals(6, foundRegistrations.size()); +// oAuth2Info.getOauth2ParamsInfos().stream() +// .flatMap(paramsInfo -> paramsInfo.getClientRegistrations().stream()) +// .forEach(registrationInfo -> +// Assert.assertTrue( +// foundRegistrations.stream() +// .anyMatch(registration -> registration.getClientId().equals(registrationInfo.getClientId())) +// ) +// ); +// } +// +// @Test +// public void testFindRegistrationById() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), +// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo() +// )) +// .build() +// )); +// +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// List foundRegistrations = oAuth2Service.findOauth2ProvidersByTenantId(); +// foundRegistrations.forEach(registration -> { +// OAuth2Provider foundRegistration = oAuth2Service.findProvider(registration.getUuidId()); +// Assert.assertEquals(registration, foundRegistration); +// }); +// } +// +// @Test +// public void testFindAppSecret() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Lists.newArrayList( +// validMobileInfo("com.test.pkg1", "testPkg1AppSecret"), +// validMobileInfo("com.test.pkg2", "testPkg2AppSecret") +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build() +// )); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); +// Assert.assertEquals(3, firstDomainHttpClients.size()); +// for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) { +// String[] segments = clientInfo.getUrl().split("/"); +// String registrationId = segments[segments.length-1]; +// String appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg1"); +// Assert.assertNotNull(appSecret); +// Assert.assertEquals("testPkg1AppSecret", appSecret); +// appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg2"); +// Assert.assertNotNull(appSecret); +// Assert.assertEquals("testPkg2AppSecret", appSecret); +// appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg3"); +// Assert.assertNull(appSecret); +// } +// } +// +// @Test +// public void testFindClientsByPackageAndPlatform() { +// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Lists.newArrayList( +// validMobileInfo("com.test.pkg1", "testPkg1Callback"), +// validMobileInfo("com.test.pkg2", "testPkg2Callback") +// )) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo("Google", Arrays.asList(PlatformType.WEB, PlatformType.ANDROID)), +// validRegistrationInfo("Facebook", Arrays.asList(PlatformType.IOS)), +// validRegistrationInfo("GitHub", Collections.emptyList()) +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build() +// )); +// oAuth2Service.saveOAuth2Info(oAuth2Info); +// +// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); +// Assert.assertEquals(oAuth2Info, foundOAuth2Info); +// +// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); +// Assert.assertEquals(3, firstDomainHttpClients.size()); +// List pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); +// Assert.assertEquals(3, pkg1Clients.size()); +// List pkg1AndroidClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.ANDROID); +// Assert.assertEquals(2, pkg1AndroidClients.size()); +// Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("Google"))); +// Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); +// List pkg1IOSClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.IOS); +// Assert.assertEquals(2, pkg1IOSClients.size()); +// Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("Facebook"))); +// Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); +// } +// +// private OAuth2Info createDefaultOAuth2Info() { +// return new OAuth2Info(true, false, Lists.newArrayList( +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build(), +// OAuth2ParamsInfo.builder() +// .domainInfos(Lists.newArrayList( +// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), +// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() +// )) +// .mobileInfos(Collections.emptyList()) +// .clientRegistrations(Lists.newArrayList( +// validRegistrationInfo(), +// validRegistrationInfo() +// )) +// .build() +// )); +// } +// +// private OAuth2RegistrationInfo validRegistrationInfo() { +// return validRegistrationInfo(null, Collections.emptyList()); +// } +// +// private OAuth2RegistrationInfo validRegistrationInfo(String label, List platforms) { +// return OAuth2RegistrationInfo.builder() +// .clientId(UUID.randomUUID().toString()) +// .clientSecret(UUID.randomUUID().toString()) +// .authorizationUri(UUID.randomUUID().toString()) +// .accessTokenUri(UUID.randomUUID().toString()) +// .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) +// .platforms(platforms == null ? Collections.emptyList() : platforms) +// .userInfoUri(UUID.randomUUID().toString()) +// .userNameAttributeName(UUID.randomUUID().toString()) +// .jwkSetUri(UUID.randomUUID().toString()) +// .clientAuthenticationMethod(UUID.randomUUID().toString()) +// .loginButtonLabel(label != null ? label : UUID.randomUUID().toString()) +// .loginButtonIcon(UUID.randomUUID().toString()) +// .additionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) +// .mapperConfig( +// OAuth2MapperConfig.builder() +// .allowUserCreation(true) +// .activateUser(true) +// .type(MapperType.CUSTOM) +// .custom( +// OAuth2CustomMapperConfig.builder() +// .url(UUID.randomUUID().toString()) +// .build() +// ) +// .build() +// ) +// .build(); +// } +// +// private MobileAppInfo validMobileInfo(String pkgName, String appSecret) { +// return MobileAppInfo.builder().pkgName(pkgName) +// .appSecret(appSecret != null ? appSecret : StringUtils.randomAlphanumeric(24)) +// .build(); +// } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java deleted file mode 100644 index 281709c9af..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java +++ /dev/null @@ -1,668 +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; - -import com.google.common.collect.Lists; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.junit.jupiter.api.Assertions; -import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.oauth2.MapperType; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; -import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2MobileInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; -import org.thingsboard.server.common.data.oauth2.PlatformType; -import org.thingsboard.server.common.data.oauth2.SchemeType; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.oauth2.OAuth2Service; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.stream.Collectors; - -@DaoSqlTest -public class OAuth2ServiceTest extends AbstractServiceTest { - private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, false, Collections.emptyList()); - - @Autowired - protected OAuth2Service oAuth2Service; - - @Before - public void beforeRun() { - Assert.assertTrue(oAuth2Service.findAllRegistrations().isEmpty()); - } - - @After - public void after() { - oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); - Assert.assertTrue(oAuth2Service.findAllRegistrations().isEmpty()); - Assert.assertTrue(oAuth2Service.findOAuth2Info().getOauth2ParamsInfos().isEmpty()); - } - - @Test - public void testSaveHttpAndMixedDomainsTogether() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - Assertions.assertThrows(DataValidationException.class, () -> { - oAuth2Service.saveOAuth2Info(oAuth2Info); - }); - } - - @Test - public void testSaveHttpsAndMixedDomainsTogether() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - Assertions.assertThrows(DataValidationException.class, () -> { - oAuth2Service.saveOAuth2Info(oAuth2Info); - }); - } - - @Test - public void testCreateAndFindParams() { - OAuth2Info oAuth2Info = createDefaultOAuth2Info(); - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - // TODO ask if it's safe to check equality on AdditionalProperties - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - } - - @Test - public void testDisableParams() { - OAuth2Info oAuth2Info = createDefaultOAuth2Info(); - oAuth2Info.setEnabled(true); - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - oAuth2Info.setEnabled(false); - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundDisabledOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertEquals(oAuth2Info, foundDisabledOAuth2Info); - } - - @Test - public void testClearDomainParams() { - OAuth2Info oAuth2Info = createDefaultOAuth2Info(); - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); - OAuth2Info foundAfterClearClientsParams = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundAfterClearClientsParams); - Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); - } - - @Test - public void testUpdateClientsParams() { - OAuth2Info oAuth2Info = createDefaultOAuth2Info(); - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - OAuth2Info newOAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo() - )) - .build() - )); - oAuth2Service.saveOAuth2Info(newOAuth2Info); - OAuth2Info foundAfterUpdateOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundAfterUpdateOAuth2Info); - Assert.assertEquals(newOAuth2Info, foundAfterUpdateOAuth2Info); - } - - @Test - public void testGetOAuth2Clients() { - List firstGroup = Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - ); - List secondGroup = Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - ); - List thirdGroup = Lists.newArrayList( - validRegistrationInfo() - ); - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(firstGroup) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(secondGroup) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(thirdGroup) - .build() - )); - - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - List firstGroupClientInfos = firstGroup.stream() - .map(registrationInfo -> new OAuth2ClientInfo( - registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) - .collect(Collectors.toList()); - List secondGroupClientInfos = secondGroup.stream() - .map(registrationInfo -> new OAuth2ClientInfo( - registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) - .collect(Collectors.toList()); - List thirdGroupClientInfos = thirdGroup.stream() - .map(registrationInfo -> new OAuth2ClientInfo( - registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) - .collect(Collectors.toList()); - - List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null); - Assert.assertTrue(nonExistentDomainClients.isEmpty()); - - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); - Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); - firstGroupClientInfos.forEach(firstGroupClientInfo -> { - Assert.assertTrue( - firstDomainHttpClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) - && clientInfo.getName().equals(firstGroupClientInfo.getName())) - ); - }); - - List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); - Assert.assertTrue(firstDomainHttpsClients.isEmpty()); - - List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null, null); - Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); - secondGroupClientInfos.forEach(secondGroupClientInfo -> { - Assert.assertTrue( - fourthDomainHttpClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) - && clientInfo.getName().equals(secondGroupClientInfo.getName())) - ); - }); - List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null, null); - Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); - secondGroupClientInfos.forEach(secondGroupClientInfo -> { - Assert.assertTrue( - fourthDomainHttpsClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) - && clientInfo.getName().equals(secondGroupClientInfo.getName())) - ); - }); - - List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); - Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); - firstGroupClientInfos.forEach(firstGroupClientInfo -> { - Assert.assertTrue( - secondDomainHttpClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) - && clientInfo.getName().equals(firstGroupClientInfo.getName())) - ); - }); - secondGroupClientInfos.forEach(secondGroupClientInfo -> { - Assert.assertTrue( - secondDomainHttpClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) - && clientInfo.getName().equals(secondGroupClientInfo.getName())) - ); - }); - - List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null, null); - Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); - firstGroupClientInfos.forEach(firstGroupClientInfo -> { - Assert.assertTrue( - secondDomainHttpsClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) - && clientInfo.getName().equals(firstGroupClientInfo.getName())) - ); - }); - thirdGroupClientInfos.forEach(thirdGroupClientInfo -> { - Assert.assertTrue( - secondDomainHttpsClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon()) - && clientInfo.getName().equals(thirdGroupClientInfo.getName())) - ); - }); - } - - @Test - public void testGetOAuth2ClientsForHttpAndHttps() { - List firstGroup = Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - ); - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(firstGroup) - .build() - )); - - oAuth2Service.saveOAuth2Info(oAuth2Info); - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertNotNull(foundOAuth2Info); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - List firstGroupClientInfos = firstGroup.stream() - .map(registrationInfo -> new OAuth2ClientInfo( - registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) - .collect(Collectors.toList()); - - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); - Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); - firstGroupClientInfos.forEach(firstGroupClientInfo -> { - Assert.assertTrue( - firstDomainHttpClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) - && clientInfo.getName().equals(firstGroupClientInfo.getName())) - ); - }); - - List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); - Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); - firstGroupClientInfos.forEach(firstGroupClientInfo -> { - Assert.assertTrue( - firstDomainHttpsClients.stream().anyMatch(clientInfo -> - clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) - && clientInfo.getName().equals(firstGroupClientInfo.getName())) - ); - }); - } - - @Test - public void testGetDisabledOAuth2Clients() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - - oAuth2Service.saveOAuth2Info(oAuth2Info); - - List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); - Assert.assertEquals(5, secondDomainHttpClients.size()); - - oAuth2Info.setEnabled(false); - oAuth2Service.saveOAuth2Info(oAuth2Info); - - List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); - Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); - } - - @Test - public void testFindAllRegistrations() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo() - )) - .build() - )); - - oAuth2Service.saveOAuth2Info(oAuth2Info); - List foundRegistrations = oAuth2Service.findAllRegistrations(); - Assert.assertEquals(6, foundRegistrations.size()); - oAuth2Info.getOauth2ParamsInfos().stream() - .flatMap(paramsInfo -> paramsInfo.getClientRegistrations().stream()) - .forEach(registrationInfo -> - Assert.assertTrue( - foundRegistrations.stream() - .anyMatch(registration -> registration.getClientId().equals(registrationInfo.getClientId())) - ) - ); - } - - @Test - public void testFindRegistrationById() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo() - )) - .build() - )); - - oAuth2Service.saveOAuth2Info(oAuth2Info); - List foundRegistrations = oAuth2Service.findAllRegistrations(); - foundRegistrations.forEach(registration -> { - OAuth2Registration foundRegistration = oAuth2Service.findRegistration(registration.getUuidId()); - Assert.assertEquals(registration, foundRegistration); - }); - } - - @Test - public void testFindAppSecret() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Lists.newArrayList( - validMobileInfo("com.test.pkg1", "testPkg1AppSecret"), - validMobileInfo("com.test.pkg2", "testPkg2AppSecret") - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - oAuth2Service.saveOAuth2Info(oAuth2Info); - - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); - Assert.assertEquals(3, firstDomainHttpClients.size()); - for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) { - String[] segments = clientInfo.getUrl().split("/"); - String registrationId = segments[segments.length-1]; - String appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg1"); - Assert.assertNotNull(appSecret); - Assert.assertEquals("testPkg1AppSecret", appSecret); - appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg2"); - Assert.assertNotNull(appSecret); - Assert.assertEquals("testPkg2AppSecret", appSecret); - appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg3"); - Assert.assertNull(appSecret); - } - } - - @Test - public void testFindClientsByPackageAndPlatform() { - OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Lists.newArrayList( - validMobileInfo("com.test.pkg1", "testPkg1Callback"), - validMobileInfo("com.test.pkg2", "testPkg2Callback") - )) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo("Google", Arrays.asList(PlatformType.WEB, PlatformType.ANDROID)), - validRegistrationInfo("Facebook", Arrays.asList(PlatformType.IOS)), - validRegistrationInfo("GitHub", Collections.emptyList()) - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - oAuth2Service.saveOAuth2Info(oAuth2Info); - - OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); - Assert.assertEquals(oAuth2Info, foundOAuth2Info); - - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); - Assert.assertEquals(3, firstDomainHttpClients.size()); - List pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); - Assert.assertEquals(3, pkg1Clients.size()); - List pkg1AndroidClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.ANDROID); - Assert.assertEquals(2, pkg1AndroidClients.size()); - Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("Google"))); - Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); - List pkg1IOSClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.IOS); - Assert.assertEquals(2, pkg1IOSClients.size()); - Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("Facebook"))); - Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); - } - - private OAuth2Info createDefaultOAuth2Info() { - return new OAuth2Info(true, false, Lists.newArrayList( - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo(), - validRegistrationInfo() - )) - .build(), - OAuth2ParamsInfo.builder() - .domainInfos(Lists.newArrayList( - OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() - )) - .mobileInfos(Collections.emptyList()) - .clientRegistrations(Lists.newArrayList( - validRegistrationInfo(), - validRegistrationInfo() - )) - .build() - )); - } - - private OAuth2RegistrationInfo validRegistrationInfo() { - return validRegistrationInfo(null, Collections.emptyList()); - } - - private OAuth2RegistrationInfo validRegistrationInfo(String label, List platforms) { - return OAuth2RegistrationInfo.builder() - .clientId(UUID.randomUUID().toString()) - .clientSecret(UUID.randomUUID().toString()) - .authorizationUri(UUID.randomUUID().toString()) - .accessTokenUri(UUID.randomUUID().toString()) - .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) - .platforms(platforms == null ? Collections.emptyList() : platforms) - .userInfoUri(UUID.randomUUID().toString()) - .userNameAttributeName(UUID.randomUUID().toString()) - .jwkSetUri(UUID.randomUUID().toString()) - .clientAuthenticationMethod(UUID.randomUUID().toString()) - .loginButtonLabel(label != null ? label : UUID.randomUUID().toString()) - .loginButtonIcon(UUID.randomUUID().toString()) - .additionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) - .mapperConfig( - OAuth2MapperConfig.builder() - .allowUserCreation(true) - .activateUser(true) - .type(MapperType.CUSTOM) - .custom( - OAuth2CustomMapperConfig.builder() - .url(UUID.randomUUID().toString()) - .build() - ) - .build() - ) - .build(); - } - - private OAuth2MobileInfo validMobileInfo(String pkgName, String appSecret) { - return OAuth2MobileInfo.builder().pkgName(pkgName) - .appSecret(appSecret != null ? appSecret : StringUtils.randomAlphanumeric(24)) - .build(); - } - -} 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 8783770be3..1415edddc8 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 @@ -119,7 +119,6 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; -import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; @@ -2071,13 +2070,13 @@ 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 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 String getLoginProcessingUrl() { return restTemplate.getForEntity(baseURL + "/api/oauth2/loginProcessingUrl", String.class).getBody(); From 1429d6a97c931322270123ac30c9a452520bf05c Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 19 Jul 2024 11:25:09 +0300 Subject: [PATCH 02/29] renamed oauth2_registration table to oauth2_client --- .../main/data/upgrade/3.7.0/schema_update.sql | 34 +- .../server/controller/BaseController.java | 10 +- .../server/controller/DomainController.java | 19 +- .../controller/MobileAppController.java | 31 +- .../server/controller/OAuth2Controller.java | 41 +- .../edge/EdgeEventSourcingListener.java | 6 +- .../oauth2/OAuth2MsgConstructor.java | 6 +- .../rpc/fetch/OAuth2EdgeEventFetcher.java | 6 +- .../processor/oauth2/OAuth2EdgeProcessor.java | 10 +- .../domain/DefaultTbDomainService.java | 8 +- .../entitiy/domain/TbDomainService.java | 4 +- .../mobile/DefaultTbMobileAppService.java | 6 +- .../entitiy/mobile/TbMobileAppService.java | 4 +- .../DefaultTbOauth2ClientService.java | 28 +- .../oauth2client/TbOauth2ClientService.java | 10 +- .../oauth2/AbstractOAuth2ClientMapper.java | 5 +- .../auth/oauth2/AppleOAuth2ClientMapper.java | 4 +- .../auth/oauth2/BasicOAuth2ClientMapper.java | 4 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 4 +- .../auth/oauth2/GithubOAuth2ClientMapper.java | 4 +- .../auth/oauth2/OAuth2ClientMapper.java | 4 +- .../Oauth2AuthenticationSuccessHandler.java | 6 +- .../server/controller/HomePageApiTest.java | 10 +- .../server/edge/AbstractEdgeTest.java | 10 +- .../server/edge/OAuth2EdgeTest.java | 48 +- .../server/dao/domain/DomainService.java | 6 +- .../server/dao/mobile/MobileAppService.java | 5 +- .../dao/oauth2/OAuth2ClientService.java | 24 +- .../server/common/data/audit/ActionType.java | 3 +- .../server/common/data/domain/DomainInfo.java | 18 +- ...istration.java => DomainOauth2Client.java} | 6 +- .../common/data/id/EntityIdFactory.java | 2 +- ...egistrationId.java => OAuth2ClientId.java} | 8 +- .../common/data/mobile/MobileAppInfo.java | 12 +- ...ration.java => MobileAppOauth2Client.java} | 6 +- ...gistrations.java => HasOauth2Clients.java} | 6 +- ...th2Registration.java => OAuth2Client.java} | 20 +- .../common/data/oauth2/OAuth2ClientInfo.java | 43 +- .../data/oauth2/OAuth2ClientLoginInfo.java | 39 ++ .../data/oauth2/OAuth2MapperConfig.java | 2 + .../data/oauth2/OAuth2RegistrationInfo.java | 49 -- .../server/dao/domain/DomainDao.java | 11 +- .../server/dao/domain/DomainServiceImpl.java | 50 +- .../server/dao/mobile/MobileAppDao.java | 12 +- .../dao/mobile/MobileAppServiceImpl.java | 50 +- .../server/dao/model/ModelConstants.java | 16 +- ...va => DomainOauth2ClientCompositeKey.java} | 4 +- ...ity.java => DomainOauth2ClientEntity.java} | 34 +- ...=> MobileAppOauth2ClientCompositeKey.java} | 4 +- ....java => MobileAppOauth2ClientEntity.java} | 36 +- ...ionEntity.java => OAuth2ClientEntity.java} | 18 +- ...ntity.java => OAuth2ClientInfoEntity.java} | 24 +- .../HybridClientRegistrationRepository.java | 8 +- ...istrationDao.java => OAuth2ClientDao.java} | 19 +- .../dao/oauth2/OAuth2ClientServiceImpl.java | 96 +-- .../server/dao/oauth2/OAuth2Utils.java | 16 +- .../validator/MobileAppDataValidator.java | 1 - ...or.java => Oauth2ClientDataValidator.java} | 42 +- ...java => DomainOauth2ClientRepository.java} | 12 +- .../dao/sql/domain/DomainRepository.java | 2 +- .../server/dao/sql/domain/JpaDomainDao.java | 29 +- .../dao/sql/mobile/JpaMobileAppDao.java | 24 +- ...a => MobileAppOauth2ClientRepository.java} | 10 +- ...rationDao.java => JpaOAuth2ClientDao.java} | 45 +- .../sql/oauth2/OAuth2ClientRepository.java | 76 ++ .../oauth2/OAuth2RegistrationRepository.java | 76 -- .../main/resources/sql/schema-entities.sql | 17 +- .../dao/service/AbstractServiceTest.java | 43 ++ .../server/dao/service/DomainServiceTest.java | 131 ++++ .../dao/service/MobileAppServiceTest.java | 113 +++ .../dao/service/OAuth2ClientServiceTest.java | 662 ++---------------- .../thingsboard/rest/client/RestClient.java | 6 +- 72 files changed, 998 insertions(+), 1260 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/domain/{DomainOauth2Registration.java => DomainOauth2Client.java} (84%) rename common/data/src/main/java/org/thingsboard/server/common/data/id/{OAuth2RegistrationId.java => OAuth2ClientId.java} (77%) rename common/data/src/main/java/org/thingsboard/server/common/data/mobile/{MobileAppOauth2Registration.java => MobileAppOauth2Client.java} (84%) rename common/data/src/main/java/org/thingsboard/server/common/data/oauth2/{HasOauth2Registrations.java => HasOauth2Clients.java} (80%) rename common/data/src/main/java/org/thingsboard/server/common/data/oauth2/{OAuth2Registration.java => OAuth2Client.java} (89%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{DomainOauth2RegistrationCompositeKey.java => DomainOauth2ClientCompositeKey.java} (89%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{DomainOauth2RegistrationEntity.java => DomainOauth2ClientEntity.java} (53%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{MobileAppOauth2RegistrationCompositeKey.java => MobileAppOauth2ClientCompositeKey.java} (89%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{MobileAppOauth2RegistrationEntity.java => MobileAppOauth2ClientEntity.java} (56%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{OAuth2RegistrationEntity.java => OAuth2ClientEntity.java} (95%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{OAuth2RegistrationInfoEntity.java => OAuth2ClientInfoEntity.java} (63%) rename dao/src/main/java/org/thingsboard/server/dao/oauth2/{OAuth2RegistrationDao.java => OAuth2ClientDao.java} (55%) rename dao/src/main/java/org/thingsboard/server/dao/service/validator/{Oauth2RegistrationDataValidator.java => Oauth2ClientDataValidator.java} (62%) rename dao/src/main/java/org/thingsboard/server/dao/sql/domain/{DomainOauth2RegistrationRepository.java => DomainOauth2ClientRepository.java} (56%) rename dao/src/main/java/org/thingsboard/server/dao/sql/mobile/{MobileAppOauth2RegistrationRepository.java => MobileAppOauth2ClientRepository.java} (59%) rename dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/{JpaOAuth2RegistrationDao.java => JpaOAuth2ClientDao.java} (52%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java diff --git a/application/src/main/data/upgrade/3.7.0/schema_update.sql b/application/src/main/data/upgrade/3.7.0/schema_update.sql index caa98e6470..945051a3a2 100644 --- a/application/src/main/data/upgrade/3.7.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -18,28 +18,29 @@ ALTER TABLE IF EXISTS oauth2_mobile RENAME TO mobile_app; ALTER TABLE IF EXISTS oauth2_domain RENAME TO domain; +ALTER TABLE IF EXISTS oauth2_registration RENAME TO oauth2_client; ALTER TABLE domain ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, - ADD COLUMN IF NOT EXISTS propagate_to_edge boolean, + ADD COLUMN IF NOT EXISTS edge_enabled boolean, ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080', DROP COLUMN IF EXISTS domain_scheme; ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; -ALTER TABLE oauth2_registration ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; -ALTER TABLE oauth2_registration ADD COLUMN IF NOT EXISTS title varchar(100); +ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; +ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS title varchar(100); -CREATE TABLE IF NOT EXISTS domain_oauth2_registration ( +CREATE TABLE IF NOT EXISTS domain_oauth2_client ( domain_id uuid NOT NULL, - oauth2_registration_id uuid NOT NULL, + oauth2_client_id uuid NOT NULL, CONSTRAINT fk_domain FOREIGN KEY (domain_id) REFERENCES domain(id) ON DELETE CASCADE, - CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE + CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS mobile_app_oauth2_registration ( +CREATE TABLE IF NOT EXISTS mobile_app_oauth2_client ( mobile_app_id uuid NOT NULL, - oauth2_registration_id uuid NOT NULL, + oauth2_client_id uuid NOT NULL, CONSTRAINT fk_domain FOREIGN KEY (mobile_app_id) REFERENCES mobile_app(id) ON DELETE CASCADE, - CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE + CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE ); DO @@ -50,27 +51,28 @@ $$ DELETE FROM domain d1 USING domain d2 WHERE d1.created_time < d2.created_time AND d1.domain_name = d2.domain_name; UPDATE domain SET oauth2_enabled = p.enabled, - propagate_to_edge = p.edge_enabled + edge_enabled = p.edge_enabled FROM oauth2_params p WHERE p.id = domain.oauth2_params_id; UPDATE mobile_app SET oauth2_enabled = p.enabled FROM oauth2_params p WHERE p.id = mobile_app.oauth2_params_id; - INSERT INTO domain_oauth2_registration(domain_id, oauth2_registration_id) - (SELECT d.id, r.id FROM domain d LEFT JOIN oauth2_registration r on d.oauth2_params_id = r.oauth2_params_id + INSERT INTO domain_oauth2_client(domain_id, oauth2_client_id) + (SELECT d.id, r.id FROM domain d LEFT JOIN oauth2_client r on d.oauth2_params_id = r.oauth2_params_id WHERE r.platforms IS NULL OR r.platforms IN ('','WEB')); - INSERT INTO mobile_app_oauth2_registration(mobile_app_id, oauth2_registration_id) - (SELECT m.id, r.id FROM mobile_app m LEFT JOIN oauth2_registration r on m.oauth2_params_id = r.oauth2_params_id + INSERT INTO mobile_app_oauth2_client(mobile_app_id, oauth2_client_id) + (SELECT m.id, r.id FROM mobile_app m LEFT JOIN oauth2_client r on m.oauth2_params_id = r.oauth2_params_id WHERE r.platforms IS NULL OR r.platforms IN ('','ANDROID','IOS')); ALTER TABLE mobile_app RENAME CONSTRAINT oauth2_mobile_pkey TO mobile_app_pkey; ALTER TABLE domain RENAME CONSTRAINT oauth2_domain_pkey TO domain_pkey; - UPDATE oauth2_registration SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL; + ALTER TABLE oauth2_client RENAME CONSTRAINT oauth2_registration_pkey TO oauth2_client_pkey; + UPDATE oauth2_client SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL; ALTER TABLE domain DROP COLUMN oauth2_params_id; ALTER TABLE mobile_app DROP COLUMN oauth2_params_id; - ALTER TABLE oauth2_registration DROP COLUMN oauth2_params_id; + ALTER TABLE oauth2_client DROP COLUMN oauth2_params_id; ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name); ALTER TABLE domain ADD CONSTRAINT domain_unq_key UNIQUE (domain_name); 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 1dbee4abce..a3d4243411 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -82,7 +82,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RpcId; @@ -96,7 +96,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -615,7 +615,7 @@ public abstract class BaseController { checkQueueId(new QueueId(entityId.getId()), operation); return; case OAUTH2_CLIENT: - checkOauth2ClientId(new OAuth2RegistrationId(entityId.getId()), operation); + checkOauth2ClientId(new OAuth2ClientId(entityId.getId()), operation); return; case DOMAIN: checkDomainId(new DomainId(entityId.getId()), operation); @@ -799,8 +799,8 @@ public abstract class BaseController { return queue; } - OAuth2Registration checkOauth2ClientId(OAuth2RegistrationId oAuth2RegistrationId, Operation operation) throws ThingsboardException { - return checkEntityId(oAuth2RegistrationId, oAuth2ClientService::findOAuth2ClientById, operation); + OAuth2Client checkOauth2ClientId(OAuth2ClientId oAuth2ClientId, Operation operation) throws ThingsboardException { + return checkEntityId(oAuth2ClientId, oAuth2ClientService::findOAuth2ClientById, operation); } Domain checkDomainId(DomainId domainId, Operation operation) throws ThingsboardException { 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 c17fbfd709..3b9001695d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DomainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DomainController.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.domain.TbDomainService; @@ -47,7 +47,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -77,9 +76,9 @@ public class DomainController extends BaseController { List oauth2ClientIds = ids != null ? Arrays.asList(ids) : Collections.emptyList(); domain.setTenantId(getCurrentUser().getTenantId()); checkEntity(domain.getId(), domain, Resource.DOMAIN); - List oAuth2ClientIds = new ArrayList<>(); + List oAuth2ClientIds = new ArrayList<>(); for (String id : oauth2ClientIds) { - OAuth2RegistrationId oauth2ClientId = new OAuth2RegistrationId(toUUID(id)); + OAuth2ClientId oauth2ClientId = new OAuth2ClientId(toUUID(id)); checkOauth2ClientId(oauth2ClientId, Operation.READ); oAuth2ClientIds.add(oauth2ClientId); } @@ -96,19 +95,19 @@ public class DomainController extends BaseController { Domain domain = null; try { domain = checkDomainId(domainId, Operation.WRITE); - List oAuth2ClientIds = new ArrayList<>(); + List oAuth2ClientIds = new ArrayList<>(); for (UUID outh2CLientId : oauth2ClientIds) { - OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(outh2CLientId); - checkEntityId(oAuth2RegistrationId, Operation.READ); - oAuth2ClientIds.add(oAuth2RegistrationId); + OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(outh2CLientId); + checkEntityId(oAuth2ClientId, Operation.READ); + oAuth2ClientIds.add(oAuth2ClientId); } domainService.updateOauth2Clients(getTenantId(), domainId, oAuth2ClientIds); logEntityActionService.logEntityAction(domain.getTenantId(), domain.getId(), domain, - UPDATED_OAUTH2_CLIENTS, getCurrentUser(), oAuth2ClientIds.toString()); + ActionType.UPDATED, getCurrentUser(), oAuth2ClientIds.toString()); } catch (Exception e) { if (domain != null) { logEntityActionService.logEntityAction(getTenantId(), domainId, domain, - ActionType.UPDATED_OAUTH2_CLIENTS, getCurrentUser(), e); + ActionType.UPDATED, getCurrentUser(), e); } throw e; } 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 f245ecfdd1..06a80d71bc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -30,11 +30,9 @@ 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.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.mobile.MobileAppInfo; @@ -47,7 +45,6 @@ import java.util.ArrayList; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @@ -73,41 +70,39 @@ public class MobileAppController extends BaseController { @Parameter(description = "A JSON value representing the Domain.", required = true) @RequestBody MobileApp mobileApp, @Parameter(description = "A list of entity group ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string"))) - @RequestParam(name = "oauth2RegistrationIds", required = false) UUID[] oauth2RegistrationIds) throws Exception { + @RequestParam(name = "oauth2ClientIds", required = false) UUID[] oauth2ClientIds) throws Exception { mobileApp.setTenantId(getCurrentUser().getTenantId()); - List oAuth2Registrations = new ArrayList<>(); - for (UUID id : oauth2RegistrationIds) { - OAuth2RegistrationId oauth2ClientId = new OAuth2RegistrationId(id); + List oAuth2Clients = new ArrayList<>(); + for (UUID id : oauth2ClientIds) { + OAuth2ClientId oauth2ClientId = new OAuth2ClientId(id); checkOauth2ClientId(oauth2ClientId, Operation.READ); - oAuth2Registrations.add(oauth2ClientId); + oAuth2Clients.add(oauth2ClientId); } - return tbMobileAppService.save(mobileApp, oAuth2Registrations, getCurrentUser()); + return tbMobileAppService.save(mobileApp, oAuth2Clients, getCurrentUser()); } @ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)", notes = "Update oauth2 clients to the specified mobile app. ") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") - @PostMapping(value = "/mobileApp/{id}/updateOauth2Clients") + @PostMapping(value = "/mobileApp/{id}/oauth2Clients") public void updateOauth2Clients(@PathVariable UUID id, @RequestBody UUID[] oauth2ClientIds) throws ThingsboardException { MobileAppId mobileAppId = new MobileAppId(id); MobileApp mobileApp = null; try { mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE); - List oAuth2ClientIds = new ArrayList<>(); + List oAuth2ClientIds = new ArrayList<>(); for (UUID outh2CLientId : oauth2ClientIds) { - OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(outh2CLientId); - checkEntityId(oAuth2RegistrationId, Operation.READ); - oAuth2ClientIds.add(oAuth2RegistrationId); + OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(outh2CLientId); + checkEntityId(oAuth2ClientId, Operation.READ); + oAuth2ClientIds.add(oAuth2ClientId); } mobileAppService.updateOauth2Clients(getTenantId(), mobileAppId, oAuth2ClientIds); - logEntityActionService.logEntityAction(getTenantId(), mobileAppId, mobileApp, - UPDATED_OAUTH2_CLIENTS, getCurrentUser(), oAuth2ClientIds.toString()); } catch (Exception e) { if (mobileApp != null) { logEntityActionService.logEntityAction(getTenantId(), mobileAppId, mobileApp, - ActionType.UPDATED_OAUTH2_CLIENTS, getCurrentUser(), e); + ActionType.UPDATED, getCurrentUser(), e); } throw e; } 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 1b64e7eb60..8739c492cf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -20,7 +20,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; @@ -34,11 +33,11 @@ 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.OAuth2RegistrationId; +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.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; @@ -71,11 +70,11 @@ public class OAuth2Controller extends BaseController { "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") - public List getOAuth2Clients(HttpServletRequest request, - @Parameter(description = "Mobile application package name, to find OAuth2 clients " + + 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, - @Parameter(description = "Platform type to search OAuth2 clients for which " + + @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"})) @@ -96,35 +95,35 @@ public class OAuth2Controller extends BaseController { } } if (StringUtils.isNotEmpty(pkgName)) { - return oAuth2ClientService.getMobileOAuth2Clients(pkgName, platformType); + return oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(pkgName, platformType); } else { - return oAuth2ClientService.getWebOAuth2Clients(MiscUtils.getDomainNameAndPort(request), platformType); + return oAuth2ClientService.findOAuth2ClientLoginInfosByDomainName(MiscUtils.getDomainNameAndPort(request)); } } @ApiOperation(value = "Save OAuth2 Client Registration (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/oauth2/client") - public OAuth2Registration saveOAuth2Client(@RequestBody OAuth2Registration oAuth2Registration) throws Exception { + public OAuth2Client saveOAuth2Client(@RequestBody OAuth2Client oAuth2Client) throws Exception { TenantId tenantId = getTenantId(); - oAuth2Registration.setTenantId(tenantId); - checkEntity(oAuth2Registration.getId(), oAuth2Registration, Resource.OAUTH2_CLIENT); - return tbOauth2ClientService.save(oAuth2Registration, getCurrentUser()); + oAuth2Client.setTenantId(tenantId); + checkEntity(oAuth2Client.getId(), oAuth2Client, Resource.OAUTH2_CLIENT); + return tbOauth2ClientService.save(oAuth2Client, getCurrentUser()); } @ApiOperation(value = "Get OAuth2 Client Registration infos (findTenantOAuth2ClientInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/oauth2/client/infos") - public List findTenantOAuth2ClientInfos() throws ThingsboardException { - return oAuth2ClientService.findOauth2ClientInfosByTenantId(getTenantId()); + public List findTenantOAuth2ClientInfos() throws ThingsboardException { + return oAuth2ClientService.findOAuth2ClientInfosByTenantId(getTenantId()); } @ApiOperation(value = "Get OAuth2 Client Registration by id (getOAuth2ClientById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/oauth2/client/{id}") - public OAuth2Registration getOAuth2ClientById(@PathVariable UUID id) throws ThingsboardException { - OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(id); - return checkEntityId(oAuth2RegistrationId, oAuth2ClientService::findOAuth2ClientById, Operation.READ); + public OAuth2Client getOAuth2ClientById(@PathVariable UUID id) throws ThingsboardException { + OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(id); + return checkEntityId(oAuth2ClientId, oAuth2ClientService::findOAuth2ClientById, Operation.READ); } @ApiOperation(value = "Delete oauth2 client (deleteAsset)", @@ -133,9 +132,9 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/oauth2/client/{id}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteOauth2Client(@PathVariable UUID id) throws Exception { - OAuth2RegistrationId oAuth2RegistrationId = new OAuth2RegistrationId(id); - OAuth2Registration oAuth2Registration = checkOauth2ClientId(oAuth2RegistrationId, Operation.DELETE); - tbOauth2ClientService.delete(oAuth2Registration, getCurrentUser()); + OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(id); + OAuth2Client oAuth2Client = checkOauth2ClientId(oAuth2ClientId, Operation.DELETE); + tbOauth2ClientService.delete(oAuth2Client, getCurrentUser()); } @ApiOperation(value = "Get OAuth2 log in processing URL (getLoginProcessingUrl)", notes = "Returns the URL enclosed in " + diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 2a11c7df29..075a98cd35 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; @@ -227,7 +227,7 @@ public class EdgeEventSourcingListener { private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) { if (entity instanceof AlarmComment) { return EdgeEventType.ALARM_COMMENT; - } else if (entity instanceof OAuth2Registration) { + } else if (entity instanceof OAuth2Client) { return EdgeEventType.OAUTH2_CLIENT; } return null; @@ -236,7 +236,7 @@ public class EdgeEventSourcingListener { private String getBodyMsgForEntityEvent(Object entity) { if (entity instanceof AlarmComment) { return JacksonUtil.toString(entity); - } else if (entity instanceof OAuth2Registration) { + } else if (entity instanceof OAuth2Client) { return JacksonUtil.toString(entity); } return null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java index 8c734da83c..3c3f1a378c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java @@ -17,7 +17,7 @@ package org.thingsboard.server.service.edge.rpc.constructor.oauth2; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -25,8 +25,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class OAuth2MsgConstructor { - public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Registration oAuth2Registration) { - return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Registration)).build(); + public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Client oAuth2Client) { + return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Client)).build(); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java index e491f8bf2e..a0ffaa317f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; @@ -46,9 +46,9 @@ public class OAuth2EdgeEventFetcher implements EdgeEventFetcher { @Override public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { List result = new ArrayList<>(); - List oauth2Registrations = oAuth2ClientService.findOauth2ClientsByTenantId(TenantId.SYS_TENANT_ID); + List oauth2Clients = oAuth2ClientService.findOAuth2ClientsByTenantId(TenantId.SYS_TENANT_ID); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2_CLIENT, - EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oauth2Registrations))); + EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oauth2Clients))); // returns PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java index a6bcb663a2..8942eb7cf6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; @@ -39,9 +39,9 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { public DownlinkMsg convertOAuth2ProviderEventToDownlink(EdgeEvent edgeEvent) { DownlinkMsg downlinkMsg = null; - OAuth2Registration oAuth2Registration = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Registration.class); - if (oAuth2Registration != null) { - OAuth2UpdateMsg oAuth2ProviderUpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Registration); + OAuth2Client oAuth2Client = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Client.class); + if (oAuth2Client != null) { + OAuth2UpdateMsg oAuth2ProviderUpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Client); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addOAuth2UpdateMsg(oAuth2ProviderUpdateMsg) @@ -51,7 +51,7 @@ public class OAuth2EdgeProcessor extends BaseEdgeProcessor { } public ListenableFuture processOAuth2Notification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - OAuth2Registration oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Registration.class); + OAuth2Client oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Client.class); if (oAuth2Info == null) { return Futures.immediateFuture(null); } 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 882a444aed..1abb6215d0 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 @@ -24,15 +24,13 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.List; -import static org.thingsboard.server.common.data.audit.ActionType.UPDATED_OAUTH2_CLIENTS; - @Service @AllArgsConstructor public class DefaultTbDomainService extends AbstractTbEntityService implements TbDomainService { @@ -40,7 +38,7 @@ public class DefaultTbDomainService extends AbstractTbEntityService implements T private final DomainService domainService; @Override - public Domain save(Domain domain, List oAuth2Clients, User user) throws Exception { + public Domain save(Domain domain, List oAuth2Clients, User user) throws Exception { ActionType actionType = domain.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = domain.getTenantId(); try { @@ -48,8 +46,6 @@ public class DefaultTbDomainService extends AbstractTbEntityService implements T logEntityActionService.logEntityAction(tenantId, savedDomain.getId(), domain, actionType, user); if (!CollectionUtils.isEmpty(oAuth2Clients)) { domainService.updateOauth2Clients(domain.getTenantId(), savedDomain.getId(), oAuth2Clients); - logEntityActionService.logEntityAction(domain.getTenantId(), savedDomain.getId(), savedDomain, - UPDATED_OAUTH2_CLIENTS, user, oAuth2Clients.toString()); } return savedDomain; } catch (Exception 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 4170558e52..0099c845a5 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 @@ -17,13 +17,13 @@ package org.thingsboard.server.service.entitiy.domain; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.domain.Domain; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import java.util.List; public interface TbDomainService { - Domain save(Domain domain, List oAuth2Clients, User user) throws Exception; + Domain save(Domain domain, List oAuth2Clients, User user) throws Exception; 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 6865a837c0..75243aefcd 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 @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.dao.mobile.MobileAppService; @@ -38,7 +38,7 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement private final MobileAppService mobileAppService; @Override - public MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception { + public MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception { ActionType actionType = mobileApp.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = mobileApp.getTenantId(); try { @@ -46,8 +46,6 @@ public class DefaultTbMobileAppService extends AbstractTbEntityService implement logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), mobileApp, actionType, user); if (!CollectionUtils.isEmpty(oauth2Clients)) { mobileAppService.updateOauth2Clients(tenantId, savedMobileApp.getId(), oauth2Clients); - logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), savedMobileApp, - ActionType.UPDATED_OAUTH2_CLIENTS, user, oauth2Clients.toString()); } return savedMobileApp; } catch (Exception 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 ebcdfc3cd0..fdd5c8b353 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 @@ -16,14 +16,14 @@ package org.thingsboard.server.service.entitiy.mobile; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.mobile.MobileApp; import java.util.List; public interface TbMobileAppService { - MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception; + MobileApp save(MobileApp mobileApp, List oauth2Clients, User user) throws Exception; 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 e24f173c6a..5c55edfc60 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 @@ -20,9 +20,9 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; @@ -33,30 +33,30 @@ public class DefaultTbOauth2ClientService extends AbstractTbEntityService implem private final OAuth2ClientService oAuth2ClientService; @Override - public OAuth2Registration save(OAuth2Registration oAuth2Registration, User user) throws Exception { - ActionType actionType = oAuth2Registration.getId() == null ? ActionType.ADDED : ActionType.UPDATED; - TenantId tenantId = oAuth2Registration.getTenantId(); + public OAuth2Client save(OAuth2Client oAuth2Client, User user) throws Exception { + ActionType actionType = oAuth2Client.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = oAuth2Client.getTenantId(); try { - OAuth2Registration savedRegistration = checkNotNull(oAuth2ClientService.saveOAuth2Client(tenantId, oAuth2Registration)); - logEntityActionService.logEntityAction(tenantId, savedRegistration.getId(), oAuth2Registration, actionType, user); + OAuth2Client savedRegistration = checkNotNull(oAuth2ClientService.saveOAuth2Client(tenantId, oAuth2Client)); + logEntityActionService.logEntityAction(tenantId, savedRegistration.getId(), oAuth2Client, actionType, user); return savedRegistration; } catch (Exception e) { - logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), oAuth2Registration, actionType, user, e); + logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), oAuth2Client, actionType, user, e); throw e; } } @Override - public void delete(OAuth2Registration oAuth2Registration, User user) { + public void delete(OAuth2Client oAuth2Client, User user) { ActionType actionType = ActionType.DELETED; - TenantId tenantId = oAuth2Registration.getTenantId(); - OAuth2RegistrationId oAuth2RegistrationId = oAuth2Registration.getId(); + TenantId tenantId = oAuth2Client.getTenantId(); + OAuth2ClientId oAuth2ClientId = oAuth2Client.getId(); try { - oAuth2ClientService.deleteById(tenantId, oAuth2RegistrationId); - logEntityActionService.logEntityAction(tenantId, oAuth2RegistrationId, oAuth2Registration, actionType, user, oAuth2Registration.getName()); + oAuth2ClientService.deleteOAuth2ClientById(tenantId, oAuth2ClientId); + logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user, oAuth2Client.getName()); } catch (Exception e) { logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), actionType, user, e, - oAuth2RegistrationId.toString()); + oAuth2ClientId.toString()); throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java index de6d45a6c0..c4ac3c7839 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java @@ -16,16 +16,12 @@ package org.thingsboard.server.service.entitiy.oauth2client; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.domain.Domain; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; - -import java.util.List; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; public interface TbOauth2ClientService { - OAuth2Registration save(OAuth2Registration oAuth2Registration, User user) throws Exception; + OAuth2Client save(OAuth2Client oAuth2Client, User user) throws Exception; - void delete(OAuth2Registration oAuth2Registration, User user); + void delete(OAuth2Client oAuth2Client, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 22d3b9b5ea..0182d5026c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -35,14 +35,13 @@ import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; @@ -98,7 +97,7 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Client registration) { OAuth2MapperConfig config = registration.getMapperConfig(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java index 26935ad0ee..0e4ee821f7 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java @@ -24,7 +24,7 @@ import org.springframework.util.MultiValueMap; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; @@ -45,7 +45,7 @@ public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen private static final String EMAIL = "email"; @Override - public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes()); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 693dfa5434..eac9f945eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; @@ -33,7 +33,7 @@ import java.util.Map; public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @Override - public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map attributes = token.getPrincipal().getAttributes(); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index ed1d5cfd6d..6854292ebb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -25,7 +25,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; @@ -42,7 +42,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client registration) { OAuth2MapperConfig config = registration.getMapperConfig(); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index 1b42946ad6..0cdc414c53 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -24,7 +24,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -49,7 +49,7 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private OAuth2Configuration oAuth2Configuration; @Override - public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index cc877f36d4..2d90d82fc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,11 +16,11 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.service.security.model.SecurityUser; import jakarta.servlet.http.HttpServletRequest; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); + SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client registration); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index b6e0d39ec6..85a9079af8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -28,9 +28,9 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -97,7 +97,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2Registration registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2RegistrationId(UUID.fromString(token.getAuthorizedClientRegistrationId()))); + OAuth2Client registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(token.getAuthorizedClientRegistrationId()))); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); diff --git a/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java index 8c460d0845..e796c7346a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java @@ -40,7 +40,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.ApiUsageStateFilter; import org.thingsboard.server.common.data.query.EntityCountQuery; @@ -492,9 +492,9 @@ public class HomePageApiTest extends AbstractControllerTest { return domain; } - private OAuth2Registration validRegistration() { - OAuth2Registration oAuth2Registration = new OAuth2Registration(); - oAuth2Registration.setClientId(UUID.randomUUID().toString()); + private OAuth2Client validRegistration() { + OAuth2Client oAuth2Client = new OAuth2Client(); + oAuth2Client.setClientId(UUID.randomUUID().toString()); // .clientSecret(UUID.randomUUID().toString()) // .authorizationUri(UUID.randomUUID().toString()) // .accessTokenUri(UUID.randomUUID().toString()) @@ -516,6 +516,6 @@ public class HomePageApiTest extends AbstractControllerTest { // .build() // ) // .build(); - return oAuth2Registration; + return oAuth2Client; } } diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index fa17ec6865..f2a4c40351 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -63,7 +63,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; @@ -545,11 +545,11 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Optional oAuth2UpdateMsgOpt = edgeImitator.findMessageByType(OAuth2UpdateMsg.class); Assert.assertTrue(oAuth2UpdateMsgOpt.isPresent()); OAuth2UpdateMsg oAuth2ProviderUpdateMsg = oAuth2UpdateMsgOpt.get(); - OAuth2Registration oAuth2Registration = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); - Assert.assertNotNull(oAuth2Registration); - OAuth2Registration auth2Info = doGet("/api/oauth2/config", OAuth2Registration.class); + OAuth2Client oAuth2Client = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Client.class, true); + Assert.assertNotNull(oAuth2Client); + OAuth2Client auth2Info = doGet("/api/oauth2/config", OAuth2Client.class); Assert.assertNotNull(auth2Info); - Assert.assertEquals(oAuth2Registration, auth2Info); + Assert.assertEquals(oAuth2Client, auth2Info); testAutoGeneratedCodeByProtobuf(oAuth2ProviderUpdateMsg); } diff --git a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java index f3e5e30457..68a8608fc1 100644 --- a/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java @@ -22,7 +22,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg; @@ -40,47 +40,47 @@ public class OAuth2EdgeTest extends AbstractEdgeTest { // enable oauth edgeImitator.allowIgnoredTypes(); edgeImitator.expectMessageAmount(1); - OAuth2Registration oAuth2Registration = createDefaultOAuth2Info(); - oAuth2Registration = doPost("/api/oauth2/config", oAuth2Registration, OAuth2Registration.class); + OAuth2Client oAuth2Client = createDefaultOAuth2Info(); + oAuth2Client = doPost("/api/oauth2/config", oAuth2Client, OAuth2Client.class); Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg); OAuth2UpdateMsg oAuth2ProviderUpdateMsg = (OAuth2UpdateMsg) latestMessage; - OAuth2Registration result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); - Assert.assertEquals(oAuth2Registration, result); + OAuth2Client result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Client.class, true); + Assert.assertEquals(oAuth2Client, result); // disable oauth support edgeImitator.expectMessageAmount(1); - doPost("/api/oauth2/config", oAuth2Registration, OAuth2Registration.class); + doPost("/api/oauth2/config", oAuth2Client, OAuth2Client.class); Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg); oAuth2ProviderUpdateMsg = (OAuth2UpdateMsg) latestMessage; - result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Registration.class, true); - Assert.assertEquals(oAuth2Registration, result); + result = JacksonUtil.fromString(oAuth2ProviderUpdateMsg.getEntity(), OAuth2Client.class, true); + Assert.assertEquals(oAuth2Client, result); edgeImitator.ignoreType(OAuth2UpdateMsg.class); loginTenantAdmin(); } - private OAuth2Registration createDefaultOAuth2Info() { + private OAuth2Client createDefaultOAuth2Info() { return validRegistrationInfo(); } - private OAuth2Registration validRegistrationInfo() { - OAuth2Registration oAuth2Registration = new OAuth2Registration(); - oAuth2Registration.setClientId(UUID.randomUUID().toString()); - oAuth2Registration.setClientSecret(UUID.randomUUID().toString()); - oAuth2Registration.setAuthorizationUri(UUID.randomUUID().toString()); - oAuth2Registration.setAccessTokenUri(UUID.randomUUID().toString()); - oAuth2Registration.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())); - oAuth2Registration.setPlatforms(Collections.emptyList()); - oAuth2Registration.setUserInfoUri(UUID.randomUUID().toString()); - oAuth2Registration.setUserNameAttributeName(UUID.randomUUID().toString()); - oAuth2Registration.setJwkSetUri(UUID.randomUUID().toString()); - oAuth2Registration.setClientAuthenticationMethod(UUID.randomUUID().toString()); - oAuth2Registration.setLoginButtonLabel(UUID.randomUUID().toString()); - oAuth2Registration.setMapperConfig( + private OAuth2Client validRegistrationInfo() { + OAuth2Client oAuth2Client = new OAuth2Client(); + 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(Collections.emptyList()); + 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.setMapperConfig( OAuth2MapperConfig.builder() .type(MapperType.CUSTOM) .custom( @@ -89,7 +89,7 @@ public class OAuth2EdgeTest extends AbstractEdgeTest { .build() ) .build()); - return oAuth2Registration; + return oAuth2Client; } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java index 84ab33fbce..312e6391c6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java @@ -18,12 +18,11 @@ package org.thingsboard.server.dao.domain; import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; -import java.util.UUID; public interface DomainService extends EntityDaoService { @@ -39,6 +38,7 @@ public interface DomainService extends EntityDaoService { boolean isOauth2Enabled(TenantId tenantId); - void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds); + void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds); + void deleteDomainsByTenantId(TenantId tenantId); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java index da26f2f41a..076198823c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.mobile; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.mobile.MobileAppInfo; @@ -36,6 +36,7 @@ public interface MobileAppService extends EntityDaoService { MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId); - void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds); + void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds); + void deleteMobileAppsByTenantId(TenantId tenantId); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java index dd9921d5d1..232c9cf1a3 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +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.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.dao.entity.EntityDaoService; @@ -28,19 +28,21 @@ import java.util.UUID; public interface OAuth2ClientService extends EntityDaoService { - List getWebOAuth2Clients(String domainName, PlatformType platformType); + List findOAuth2ClientLoginInfosByDomainName(String domainName); - List getMobileOAuth2Clients(String pkgName, PlatformType platformType); + List findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType); - List findOauth2ClientInfosByTenantId(TenantId tenantId); + List findOAuth2ClientsByTenantId(TenantId tenantId); - List findOauth2ClientsByTenantId(TenantId tenantId); + OAuth2Client saveOAuth2Client(TenantId tenantId, OAuth2Client oAuth2Client); - OAuth2Registration saveOAuth2Client(TenantId tenantId, OAuth2Registration oAuth2Registration); - - OAuth2Registration findOAuth2ClientById(TenantId tenantId, OAuth2RegistrationId providerId); + OAuth2Client findOAuth2ClientById(TenantId tenantId, OAuth2ClientId providerId); String findAppSecret(UUID registrationId, String pkgName); - void deleteById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId); + void deleteOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId); + + void deleteOauth2ClientsByTenantId(TenantId tenantId); + + List findOAuth2ClientInfosByTenantId(TenantId tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index edcc216d1e..d3c0889d6b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -57,8 +57,7 @@ public enum ActionType { ADDED_COMMENT(false, TbMsgType.COMMENT_CREATED), UPDATED_COMMENT(false, TbMsgType.COMMENT_UPDATED), DELETED_COMMENT(false, null), - SMS_SENT(false, null), - UPDATED_OAUTH2_CLIENTS(false, null); + SMS_SENT(false, null); @Getter private final boolean isRead; 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 edca39a828..98925cfeb0 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,29 +16,23 @@ package org.thingsboard.server.common.data.domain; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.thingsboard.server.common.data.oauth2.HasOauth2Registrations; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.oauth2.HasOauth2Clients; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import java.util.List; @EqualsAndHashCode(callSuper = true) @Data -@ToString -@AllArgsConstructor -@Builder @Schema -public class DomainInfo extends Domain implements HasOauth2Registrations { +public class DomainInfo extends Domain implements HasOauth2Clients { @Schema(description = "List of available oauth2 client registration") - private List oauth2RegistrationInfos; + private List oauth2ClientInfos; - public DomainInfo(Domain domain, List oauth2RegistrationInfos) { + public DomainInfo(Domain domain, List oauth2ClientInfos) { super(domain); - this.oauth2RegistrationInfos = oauth2RegistrationInfos; + this.oauth2ClientInfos = oauth2ClientInfos; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Client.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Client.java index 13b3b15093..11c872c8be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Client.java @@ -19,14 +19,14 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; @Data @NoArgsConstructor @AllArgsConstructor -public class DomainOauth2Registration { +public class DomainOauth2Client { private DomainId domainId; - private OAuth2RegistrationId oAuth2RegistrationId; + private OAuth2ClientId oAuth2ClientId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 48e841fb46..cbdaae3681 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -106,7 +106,7 @@ public class EntityIdFactory { case QUEUE_STATS: return new QueueStatsId(uuid); case OAUTH2_CLIENT: - return new OAuth2RegistrationId(uuid); + return new OAuth2ClientId(uuid); case MOBILE_APP: return new MobileAppId(uuid); case DOMAIN: diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java similarity index 77% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java index 5b363d6cfd..f29067a6c9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java @@ -21,15 +21,15 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class OAuth2RegistrationId extends UUIDBased implements EntityId { +public class OAuth2ClientId extends UUIDBased implements EntityId { @JsonCreator - public OAuth2RegistrationId(@JsonProperty("id") UUID id) { + public OAuth2ClientId(@JsonProperty("id") UUID id) { super(id); } - public static OAuth2RegistrationId fromString(String oauth2RegistrationId) { - return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId)); + public static OAuth2ClientId fromString(String oauth2ClientId) { + return new OAuth2ClientId(UUID.fromString(oauth2ClientId)); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java index 519fd7bf23..541f1e6753 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java @@ -22,8 +22,8 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; -import org.thingsboard.server.common.data.oauth2.HasOauth2Registrations; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.oauth2.HasOauth2Clients; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import java.util.List; @@ -34,14 +34,14 @@ import java.util.List; @AllArgsConstructor @Builder @Schema -public class MobileAppInfo extends MobileApp implements HasOauth2Registrations { +public class MobileAppInfo extends MobileApp implements HasOauth2Clients { @Schema(description = "List of available oauth2 client registrations") - private List oauth2RegistrationInfos; + private List oauth2ClientInfos; - public MobileAppInfo(MobileApp mobileApp, List oauth2RegistrationInfos) { + public MobileAppInfo(MobileApp mobileApp, List oauth2ClientInfos) { super(mobileApp); - this.oauth2RegistrationInfos = oauth2RegistrationInfos; + this.oauth2ClientInfos = oauth2ClientInfos; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java similarity index 84% rename from common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java index b309aafe32..2be75db92f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java @@ -19,14 +19,14 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; @Data @NoArgsConstructor @AllArgsConstructor -public class MobileAppOauth2Registration { +public class MobileAppOauth2Client { private MobileAppId mobileAppId; - private OAuth2RegistrationId oAuth2RegistrationId; + private OAuth2ClientId oAuth2ClientId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java similarity index 80% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java index 51524f458d..86cdfd1d92 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java @@ -16,13 +16,11 @@ package org.thingsboard.server.common.data.oauth2; -import org.thingsboard.server.common.data.EntityInfo; - import java.io.Serializable; import java.util.List; -public interface HasOauth2Registrations extends Serializable { +public interface HasOauth2Clients extends Serializable { - List getOauth2RegistrationInfos(); + List getOauth2ClientInfos(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java index 5a4ec293be..a8a5f0eacd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java @@ -18,6 +18,9 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -25,7 +28,7 @@ import lombok.ToString; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import java.util.List; @@ -34,33 +37,43 @@ import java.util.List; @Data @ToString(exclude = {"clientSecret"}) @NoArgsConstructor -public class OAuth2Registration extends BaseDataWithAdditionalInfo implements HasName, HasTenantId { +public class OAuth2Client extends BaseDataWithAdditionalInfo implements HasName, HasTenantId { @Schema(description = "JSON object with Tenant Id") private TenantId tenantId; @Schema(description = "Oauth2 client title") + @NotBlank private String title; @Schema(description = "Config for mapping OAuth2 log in response to platform entities", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull private OAuth2MapperConfig mapperConfig; @Schema(description = "OAuth2 client ID. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String clientId; @Schema(description = "OAuth2 client secret. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String clientSecret; @Schema(description = "Authorization URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String authorizationUri; @Schema(description = "Access token URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String accessTokenUri; @Schema(description = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty private List scope; @Schema(description = "User info URI of the OAuth2 provider") private String userInfoUri; @Schema(description = "Name of the username attribute in OAuth2 provider response. Cannot be empty") + @NotBlank private String userNameAttributeName; @Schema(description = "JSON Web Key URI of the OAuth2 provider") private String jwkSetUri; @Schema(description = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String clientAuthenticationMethod; @Schema(description = "OAuth2 provider label. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank private String loginButtonLabel; @Schema(description = "Log in button icon for OAuth2 provider") private String loginButtonIcon; @@ -69,9 +82,10 @@ public class OAuth2Registration extends BaseDataWithAdditionalInfo { + + @Schema(description = "Oauth2 client registration title (e.g. Google)") + private String title; + @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") + private List platforms; + + public OAuth2ClientInfo() { + super(); + } + + public OAuth2ClientInfo(OAuth2ClientId id) { + super(id); + } + + public OAuth2ClientInfo(OAuth2Client oAuth2Client) { + super(oAuth2Client); + this.title = oAuth2Client.getTitle(); + this.platforms = oAuth2Client.getPlatforms(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java new file mode 100644 index 0000000000..7662b8f9b0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java @@ -0,0 +1,39 @@ +/** + * 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.common.data.oauth2; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@EqualsAndHashCode +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema +public class OAuth2ClientLoginInfo { + + @Schema(description = "OAuth2 client name", example = "GitHub") + private String name; + @Schema(description = "Name of the icon, displayed on OAuth2 log in button", example = "github-logo") + private String icon; + @Schema(description = "URI for OAuth2 log in. On HTTP GET request to this URI, it redirects to the OAuth2 provider page", + example = "/oauth2/authorization/8352f191-2b4d-11ec-9ed1-cbf57c026ecc") + private String url; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java index be13a032e2..709f7c9175 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.oauth2; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; @@ -33,6 +34,7 @@ public class OAuth2MapperConfig { @Schema(description = "Whether user credentials should be activated when user is created after successful authentication") private boolean activateUser; @Schema(description = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull private MapperType type; @Valid @Schema(description = "Mapper config for BASIC and GITHUB mapper types") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java deleted file mode 100644 index 4e360993c1..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java +++ /dev/null @@ -1,49 +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.common.data.oauth2; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; - -import java.util.List; - -@Data -@Schema -@EqualsAndHashCode(callSuper = true) -public class OAuth2RegistrationInfo extends BaseData { - @Schema(description = "Oauth2 client registration title (e.g. Google)") - private String title; - @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") - private List platforms; - - public OAuth2RegistrationInfo() { - super(); - } - - public OAuth2RegistrationInfo(OAuth2RegistrationId id) { - super(id); - } - - public OAuth2RegistrationInfo(OAuth2RegistrationId id, String title, List platforms) { - super(id); - this.title = title; - this.platforms = platforms; - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java index 898598b38f..b4309b6214 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.domain; import org.thingsboard.server.common.data.domain.Domain; -import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; @@ -30,10 +30,11 @@ public interface DomainDao extends Dao { int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean oauth2Enabled); - List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId); + List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId); - void saveOauth2Clients(DomainOauth2Registration domainOauth2Registration); + void saveOauth2Clients(DomainOauth2Client domainOauth2Client); - void removeOauth2Clients(DomainId domainId, OAuth2RegistrationId oAuth2RegistrationId); + void removeOauth2Clients(DomainId domainId, OAuth2ClientId oAuth2ClientId); + void deleteByTenantId(TenantId tenantId); } 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 e260d5b154..9405c89cbb 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 @@ -24,17 +24,18 @@ import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.domain.Domain; import org.thingsboard.server.common.data.domain.DomainInfo; -import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; 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.OAuth2RegistrationDao; +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; @@ -42,6 +43,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import static org.thingsboard.server.dao.service.Validator.validateIds; @@ -53,7 +55,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe public static final String INCORRECT_DOMAIN_ID = "Incorrect domainId "; @Autowired - private OAuth2RegistrationDao oauth2RegistrationDao; + private OAuth2ClientDao oauth2ClientDao; @Autowired private DomainDao domainDao; @Autowired @@ -78,7 +80,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe } @Override - public void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds) { + public void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds) { log.trace("Executing addOauth2Clients, domainId [{}], oAuth2ClientIds [{}]", domainId, oAuth2ClientIds); Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); Validator.validateId(domainId, id -> INCORRECT_DOMAIN_ID + id); @@ -86,20 +88,20 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe if (!oAuth2ClientIds.isEmpty()) { validateIds(oAuth2ClientIds, ids -> "Incorrect oAuth2ClientIds " + ids); } - List oauth2Clients = new ArrayList<>(); - for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) { - oauth2Clients.add(new DomainOauth2Registration(domainId, oAuth2RegistrationId)); + List oauth2Clients = new ArrayList<>(); + for (OAuth2ClientId oAuth2ClientId : oAuth2ClientIds) { + oauth2Clients.add(new DomainOauth2Client(domainId, oAuth2ClientId)); } - List existingClients = domainDao.findOauth2ClientsByDomainId(tenantId, domainId); - List toRemove = existingClients.stream() - .map(DomainOauth2Registration::getOAuth2RegistrationId) + List existingClients = domainDao.findOauth2ClientsByDomainId(tenantId, domainId); + List toRemove = existingClients.stream() + .map(DomainOauth2Client::getOAuth2ClientId) .filter(clientId -> oAuth2ClientIds.stream().noneMatch(oauth2ClientId -> oauth2ClientId.equals(clientId))).toList(); - for (OAuth2RegistrationId clientId : toRemove) { + for (OAuth2ClientId clientId : toRemove) { domainDao.removeOauth2Clients(domainId, clientId); } - for (DomainOauth2Registration domainOauth2Registration : oauth2Clients) { - domainDao.saveOauth2Clients(domainOauth2Registration); + for (DomainOauth2Client domainOauth2Client : oauth2Clients) { + domainDao.saveOauth2Clients(domainOauth2Client); } eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) .entityId(domainId).created(false).build()); @@ -124,7 +126,9 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe List domains = domainDao.findByTenantId(tenantId); List domainInfos = new ArrayList<>(); domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { - domainInfos.add(new DomainInfo(domain, oauth2RegistrationDao.findInfosByDomainId(domain.getUuidId()))); + domainInfos.add(new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()))); }); return domainInfos; } @@ -136,7 +140,9 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe if (domain == null) { return null; } - return new DomainInfo(domain, oauth2RegistrationDao.findInfosByDomainId(domain.getUuidId())); + return new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList())); } @Override @@ -145,6 +151,18 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe return domainDao.countDomainByTenantIdAndOauth2Enabled(tenantId, true) > 0; } + @Override + public void deleteDomainsByTenantId(TenantId tenantId) { + log.trace("Executing deleteDomainsByTenantId, tenantId [{}]", tenantId); + Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + domainDao.deleteByTenantId(tenantId); + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + deleteDomainsByTenantId(tenantId); + } + @Override public Optional> findEntity(TenantId tenantId, EntityId entityId) { return Optional.ofNullable(findDomainById(tenantId, new DomainId(entityId.getId()))); diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java index caa77b5f7a..c5df586973 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java @@ -16,10 +16,10 @@ package org.thingsboard.server.dao.mobile; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -28,9 +28,11 @@ public interface MobileAppDao extends Dao { List findByTenantId(TenantId tenantId); - List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId); + List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId); - void saveOauth2Clients(MobileAppOauth2Registration mobileAppOauth2Registration); + void saveOauth2Clients(MobileAppOauth2Client mobileAppOauth2Client); - void removeOauth2Clients(MobileAppId mobileAppId, OAuth2RegistrationId oAuth2RegistrationId); + void removeOauth2Clients(MobileAppId mobileAppId, OAuth2ClientId oAuth2ClientId); + + void deleteByTenantId(TenantId tenantId); } 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 9f049518e5..79b7c4a095 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 @@ -25,17 +25,17 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; 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.MobileAppOauth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; 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.OAuth2RegistrationDao; +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; @@ -55,7 +55,7 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil public static final String INCORRECT_MOBILE_APP_ID = "Incorrect mobileApppId "; @Autowired - private OAuth2RegistrationDao oauth2RegistrationDao; + private OAuth2ClientDao oauth2ClientDao; @Autowired private MobileAppDao mobileAppDao; @Autowired @@ -98,7 +98,9 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil List mobileApps = mobileAppDao.findByTenantId(tenantId); List mobileAppInfos = new ArrayList<>(); mobileApps.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { - mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2RegistrationDao.findInfosByMobileAppId(mobileApp.getUuidId()))); + mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()))); }); return mobileAppInfos; } @@ -110,11 +112,13 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil if (mobileApp == null) { return null; } - return new MobileAppInfo(mobileApp, oauth2RegistrationDao.findInfosByMobileAppId(mobileApp.getUuidId())); + return new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList())); } @Override - public void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds) { + public void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List oAuth2ClientIds) { log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppId, oAuth2ClientIds); Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); Validator.validateId(mobileAppId, id -> INCORRECT_MOBILE_APP_ID + id); @@ -122,20 +126,20 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil if (!oAuth2ClientIds.isEmpty()) { validateIds(oAuth2ClientIds, ids -> "Incorrect oAuth2ClientIds " + ids); } - List oauth2Clients = new ArrayList<>(); - for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) { - oauth2Clients.add(new MobileAppOauth2Registration(mobileAppId, oAuth2RegistrationId)); + List oauth2Clients = new ArrayList<>(); + for (OAuth2ClientId oAuth2ClientId : oAuth2ClientIds) { + oauth2Clients.add(new MobileAppOauth2Client(mobileAppId, oAuth2ClientId)); } - List existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId); - List toRemove = existingClients.stream() - .map(MobileAppOauth2Registration::getOAuth2RegistrationId) + List existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId); + List toRemove = existingClients.stream() + .map(MobileAppOauth2Client::getOAuth2ClientId) .filter(clientId -> oAuth2ClientIds.stream().noneMatch(oauth2ClientId -> oauth2ClientId.equals(clientId))).toList(); - for (OAuth2RegistrationId clientId : toRemove) { + for (OAuth2ClientId clientId : toRemove) { mobileAppDao.removeOauth2Clients(mobileAppId, clientId); } - for (MobileAppOauth2Registration mobileAppOauth2Registration : oauth2Clients) { - mobileAppDao.saveOauth2Clients(mobileAppOauth2Registration); + for (MobileAppOauth2Client mobileAppOauth2Client : oauth2Clients) { + mobileAppDao.saveOauth2Clients(mobileAppOauth2Client); } eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId) .entityId(mobileAppId).created(false).build()); @@ -160,4 +164,16 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil } deleteMobileAppById(tenantId, mobileApp.getId()); } + + @Override + public void deleteMobileAppsByTenantId(TenantId tenantId) { + log.trace("Executing deleteDomainsByTenantId, tenantId [{}]", tenantId); + Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + mobileAppDao.deleteByTenantId(tenantId); + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + deleteMobileAppsByTenantId(tenantId); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 0b540066a7..96c3a9a562 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -429,11 +429,11 @@ public class ModelConstants { public static final String DOMAIN_TABLE_NAME = "domain"; public static final String DOMAIN_DOMAIN_NAME_PROPERTY = "domain_name"; public static final String DOMAIN_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled"; - public static final String DOMAIN_PROPAGATE_TO_EDGE_PROPERTY = "propagate_to_edge"; + public static final String DOMAIN_PROPAGATE_TO_EDGE_PROPERTY = "edge_enabled"; - public static final String DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME = "domain_oauth2_registration"; - public static final String DOMAIN_OAUTH2_PROVIDER_PROVIDER_ID_PROPERTY = "oauth2_registration_id"; - public static final String DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY = "domain_id"; + public static final String DOMAIN_OAUTH2_CLIENT_TABLE_NAME = "domain_oauth2_client"; + public static final String DOMAIN_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id"; + public static final String DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY = "domain_id"; /** * Mobile application constants. @@ -443,15 +443,15 @@ public class ModelConstants { public static final String MOBILE_APP_APP_SECRET_PROPERTY = "app_secret"; public static final String MOBILE_APP_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled"; - public static final String MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME = "mobile_app_oauth2_registration"; - public static final String MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY = "oauth2_registration_id"; - public static final String MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY = "mobile_app_id"; + public static final String MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME = "mobile_app_oauth2_client"; + public static final String MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id"; + public static final String MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY = "mobile_app_id"; /** * OAuth2 client registration constants. */ - public static final String OAUTH2_REGISTRATION_TABLE_NAME = "oauth2_registration"; + public static final String OAUTH2_CLIENT_TABLE_NAME = "oauth2_client"; public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_TABLE_NAME = "oauth2_client_registration_template"; public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; public static final String OAUTH2_CLIENT_TITLE_PROPERTY = "title"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientCompositeKey.java similarity index 89% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientCompositeKey.java index 33b6047808..bdc9ca241c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientCompositeKey.java @@ -26,12 +26,12 @@ import java.util.UUID; @NoArgsConstructor @AllArgsConstructor @Data -public class DomainOauth2RegistrationCompositeKey implements Serializable { +public class DomainOauth2ClientCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -245388185894468455L; private UUID domainId; - private UUID oauth2RegistrationId; + private UUID oauth2ClientId; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientEntity.java similarity index 53% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientEntity.java index 0699c3e7a6..5b5efd489f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientEntity.java @@ -21,46 +21,46 @@ import jakarta.persistence.Id; import jakarta.persistence.IdClass; import jakarta.persistence.Table; import lombok.Data; -import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ToData; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_CLIENT_TABLE_NAME; @Data @Entity -@Table(name = DOMAIN_OAUTH2_REGISTRATION_TABLE_NAME) -@IdClass(DomainOauth2RegistrationCompositeKey.class) -public final class DomainOauth2RegistrationEntity implements ToData { +@Table(name = DOMAIN_OAUTH2_CLIENT_TABLE_NAME) +@IdClass(DomainOauth2ClientCompositeKey.class) +public final class DomainOauth2ClientEntity implements ToData { @Id - @Column(name = DOMAIN_OAUTH2_PROVIDER_DOMAIN_ID_PROPERTY, columnDefinition = "uuid") + @Column(name = DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY, columnDefinition = "uuid") private UUID domainId; @Id - @Column(name = ModelConstants.DOMAIN_OAUTH2_PROVIDER_PROVIDER_ID_PROPERTY, columnDefinition = "uuid") - private UUID oauth2RegistrationId; + @Column(name = ModelConstants.DOMAIN_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid") + private UUID oauth2ClientId; - public DomainOauth2RegistrationEntity() { + public DomainOauth2ClientEntity() { super(); } - public DomainOauth2RegistrationEntity(DomainOauth2Registration domainOauth2Registration) { - domainId = domainOauth2Registration.getDomainId().getId(); - oauth2RegistrationId = domainOauth2Registration.getOAuth2RegistrationId().getId(); + public DomainOauth2ClientEntity(DomainOauth2Client domainOauth2Client) { + domainId = domainOauth2Client.getDomainId().getId(); + oauth2ClientId = domainOauth2Client.getOAuth2ClientId().getId(); } @Override - public DomainOauth2Registration toData() { - DomainOauth2Registration result = new DomainOauth2Registration(); + public DomainOauth2Client toData() { + DomainOauth2Client result = new DomainOauth2Client(); result.setDomainId(new DomainId(domainId)); - result.setOAuth2RegistrationId(new OAuth2RegistrationId(oauth2RegistrationId)); + result.setOAuth2ClientId(new OAuth2ClientId(oauth2ClientId)); return result; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java similarity index 89% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java index feccbe8aaa..b372751549 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java @@ -26,12 +26,12 @@ import java.util.UUID; @NoArgsConstructor @AllArgsConstructor @Data -public class MobileAppOauth2RegistrationCompositeKey implements Serializable { +public class MobileAppOauth2ClientCompositeKey implements Serializable { @Transient private static final long serialVersionUID = -245388185894468455L; private UUID mobileAppId; - private UUID oauth2RegistrationId; + private UUID oauth2ClientId; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java similarity index 56% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java index c68690e167..b2c7a55855 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java @@ -22,46 +22,44 @@ import jakarta.persistence.IdClass; import jakarta.persistence.Table; import lombok.Data; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; import org.thingsboard.server.dao.model.ToData; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY; -import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME; @Data @Entity -@Table(name = MOBILE_APP_OAUTH2_REGISTRATION_TABLE_NAME) -@IdClass(MobileAppOauth2RegistrationCompositeKey.class) -public final class MobileAppOauth2RegistrationEntity implements ToData { +@Table(name = MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME) +@IdClass(MobileAppOauth2ClientCompositeKey.class) +public final class MobileAppOauth2ClientEntity implements ToData { @Id - @Column(name = MOBILE_APP_OAUTH2_REGISTRATION_MOBILE_APP_ID_PROPERTY, columnDefinition = "uuid") + @Column(name = MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY, columnDefinition = "uuid") private UUID mobileAppId; @Id - @Column(name = MOBILE_APP_OAUTH2_REGISTRATION_REGISTRATION_ID_PROPERTY, columnDefinition = "uuid") - private UUID oauth2RegistrationId; + @Column(name = MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid") + private UUID oauth2ClientId; - - public MobileAppOauth2RegistrationEntity() { + public MobileAppOauth2ClientEntity() { super(); } - public MobileAppOauth2RegistrationEntity(MobileAppOauth2Registration domainOauth2Provider) { + public MobileAppOauth2ClientEntity(MobileAppOauth2Client domainOauth2Provider) { mobileAppId = domainOauth2Provider.getMobileAppId().getId(); - oauth2RegistrationId = domainOauth2Provider.getOAuth2RegistrationId().getId(); + oauth2ClientId = domainOauth2Provider.getOAuth2ClientId().getId(); } - @Override - public MobileAppOauth2Registration toData() { - MobileAppOauth2Registration result = new MobileAppOauth2Registration(); + public MobileAppOauth2Client toData() { + MobileAppOauth2Client result = new MobileAppOauth2Client(); result.setMobileAppId(new MobileAppId(mobileAppId)); - result.setOAuth2RegistrationId(new OAuth2RegistrationId(oauth2RegistrationId)); + result.setOAuth2ClientId(new OAuth2ClientId(oauth2ClientId)); return result; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java similarity index 95% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java index 9ba8bd0ae1..b9792de53d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java @@ -25,13 +25,13 @@ import jakarta.persistence.Table; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -46,8 +46,8 @@ import java.util.stream.Collectors; @Data @EqualsAndHashCode(callSuper = true) @Entity -@Table(name = ModelConstants.OAUTH2_REGISTRATION_TABLE_NAME) -public class OAuth2RegistrationEntity extends BaseSqlEntity { +@Table(name = ModelConstants.OAUTH2_CLIENT_TABLE_NAME) +public class OAuth2ClientEntity extends BaseSqlEntity { @Column(name = ModelConstants.TENANT_ID_COLUMN) private UUID tenantId; @@ -114,11 +114,11 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; - public OAuth2RegistrationEntity() { + public OAuth2ClientEntity() { super(); } - public OAuth2RegistrationEntity(OAuth2Registration registration) { + public OAuth2ClientEntity(OAuth2Client registration) { if (registration.getId() != null) { this.setUuid(registration.getId().getId()); } @@ -167,9 +167,9 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity } @Override - public OAuth2Registration toData() { - OAuth2Registration registration = new OAuth2Registration(); - registration.setId(new OAuth2RegistrationId(id)); + public OAuth2Client toData() { + OAuth2Client registration = new OAuth2Client(); + registration.setId(new OAuth2ClientId(id)); registration.setCreatedTime(createdTime); registration.setTenantId(new TenantId(tenantId)); registration.setTitle(title); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientInfoEntity.java similarity index 63% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientInfoEntity.java index 855dcb3446..731cf70fae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientInfoEntity.java @@ -19,8 +19,8 @@ import jakarta.persistence.Entity; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -32,16 +32,16 @@ import java.util.stream.Collectors; @Data @EqualsAndHashCode(callSuper = true) @Entity -public class OAuth2RegistrationInfoEntity extends BaseSqlEntity { +public class OAuth2ClientInfoEntity extends BaseSqlEntity { private String platforms; private String title; - public OAuth2RegistrationInfoEntity() { + public OAuth2ClientInfoEntity() { super(); } - public OAuth2RegistrationInfoEntity(UUID id, long createdTime, String platforms, String title) { + public OAuth2ClientInfoEntity(UUID id, long createdTime, String platforms, String title) { this.id = id; this.createdTime = createdTime; this.platforms = platforms; @@ -49,13 +49,13 @@ public class OAuth2RegistrationInfoEntity extends BaseSqlEntity PlatformType.valueOf(str)).collect(Collectors.toList()) : Collections.emptyList()); - return oAuth2RegistrationInfo; + return oAuth2ClientInfo; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 4ac2de4297..0315f93628 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -21,9 +21,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import java.util.UUID; @@ -36,12 +36,12 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2Registration registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2RegistrationId(UUID.fromString(registrationId))); + OAuth2Client registration = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(registrationId))); return registration == null ? null : toSpringClientRegistration(registration); } - private ClientRegistration toSpringClientRegistration(OAuth2Registration registration){ + private ClientRegistration toSpringClientRegistration(OAuth2Client registration){ String registrationId = registration.getUuidId().toString(); return ClientRegistration.withRegistrationId(registrationId) .clientName(registration.getName()) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java similarity index 55% rename from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java rename to dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java index a0dcd22801..fd2623acda 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java @@ -15,28 +15,27 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.dao.Dao; import java.util.List; import java.util.UUID; -public interface OAuth2RegistrationDao extends Dao { +public interface OAuth2ClientDao extends Dao { - List findInfosByTenantId(UUID tenantId); + List findByTenantId(UUID tenantId); - List findByTenantId(UUID tenantId); + List findEnabledByDomainName(String domainName); - List findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType); + List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType); - List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType); + List findByDomainId(UUID domainId); - List findInfosByDomainId(UUID domainId); - - List findInfosByMobileAppId(UUID domainId); + List findByMobileAppId(UUID domainId); String findAppSecret(UUID id, String pkgName); + void deleteByTenantId(TenantId tenantId); } 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 2e2a1fa5c0..75e63ec48c 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 @@ -22,16 +22,17 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +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.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; import org.thingsboard.server.common.data.oauth2.PlatformType; 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.service.DataValidator; +import org.thingsboard.server.dao.service.Validator; import java.util.List; import java.util.Optional; @@ -42,7 +43,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @Slf4j -@Service +@Service("OAuth2ClientService") public class OAuth2ClientServiceImpl extends AbstractEntityService implements OAuth2ClientService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -50,56 +51,50 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA public static final String INCORRECT_DOMAIN_NAME = "Incorrect domainName "; @Autowired - private OAuth2RegistrationDao oauth2RegistrationDao; + private OAuth2ClientDao oauth2ClientDao; @Autowired - private DataValidator oAuth2RegistrationDataValidator; + private DataValidator oAuth2ClientDataValidator; @Override - public List getWebOAuth2Clients(String domainName, PlatformType platformType) { + public List findOAuth2ClientLoginInfosByDomainName(String domainName) { log.trace("Executing getOAuth2Clients [{}] ", domainName); validateString(domainName, dn -> INCORRECT_DOMAIN_NAME + dn); - return oauth2RegistrationDao.findEnabledByDomainNameAndPlatformType(domainName, platformType) + return oauth2ClientDao.findEnabledByDomainName(domainName) .stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2Utils::toClientLoginInfo) .collect(Collectors.toList()); } @Override - public List getMobileOAuth2Clients(String pkgName, PlatformType platformType) { + public List findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType) { log.trace("Executing getOAuth2Clients pkgName=[{}] platformType=[{}]",pkgName, platformType); - return oauth2RegistrationDao.findEnabledByPckNameAndPlatformType(pkgName, platformType) + return oauth2ClientDao.findEnabledByPckNameAndPlatformType(pkgName, platformType) .stream() - .map(OAuth2Utils::toClientInfo) + .map(OAuth2Utils::toClientLoginInfo) .collect(Collectors.toList()); } @Override @Transactional - public OAuth2Registration saveOAuth2Client(TenantId tenantId, OAuth2Registration oAuth2Registration) { - log.trace("Executing saveOAuth2Client [{}]", oAuth2Registration); - oAuth2RegistrationDataValidator.validate(oAuth2Registration, OAuth2Registration::getTenantId); - OAuth2Registration savedOauth2Registration = oauth2RegistrationDao.save(tenantId, oAuth2Registration); - eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entity(oAuth2Registration).build()); - return savedOauth2Registration; - } - - @Override - public OAuth2Registration findOAuth2ClientById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId) { - log.trace("Executing findOAuth2ClientById [{}]", oAuth2RegistrationId); - validateId(oAuth2RegistrationId, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); - return oauth2RegistrationDao.findById(tenantId, oAuth2RegistrationId.getId()); + public OAuth2Client saveOAuth2Client(TenantId tenantId, OAuth2Client oAuth2Client) { + log.trace("Executing saveOAuth2Client [{}]", oAuth2Client); + oAuth2ClientDataValidator.validate(oAuth2Client, OAuth2Client::getTenantId); + OAuth2Client savedOauth2Client = oauth2ClientDao.save(tenantId, oAuth2Client); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entity(oAuth2Client).build()); + return savedOauth2Client; } @Override - public List findOauth2ClientInfosByTenantId(TenantId tenantId) { - log.trace("Executing findOauth2ClientInfosByTenantId"); - return oauth2RegistrationDao.findInfosByTenantId(tenantId.getId()); + public OAuth2Client findOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId) { + log.trace("Executing findOAuth2ClientById [{}]", oAuth2ClientId); + validateId(oAuth2ClientId, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); + return oauth2ClientDao.findById(tenantId, oAuth2ClientId.getId()); } @Override - public List findOauth2ClientsByTenantId(TenantId tenantId) { - log.trace("Executing findOauth2ClientsByTenantId [{}]", tenantId); - return oauth2RegistrationDao.findByTenantId(tenantId.getId()); + public List findOAuth2ClientsByTenantId(TenantId tenantId) { + log.trace("Executing findOAuth2ClientsByTenantId [{}]", tenantId); + return oauth2ClientDao.findByTenantId(tenantId.getId()); } @Override @@ -107,34 +102,55 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA log.trace("Executing findAppSecret [{}][{}]", id, pkgName); validateId(id, uuid -> INCORRECT_CLIENT_REGISTRATION_ID + uuid); validateString(pkgName, "Incorrect package name"); - return oauth2RegistrationDao.findAppSecret(id, pkgName); + return oauth2ClientDao.findAppSecret(id, pkgName); } @Override @Transactional - public void deleteById(TenantId tenantId, OAuth2RegistrationId oAuth2RegistrationId) { - log.trace("[{}][{}] Executing deleteById [{}]", tenantId, oAuth2RegistrationId); - oauth2RegistrationDao.removeById(tenantId, oAuth2RegistrationId.getId()); + public void deleteOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId) { + log.trace("[{}][{}] Executing deleteOAuth2ClientById [{}]", tenantId, oAuth2ClientId); + oauth2ClientDao.removeById(tenantId, oAuth2ClientId.getId()); eventPublisher.publishEvent(DeleteEntityEvent.builder() .tenantId(tenantId) - .entityId(oAuth2RegistrationId) + .entityId(oAuth2ClientId) .build()); } + @Override + public void deleteOauth2ClientsByTenantId(TenantId tenantId) { + log.trace("Executing deleteOauth2ClientsByTenantId, tenantId [{}]", tenantId); + Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + oauth2ClientDao.deleteByTenantId(tenantId); + } + + @Override + public List findOAuth2ClientInfosByTenantId(TenantId tenantId) { + log.trace("Executing findOAuth2ClientInfosByTenantId tenantId=[{}]", tenantId); + return oauth2ClientDao.findByTenantId(tenantId.getId()) + .stream() + .map(OAuth2Utils::toClientInfo) + .collect(Collectors.toList()); + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + deleteOauth2ClientsByTenantId(tenantId); + } + @Override public Optional> findEntity(TenantId tenantId, EntityId entityId) { - return Optional.ofNullable(findOAuth2ClientById(tenantId, new OAuth2RegistrationId(entityId.getId()))); + return Optional.ofNullable(findOAuth2ClientById(tenantId, new OAuth2ClientId(entityId.getId()))); } @Override @Transactional public void deleteEntity(TenantId tenantId, EntityId id, boolean force) { - OAuth2Registration oAuth2Registration = oauth2RegistrationDao.findById(tenantId, id.getId()); - if (oAuth2Registration == null) { + OAuth2Client oAuth2Client = oauth2ClientDao.findById(tenantId, id.getId()); + if (oAuth2Client == null) { return; } - deleteById(tenantId, oAuth2Registration.getId()); + deleteOAuth2ClientById(tenantId, oAuth2Client.getId()); } @Override 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 c3fc12748b..22a100305a 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 @@ -16,17 +16,27 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; public class OAuth2Utils { public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - public static OAuth2ClientInfo toClientInfo(OAuth2Registration registration) { - OAuth2ClientInfo client = new OAuth2ClientInfo(); + public static OAuth2ClientLoginInfo toClientLoginInfo(OAuth2Client registration) { + OAuth2ClientLoginInfo client = new OAuth2ClientLoginInfo(); client.setName(registration.getLoginButtonLabel()); client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, registration.getUuidId().toString())); client.setIcon(registration.getLoginButtonIcon()); 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/validator/MobileAppDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java index a96f7a0ac6..89082bc02b 100644 --- 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 @@ -18,7 +18,6 @@ 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.common.data.mobile.MobileApp; import org.thingsboard.server.dao.exception.DataValidationException; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java similarity index 62% rename from dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java rename to dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java index e5efbd13e7..78197ed1ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.service.validator; import lombok.AllArgsConstructor; -import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; @@ -24,51 +23,18 @@ import org.thingsboard.server.common.data.oauth2.MapperType; import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; -import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Component @AllArgsConstructor -public class Oauth2RegistrationDataValidator extends DataValidator { +public class Oauth2ClientDataValidator extends DataValidator { @Override - protected void validateDataImpl(TenantId tenantId, OAuth2Registration oAuth2Registration) { - if (StringUtils.isEmpty(oAuth2Registration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getClientId())) { - throw new DataValidationException("Client ID should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getClientSecret())) { - throw new DataValidationException("Client secret should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getAuthorizationUri())) { - throw new DataValidationException("Authorization uri should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getAccessTokenUri())) { - throw new DataValidationException("Token uri should be specified!"); - } - if (CollectionUtils.isEmpty(oAuth2Registration.getScope())) { - throw new DataValidationException("Scope should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getUserNameAttributeName())) { - throw new DataValidationException("User name attribute name should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getClientAuthenticationMethod())) { - throw new DataValidationException("Client authentication method should be specified!"); - } - if (StringUtils.isEmpty(oAuth2Registration.getLoginButtonLabel())) { - throw new DataValidationException("Login button label should be specified!"); - } - OAuth2MapperConfig mapperConfig = oAuth2Registration.getMapperConfig(); - if (mapperConfig == null) { - throw new DataValidationException("Mapper config should be specified!"); - } - if (mapperConfig.getType() == null) { - throw new DataValidationException("Mapper config type should be specified!"); - } + protected void validateDataImpl(TenantId tenantId, OAuth2Client oAuth2Client) { + OAuth2MapperConfig mapperConfig = oAuth2Client.getMapperConfig(); if (mapperConfig.getType() == MapperType.BASIC) { OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); if (basicConfig == null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2ClientRepository.java similarity index 56% rename from dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2ClientRepository.java index dd15bcacab..3fe20226d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2ClientRepository.java @@ -16,19 +16,15 @@ package org.thingsboard.server.dao.sql.domain; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; -import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationCompositeKey; -import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; -import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.DomainOauth2ClientCompositeKey; +import org.thingsboard.server.dao.model.sql.DomainOauth2ClientEntity; import java.util.List; import java.util.UUID; -public interface DomainOauth2RegistrationRepository extends JpaRepository { +public interface DomainOauth2ClientRepository extends JpaRepository { - List findAllByDomainId(@Param("domainId") UUID domainId); + List findAllByDomainId(@Param("domainId") UUID domainId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java index 1de8f939dd..c6566e4302 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java @@ -31,7 +31,7 @@ public interface DomainRepository extends JpaRepository { @Transactional @Modifying - @Query("DELETE FROM MobileAppEntity r WHERE r.tenantId = :tenantId") + @Query("DELETE FROM DomainEntity r WHERE r.tenantId = :tenantId") void deleteByTenantId(@Param("tenantId") UUID tenantId); int countByTenantIdAndOauth2Enabled(@Param("tenantId") UUID tenantId, @Param("oauth2Enabled") boolean oauth2Enabled); 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 f4e10bda7a..55c7be8e56 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 @@ -19,20 +19,18 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.domain.Domain; -import org.thingsboard.server.common.data.domain.DomainOauth2Registration; +import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.domain.DomainDao; import org.thingsboard.server.dao.model.sql.DomainEntity; -import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationCompositeKey; -import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; -import org.thingsboard.server.dao.model.sql.WidgetsBundleWidgetEntity; +import org.thingsboard.server.dao.model.sql.DomainOauth2ClientCompositeKey; +import org.thingsboard.server.dao.model.sql.DomainOauth2ClientEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; -import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -42,7 +40,7 @@ import java.util.UUID; public class JpaDomainDao extends JpaAbstractDao implements DomainDao { private final DomainRepository domainRepository; - private final DomainOauth2RegistrationRepository domainOauth2RegistrationRepository; + private final DomainOauth2ClientRepository domainOauth2ClientRepository; @Override protected Class getEntityClass() { @@ -65,18 +63,23 @@ public class JpaDomainDao extends JpaAbstractDao implement } @Override - public List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId) { - return DaoUtil.convertDataList(domainOauth2RegistrationRepository.findAllByDomainId(domainId.getId())); + public List findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId) { + return DaoUtil.convertDataList(domainOauth2ClientRepository.findAllByDomainId(domainId.getId())); } @Override - public void saveOauth2Clients(DomainOauth2Registration domainOauth2Registration) { - domainOauth2RegistrationRepository.save(new DomainOauth2RegistrationEntity(domainOauth2Registration)); + public void saveOauth2Clients(DomainOauth2Client domainOauth2Client) { + domainOauth2ClientRepository.save(new DomainOauth2ClientEntity(domainOauth2Client)); } @Override - public void removeOauth2Clients(DomainId domainId, OAuth2RegistrationId oAuth2RegistrationId) { - domainOauth2RegistrationRepository.deleteById(new DomainOauth2RegistrationCompositeKey(domainId.getId(), oAuth2RegistrationId.getId())); + public void removeOauth2Clients(DomainId domainId, OAuth2ClientId oAuth2ClientId) { + domainOauth2ClientRepository.deleteById(new DomainOauth2ClientCompositeKey(domainId.getId(), oAuth2ClientId.getId())); + } + + @Override + public void deleteByTenantId(TenantId tenantId) { + domainRepository.deleteByTenantId(tenantId.getId()); } } 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 96e40a742f..850243a02d 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 @@ -19,15 +19,15 @@ import lombok.RequiredArgsConstructor; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.MobileAppId; -import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; +import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.mobile.MobileAppDao; import org.thingsboard.server.dao.model.sql.MobileAppEntity; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationCompositeKey; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; @@ -40,7 +40,7 @@ import java.util.UUID; public class JpaMobileAppDao extends JpaAbstractDao implements MobileAppDao { private final MobileAppRepository repository; - private final MobileAppOauth2RegistrationRepository mobileOauth2ProviderRepository; + private final MobileAppOauth2ClientRepository mobileOauth2ProviderRepository; @Override protected Class getEntityClass() { @@ -58,19 +58,23 @@ public class JpaMobileAppDao extends JpaAbstractDao } @Override - public List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId) { + public List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId) { return DaoUtil.convertDataList(mobileOauth2ProviderRepository.findAllByMobileAppId(mobileAppId.getId())); } @Override - public void saveOauth2Clients(MobileAppOauth2Registration mobileAppOauth2Registration) { - mobileOauth2ProviderRepository.save(new MobileAppOauth2RegistrationEntity(mobileAppOauth2Registration)); + public void saveOauth2Clients(MobileAppOauth2Client mobileAppOauth2Client) { + mobileOauth2ProviderRepository.save(new MobileAppOauth2ClientEntity(mobileAppOauth2Client)); } @Override - public void removeOauth2Clients(MobileAppId mobileAppId, OAuth2RegistrationId oAuth2RegistrationId) { - mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2RegistrationCompositeKey(mobileAppId.getId(), oAuth2RegistrationId.getId())); + public void removeOauth2Clients(MobileAppId mobileAppId, OAuth2ClientId oAuth2ClientId) { + mobileOauth2ProviderRepository.deleteById(new MobileAppOauth2ClientCompositeKey(mobileAppId.getId(), oAuth2ClientId.getId())); + } + @Override + public void deleteByTenantId(TenantId tenantId) { + repository.deleteByTenantId(tenantId.getId()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java similarity index 59% rename from dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java index c00f65d18f..6ce4c99977 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2ClientRepository.java @@ -17,16 +17,14 @@ package org.thingsboard.server.dao.sql.mobile; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.query.Param; -import org.thingsboard.server.common.data.mobile.MobileAppOauth2Registration; -import org.thingsboard.server.dao.model.sql.DomainOauth2RegistrationEntity; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationCompositeKey; -import org.thingsboard.server.dao.model.sql.MobileAppOauth2RegistrationEntity; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientCompositeKey; +import org.thingsboard.server.dao.model.sql.MobileAppOauth2ClientEntity; import java.util.List; import java.util.UUID; -public interface MobileAppOauth2RegistrationRepository extends JpaRepository { +public interface MobileAppOauth2ClientRepository extends JpaRepository { - List findAllByMobileAppId(@Param("mobileAppId") UUID mobileAppId); + List findAllByMobileAppId(@Param("mobileAppId") UUID mobileAppId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java similarity index 52% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java index a4e94eb6d9..d8ca155153 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientDao.java @@ -18,12 +18,12 @@ 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.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; -import org.thingsboard.server.dao.oauth2.OAuth2RegistrationDao; +import org.thingsboard.server.dao.model.sql.OAuth2ClientEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.util.SqlDao; @@ -33,50 +33,44 @@ import java.util.UUID; @Component @RequiredArgsConstructor @SqlDao -public class JpaOAuth2RegistrationDao extends JpaAbstractDao implements OAuth2RegistrationDao { +public class JpaOAuth2ClientDao extends JpaAbstractDao implements OAuth2ClientDao { - private final OAuth2RegistrationRepository repository; + private final OAuth2ClientRepository repository; @Override - protected Class getEntityClass() { - return OAuth2RegistrationEntity.class; + protected Class getEntityClass() { + return OAuth2ClientEntity.class; } @Override - protected JpaRepository getRepository() { + protected JpaRepository getRepository() { return repository; } @Override - public List findInfosByTenantId(UUID tenantId) { - return DaoUtil.convertDataList(repository.findInfosByTenantId(tenantId)); - } - - @Override - public List findByTenantId(UUID tenantId) { + public List findByTenantId(UUID tenantId) { return DaoUtil.convertDataList(repository.findByTenantId(tenantId)); } @Override - public List findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType) { - return DaoUtil.convertDataList(repository.findEnabledByDomainNameAndPlatformType(domainName, - platformType != null ? "%" + platformType.name() + "%" : null)); + public List findEnabledByDomainName(String domainName) { + return DaoUtil.convertDataList(repository.findEnabledByDomainNameAndPlatformType(domainName, PlatformType.WEB.name())); } @Override - public List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType) { + public List findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType) { return DaoUtil.convertDataList(repository.findEnabledByPkgNameAndPlatformType(pkgName, platformType != null ? "%" + platformType.name() + "%" : null)); } @Override - public List findInfosByDomainId(UUID oauth2ParamsId) { - return DaoUtil.convertDataList(repository.findInfosByDomainId(oauth2ParamsId)); + public List findByDomainId(UUID oauth2ParamsId) { + return DaoUtil.convertDataList(repository.findByDomainId(oauth2ParamsId)); } @Override - public List findInfosByMobileAppId(UUID mobileAppId) { - return DaoUtil.convertDataList(repository.findInfosByMobileAppId(mobileAppId)); + public List findByMobileAppId(UUID mobileAppId) { + return DaoUtil.convertDataList(repository.findByMobileAppId(mobileAppId)); } @Override @@ -84,4 +78,9 @@ public class JpaOAuth2RegistrationDao extends JpaAbstractDao { + + List findByTenantId(@Param("tenantId") UUID tenantId); + + @Query("SELECT c " + + "FROM OAuth2ClientEntity c " + + "LEFT JOIN DomainOauth2ClientEntity dc on c.id = dc.oauth2ClientId " + + "LEFT JOIN DomainEntity domain on dc.domainId = domain.id " + + "AND domain.name = :domainName " + + "AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR c.platforms LIKE :platformFilter)") + List findEnabledByDomainNameAndPlatformType(@Param("domainName") String domainName, + @Param("platformFilter") String platformFilter); + + @Query("SELECT c " + + "FROM OAuth2ClientEntity c " + + "LEFT JOIN MobileAppOauth2ClientEntity mc on c.id = mc.oauth2ClientId " + + "LEFT JOIN MobileAppEntity app on mc.mobileAppId = app.id " + + "AND app.pkgName = :pkgName " + + "AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR c.platforms LIKE :platformFilter)") + List findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName, + @Param("platformFilter") String platformFilter); + + @Query("SELECT c " + + "FROM OAuth2ClientEntity c " + + "LEFT JOIN DomainOauth2ClientEntity dc on dc.oauth2ClientId = c.id " + + "WHERE dc.domainId = :domainId ") + List findByDomainId(UUID domainId); + + @Query("SELECT c " + + "FROM OAuth2ClientEntity c " + + "LEFT JOIN MobileAppOauth2ClientEntity mc on mc.oauth2ClientId = c.id " + + "WHERE mc.mobileAppId = :mobileAppId ") + List findByMobileAppId(UUID mobileAppId); + + @Query("SELECT m.appSecret " + + "FROM MobileAppEntity m " + + "LEFT JOIN MobileAppOauth2ClientEntity mp on m.id = mp.mobileAppId " + + "LEFT JOIN OAuth2ClientEntity p on mp.oauth2ClientId = p.id " + + "WHERE p.id = :clientId " + + "AND m.pkgName = :pkgName") + String findAppSecret(@Param("clientId") UUID id, + @Param("pkgName") String pkgName); + + @Transactional + @Modifying + @Query("DELETE FROM OAuth2ClientEntity t WHERE t.tenantId = :tenantId") + void deleteByTenantId(@Param("tenantId") UUID tenantId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java deleted file mode 100644 index 16fbe74aed..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java +++ /dev/null @@ -1,76 +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.sql.oauth2; - -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.dao.model.sql.OAuth2RegistrationEntity; -import org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity; - -import java.util.List; -import java.util.UUID; - -public interface OAuth2RegistrationRepository extends JpaRepository { - - List findByTenantId(@Param("tenantId") UUID tenantId); - - @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + - "FROM OAuth2RegistrationEntity r " + - "WHERE r.tenantId = :tenantId") - List findInfosByTenantId(@Param("tenantId") UUID tenantId); - - @Query(value = "SELECT r " + - "FROM oauth2_registration r " + - "LEFT JOIN domain_oauth2_registration dr on dr.oauth2_registration_id = r.id " + - "LEFT JOIN domain d on dr.domain_id = d.id " + - "WHERE d.oauth2_enabled = true " + - "AND d.domain_name = :domainName " + - "AND (:platformFilter IS NULL OR r.platforms IS NULL OR r.platforms = '' OR r.platforms LIKE :platformFilter)", nativeQuery = true) - List findEnabledByDomainNameAndPlatformType(@Param("domainName") String domainName, - @Param("platformFilter") String platformFilter); - - @Query(value = "SELECT r " + - "FROM oauth2_registration r " + - "LEFT JOIN mobile_app_oauth2_registration mr on mr.oauth2_registration_id = r.id " + - "LEFT JOIN mobile_app m on mr.mobile_app_id = m.id " + - "WHERE m.oauth2_enabled = true " + - "AND m.pck_name = :pkgName " + - "AND (:platformFilter IS NULL OR r.platforms IS NULL OR r.platforms = '' OR r.platforms LIKE :platformFilter)", nativeQuery = true) - List findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName, - @Param("platformFilter") String platformFilter); - - @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + - "FROM OAuth2RegistrationEntity r " + - "LEFT JOIN DomainOauth2RegistrationEntity dr on dr.oauth2RegistrationId = r.id " + - "WHERE dr.domainId = :domainId ") - List findInfosByDomainId(UUID domainId); - - @Query("SELECT new org.thingsboard.server.dao.model.sql.OAuth2RegistrationInfoEntity(r.id, r.createdTime, r.platforms, r.title) " + - "FROM OAuth2RegistrationEntity r " + - "LEFT JOIN MobileAppOauth2RegistrationEntity mr on mr.oauth2RegistrationId = r.id " + - "WHERE mr.mobileAppId = :mobileAppId ") - List findInfosByMobileAppId(UUID mobileAppId); - - @Query("SELECT m.appSecret " + - "FROM MobileAppEntity m " + - "LEFT JOIN MobileAppOauth2RegistrationEntity mp on m.id = mp.mobileAppId " + - "LEFT JOIN OAuth2RegistrationEntity p on mp.oauth2RegistrationId = p.id " + - "WHERE p.id = :registrationId " + - "AND m.pkgName = :pkgName") - String findAppSecret(@Param("registrationId") UUID id, - @Param("pkgName") String pkgName); -} diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 0499f64539..93adc65135 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -558,9 +558,10 @@ CREATE TABLE IF NOT EXISTS key_dictionary CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key) ); -CREATE TABLE IF NOT EXISTS oauth2_registration ( - id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_client ( + id uuid NOT NULL CONSTRAINT oauth2_client_pkey PRIMARY KEY, tenant_id uuid NOT NULL, + title varchar(100) NOT NULL, created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), @@ -612,18 +613,18 @@ CREATE TABLE IF NOT EXISTS mobile_app ( CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name) ); -CREATE TABLE IF NOT EXISTS domain_oauth2_registration ( +CREATE TABLE IF NOT EXISTS domain_oauth2_client ( domain_id uuid NOT NULL, - oauth2_registration_id uuid NOT NULL, + oauth2_client_id uuid NOT NULL, CONSTRAINT fk_domain FOREIGN KEY (domain_id) REFERENCES domain(id) ON DELETE CASCADE, - CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE + CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS mobile_app_oauth2_registration ( +CREATE TABLE IF NOT EXISTS mobile_app_oauth2_client ( mobile_app_id uuid NOT NULL, - oauth2_registration_id uuid NOT NULL, + oauth2_client_id uuid NOT NULL, CONSTRAINT fk_domain FOREIGN KEY (mobile_app_id) REFERENCES mobile_app(id) ON DELETE CASCADE, - CONSTRAINT fk_oauth2_registration FOREIGN KEY (oauth2_registration_id) REFERENCES oauth2_registration(id) ON DELETE CASCADE + CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 6127a4192a..ea0b6eab8d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -48,6 +48,11 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; 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.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.dao.audit.AuditLogLevelFilter; @@ -61,8 +66,11 @@ import org.thingsboard.server.dao.tenant.TenantService; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; @@ -211,4 +219,39 @@ public abstract class AbstractServiceTest { return firmware; } + 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/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java new file mode 100644 index 0000000000..95457dc856 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java @@ -0,0 +1,131 @@ +/** + * 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; + +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.StringUtils; +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.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; +import org.thingsboard.server.dao.domain.DomainService; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.OAUTH2_AUTHORIZATION_PATH_TEMPLATE; + +@DaoSqlTest +public class DomainServiceTest extends AbstractServiceTest { + + @Autowired + protected DomainService domainService; + + @Autowired + protected OAuth2ClientService oAuth2ClientService; + + @After + public void after() { + domainService.deleteByTenantId(TenantId.SYS_TENANT_ID); + } + + @Test + public void testSaveDomain() { + Domain domain = validDomain(TenantId.SYS_TENANT_ID, "test.domain.com", true, true); + Domain savedDomain = domainService.saveDomain(SYSTEM_TENANT_ID, domain); + + Domain retrievedDomain = domainService.findDomainById(savedDomain.getTenantId(), savedDomain.getId()); + assertThat(retrievedDomain).isEqualTo(savedDomain); + + // update domain name + savedDomain.setName("test.domain2.com"); + Domain updatedDomain = domainService.saveDomain(SYSTEM_TENANT_ID, savedDomain); + + Domain retrievedDomain2 = domainService.findDomainById(savedDomain.getTenantId(), savedDomain.getId()); + assertThat(retrievedDomain2).isEqualTo(updatedDomain); + + // check domain info + DomainInfo retrievedInfo = domainService.findDomainInfoById(SYSTEM_TENANT_ID, savedDomain.getId()); + assertThat(retrievedInfo).isEqualTo(new DomainInfo(updatedDomain, Collections.emptyList())); + + boolean oauth2Enabled = domainService.isOauth2Enabled(SYSTEM_TENANT_ID); + assertThat(oauth2Enabled).isTrue(); + + // update domain oauth2 enabled to false + updatedDomain.setOauth2Enabled(false); + domainService.saveDomain(SYSTEM_TENANT_ID, updatedDomain); + + boolean oauth2Enabled2 = domainService.isOauth2Enabled(SYSTEM_TENANT_ID); + assertThat(oauth2Enabled2).isFalse(); + + //delete domain + domainService.deleteDomainById(SYSTEM_TENANT_ID, savedDomain.getId()); + assertThat(domainService.findDomainById(SYSTEM_TENANT_ID, savedDomain.getId())).isNull(); + } + + @Test + public void testGetTenantDomains() { + List domains = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + Domain oAuth2Client = validDomain(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true, false); + Domain savedOauth2Client = domainService.saveDomain(SYSTEM_TENANT_ID, oAuth2Client); + domains.add(savedOauth2Client); + } + List retrieved = domainService.findDomainInfosByTenantId(TenantId.SYS_TENANT_ID); + List domainInfos = domains.stream().map(domain -> new DomainInfo(domain, Collections.emptyList())).toList(); + assertThat(retrieved).containsOnlyOnceElementsOf(domainInfos); + } + + @Test + public void tesGetDomainInfo() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client"); + 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 savedDomain = domainService.saveDomain(SYSTEM_TENANT_ID, domain); + + domainService.updateOauth2Clients(TenantId.SYS_TENANT_ID, savedDomain.getId(), List.of(savedOauth2Client.getId())); + + // check domain info + DomainInfo retrievedInfo = domainService.findDomainInfoById(SYSTEM_TENANT_ID, savedDomain.getId()); + assertThat(retrievedInfo).isEqualTo(new DomainInfo(savedDomain, infos)); + + //find clients by domain name + List oauth2LoginInfo = oAuth2ClientService.findOAuth2ClientLoginInfosByDomainName(savedDomain.getName()); + 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) { + Domain domain = new Domain(); + domain.setTenantId(tenantId); + domain.setName(domainName); + domain.setOauth2Enabled(oauth2Enabled); + domain.setPropagateToEdge(propagateToEdge); + return domain; + } + + + + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java new file mode 100644 index 0000000000..697cbd69fd --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java @@ -0,0 +1,113 @@ +/** + * 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; + +import org.junit.After; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +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.OAuth2Client; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo; +import org.thingsboard.server.dao.mobile.MobileAppService; +import org.thingsboard.server.dao.oauth2.OAuth2ClientService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.dao.oauth2.OAuth2Utils.OAUTH2_AUTHORIZATION_PATH_TEMPLATE; + +@DaoSqlTest +public class MobileAppServiceTest extends AbstractServiceTest { + + @Autowired + protected MobileAppService mobileAppService; + + @Autowired + protected OAuth2ClientService oAuth2ClientService; + + @After + public void after() { + mobileAppService.deleteByTenantId(TenantId.SYS_TENANT_ID); + } + + @Test + public void testSaveMobileApp() { + MobileApp MobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce", true); + MobileApp savedMobileApp = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, MobileApp); + + MobileApp retrievedMobileApp = mobileAppService.findMobileAppById(savedMobileApp.getTenantId(), savedMobileApp.getId()); + assertThat(retrievedMobileApp).isEqualTo(savedMobileApp); + + // update MobileApp name + savedMobileApp.setPkgName("mobileApp.pe"); + MobileApp updatedMobileApp = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, savedMobileApp); + + MobileApp retrievedMobileApp2 = mobileAppService.findMobileAppById(savedMobileApp.getTenantId(), savedMobileApp.getId()); + assertThat(retrievedMobileApp2).isEqualTo(updatedMobileApp); + + //delete MobileApp + mobileAppService.deleteMobileAppById(SYSTEM_TENANT_ID, savedMobileApp.getId()); + assertThat(mobileAppService.findMobileAppById(SYSTEM_TENANT_ID, savedMobileApp.getId())).isNull(); + } + + @Test + public void testGetTenantMobileApps() { + List MobileApps = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + MobileApp oAuth2Client = validMobileApp(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5), true); + MobileApp savedOauth2Client = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, oAuth2Client); + MobileApps.add(savedOauth2Client); + } + List retrieved = mobileAppService.findMobileAppInfosByTenantId(TenantId.SYS_TENANT_ID); + List MobileAppInfos = MobileApps.stream().map(MobileApp -> new MobileAppInfo(MobileApp, Collections.emptyList())).toList(); + assertThat(retrieved).containsOnlyOnceElementsOf(MobileAppInfos); + } + + @Test + public void tesGetMobileAppInfo() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client"); + OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); + List infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); + + MobileApp MobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.app", true); + MobileApp savedMobileApp = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, MobileApp); + + mobileAppService.updateOauth2Clients(TenantId.SYS_TENANT_ID, savedMobileApp.getId(), List.of(savedOauth2Client.getId())); + + // check MobileApp info + MobileAppInfo retrievedInfo = mobileAppService.findMobileAppInfoById(SYSTEM_TENANT_ID, savedMobileApp.getId()); + assertThat(retrievedInfo).isEqualTo(new MobileAppInfo(savedMobileApp, infos)); + + //find clients by MobileApp name + List oauth2LoginInfo = oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(savedMobileApp.getName(), null); + assertThat(oauth2LoginInfo).containsOnly(new OAuth2ClientLoginInfo(savedOauth2Client.getName(), savedOauth2Client.getLoginButtonIcon(), String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, savedOauth2Client.getUuidId().toString()))); + } + + 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; + } +} 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 157bf74d96..80bc79e39b 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 @@ -15,627 +15,63 @@ */ package org.thingsboard.server.dao.service; +import org.junit.After; +import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +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.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; @DaoSqlTest public class OAuth2ClientServiceTest extends AbstractServiceTest { -// private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, false, Collections.emptyList()); @Autowired protected OAuth2ClientService oAuth2ClientService; -// @Before -// public void beforeRun() { -// Assert.assertTrue(oAuth2Service.findOauth2ProvidersByTenantId().isEmpty()); -// } -// -// @After -// public void after() { -// oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); -// Assert.assertTrue(oAuth2Service.findOauth2ProvidersByTenantId().isEmpty()); -// Assert.assertTrue(oAuth2Service.findOAuth2Info().getOauth2ParamsInfos().isEmpty()); -// } -// -// @Test -// public void testSaveHttpAndMixedDomainsTogether() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// Assertions.assertThrows(DataValidationException.class, () -> { -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// }); -// } -// -// @Test -// public void testSaveHttpsAndMixedDomainsTogether() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// Assertions.assertThrows(DataValidationException.class, () -> { -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// }); -// } -// -// @Test -// public void testCreateAndFindParams() { -// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// // TODO ask if it's safe to check equality on AdditionalProperties -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// } -// -// @Test -// public void testDisableParams() { -// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); -// oAuth2Info.setEnabled(true); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// oAuth2Info.setEnabled(false); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundDisabledOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertEquals(oAuth2Info, foundDisabledOAuth2Info); -// } -// -// @Test -// public void testClearDomainParams() { -// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); -// OAuth2Info foundAfterClearClientsParams = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundAfterClearClientsParams); -// Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); -// } -// -// @Test -// public void testUpdateClientsParams() { -// OAuth2Info oAuth2Info = createDefaultOAuth2Info(); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// OAuth2Info newOAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo() -// )) -// .build() -// )); -// oAuth2Service.saveOAuth2Info(newOAuth2Info); -// OAuth2Info foundAfterUpdateOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundAfterUpdateOAuth2Info); -// Assert.assertEquals(newOAuth2Info, foundAfterUpdateOAuth2Info); -// } -// -// @Test -// public void testGetOAuth2Clients() { -// List firstGroup = Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// ); -// List secondGroup = Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// ); -// List thirdGroup = Lists.newArrayList( -// validRegistrationInfo() -// ); -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(firstGroup) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(secondGroup) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), -// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(thirdGroup) -// .build() -// )); -// -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// List firstGroupClientInfos = firstGroup.stream() -// .map(registrationInfo -> new OAuth2ClientInfo( -// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) -// .collect(Collectors.toList()); -// List secondGroupClientInfos = secondGroup.stream() -// .map(registrationInfo -> new OAuth2ClientInfo( -// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) -// .collect(Collectors.toList()); -// List thirdGroupClientInfos = thirdGroup.stream() -// .map(registrationInfo -> new OAuth2ClientInfo( -// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) -// .collect(Collectors.toList()); -// -// List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null); -// Assert.assertTrue(nonExistentDomainClients.isEmpty()); -// -// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); -// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); -// firstGroupClientInfos.forEach(firstGroupClientInfo -> { -// Assert.assertTrue( -// firstDomainHttpClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(firstGroupClientInfo.getName())) -// ); -// }); -// -// List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); -// Assert.assertTrue(firstDomainHttpsClients.isEmpty()); -// -// List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null, null); -// Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); -// secondGroupClientInfos.forEach(secondGroupClientInfo -> { -// Assert.assertTrue( -// fourthDomainHttpClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(secondGroupClientInfo.getName())) -// ); -// }); -// List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null, null); -// Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); -// secondGroupClientInfos.forEach(secondGroupClientInfo -> { -// Assert.assertTrue( -// fourthDomainHttpsClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(secondGroupClientInfo.getName())) -// ); -// }); -// -// List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); -// Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); -// firstGroupClientInfos.forEach(firstGroupClientInfo -> { -// Assert.assertTrue( -// secondDomainHttpClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(firstGroupClientInfo.getName())) -// ); -// }); -// secondGroupClientInfos.forEach(secondGroupClientInfo -> { -// Assert.assertTrue( -// secondDomainHttpClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(secondGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(secondGroupClientInfo.getName())) -// ); -// }); -// -// List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null, null); -// Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); -// firstGroupClientInfos.forEach(firstGroupClientInfo -> { -// Assert.assertTrue( -// secondDomainHttpsClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(firstGroupClientInfo.getName())) -// ); -// }); -// thirdGroupClientInfos.forEach(thirdGroupClientInfo -> { -// Assert.assertTrue( -// secondDomainHttpsClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(thirdGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(thirdGroupClientInfo.getName())) -// ); -// }); -// } -// -// @Test -// public void testGetOAuth2ClientsForHttpAndHttps() { -// List firstGroup = Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// ); -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(firstGroup) -// .build() -// )); -// -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertNotNull(foundOAuth2Info); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// List firstGroupClientInfos = firstGroup.stream() -// .map(registrationInfo -> new OAuth2ClientInfo( -// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) -// .collect(Collectors.toList()); -// -// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); -// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); -// firstGroupClientInfos.forEach(firstGroupClientInfo -> { -// Assert.assertTrue( -// firstDomainHttpClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(firstGroupClientInfo.getName())) -// ); -// }); -// -// List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null); -// Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); -// firstGroupClientInfos.forEach(firstGroupClientInfo -> { -// Assert.assertTrue( -// firstDomainHttpsClients.stream().anyMatch(clientInfo -> -// clientInfo.getIcon().equals(firstGroupClientInfo.getIcon()) -// && clientInfo.getName().equals(firstGroupClientInfo.getName())) -// ); -// }); -// } -// -// @Test -// public void testGetDisabledOAuth2Clients() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// -// List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); -// Assert.assertEquals(5, secondDomainHttpClients.size()); -// -// oAuth2Info.setEnabled(false); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// -// List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null); -// Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); -// } -// -// @Test -// public void testFindAllRegistrations() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), -// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo() -// )) -// .build() -// )); -// -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// List foundRegistrations = oAuth2Service.findOauth2ProvidersByTenantId(); -// Assert.assertEquals(6, foundRegistrations.size()); -// oAuth2Info.getOauth2ParamsInfos().stream() -// .flatMap(paramsInfo -> paramsInfo.getClientRegistrations().stream()) -// .forEach(registrationInfo -> -// Assert.assertTrue( -// foundRegistrations.stream() -// .anyMatch(registration -> registration.getClientId().equals(registrationInfo.getClientId())) -// ) -// ); -// } -// -// @Test -// public void testFindRegistrationById() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), -// OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo() -// )) -// .build() -// )); -// -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// List foundRegistrations = oAuth2Service.findOauth2ProvidersByTenantId(); -// foundRegistrations.forEach(registration -> { -// OAuth2Provider foundRegistration = oAuth2Service.findProvider(registration.getUuidId()); -// Assert.assertEquals(registration, foundRegistration); -// }); -// } -// -// @Test -// public void testFindAppSecret() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Lists.newArrayList( -// validMobileInfo("com.test.pkg1", "testPkg1AppSecret"), -// validMobileInfo("com.test.pkg2", "testPkg2AppSecret") -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); -// Assert.assertEquals(3, firstDomainHttpClients.size()); -// for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) { -// String[] segments = clientInfo.getUrl().split("/"); -// String registrationId = segments[segments.length-1]; -// String appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg1"); -// Assert.assertNotNull(appSecret); -// Assert.assertEquals("testPkg1AppSecret", appSecret); -// appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg2"); -// Assert.assertNotNull(appSecret); -// Assert.assertEquals("testPkg2AppSecret", appSecret); -// appSecret = oAuth2Service.findAppSecret(UUID.fromString(registrationId), "com.test.pkg3"); -// Assert.assertNull(appSecret); -// } -// } -// -// @Test -// public void testFindClientsByPackageAndPlatform() { -// OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Lists.newArrayList( -// validMobileInfo("com.test.pkg1", "testPkg1Callback"), -// validMobileInfo("com.test.pkg2", "testPkg2Callback") -// )) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo("Google", Arrays.asList(PlatformType.WEB, PlatformType.ANDROID)), -// validRegistrationInfo("Facebook", Arrays.asList(PlatformType.IOS)), -// validRegistrationInfo("GitHub", Collections.emptyList()) -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// oAuth2Service.saveOAuth2Info(oAuth2Info); -// -// OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); -// Assert.assertEquals(oAuth2Info, foundOAuth2Info); -// -// List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null); -// Assert.assertEquals(3, firstDomainHttpClients.size()); -// List pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null); -// Assert.assertEquals(3, pkg1Clients.size()); -// List pkg1AndroidClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.ANDROID); -// Assert.assertEquals(2, pkg1AndroidClients.size()); -// Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("Google"))); -// Assert.assertTrue(pkg1AndroidClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); -// List pkg1IOSClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", PlatformType.IOS); -// Assert.assertEquals(2, pkg1IOSClients.size()); -// Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("Facebook"))); -// Assert.assertTrue(pkg1IOSClients.stream().anyMatch(client -> client.getName().equals("GitHub"))); -// } -// -// private OAuth2Info createDefaultOAuth2Info() { -// return new OAuth2Info(true, false, Lists.newArrayList( -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build(), -// OAuth2ParamsInfo.builder() -// .domainInfos(Lists.newArrayList( -// OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), -// OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() -// )) -// .mobileInfos(Collections.emptyList()) -// .clientRegistrations(Lists.newArrayList( -// validRegistrationInfo(), -// validRegistrationInfo() -// )) -// .build() -// )); -// } -// -// private OAuth2RegistrationInfo validRegistrationInfo() { -// return validRegistrationInfo(null, Collections.emptyList()); -// } -// -// private OAuth2RegistrationInfo validRegistrationInfo(String label, List platforms) { -// return OAuth2RegistrationInfo.builder() -// .clientId(UUID.randomUUID().toString()) -// .clientSecret(UUID.randomUUID().toString()) -// .authorizationUri(UUID.randomUUID().toString()) -// .accessTokenUri(UUID.randomUUID().toString()) -// .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) -// .platforms(platforms == null ? Collections.emptyList() : platforms) -// .userInfoUri(UUID.randomUUID().toString()) -// .userNameAttributeName(UUID.randomUUID().toString()) -// .jwkSetUri(UUID.randomUUID().toString()) -// .clientAuthenticationMethod(UUID.randomUUID().toString()) -// .loginButtonLabel(label != null ? label : UUID.randomUUID().toString()) -// .loginButtonIcon(UUID.randomUUID().toString()) -// .additionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString())) -// .mapperConfig( -// OAuth2MapperConfig.builder() -// .allowUserCreation(true) -// .activateUser(true) -// .type(MapperType.CUSTOM) -// .custom( -// OAuth2CustomMapperConfig.builder() -// .url(UUID.randomUUID().toString()) -// .build() -// ) -// .build() -// ) -// .build(); -// } -// -// private MobileAppInfo validMobileInfo(String pkgName, String appSecret) { -// return MobileAppInfo.builder().pkgName(pkgName) -// .appSecret(appSecret != null ? appSecret : StringUtils.randomAlphanumeric(24)) -// .build(); -// } + @After + public void after() { + oAuth2ClientService.deleteByTenantId(TenantId.SYS_TENANT_ID); + } + + @Test + public void testSaveOauth2Client() { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client", List.of(PlatformType.ANDROID)); + OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); + + OAuth2Client retrievedOauth2Client = oAuth2ClientService.findOAuth2ClientById(savedOauth2Client.getTenantId(), savedOauth2Client.getId()); + assertThat(retrievedOauth2Client).isEqualTo(savedOauth2Client); + + savedOauth2Client.setTitle("New title"); + OAuth2Client updatedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, savedOauth2Client); + + OAuth2Client retrievedOauth2Client2 = oAuth2ClientService.findOAuth2ClientById(savedOauth2Client.getTenantId(), savedOauth2Client.getId()); + assertThat(retrievedOauth2Client2).isEqualTo(updatedOauth2Client); + } + + @Test + public void testGetTenantOAuth2Clients() { + List oAuth2Clients = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, StringUtils.randomAlphabetic(5)); + OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); + oAuth2Clients.add(savedOauth2Client); + } + List retrieved = oAuth2ClientService.findOAuth2ClientsByTenantId(TenantId.SYS_TENANT_ID); + assertThat(retrieved).containsOnlyOnceElementsOf(oAuth2Clients); + + List retrievedInfos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); + List oAuth2ClientInfos = oAuth2Clients.stream().map(OAuth2Utils::toClientInfo).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 1415edddc8..c28da9332e 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 @@ -117,7 +117,7 @@ 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.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; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; @@ -2045,7 +2045,7 @@ public class RestClient implements Closeable { }).getBody(); } - public List getOAuth2Clients(String pkgName, PlatformType platformType) { + public List getOAuth2Clients(String pkgName, PlatformType platformType) { Map params = new HashMap<>(); StringBuilder urlBuilder = new StringBuilder(baseURL); urlBuilder.append("/api/noauth/oauth2Clients"); @@ -2066,7 +2066,7 @@ public class RestClient implements Closeable { urlBuilder.toString(), HttpMethod.POST, HttpEntity.EMPTY, - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }, params).getBody(); } From b57744f5d56afc5f77fb529d7c10d9888273615d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 19 Jul 2024 17:16:41 +0300 Subject: [PATCH 03/29] 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(); From 333154ad620cedd82c29693556b62be0cdc9faea Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 22 Jul 2024 13:48:43 +0300 Subject: [PATCH 04/29] fixed SqlDatabaseUpgradeService with new upgrade, refactoring --- .../main/data/upgrade/3.7.0/schema_update.sql | 18 +++++--- .../server/controller/DomainController.java | 8 ++-- .../controller/MobileAppController.java | 8 ++-- .../server/controller/OAuth2Controller.java | 5 +-- .../install/ThingsboardInstallService.java | 3 ++ .../install/SqlDatabaseUpgradeService.java | 3 ++ .../server/controller/AbstractWebTest.java | 40 +++++++++++++++++ .../controller/DomainControllerTest.java | 35 --------------- .../server/controller/HomePageApiTest.java | 37 +++------------- .../controller/MobileAppControllerTest.java | 35 --------------- .../Oauth2ClientControllerTest.java | 35 --------------- .../server/common/data/domain/Domain.java | 10 ++++- .../server/common/data/domain/DomainInfo.java | 16 ++++--- .../server/common/data/mobile/MobileApp.java | 13 ++++-- .../common/data/mobile/MobileAppInfo.java | 20 ++++----- .../common/data/oauth2/HasOauth2Clients.java | 26 ----------- .../common/data/oauth2/OAuth2Client.java | 44 +++++++++++-------- common/proto/src/main/proto/queue.proto | 5 ++- .../server/dao/domain/DomainServiceImpl.java | 4 +- .../dao/mobile/MobileAppServiceImpl.java | 2 +- .../dao/oauth2/OAuth2ClientServiceImpl.java | 6 +-- .../dao/sql/mobile/JpaMobileAppDao.java | 8 ++-- .../main/resources/sql/schema-entities.sql | 18 ++++---- 23 files changed, 160 insertions(+), 239 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java diff --git a/application/src/main/data/upgrade/3.7.0/schema_update.sql b/application/src/main/data/upgrade/3.7.0/schema_update.sql index 945051a3a2..dce84e3a40 100644 --- a/application/src/main/data/upgrade/3.7.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.7.0/schema_update.sql @@ -24,10 +24,19 @@ ALTER TABLE domain ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, ADD COLUMN IF NOT EXISTS edge_enabled boolean, ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080', DROP COLUMN IF EXISTS domain_scheme; + +-- delete duplicated domains +DELETE FROM domain d1 USING domain d2 WHERE d1.created_time < d2.created_time AND d1.domain_name = d2.domain_name; + ALTER TABLE mobile_app ADD COLUMN IF NOT EXISTS oauth2_enabled boolean, ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; -ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080'; -ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS title varchar(100); + +-- delete duplicated apps +DELETE FROM mobile_app m1 USING mobile_app m2 WHERE m1.created_time < m2.created_time AND m1.pkg_name = m2.pkg_name; + +ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080', + ADD COLUMN IF NOT EXISTS title varchar(100); +UPDATE oauth2_client SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL; CREATE TABLE IF NOT EXISTS domain_oauth2_client ( domain_id uuid NOT NULL, @@ -43,13 +52,11 @@ CREATE TABLE IF NOT EXISTS mobile_app_oauth2_client ( CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE ); +-- migrate oauth2_params table DO $$ BEGIN IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'oauth2_params') THEN - -- delete duplicated domains - DELETE FROM domain d1 USING domain d2 WHERE d1.created_time < d2.created_time AND d1.domain_name = d2.domain_name; - UPDATE domain SET oauth2_enabled = p.enabled, edge_enabled = p.edge_enabled FROM oauth2_params p WHERE p.id = domain.oauth2_params_id; @@ -68,7 +75,6 @@ $$ ALTER TABLE mobile_app RENAME CONSTRAINT oauth2_mobile_pkey TO mobile_app_pkey; ALTER TABLE domain RENAME CONSTRAINT oauth2_domain_pkey TO domain_pkey; ALTER TABLE oauth2_client RENAME CONSTRAINT oauth2_registration_pkey TO oauth2_client_pkey; - UPDATE oauth2_client SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL; ALTER TABLE domain DROP COLUMN oauth2_params_id; ALTER TABLE mobile_app DROP COLUMN oauth2_params_id; 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 33de93cdd7..da0eeababf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DomainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DomainController.java @@ -63,7 +63,7 @@ public class DomainController extends BaseController { "The newly created Domain Id will be present in the response. " + "Specify existing Domain Id to update the domain. " + "Referencing non-existing Domain Id will cause 'Not Found' error." + - "\n\nDomain name is unique for entire platform setup.\n\n") + "\n\nDomain name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/domain") public Domain saveDomain( @@ -104,13 +104,13 @@ public class DomainController extends BaseController { } @ApiOperation(value = "Delete Domain by ID (deleteDomain)", - notes = "Deletes Domain by ID. Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + notes = "Deletes Domain by ID. Referencing non-existing asset Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @DeleteMapping(value = "/domain/{id}") public void deleteDomain(@PathVariable UUID id) throws Exception { DomainId domainId = new DomainId(id); - checkDomainId(domainId, Operation.DELETE); - domainService.deleteDomainById(getTenantId(), domainId); + Domain domain = checkDomainId(domainId, Operation.DELETE); + tbDomainService.delete(domain, getCurrentUser()); } } 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 0f39d8f0ea..06e2c991fa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -64,7 +64,7 @@ public class MobileAppController extends BaseController { "The newly created Mobile App Id will be present in the response. " + "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") + "\n\nMobile app package name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/mobileApp") public MobileApp saveMobileApp( @@ -106,13 +106,13 @@ public class MobileAppController extends BaseController { } @ApiOperation(value = "Delete Mobile App by ID (deleteMobileApp)", - notes = "Deletes Mobile App by ID. Referencing non-existing asset Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + notes = "Deletes Mobile App by ID. Referencing non-existing asset Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @DeleteMapping(value = "/mobileApp/{id}") public void deleteMobileApp(@PathVariable UUID id) throws Exception { MobileAppId mobileAppId = new MobileAppId(id); - checkMobileAppId(mobileAppId, Operation.DELETE); - mobileAppService.deleteMobileAppById(getTenantId(), mobileAppId); + MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.DELETE); + tbMobileAppService.delete(mobileApp, getCurrentUser()); } } 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 7134bf1fd5..001438ce43 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -51,7 +51,6 @@ import java.util.List; import java.util.UUID; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; @RestController @TbCoreComponent @@ -100,7 +99,7 @@ public class OAuth2Controller extends BaseController { } } - @ApiOperation(value = "Save OAuth2 Client Registration (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Save OAuth2 Client (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PostMapping(value = "/oauth2/client") public OAuth2Client saveOAuth2Client(@RequestBody @Valid OAuth2Client oAuth2Client) throws Exception { @@ -126,7 +125,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) + notes = "Deletes the asset and all the relations (from and to the asset). Referencing non-existing asset Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @DeleteMapping(value = "/oauth2/client/{id}") public void deleteOauth2Client(@PathVariable UUID id) throws Exception { diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 4bef0d420a..75ac97881c 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -137,6 +137,9 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createCustomerTitleUniqueConstraintIfNotExists(); systemDataLoaderService.updateDefaultNotificationConfigs(false); systemDataLoaderService.updateSecuritySettings(); + case "3.7.0": + log.info("Upgrading ThingsBoard from version 3.7.0 to 3.7.1 ..."); + databaseEntitiesUpgradeService.upgradeDatabase("3.7.0"); //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 86d922c305..000c0536cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -121,6 +121,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService case "3.6.4": updateSchema("3.6.4", 3006004, "3.7.0", 3007000, null); break; + case "3.7.0": + updateSchema("3.7.0", 3007000, "3.7.1", 3007001, null); + break; default: throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); } 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 b83a1b567c..59c1a50fa1 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -99,6 +99,11 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2Client; +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.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; @@ -1109,4 +1114,39 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, oldTenantProfile); } + 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/DomainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java index 9fe161d936..d2ade5de11 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java @@ -140,39 +140,4 @@ public class DomainControllerTest extends AbstractControllerTest { 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/HomePageApiTest.java b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java index e796c7346a..6b46e64cfe 100644 --- a/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java @@ -60,7 +60,6 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -361,8 +360,11 @@ public class HomePageApiTest extends AbstractControllerTest { Assert.assertTrue(featuresInfo.isNotificationEnabled()); Assert.assertFalse(featuresInfo.isOauthEnabled()); - Domain domain = createDomain(TenantId.SYS_TENANT_ID, "mydomain", true); - doPost("/api/domain", domain).andExpect(status().isOk()); + OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test google client"); + OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class); + + Domain domain = createDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true); + doPost("/api/domain?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), domain, Domain.class); featuresInfo = doGet("/api/admin/featuresInfo", FeaturesInfo.class); Assert.assertNotNull(featuresInfo); @@ -484,38 +486,13 @@ public class HomePageApiTest extends AbstractControllerTest { return doPostWithResponse("/api/entitiesQuery/count", query, Long.class); } - private Domain createDomain(TenantId tenantId, String domainName, boolean oauth2Enabled) { + private Domain createDomain(TenantId tenantId, String domainName, boolean oauth2Enabled, boolean edgeEnabled) { Domain domain = new Domain(); domain.setTenantId(tenantId); domain.setName(domainName); domain.setOauth2Enabled(oauth2Enabled); + domain.setPropagateToEdge(edgeEnabled); return domain; } - private OAuth2Client validRegistration() { - OAuth2Client oAuth2Client = new OAuth2Client(); - oAuth2Client.setClientId(UUID.randomUUID().toString()); -// .clientSecret(UUID.randomUUID().toString()) -// .authorizationUri(UUID.randomUUID().toString()) -// .accessTokenUri(UUID.randomUUID().toString()) -// .scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString())) -// .platforms(Collections.emptyList()) -// .userInfoUri(UUID.randomUUID().toString()) -// .userNameAttributeName(UUID.randomUUID().toString()) -// .jwkSetUri(UUID.randomUUID().toString()) -// .clientAuthenticationMethod(UUID.randomUUID().toString()) -// .loginButtonLabel(UUID.randomUUID().toString()) -// .mapperConfig( -// OAuth2MapperConfig.builder() -// .type(MapperType.CUSTOM) -// .custom( -// OAuth2CustomMapperConfig.builder() -// .url(UUID.randomUUID().toString()) -// .build() -// ) -// .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 index 3ba8bd29dd..efe6318428 100644 --- a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java @@ -142,39 +142,4 @@ public class MobileAppControllerTest extends AbstractControllerTest { 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 index 8ab7233286..42371c8769 100644 --- a/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java @@ -80,39 +80,4 @@ public class Oauth2ClientControllerTest extends AbstractControllerTest { .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 6b1853c890..e2414f11d8 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 @@ -19,7 +19,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; @@ -30,7 +29,6 @@ import org.thingsboard.server.common.data.id.TenantId; @EqualsAndHashCode(callSuper = true) @Data @ToString -@NoArgsConstructor public class Domain extends BaseData implements HasTenantId, HasName { @Schema(description = "JSON object with Tenant Id") @@ -43,6 +41,14 @@ public class Domain extends BaseData implements HasTenantId, HasName { @Schema(description = "Whether OAuth2 settings are enabled on Edge or not") private boolean propagateToEdge; + public Domain() { + super(); + } + + public Domain(DomainId id) { + super(id); + } + public Domain(Domain domain) { super(domain); this.tenantId = domain.tenantId; 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 83ea9998df..335d5b1604 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,21 +16,17 @@ 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.id.DomainId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import java.util.List; @EqualsAndHashCode(callSuper = true) @Data -@NoArgsConstructor -@AllArgsConstructor @Schema -public class DomainInfo extends Domain implements HasOauth2Clients { +public class DomainInfo extends Domain { @Schema(description = "List of available oauth2 client registration") private List oauth2ClientInfos; @@ -39,4 +35,12 @@ public class DomainInfo extends Domain implements HasOauth2Clients { super(domain); this.oauth2ClientInfos = oauth2ClientInfos; } + + public DomainInfo() { + super(); + } + + public DomainInfo(DomainId domainId) { + super(domainId); + } } 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 ddbb3fcf58..3dad679532 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,10 +17,10 @@ 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.NotBlank; import jakarta.validation.constraints.NotEmpty; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; @@ -32,13 +32,12 @@ import org.thingsboard.server.common.data.validation.Length; @EqualsAndHashCode(callSuper = true) @Data @ToString -@NoArgsConstructor public class MobileApp extends BaseData implements HasTenantId, HasName { @Schema(description = "JSON object with Tenant Id") private TenantId tenantId; @Schema(description = "Application package name. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED) - @NotEmpty + @NotBlank private String pkgName; @Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED) @NotEmpty @@ -47,6 +46,14 @@ public class MobileApp extends BaseData implements HasTenantId, Has @Schema(description = "Whether OAuth2 settings are enabled or not") private boolean oauth2Enabled; + public MobileApp() { + super(); + } + + public MobileApp(MobileAppId id) { + super(id); + } + public MobileApp(MobileApp mobile) { super(mobile); this.tenantId = mobile.tenantId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java index 541f1e6753..9a0b0b3b1c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java @@ -16,25 +16,17 @@ package org.thingsboard.server.common.data.mobile; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; -import lombok.ToString; -import org.thingsboard.server.common.data.oauth2.HasOauth2Clients; +import org.thingsboard.server.common.data.id.MobileAppId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import java.util.List; @EqualsAndHashCode(callSuper = true) @Data -@ToString -@NoArgsConstructor -@AllArgsConstructor -@Builder @Schema -public class MobileAppInfo extends MobileApp implements HasOauth2Clients { +public class MobileAppInfo extends MobileApp { @Schema(description = "List of available oauth2 client registrations") private List oauth2ClientInfos; @@ -44,4 +36,12 @@ public class MobileAppInfo extends MobileApp implements HasOauth2Clients { this.oauth2ClientInfos = oauth2ClientInfos; } + public MobileAppInfo() { + super(); + } + + public MobileAppInfo(MobileAppId mobileAppId) { + super(mobileAppId); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java deleted file mode 100644 index 86cdfd1d92..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Clients.java +++ /dev/null @@ -1,26 +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.common.data.oauth2; - - -import java.io.Serializable; -import java.util.List; - -public interface HasOauth2Clients extends Serializable { - - List getOauth2ClientInfos(); - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java index a8a5f0eacd..7624a6ccd5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java @@ -23,7 +23,6 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo; import org.thingsboard.server.common.data.HasName; @@ -36,7 +35,6 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) @Data @ToString(exclude = {"clientSecret"}) -@NoArgsConstructor public class OAuth2Client extends BaseDataWithAdditionalInfo implements HasName, HasTenantId { @Schema(description = "JSON object with Tenant Id") @@ -82,23 +80,31 @@ public class OAuth2Client extends BaseDataWithAdditionalInfo imp @Schema(description = "Additional info of OAuth2 client (e.g. providerName)", requiredMode = Schema.RequiredMode.REQUIRED) private JsonNode additionalInfo; - public OAuth2Client(OAuth2Client registration) { - super(registration); - this.tenantId = registration.tenantId; - this.title = registration.title; - this.mapperConfig = registration.mapperConfig; - this.clientId = registration.clientId; - this.clientSecret = registration.clientSecret; - this.authorizationUri = registration.authorizationUri; - this.accessTokenUri = registration.accessTokenUri; - this.scope = registration.scope; - this.userInfoUri = registration.userInfoUri; - this.userNameAttributeName = registration.userNameAttributeName; - this.jwkSetUri = registration.jwkSetUri; - this.clientAuthenticationMethod = registration.clientAuthenticationMethod; - this.loginButtonLabel = registration.loginButtonLabel; - this.loginButtonIcon = registration.loginButtonIcon; - this.platforms = registration.platforms; + public OAuth2Client() { + super(); + } + + public OAuth2Client(OAuth2ClientId id) { + super(id); + } + + public OAuth2Client(OAuth2Client oAuth2Client) { + super(oAuth2Client); + this.tenantId = oAuth2Client.tenantId; + this.title = oAuth2Client.title; + this.mapperConfig = oAuth2Client.mapperConfig; + this.clientId = oAuth2Client.clientId; + this.clientSecret = oAuth2Client.clientSecret; + this.authorizationUri = oAuth2Client.authorizationUri; + this.accessTokenUri = oAuth2Client.accessTokenUri; + this.scope = oAuth2Client.scope; + this.userInfoUri = oAuth2Client.userInfoUri; + this.userNameAttributeName = oAuth2Client.userNameAttributeName; + this.jwkSetUri = oAuth2Client.jwkSetUri; + this.clientAuthenticationMethod = oAuth2Client.clientAuthenticationMethod; + this.loginButtonLabel = oAuth2Client.loginButtonLabel; + this.loginButtonIcon = oAuth2Client.loginButtonIcon; + this.platforms = oAuth2Client.platforms; } @Override diff --git a/common/proto/src/main/proto/queue.proto b/common/proto/src/main/proto/queue.proto index 581b993120..974ec504fe 100644 --- a/common/proto/src/main/proto/queue.proto +++ b/common/proto/src/main/proto/queue.proto @@ -54,7 +54,10 @@ enum EntityTypeProto { NOTIFICATION_REQUEST = 31; NOTIFICATION = 32; NOTIFICATION_RULE = 33; - QUEUE_STATS = 34; + QUEUE_STATS = 34; + OAUTH2_CLIENT = 35; + DOMAIN = 36; + MOBILE_APP = 37; } /** 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 bb1c89b012..ee82b0cf8a 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 @@ -77,7 +77,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe @Override public void updateOauth2Clients(TenantId tenantId, DomainId domainId, List oAuth2ClientIds) { - log.trace("Executing addOauth2Clients, domainId [{}], oAuth2ClientIds [{}]", domainId, oAuth2ClientIds); + log.trace("Executing updateOauth2Clients, domainId [{}], oAuth2ClientIds [{}]", domainId, oAuth2ClientIds); Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); Validator.validateId(domainId, id -> INCORRECT_DOMAIN_ID + id); Validator.checkNotNull(oAuth2ClientIds, "Incorrect oAuth2ClientIds " + oAuth2ClientIds); @@ -118,7 +118,7 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe @Override public List findDomainInfosByTenantId(TenantId tenantId) { - log.trace("Executing findDomainInfo [{}]", tenantId); + log.trace("Executing findDomainInfosByTenantId [{}]", tenantId); List domains = domainDao.findByTenantId(tenantId); List domainInfos = new ArrayList<>(); domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { 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 6dc5d1c1f5..9466c3c42b 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 @@ -163,7 +163,7 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil @Override public void deleteMobileAppsByTenantId(TenantId tenantId) { - log.trace("Executing deleteDomainsByTenantId, tenantId [{}]", tenantId); + log.trace("Executing deleteMobileAppsByTenantId, tenantId [{}]", tenantId); Validator.validateId(tenantId, id -> INCORRECT_TENANT_ID + id); mobileAppDao.deleteByTenantId(tenantId); } 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 6cf944804d..d94f0c7fce 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 @@ -57,7 +57,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA @Override public List findOAuth2ClientLoginInfosByDomainName(String domainName) { - log.trace("Executing getOAuth2Clients [{}] ", domainName); + log.trace("Executing findOAuth2ClientLoginInfosByDomainName [{}] ", domainName); validateString(domainName, dn -> INCORRECT_DOMAIN_NAME + dn); return oauth2ClientDao.findEnabledByDomainName(domainName) .stream() @@ -67,7 +67,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA @Override public List findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType) { - log.trace("Executing getOAuth2Clients pkgName=[{}] platformType=[{}]",pkgName, platformType); + log.trace("Executing findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType pkgName=[{}] platformType=[{}]",pkgName, platformType); return oauth2ClientDao.findEnabledByPckNameAndPlatformType(pkgName, platformType) .stream() .map(OAuth2Utils::toClientLoginInfo) @@ -108,7 +108,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA @Override @Transactional public void deleteOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId) { - log.trace("[{}][{}] Executing deleteOAuth2ClientById [{}]", tenantId, oAuth2ClientId); + log.trace("Executing deleteOAuth2ClientById [{}]", oAuth2ClientId); oauth2ClientDao.removeById(tenantId, oAuth2ClientId.getId()); eventPublisher.publishEvent(DeleteEntityEvent.builder() .tenantId(tenantId) 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 98442974f8..3ed91d722a 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 @@ -40,7 +40,7 @@ import java.util.UUID; @SqlDao public class JpaMobileAppDao extends JpaAbstractDao implements MobileAppDao { - private final MobileAppRepository repository; + private final MobileAppRepository mobileAppRepository; private final MobileAppOauth2ClientRepository mobileOauth2ProviderRepository; @Override @@ -50,12 +50,12 @@ public class JpaMobileAppDao extends JpaAbstractDao @Override protected JpaRepository getRepository() { - return repository; + return mobileAppRepository; } @Override public List findByTenantId(TenantId tenantId) { - return DaoUtil.convertDataList(repository.findByTenantId(tenantId.getId())); + return DaoUtil.convertDataList(mobileAppRepository.findByTenantId(tenantId.getId())); } @Override @@ -75,7 +75,7 @@ public class JpaMobileAppDao extends JpaAbstractDao @Override public void deleteByTenantId(TenantId tenantId) { - repository.deleteByTenantId(tenantId.getId()); + mobileAppRepository.deleteByTenantId(tenantId.getId()); } @Override diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 93adc65135..382b95603a 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -560,9 +560,9 @@ CREATE TABLE IF NOT EXISTS key_dictionary CREATE TABLE IF NOT EXISTS oauth2_client ( id uuid NOT NULL CONSTRAINT oauth2_client_pkey PRIMARY KEY, - tenant_id uuid NOT NULL, - title varchar(100) NOT NULL, created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + title varchar(100) NOT NULL, additional_info varchar, client_id varchar(255), client_secret varchar(2048), @@ -595,22 +595,20 @@ CREATE TABLE IF NOT EXISTS oauth2_client ( CREATE TABLE IF NOT EXISTS domain ( id uuid NOT NULL CONSTRAINT domain_pkey PRIMARY KEY, - tenant_id uuid NOT NULL, created_time bigint NOT NULL, - domain_name varchar(255), + tenant_id uuid NOT NULL, + domain_name varchar(255) UNIQUE, oauth2_enabled boolean, - edge_enabled boolean, - CONSTRAINT domain_unq_key UNIQUE (domain_name) + edge_enabled boolean ); CREATE TABLE IF NOT EXISTS mobile_app ( id uuid NOT NULL CONSTRAINT mobile_app_pkey PRIMARY KEY, - tenant_id uuid, created_time bigint NOT NULL, - pkg_name varchar(255), + tenant_id uuid, + pkg_name varchar(255) UNIQUE, app_secret varchar(2048), - oauth2_enabled boolean, - CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name) + oauth2_enabled boolean ); CREATE TABLE IF NOT EXISTS domain_oauth2_client ( From 60b03288cd8d802d176602a923247d623224da59 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 2 Aug 2024 12:03:18 +0300 Subject: [PATCH 05/29] wrapped /infos endpoint by PageData for UI opportunities --- .../server/controller/DomainController.java | 21 +++++++++-- .../controller/MobileAppController.java | 21 +++++++++-- .../server/controller/OAuth2Controller.java | 20 +++++++++-- .../DefaultSystemDataLoaderService.java | 2 +- .../controller/DomainControllerTest.java | 36 +++++++++---------- .../controller/MobileAppControllerTest.java | 36 +++++++++---------- .../Oauth2ClientControllerTest.java | 33 +++++++---------- .../server/dao/domain/DomainService.java | 4 ++- .../server/dao/mobile/MobileAppService.java | 4 ++- .../dao/oauth2/OAuth2ClientService.java | 4 ++- .../server/dao/domain/DomainDao.java | 4 ++- .../server/dao/domain/DomainServiceImpl.java | 10 +++--- .../server/dao/mobile/MobileAppDao.java | 4 ++- .../dao/mobile/MobileAppServiceImpl.java | 10 +++--- .../server/dao/oauth2/OAuth2ClientDao.java | 4 ++- .../dao/oauth2/OAuth2ClientServiceImpl.java | 11 ++++-- .../dao/sql/domain/DomainRepository.java | 5 +-- .../server/dao/sql/domain/JpaDomainDao.java | 6 ++-- .../dao/sql/mobile/JpaMobileAppDao.java | 6 ++-- .../dao/sql/mobile/MobileAppRepository.java | 5 +-- .../dao/sql/oauth2/JpaOAuth2ClientDao.java | 6 ++-- .../sql/oauth2/OAuth2ClientRepository.java | 4 ++- .../server/dao/service/DomainServiceTest.java | 10 +++--- .../dao/service/MobileAppServiceTest.java | 10 +++--- .../dao/service/OAuth2ClientServiceTest.java | 6 ++-- 25 files changed, 175 insertions(+), 107 deletions(-) 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 da0eeababf..79326f56bc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DomainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DomainController.java @@ -36,6 +36,8 @@ import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.OAuth2ClientId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.domain.TbDomainService; @@ -45,8 +47,11 @@ import org.thingsboard.server.service.security.permission.Resource; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @RestController @@ -91,8 +96,18 @@ public class DomainController extends BaseController { @ApiOperation(value = "Get Domain infos (getTenantDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/domain/infos") - public List getTenantDomainInfos() throws ThingsboardException { - return domainService.findDomainInfosByTenantId(getTenantId()); + public PageData getTenantDomainInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = "Case-insensitive 'substring' filter based on rule's name") + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return domainService.findDomainInfosByTenantId(getTenantId(), pageLink); } @ApiOperation(value = "Get Domain info by Id (getDomainInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) 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 06e2c991fa..4d0f19299f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java +++ b/application/src/main/java/org/thingsboard/server/controller/MobileAppController.java @@ -37,6 +37,8 @@ import org.thingsboard.server.common.data.id.OAuth2ClientId; 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.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.mobile.TbMobileAppService; @@ -46,8 +48,11 @@ import org.thingsboard.server.service.security.permission.Resource; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; -import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; @RestController @@ -92,9 +97,19 @@ public class MobileAppController extends BaseController { @ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/mobileApp/infos") - public List getTenantMobileAppInfos() throws ThingsboardException { + public PageData getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = "Case-insensitive 'substring' filter based on rule's name") + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); - return mobileAppService.findMobileAppInfosByTenantId(tenantId); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return mobileAppService.findMobileAppInfosByTenantId(tenantId, pageLink); } @ApiOperation(value = "Get mobile info by id (getMobileAppInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) 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 001438ce43..ce137be00e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -38,6 +38,8 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.config.annotations.ApiOperation; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -50,6 +52,10 @@ import java.util.Enumeration; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; @RestController @@ -112,8 +118,18 @@ public class OAuth2Controller extends BaseController { @ApiOperation(value = "Get OAuth2 Client Registration infos (findTenantOAuth2ClientInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/oauth2/client/infos") - public List findTenantOAuth2ClientInfos() throws ThingsboardException { - return oAuth2ClientService.findOAuth2ClientInfosByTenantId(getTenantId()); + public PageData findTenantOAuth2ClientInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @Parameter(description = "Case-insensitive 'substring' filter based on rule's name") + @RequestParam(required = false) String textSearch, + @Parameter(description = SORT_PROPERTY_DESCRIPTION) + @RequestParam(required = false) String sortProperty, + @Parameter(description = SORT_ORDER_DESCRIPTION) + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return oAuth2ClientService.findOAuth2ClientInfosByTenantId(getTenantId(), pageLink); } @ApiOperation(value = "Get OAuth2 Client Registration by id (getOAuth2ClientById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 3168e22f6f..dcb19ed271 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -308,7 +308,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { jwtSettingsService.saveJwtSettings(jwtSettings); } - List mobiles = oAuth2MobileDao.findByTenantId(TenantId.SYS_TENANT_ID); + List mobiles = oAuth2MobileDao.findByTenantId(TenantId.SYS_TENANT_ID, new PageLink(Integer.MAX_VALUE,0)).getData(); if (CollectionUtils.isNotEmpty(mobiles)) { mobiles.stream() .filter(config -> !validateKeyLength(config.getAppSecret())) diff --git a/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java index d2ade5de11..609444bd3d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java @@ -20,22 +20,17 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; 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; @@ -45,6 +40,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class DomainControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_DOMAIN_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() { + }; + @Before public void setUp() throws Exception { loginSysAdmin(); @@ -52,16 +52,14 @@ public class DomainControllerTest extends AbstractControllerTest { @After public void tearDown() throws Exception { - List domains = doGetTyped("/api/domain/infos", new TypeReference>() { - }); - for (Domain domain : domains) { + PageData pageData = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0)); + for (Domain domain : pageData.getData()) { doDelete("/api/domain/" + domain.getId().getId()) .andExpect(status().isOk()); } - List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { - }); - for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + PageData clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) { doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) .andExpect(status().isOk()); } @@ -69,17 +67,15 @@ public class DomainControllerTest extends AbstractControllerTest { @Test public void testSaveDomain() throws Exception { - List domainInfos = doGetTyped("/api/domain/infos", new TypeReference>() { - }); - assertThat(domainInfos).isEmpty(); + PageData pageData = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0)); + assertThat(pageData.getData()).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())); + PageData pageData2 = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0)); + assertThat(pageData2.getData()).hasSize(1); + assertThat(pageData2.getData().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())); diff --git a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java index efe6318428..a0deb39a51 100644 --- a/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java @@ -20,23 +20,18 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; 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; @@ -46,6 +41,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class MobileAppControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() { + }; + @Before public void setUp() throws Exception { loginSysAdmin(); @@ -53,16 +53,14 @@ public class MobileAppControllerTest extends AbstractControllerTest { @After public void tearDown() throws Exception { - List mobileAppInfos = doGetTyped("/api/mobileApp/infos", new TypeReference>() { - }); - for (MobileApp mobileApp : mobileAppInfos) { + PageData pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + for (MobileApp mobileApp : pageData.getData()) { doDelete("/api/mobileApp/" + mobileApp.getId().getId()) .andExpect(status().isOk()); } - List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { - }); - for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + PageData clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) { doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) .andExpect(status().isOk()); } @@ -70,17 +68,15 @@ public class MobileAppControllerTest extends AbstractControllerTest { @Test public void testSaveMobileApp() throws Exception { - List MobileAppInfos = doGetTyped("/api/mobileApp/infos", new TypeReference>() { - }); - assertThat(MobileAppInfos).isEmpty(); + PageData pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + assertThat(pageData.getData()).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())); + PageData pageData2 = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0)); + assertThat(pageData2.getData()).hasSize(1); + assertThat(pageData2.getData().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())); diff --git a/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java index 42371c8769..fbde35567c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java @@ -20,21 +20,13 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; 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; @@ -42,6 +34,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class Oauth2ClientControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() { + }; + @Before public void setUp() throws Exception { loginSysAdmin(); @@ -49,9 +44,8 @@ public class Oauth2ClientControllerTest extends AbstractControllerTest { @After public void tearDown() throws Exception { - List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { - }); - for (OAuth2ClientInfo oAuth2ClientInfo : oAuth2ClientInfos) { + PageData pageData = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + for (OAuth2ClientInfo oAuth2ClientInfo : pageData.getData()) { doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString()) .andExpect(status().isOk()); } @@ -60,17 +54,16 @@ public class Oauth2ClientControllerTest extends AbstractControllerTest { @Test public void testSaveOauth2Client() throws Exception { loginSysAdmin(); - List oAuth2ClientInfos = doGetTyped("/api/oauth2/client/infos", new TypeReference>() { - }); - assertThat(oAuth2ClientInfos).isEmpty(); + PageData pageData = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + assertThat(pageData.getData()).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)); + PageData pageData2 = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0)); + + assertThat(pageData2.getData()).hasSize(1); + assertThat(pageData2.getData().get(0)).isEqualTo(new OAuth2ClientInfo(savedOAuth2Client)); OAuth2Client retrievedOAuth2ClientInfo = doGet("/api/oauth2/client/{id}", OAuth2Client.class, savedOAuth2Client.getId().getId()); assertThat(retrievedOAuth2ClientInfo).isEqualTo(savedOAuth2Client); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java index 312e6391c6..f80e87926e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.domain.DomainInfo; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; @@ -32,7 +34,7 @@ public interface DomainService extends EntityDaoService { Domain findDomainById(TenantId tenantId, DomainId domainId); - List findDomainInfosByTenantId(TenantId tenantId); + PageData findDomainInfosByTenantId(TenantId tenantId, PageLink pageLink); DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java index 076198823c..2976022116 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.id.OAuth2ClientId; 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.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; @@ -32,7 +34,7 @@ public interface MobileAppService extends EntityDaoService { MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId); - List findMobileAppInfosByTenantId(TenantId tenantId); + PageData findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink); MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java index 232c9cf1a3..73fb8bafe7 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java @@ -21,6 +21,8 @@ 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.PlatformType; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.EntityDaoService; import java.util.List; @@ -44,5 +46,5 @@ public interface OAuth2ClientService extends EntityDaoService { void deleteOauth2ClientsByTenantId(TenantId tenantId); - List findOAuth2ClientInfosByTenantId(TenantId tenantId); + PageData findOAuth2ClientInfosByTenantId(TenantId tenantId, PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java index b4309b6214..cbc371520c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java @@ -20,13 +20,15 @@ import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; public interface DomainDao extends Dao { - List findByTenantId(TenantId tenantId); + PageData findByTenantId(TenantId tenantId, PageLink pageLink); int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean oauth2Enabled); 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 ee82b0cf8a..4dd996d75c 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 @@ -31,6 +31,8 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -117,16 +119,16 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe } @Override - public List findDomainInfosByTenantId(TenantId tenantId) { + public PageData findDomainInfosByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDomainInfosByTenantId [{}]", tenantId); - List domains = domainDao.findByTenantId(tenantId); + PageData pageData = domainDao.findByTenantId(tenantId, pageLink); List domainInfos = new ArrayList<>(); - domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { + pageData.getData().stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { domainInfos.add(new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); }); - return domainInfos; + return new PageData<>(domainInfos, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java index c5df586973..f42acb1638 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java @@ -20,13 +20,15 @@ import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; public interface MobileAppDao extends Dao { - List findByTenantId(TenantId tenantId); + PageData findByTenantId(TenantId tenantId, PageLink pageLink); List findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId); 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 9466c3c42b..5272d4e58d 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 @@ -31,6 +31,8 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -89,16 +91,16 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil } @Override - public List findMobileAppInfosByTenantId(TenantId tenantId) { + public PageData findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); - List mobileApps = mobileAppDao.findByTenantId(tenantId); + PageData pageData = mobileAppDao.findByTenantId(tenantId, pageLink); List mobileAppInfos = new ArrayList<>(); - mobileApps.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { + pageData.getData().stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); }); - return mobileAppInfos; + return new PageData<>(mobileAppInfos, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java index fd2623acda..673af18edb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.List; @@ -25,7 +27,7 @@ import java.util.UUID; public interface OAuth2ClientDao extends Dao { - List findByTenantId(UUID tenantId); + PageData findByTenantId(UUID tenantId, PageLink pageLink); List findEnabledByDomainName(String domainName); 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 d94f0c7fce..4b97f28fb4 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 @@ -28,6 +28,8 @@ 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.PlatformType; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -94,7 +96,7 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA @Override public List findOAuth2ClientsByTenantId(TenantId tenantId) { log.trace("Executing findOAuth2ClientsByTenantId [{}]", tenantId); - return oauth2ClientDao.findByTenantId(tenantId.getId()); + return oauth2ClientDao.findByTenantId(tenantId.getId(), new PageLink(Integer.MAX_VALUE)).getData(); } @Override @@ -125,12 +127,15 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA } @Override - public List findOAuth2ClientInfosByTenantId(TenantId tenantId) { + public PageData findOAuth2ClientInfosByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findOAuth2ClientInfosByTenantId tenantId=[{}]", tenantId); - return oauth2ClientDao.findByTenantId(tenantId.getId()) + PageData clientInfos = oauth2ClientDao.findByTenantId(tenantId.getId(), pageLink); + List oAuth2ClientInfos = clientInfos + .getData() .stream() .map(OAuth2ClientInfo::new) .collect(Collectors.toList()); + return new PageData<>(oAuth2ClientInfos, clientInfos.getTotalPages(), clientInfos.getTotalPages(), clientInfos.hasNext()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java index c6566e4302..baf4358e8a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.sql.domain; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -22,12 +24,11 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.DomainEntity; -import java.util.List; import java.util.UUID; public interface DomainRepository extends JpaRepository { - List findByTenantId(@Param("tenantId") UUID tenantId); + Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); @Transactional @Modifying 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 ec9f9f9ca9..4bcdeeec6b 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 @@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.domain.DomainOauth2Client; import org.thingsboard.server.common.data.id.DomainId; import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.domain.DomainDao; import org.thingsboard.server.dao.model.sql.DomainEntity; @@ -54,8 +56,8 @@ public class JpaDomainDao extends JpaAbstractDao implement } @Override - public List findByTenantId(TenantId tenantId) { - return DaoUtil.convertDataList(domainRepository.findByTenantId(tenantId.getId())); + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(domainRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); } @Override 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 3ed91d722a..2e884d0d84 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 @@ -24,6 +24,8 @@ import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.mobile.MobileApp; import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.mobile.MobileAppDao; import org.thingsboard.server.dao.model.sql.MobileAppEntity; @@ -54,8 +56,8 @@ public class JpaMobileAppDao extends JpaAbstractDao } @Override - public List findByTenantId(TenantId tenantId) { - return DaoUtil.convertDataList(mobileAppRepository.findByTenantId(tenantId.getId())); + public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(mobileAppRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java index a74a32b7d6..16ca927fbb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.sql.mobile; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -22,12 +24,11 @@ import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.MobileAppEntity; -import java.util.List; import java.util.UUID; public interface MobileAppRepository extends JpaRepository { - List findByTenantId(@Param("tenantId") UUID tenantId); + Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); @Transactional @Modifying 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 733949c212..e6fd04da78 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 @@ -22,6 +22,8 @@ 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; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.OAuth2ClientEntity; import org.thingsboard.server.dao.oauth2.OAuth2ClientDao; @@ -49,8 +51,8 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao findByTenantId(UUID tenantId) { - return DaoUtil.convertDataList(repository.findByTenantId(tenantId)); + public PageData findByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(repository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java index e00ad79cf4..8c4e6efa15 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.sql.oauth2; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -27,7 +29,7 @@ import java.util.UUID; public interface OAuth2ClientRepository extends JpaRepository { - List findByTenantId(@Param("tenantId") UUID tenantId); + Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); @Query("SELECT c " + "FROM OAuth2ClientEntity c " + diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java index ae24d48c4f..844adec27b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DomainServiceTest.java @@ -25,6 +25,8 @@ 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.OAuth2ClientLoginInfo; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.domain.DomainService; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; @@ -91,16 +93,16 @@ public class DomainServiceTest extends AbstractServiceTest { Domain savedOauth2Client = domainService.saveDomain(SYSTEM_TENANT_ID, oAuth2Client); domains.add(savedOauth2Client); } - List retrieved = domainService.findDomainInfosByTenantId(TenantId.SYS_TENANT_ID); + PageData retrieved = domainService.findDomainInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10, 0)); List domainInfos = domains.stream().map(domain -> new DomainInfo(domain, Collections.emptyList())).toList(); - assertThat(retrieved).containsOnlyOnceElementsOf(domainInfos); + assertThat(retrieved.getData()).containsOnlyOnceElementsOf(domainInfos); } @Test public void tesGetDomainInfo() { OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client"); OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); - List infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); + PageData infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10)); Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "test.domain.com", true, true); Domain savedDomain = domainService.saveDomain(SYSTEM_TENANT_ID, domain); @@ -109,7 +111,7 @@ public class DomainServiceTest extends AbstractServiceTest { // check domain info DomainInfo retrievedInfo = domainService.findDomainInfoById(SYSTEM_TENANT_ID, savedDomain.getId()); - assertThat(retrievedInfo).isEqualTo(new DomainInfo(savedDomain, infos)); + assertThat(retrievedInfo).isEqualTo(new DomainInfo(savedDomain, infos.getData())); //find clients by domain name List oauth2LoginInfo = oAuth2ClientService.findOAuth2ClientLoginInfosByDomainName(savedDomain.getName()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java index 697cbd69fd..77788c98b8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/MobileAppServiceTest.java @@ -25,6 +25,8 @@ 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.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.mobile.MobileAppService; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; @@ -77,16 +79,16 @@ public class MobileAppServiceTest extends AbstractServiceTest { MobileApp savedOauth2Client = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, oAuth2Client); MobileApps.add(savedOauth2Client); } - List retrieved = mobileAppService.findMobileAppInfosByTenantId(TenantId.SYS_TENANT_ID); + PageData retrieved = mobileAppService.findMobileAppInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10, 0)); List MobileAppInfos = MobileApps.stream().map(MobileApp -> new MobileAppInfo(MobileApp, Collections.emptyList())).toList(); - assertThat(retrieved).containsOnlyOnceElementsOf(MobileAppInfos); + assertThat(retrieved.getData()).containsOnlyOnceElementsOf(MobileAppInfos); } @Test public void tesGetMobileAppInfo() { OAuth2Client oAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "Test google client"); OAuth2Client savedOauth2Client = oAuth2ClientService.saveOAuth2Client(SYSTEM_TENANT_ID, oAuth2Client); - List infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); + PageData infos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10)); MobileApp MobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.app", true); MobileApp savedMobileApp = mobileAppService.saveMobileApp(SYSTEM_TENANT_ID, MobileApp); @@ -95,7 +97,7 @@ public class MobileAppServiceTest extends AbstractServiceTest { // check MobileApp info MobileAppInfo retrievedInfo = mobileAppService.findMobileAppInfoById(SYSTEM_TENANT_ID, savedMobileApp.getId()); - assertThat(retrievedInfo).isEqualTo(new MobileAppInfo(savedMobileApp, infos)); + assertThat(retrievedInfo).isEqualTo(new MobileAppInfo(savedMobileApp, infos.getData())); //find clients by MobileApp name List oauth2LoginInfo = oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(savedMobileApp.getName(), null); 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 ae77f74e54..5607dbfa75 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 @@ -24,6 +24,8 @@ 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.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.oauth2.OAuth2ClientService; import java.util.ArrayList; @@ -99,9 +101,9 @@ public class OAuth2ClientServiceTest extends AbstractServiceTest { List retrieved = oAuth2ClientService.findOAuth2ClientsByTenantId(TenantId.SYS_TENANT_ID); assertThat(retrieved).containsOnlyOnceElementsOf(oAuth2Clients); - List retrievedInfos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID); + PageData retrievedInfos = oAuth2ClientService.findOAuth2ClientInfosByTenantId(TenantId.SYS_TENANT_ID, new PageLink(10)); List oAuth2ClientInfos = oAuth2Clients.stream().map(OAuth2ClientInfo::new).collect(Collectors.toList()); - assertThat(retrievedInfos).containsOnlyOnceElementsOf(oAuth2ClientInfos); + assertThat(retrievedInfos.getData()).containsOnlyOnceElementsOf(oAuth2ClientInfos); } } From 7a2a194074a7b5d8b947e1e8b5bb74ce7da30e9c Mon Sep 17 00:00:00 2001 From: rusikv Date: Thu, 8 Aug 2024 15:10:32 +0300 Subject: [PATCH 06/29] UI: oauth2 configuration breakdown: initial implementation --- ui-ngx/src/app/core/auth/auth.service.ts | 10 +- ui-ngx/src/app/core/http/domain.service.ts | 56 ++ .../src/app/core/http/mobile-app.service.ts | 27 +- .../core/http/mobile-application.service.ts | 44 ++ ui-ngx/src/app/core/http/oauth2.service.ts | 29 +- ui-ngx/src/app/core/services/menu.service.ts | 25 +- .../entity/entities-table.component.html | 23 +- .../entity/entities-table.component.ts | 4 +- .../entity/entity-details-panel.component.ts | 2 +- .../home/components/home-components.module.ts | 4 +- .../lib/mobile-app-qrcode-widget.component.ts | 4 +- .../entity/entities-table-config.models.ts | 14 +- .../home/pages/admin/admin-routing.module.ts | 38 +- .../modules/home/pages/admin/admin.module.ts | 6 +- .../admin/mobile-app-settings.component.ts | 4 +- .../admin/oauth2-settings.component.html | 614 ------------------ .../admin/oauth2-settings.component.scss | 78 --- .../pages/admin/oauth2-settings.component.ts | 613 ----------------- .../clients/client-chips.component.html | 22 + .../clients/client-chips.component.scss | 34 + .../oauth2/clients/client-chips.component.ts | 40 ++ .../clients/client-dialog.component.html | 49 ++ .../oauth2/clients/client-dialog.component.ts | 80 +++ .../oauth2/clients/client-list.component.html | 53 ++ .../oauth2/clients/client-list.component.ts | 182 ++++++ .../client-table-header.component.html | 22 + .../clients/client-table-header.component.ts | 34 + .../oauth2/clients/client.component.html | 331 ++++++++++ .../oauth2/clients/client.component.scss | 36 + .../admin/oauth2/clients/client.component.ts | 377 +++++++++++ .../clients/clients-table-config.resolver.ts | 94 +++ .../domains/domain-table-config.resolver.ts | 142 ++++ .../domain-table-header.component.html | 21 + .../domains/domain-table-header.component.ts | 33 + .../oauth2/domains/domain.component.html | 68 ++ .../admin/oauth2/domains/domain.component.ts | 105 +++ .../mobile-app-table-config.resolver.ts | 138 ++++ .../mobile-app-table-header.component.html | 21 + .../mobile-app-table-header.component.ts | 33 + .../mobile-apps/mobile-app.component.html | 69 ++ .../mobile-apps/mobile-app.component.ts | 108 +++ .../admin/oauth2/oauth2-routing.module.ts | 152 +++++ .../home/pages/admin/oauth2/oauth2.module.ts | 50 ++ .../login/pages/login/login.component.ts | 6 +- ui-ngx/src/app/shared/models/constants.ts | 4 + .../app/shared/models/entity-type.models.ts | 62 +- ui-ngx/src/app/shared/models/id/domain-id.ts | 27 + .../src/app/shared/models/id/mobile-app-id.ts | 27 + .../app/shared/models/id/oauth2-client-id.ts | 27 + ui-ngx/src/app/shared/models/oauth2.models.ts | 176 +++-- .../assets/locale/locale.constant-en_US.json | 44 +- 51 files changed, 2813 insertions(+), 1449 deletions(-) create mode 100644 ui-ngx/src/app/core/http/domain.service.ts create mode 100644 ui-ngx/src/app/core/http/mobile-application.service.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.scss create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts create mode 100644 ui-ngx/src/app/shared/models/id/domain-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/mobile-app-id.ts create mode 100644 ui-ngx/src/app/shared/models/id/oauth2-client-id.ts diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 48018e9317..02133c4ca5 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -42,7 +42,7 @@ import { TimeService } from '@core/services/time.service'; import { UtilsService } from '@core/services/utils.service'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; -import { OAuth2ClientInfo, PlatformType } from '@shared/models/oauth2.models'; +import { OAuth2ClientLoginInfo, PlatformType } from '@shared/models/oauth2.models'; import { isMobileApp } from '@core/utils'; import { TwoFactorAuthProviderType, TwoFaProviderInfo } from '@shared/models/two-factor-auth.models'; import { UserPasswordPolicy } from '@shared/models/settings.models'; @@ -66,7 +66,7 @@ export class AuthService { } redirectUrl: string; - oauth2Clients: Array = null; + oauth2Clients: Array = null; twoFactorAuthProviders: Array = null; private refreshTokenSubject: ReplaySubject = null; @@ -223,9 +223,9 @@ export class AuthService { } } - public loadOAuth2Clients(): Observable> { - const url = '/api/noauth/oauth2Clients?platform=' + PlatformType.WEB; - return this.http.post>(url, + public loadOAuth2Clients(): Observable> { + const url = '/api/noauth/oauth2/client?platform=' + PlatformType.WEB; + return this.http.post>(url, null, defaultHttpOptions()).pipe( catchError(err => of([])), tap((OAuth2Clients) => { diff --git a/ui-ngx/src/app/core/http/domain.service.ts b/ui-ngx/src/app/core/http/domain.service.ts new file mode 100644 index 0000000000..206748378e --- /dev/null +++ b/ui-ngx/src/app/core/http/domain.service.ts @@ -0,0 +1,56 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { Domain, DomainInfo } from '@shared/models/oauth2.models'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; + +@Injectable({ + providedIn: 'root' +}) +export class DomainService { + + constructor( + private http: HttpClient + ) { + } + + public saveDomain(domain: Domain, oauth2ClientIds: Array, config?: RequestConfig): Observable { + return this.http.post(`/api/domain?oauth2ClientIds=${oauth2ClientIds.join(',')}`, + domain, defaultHttpOptionsFromConfig(config)); + } + + public updateOauth2Clients(id: string, oauth2ClientRegistrationIds: Array, config?: RequestConfig): Observable { + return this.http.put(`/api/domain/${id}/oauth2Clients`, oauth2ClientRegistrationIds, defaultHttpOptionsFromConfig(config)); + } + + public getTenantDomainInfos(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/domain/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public getDomainInfoById(id: string, config?: RequestConfig): Observable { + return this.http.get(`/api/domain/info/${id}`, defaultHttpOptionsFromConfig(config)); + } + + public deleteDomain(id: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/domain/${id}`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/http/mobile-app.service.ts b/ui-ngx/src/app/core/http/mobile-app.service.ts index 7b108e487c..a506d2805e 100644 --- a/ui-ngx/src/app/core/http/mobile-app.service.ts +++ b/ui-ngx/src/app/core/http/mobile-app.service.ts @@ -14,11 +14,13 @@ /// limitations under the License. /// -import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { MobileAppSettings } from '@shared/models/mobile-app.models'; +import { MobileApp, MobileAppInfo } from '@shared/models/oauth2.models'; +import { PageLink } from '@shared/models/page/page-link'; +import { PageData } from '@shared/models/page/page-data'; @Injectable({ providedIn: 'root' @@ -30,16 +32,25 @@ export class MobileAppService { ) { } - public getMobileAppSettings(config?: RequestConfig): Observable { - return this.http.get(`/api/mobile/app/settings`, defaultHttpOptionsFromConfig(config)); + public saveMobileApp(mobileApp: MobileApp, oauth2ClientIds: Array, config?: RequestConfig): Observable { + return this.http.post(`/api/mobileApp?oauth2ClientIds=${oauth2ClientIds.join(',')}`, + mobileApp, defaultHttpOptionsFromConfig(config)); + } + + public updateOauth2Clients(id: string, oauth2ClientRegistrationIds: Array, config?: RequestConfig): Observable { + return this.http.put(`/api/mobileApp/${id}/oauth2Clients`, oauth2ClientRegistrationIds, defaultHttpOptionsFromConfig(config)); + } + + public getTenantMobileAppInfos(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/mobileApp/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); } - public saveMobileAppSettings(mobileAppSettings: MobileAppSettings, config?: RequestConfig): Observable { - return this.http.post(`/api/mobile/app/settings`, mobileAppSettings, defaultHttpOptionsFromConfig(config)); + public getMobileAppInfoById(id: string, config?: RequestConfig): Observable { + return this.http.get(`/api/mobileApp/info/${id}`, defaultHttpOptionsFromConfig(config)); } - public getMobileAppDeepLink(config?: RequestConfig): Observable { - return this.http.get(`/api/mobile/deepLink`, defaultHttpOptionsFromConfig(config)); + public deleteMobileApp(id: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/mobileApp/${id}`, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/core/http/mobile-application.service.ts b/ui-ngx/src/app/core/http/mobile-application.service.ts new file mode 100644 index 0000000000..17ebdd3fef --- /dev/null +++ b/ui-ngx/src/app/core/http/mobile-application.service.ts @@ -0,0 +1,44 @@ +/// +/// 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. +/// + +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { MobileAppSettings } from '@shared/models/mobile-app.models'; + +@Injectable({ + providedIn: 'root' +}) +export class MobileApplicationService { + + constructor( + private http: HttpClient + ) {} + + public getMobileAppSettings(config?: RequestConfig): Observable { + return this.http.get(`/api/mobile/app/settings`, defaultHttpOptionsFromConfig(config)); + } + + public saveMobileAppSettings(mobileAppSettings: MobileAppSettings, config?: RequestConfig): Observable { + return this.http.post(`/api/mobile/app/settings`, mobileAppSettings, defaultHttpOptionsFromConfig(config)); + } + + public getMobileAppDeepLink(config?: RequestConfig): Observable { + return this.http.get(`/api/mobile/deepLink`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts index a0ac0d5126..14b853eb93 100644 --- a/ui-ngx/src/app/core/http/oauth2.service.ts +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -18,7 +18,9 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { OAuth2ClientRegistrationTemplate, OAuth2Info } from '@shared/models/oauth2.models'; +import { OAuth2Client, OAuth2ClientInfo, OAuth2ClientRegistrationTemplate } from '@shared/models/oauth2.models'; +import { PageData } from '@shared/models/page/page-data'; +import { PageLink } from '@shared/models/page/page-link'; @Injectable({ providedIn: 'root' @@ -27,22 +29,31 @@ export class OAuth2Service { constructor( private http: HttpClient - ) { } - - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + ) { } public getOAuth2Template(config?: RequestConfig): Observable> { return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } - public saveOAuth2Settings(OAuth2Setting: OAuth2Info, config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, - defaultHttpOptionsFromConfig(config)); + public saveOAuth2Client(oAuth2Client: OAuth2Client, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/client', oAuth2Client, defaultHttpOptionsFromConfig(config)); + } + + public findTenantOAuth2ClientInfos(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/client/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public getOAuth2ClientById(id: string, config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/client/${id}`, defaultHttpOptionsFromConfig(config)); + } + + public deleteOauth2Client(id: string, config?: RequestConfig): Observable { + return this.http.delete(`/api/oauth2/client/${id}`, defaultHttpOptionsFromConfig(config)); } public getLoginProcessingUrl(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/loginProcessingUrl`, defaultHttpOptionsFromConfig(config)); + return this.http.get('/api/oauth2/loginProcessingUrl', defaultHttpOptionsFromConfig(config)); } + } diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index ffb5b5f0a6..2fb0f17623 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -282,7 +282,30 @@ export class MenuService { name: 'admin.oauth2.oauth2', type: 'link', path: '/security-settings/oauth2', - icon: 'mdi:shield-account' + icon: 'mdi:shield-account', + pages: [ + { + id: 'domains', + name: 'admin.oauth2.domains', + type: 'link', + path: '/security-settings/oauth2/domains', + icon: 'domain' + }, + { + id: 'mobile_app', + name: 'admin.oauth2.mobile-apps', + type: 'link', + path: '/security-settings/oauth2/mobile-applications', + icon: 'smartphone' + }, + { + id: 'clients', + name: 'admin.oauth2.clients', + type: 'link', + path: '/security-settings/oauth2/clients', + icon: 'public' + } + ] } ] } diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index 5aec80aacb..952e117539 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -38,7 +38,9 @@
- {{ entitiesTableConfig.tableTitle }} + + {{ entitiesTableConfig.tableTitle }} + - - + + + + + + + + + + - - - @@ -209,7 +218,7 @@ matTooltipPosition="above" (click)="column.actionDescriptor.onAction($event, entity)"> - {{column.actionDescriptor.icon}} + {{column.actionDescriptor.iconFunction ? column.actionDescriptor.iconFunction(entity) : column.actionDescriptor.icon}} diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index dd3e846d92..a37b22826b 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -46,6 +46,7 @@ import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router'; import { CellActionDescriptor, CellActionDescriptorType, + ClientChipsEntityTableColumn, EntityActionTableColumn, EntityColumn, EntityLinkTableColumn, @@ -614,7 +615,8 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa columnsUpdated(resetData: boolean = false) { this.entityColumns = this.entitiesTableConfig.columns.filter( - (column) => column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn) + (column) => column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn || + column instanceof ClientChipsEntityTableColumn) .map(column => column as EntityTableColumn>); this.actionColumns = this.entitiesTableConfig.columns.filter( (column) => column instanceof EntityActionTableColumn) diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index ea1dfbf46d..7afbeafad4 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -228,7 +228,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV } hideDetailsTabs(): boolean { - return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; + return this.entitiesTableConfig.hideDetailsTabs || this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; } reloadEntity(): Observable> { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 5390a915d3..74fb826715 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -171,6 +171,7 @@ import { import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; +import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-chips.component'; @NgModule({ declarations: @@ -308,7 +309,8 @@ import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delet RateLimitsComponent, RateLimitsTextComponent, RateLimitsDetailsDialogComponent, - SendNotificationButtonComponent + SendNotificationButtonComponent, + ClientChipsComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts index b484b9eed1..afc508d3d2 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/mobile-app-qrcode-widget.component.ts @@ -19,7 +19,7 @@ import { PageComponent } from '@shared/components/page.component'; import { AppState } from '@core/core.state'; import { Store } from '@ngrx/store'; import { BadgePosition, MobileAppSettings } from '@shared/models/mobile-app.models'; -import { MobileAppService } from '@core/http/mobile-app.service'; +import { MobileApplicationService } from '@core/http/mobile-application.service'; import { WidgetContext } from '@home/models/widget-component.models'; import { UtilsService } from '@core/services/utils.service'; import { Observable, Subject } from 'rxjs'; @@ -78,7 +78,7 @@ export class MobileAppQrcodeWidgetComponent extends PageComponent implements OnI constructor(protected store: Store, protected cd: ChangeDetectorRef, - private mobileAppService: MobileAppService, + private mobileAppService: MobileApplicationService, private utilsService: UtilsService, private elementRef: ElementRef, private imagePipe: ImagePipe, diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index 9dd434101d..b342fd2e06 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -78,7 +78,7 @@ export interface HeaderActionDescriptor { onAction: ($event: MouseEvent) => void; } -export type EntityTableColumnType = 'content' | 'action' | 'link'; +export type EntityTableColumnType = 'content' | 'action' | 'link' | 'clients'; export class BaseEntityTableColumn> { constructor(public type: EntityTableColumnType, @@ -141,7 +141,15 @@ export class DateEntityTableColumn> extends EntityTabl } } -export type EntityColumn> = EntityTableColumn | EntityActionTableColumn | EntityLinkTableColumn; +export class ClientChipsEntityTableColumn> extends BaseEntityTableColumn { + constructor(public key: string, + public title: string, + public width: string = '0px') { + super('clients', key, title, width, false); + } +} + +export type EntityColumn> = EntityTableColumn | EntityActionTableColumn | EntityLinkTableColumn | ClientChipsEntityTableColumn; export class EntityTableConfig, P extends PageLink = PageLink, L extends BaseData = T> { @@ -159,11 +167,13 @@ export class EntityTableConfig, P extends PageLink = P defaultTimewindowInterval = historyInterval(DAY); entityType: EntityType = null; tableTitle = ''; + hideTableTitle = false; selectionEnabled = true; searchEnabled = true; addEnabled = true; entitiesDeleteEnabled = true; detailsPanelEnabled = true; + hideDetailsTabs = false; hideDetailsTabsOnEdit = true; rowPointer = false; actionsColumnTitle = null; diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts index f05fc05a25..5101a3b50a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin-routing.module.ts @@ -14,17 +14,13 @@ /// limitations under the License. /// -import { Injectable, NgModule } from '@angular/core'; -import { Resolve, RouterModule, Routes } from '@angular/router'; - +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; import { MailServerComponent } from '@modules/home/pages/admin/mail-server.component'; import { ConfirmOnExitGuard } from '@core/guards/confirm-on-exit.guard'; import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; -import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; -import { Observable } from 'rxjs'; -import { OAuth2Service } from '@core/http/oauth2.service'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; @@ -41,17 +37,7 @@ import { RouterTabsComponent } from '@home/components/router-tabs.component'; import { auditLogsRoutes } from '@home/pages/audit-log/audit-log-routing.module'; import { ImageGalleryComponent } from '@shared/components/image/image-gallery.component'; import { MobileAppSettingsComponent } from '@home/pages/admin/mobile-app-settings.component'; - -@Injectable() -export class OAuth2LoginProcessingUrlResolver implements Resolve { - - constructor(private oauth2Service: OAuth2Service) { - } - - resolve(): Observable { - return this.oauth2Service.getLoginProcessingUrl(); - } -} +import { oAuth2Routes } from '@home/pages/admin/oauth2/oauth2-routing.module'; const routes: Routes = [ { @@ -358,22 +344,7 @@ const routes: Routes = [ } } }, - { - path: 'oauth2', - component: OAuth2SettingsComponent, - canDeactivate: [ConfirmOnExitGuard], - data: { - auth: [Authority.SYS_ADMIN], - title: 'admin.oauth2.oauth2', - breadcrumb: { - label: 'admin.oauth2.oauth2', - icon: 'mdi:shield-account' - } - }, - resolve: { - loginProcessingUrl: OAuth2LoginProcessingUrlResolver - } - }, + ...oAuth2Routes, ...auditLogsRoutes ] } @@ -383,7 +354,6 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - OAuth2LoginProcessingUrlResolver, ResourcesLibraryTableConfigResolver, QueuesTableConfigResolver ] diff --git a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts index f44782e814..1904738299 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/admin.module.ts @@ -23,7 +23,6 @@ import { MailServerComponent } from '@modules/home/pages/admin/mail-server.compo import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; -import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settings.component'; import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; @@ -35,6 +34,7 @@ import { AutoCommitAdminSettingsComponent } from '@home/pages/admin/auto-commit- import { TwoFactorAuthSettingsComponent } from '@home/pages/admin/two-factor-auth-settings.component'; import { MobileAppSettingsComponent } from '@home/pages/admin/mobile-app-settings.component'; import { WidgetComponentsModule } from '@home/components/widget/widget-components.module'; +import { OAuth2Module } from '@home/pages/admin/oauth2/oauth2.module'; @NgModule({ declarations: @@ -44,7 +44,6 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component SmsProviderComponent, SendTestSmsDialogComponent, SecuritySettingsComponent, - OAuth2SettingsComponent, HomeSettingsComponent, ResourcesLibraryComponent, ResourcesTableHeaderComponent, @@ -59,7 +58,8 @@ import { WidgetComponentsModule } from '@home/components/widget/widget-component SharedModule, HomeComponentsModule, AdminRoutingModule, - WidgetComponentsModule + WidgetComponentsModule, + OAuth2Module ] }) export class AdminModule { } diff --git a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts index 0f2527d833..7f2a48253f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/mobile-app-settings.component.ts @@ -21,7 +21,7 @@ import { PageComponent } from '@shared/components/page.component'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; import { Subject, takeUntil } from 'rxjs'; -import { MobileAppService } from '@core/http/mobile-app.service'; +import { MobileApplicationService } from '@core/http/mobile-application.service'; import { BadgePosition, badgePositionTranslationsMap, @@ -45,7 +45,7 @@ export class MobileAppSettingsComponent extends PageComponent implements HasConf badgePositionTranslationsMap = badgePositionTranslationsMap; constructor(protected store: Store, - private mobileAppService: MobileAppService, + private mobileAppService: MobileApplicationService, private fb: FormBuilder) { super(store); this.buildMobileAppSettingsForm(); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html deleted file mode 100644 index 2ea96f6387..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ /dev/null @@ -1,614 +0,0 @@ - -
- - - - admin.oauth2.oauth2 - - -
-
- - -
- -
-
-
- - {{ 'admin.oauth2.enable' | translate }} - - - {{ 'admin.oauth2.edge-enable' | translate }} - -
-
- -
- - - - - - {{ domainListTittle(oauth2ParamsInfo) }} - - - - - - - - - - -
-
-
-
-
-
- - admin.oauth2.protocol - - - {{ domainSchemaTranslations.get(protocol) | translate | uppercase }} - - - - - admin.domain-name - - - {{ 'admin.error-verification-url' | translate }} - - - {{ 'admin.domain-name-max-length' | translate }} - - -
- - {{ 'admin.domain-name-unique' | translate }} - -
- -
- - admin.oauth2.redirect-uri-template - - - - - - - - - -
-
- -
- -
-
-
-
- -
-
-
- - -
-
- admin.oauth2.no-mobile-apps -
-
-
-
-
- - admin.oauth2.mobile-package - - admin.oauth2.mobile-package-hint - - - {{ 'admin.oauth2.mobile-package-unique' | translate }} - -
-
- - admin.oauth2.mobile-app-secret - - - - admin.oauth2.mobile-app-secret-hint - - {{ 'admin.oauth2.mobile-app-secret-required' | translate }} - - - {{ 'admin.oauth2.mobile-app-secret-min-length' | translate }} - - - {{ 'admin.oauth2.mobile-app-secret-base64' | translate }} - - -
-
-
- -
-
-
-
- -
-
-
-
-
admin.oauth2.providers
- -
- - - - {{ getProviderName(registration) }} - - - - - - - -
-
-
- - admin.oauth2.login-provider - - - {{ provider }} - - - -
-
- - admin.oauth2.allowed-platforms - - - {{ platformTypeTranslations.get(platform) | translate }} - - - -
-
- - admin.oauth2.client-id - - - {{ 'admin.oauth2.client-id-required' | translate }} - - - {{ 'admin.oauth2.client-id-max-length' | translate }} - - - - - admin.oauth2.client-secret - - - {{ 'admin.oauth2.client-secret-required' | translate }} - - - {{ 'admin.oauth2.client-secret-max-length' | translate }} - - -
- - - - - {{ 'admin.oauth2.custom-setting' | translate }} - - - - - -
- - admin.oauth2.access-token-uri - - - - {{ 'admin.oauth2.access-token-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.authorization-uri - - - - {{ 'admin.oauth2.authorization-uri-required' | translate }} - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- -
- - admin.oauth2.jwk-set-uri - - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - - - - admin.oauth2.user-info-uri - - - - {{ 'admin.oauth2.uri-pattern-error' | translate }} - - -
- - - admin.oauth2.client-authentication-method - - - {{ clientAuthenticationMethod | uppercase }} - - - - -
- - admin.oauth2.login-button-label - - - {{ 'admin.oauth2.login-button-label-required' | translate }} - - - - - admin.oauth2.login-button-icon - - -
- -
-
- - {{ 'admin.oauth2.allow-user-creation' | translate }} - - - {{ 'admin.oauth2.activate-user' | translate }} - -
-
- - - admin.oauth2.scope - - - {{scope}} - cancel - - - - - {{ 'admin.oauth2.scope-required' | translate }} - - - - - -
- - - admin.oauth2.user-name-attribute-name - - - {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} - - - -
- - admin.oauth2.type - - - {{ mapperConfigType }} - - - - -
- - admin.oauth2.email-attribute-key - - - {{ 'admin.oauth2.email-attribute-key-required' | translate }} - - - {{ 'admin.oauth2.email-attribute-key-max-length' | translate }} - - - -
- - admin.oauth2.first-name-attribute-key - - - {{ 'admin.oauth2.first-name-attribute-key-max-length' | translate }} - - - - - admin.oauth2.last-name-attribute-key - - - {{ 'admin.oauth2.last-name-attribute-key-max-length' | translate }} - - -
- -
- - admin.oauth2.tenant-name-strategy - - - {{ tenantNameStrategy }} - - - - - - admin.oauth2.tenant-name-pattern - - - {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} - - - {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }} - - -
- - - admin.oauth2.customer-name-pattern - - - {{ 'admin.oauth2.customer-name-pattern-max-length' | translate }} - - - -
- - admin.oauth2.default-dashboard-name - - - {{ 'admin.oauth2.default-dashboard-name-max-length' | translate }} - - - - - {{ 'admin.oauth2.always-fullscreen' | translate}} - -
-
- -
- - admin.oauth2.url - - - {{ 'admin.oauth2.url-required' | translate }} - - - {{ 'admin.oauth2.url-pattern' | translate }} - - - {{ 'admin.oauth2.url-max-length' | translate }} - - - -
- - common.username - - - {{ 'admin.oauth2.username-max-length' | translate }} - - - - - common.password - - - - {{ 'admin.oauth2.password-max-length' | translate }} - - -
-
-
-
-
-
-
- -
-
-
-
-
- -
- -
-
- -
-
-
-
-
-
-
-
- - -
-
-
-
-
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss deleted file mode 100644 index 639fb877f2..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ /dev/null @@ -1,78 +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. - */ -@import '../../../../../scss/constants'; - -:host{ - .gap-xs-12 { - @media #{$mat-xs} { - gap: 12px; - } - } - - .mdc-evolution-chip-set .mdc-evolution-chip { - margin-top: 0; - margin-bottom: 0; - } - - .checkbox-row { - margin-top: 1em; - } - - .registration-card{ - margin-bottom: 0.5em; - border: 1px solid rgba(0, 0, 0, 0.2); - - .custom-settings{ - font-size: 16px; - .mat-expansion-panel-header{ - padding: 0 2px; - } - } - } - - .container{ - margin-bottom: 16px; - - .tb-highlight{ - margin: 0; - } - - .tb-hint{ - padding-bottom: 0; - } - } - - .mat-expansion-panel { - .mat-expansion-panel-header { - &.mat-expanded { - height: 48px; - } - } - } -} - -:host ::ng-deep{ - .registration-card{ - .custom-settings{ - .mat-expansion-panel-body{ - padding: 0 2px 1em; - } - } - } - .domains-list, .apps-list { - margin-bottom: 1.5em; - } -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts deleted file mode 100644 index f1eabf2d0c..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ /dev/null @@ -1,613 +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. -/// - -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; -import { - AbstractControl, - FormControl, - UntypedFormArray, - UntypedFormBuilder, - UntypedFormGroup, - ValidationErrors, - Validators -} from '@angular/forms'; -import { - ClientAuthenticationMethod, - DomainSchema, - domainSchemaTranslations, - MapperConfig, - MapperConfigBasic, - MapperConfigCustom, - MapperConfigType, - OAuth2ClientRegistrationTemplate, - OAuth2DomainInfo, - OAuth2Info, - OAuth2MobileInfo, - OAuth2ParamsInfo, - OAuth2RegistrationInfo, - PlatformType, - platformTypeTranslations, - TenantNameStrategy -} from '@shared/models/oauth2.models'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { PageComponent } from '@shared/components/page.component'; -import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; -import { COMMA, ENTER } from '@angular/cdk/keycodes'; -import { MatChipInputEvent } from '@angular/material/chips'; -import { WINDOW } from '@core/services/window.service'; -import { forkJoin, Subscription } from 'rxjs'; -import { DialogService } from '@core/services/dialog.service'; -import { TranslateService } from '@ngx-translate/core'; -import { isDefined, isDefinedAndNotNull, randomAlphanumeric } from '@core/utils'; -import { OAuth2Service } from '@core/http/oauth2.service'; -import { ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'tb-oauth2-settings', - templateUrl: './oauth2-settings.component.html', - styleUrls: ['./oauth2-settings.component.scss', './settings-card.scss'] -}) -export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { - - constructor(protected store: Store, - private route: ActivatedRoute, - private oauth2Service: OAuth2Service, - private fb: UntypedFormBuilder, - private dialogService: DialogService, - private translate: TranslateService, - @Inject(WINDOW) private window: Window) { - super(store); - } - - get oauth2ParamsInfos(): UntypedFormArray { - return this.oauth2SettingsForm.get('oauth2ParamsInfos') as UntypedFormArray; - } - - private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; - private DOMAIN_AND_PORT_REGEXP = /^(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?$/; - private subscriptions: Subscription[] = []; - private templates = new Map(); - private defaultProvider = { - additionalInfo: { - providerName: 'Custom' - }, - clientAuthenticationMethod: ClientAuthenticationMethod.POST, - userNameAttributeName: 'email', - mapperConfig: { - allowUserCreation: true, - activateUser: false, - type: MapperConfigType.BASIC, - basic: { - emailAttributeKey: 'email', - tenantNameStrategy: TenantNameStrategy.DOMAIN, - alwaysFullScreen: false - } - } - }; - - readonly separatorKeysCodes: number[] = [ENTER, COMMA]; - - oauth2SettingsForm: UntypedFormGroup; - oauth2Info: OAuth2Info; - - clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); - mapperConfigType = MapperConfigType; - mapperConfigTypes = Object.keys(MapperConfigType); - tenantNameStrategies = Object.keys(TenantNameStrategy); - protocols = Object.values(DomainSchema); - domainSchemaTranslations = domainSchemaTranslations; - platformTypes = Object.values(PlatformType); - platformTypeTranslations = platformTypeTranslations; - - templateProvider = ['Custom']; - - showMainLoadingBar = false; - - private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; - - private static validateScope(control: AbstractControl): ValidationErrors | null { - const scope: string[] = control.value; - if (!scope || !scope.length) { - return { - required: true - }; - } - return null; - } - - ngOnInit(): void { - this.buildOAuth2SettingsForm(); - forkJoin([ - this.oauth2Service.getOAuth2Template(), - this.oauth2Service.getOAuth2Settings() - ]).subscribe( - ([templates, oauth2Info]) => { - this.initTemplates(templates); - this.oauth2Info = oauth2Info; - this.initOAuth2Settings(this.oauth2Info); - } - ); - } - - ngOnDestroy() { - super.ngOnDestroy(); - this.subscriptions.forEach((subscription) => { - subscription.unsubscribe(); - }); - } - - private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void { - templates.map(provider => { - delete provider.additionalInfo; - this.templates.set(provider.name, provider); - }); - this.templateProvider.push(...Array.from(this.templates.keys())); - this.templateProvider.sort(); - } - - private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): UntypedFormGroup { - let tenantNamePattern; - if (mapperConfigBasic?.tenantNamePattern) { - tenantNamePattern = mapperConfigBasic.tenantNamePattern; - } else { - tenantNamePattern = {value: null, disabled: true}; - } - const basicGroup = this.fb.group({ - emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', - [Validators.required, Validators.maxLength(31)]], - firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : '', - Validators.maxLength(31)], - lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : '', - Validators.maxLength(31)], - tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], - tenantNamePattern: [tenantNamePattern, [Validators.required, Validators.maxLength(255)]], - customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null, - Validators.maxLength(255)], - defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null, - Validators.maxLength(255)], - alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] - }); - - this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { - if (domain === 'CUSTOM') { - basicGroup.get('tenantNamePattern').enable(); - } else { - basicGroup.get('tenantNamePattern').disable(); - } - })); - - return basicGroup; - } - - private formCustomGroup(mapperConfigCustom?: MapperConfigCustom): UntypedFormGroup { - return this.fb.group({ - url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, - [Validators.required, Validators.pattern(this.URL_REGEXP), Validators.maxLength(255)]], - username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null, Validators.maxLength(255)], - password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)] - }); - } - - private buildOAuth2SettingsForm(): void { - this.oauth2SettingsForm = this.fb.group({ - oauth2ParamsInfos: this.fb.array([]), - enabled: [false], - edgeEnabled: [{value: false, disabled: true}] - }); - - this.subscriptions.push(this.oauth2SettingsForm.get('enabled').valueChanges.subscribe(enabled => { - if (enabled) { - this.oauth2SettingsForm.get('edgeEnabled').enable(); - } else { - this.oauth2SettingsForm.get('edgeEnabled').patchValue(false); - this.oauth2SettingsForm.get('edgeEnabled').disable(); - } - })); - } - - private initOAuth2Settings(oauth2Info: OAuth2Info): void { - if (oauth2Info) { - this.oauth2SettingsForm.patchValue({enabled: oauth2Info.enabled, edgeEnabled: oauth2Info.edgeEnabled}); - oauth2Info.oauth2ParamsInfos.forEach((oauth2ParamsInfo) => { - this.oauth2ParamsInfos.push(this.buildOAuth2ParamsInfoForm(oauth2ParamsInfo)); - }); - } - } - - private uniqueDomainValidator(control: UntypedFormGroup): { [key: string]: boolean } | null { - if (control.parent?.value) { - const domain = control.value.name; - const listProtocols = control.parent.getRawValue() - .filter((domainInfo) => domainInfo.name === domain) - .map((domainInfo) => domainInfo.scheme); - if (listProtocols.length > 1 && listProtocols.indexOf(DomainSchema.MIXED) > -1 || - new Set(listProtocols).size !== listProtocols.length) { - return {unique: true}; - } - } - return null; - } - - private uniquePkgNameValidator(control: UntypedFormGroup): { [key: string]: boolean } | null { - if (control.parent?.value) { - const pkgName = control.value.pkgName; - const mobileInfosList = control.parent.getRawValue() - .filter((mobileInfo) => mobileInfo.pkgName === pkgName); - if (mobileInfosList.length > 1) { - return {unique: true}; - } - } - return null; - } - - public domainListTittle(control: AbstractControl): string { - const domainInfos = control.get('domainInfos').value as OAuth2DomainInfo[]; - if (domainInfos.length) { - const domainList = new Set(); - domainInfos.forEach((domain) => { - domainList.add(domain.name); - }); - return Array.from(domainList).join(', '); - } - return this.translate.instant('admin.oauth2.new-domain'); - } - - private buildOAuth2ParamsInfoForm(oauth2ParamsInfo?: OAuth2ParamsInfo): UntypedFormGroup { - const formOAuth2Params = this.fb.group({ - domainInfos: this.fb.array([], Validators.required), - mobileInfos: this.fb.array([]), - clientRegistrations: this.fb.array([], Validators.required) - }); - - if (oauth2ParamsInfo) { - oauth2ParamsInfo.domainInfos.forEach((domain) => { - this.domainInfos(formOAuth2Params).push(this.buildDomainInfoForm(domain)); - }); - oauth2ParamsInfo.mobileInfos.forEach((mobile) => { - this.mobileInfos(formOAuth2Params).push(this.buildMobileInfoForm(mobile)); - }); - oauth2ParamsInfo.clientRegistrations.forEach((registration) => { - this.clientRegistrations(formOAuth2Params).push(this.buildRegistrationForm(registration)); - }); - } else { - this.clientRegistrations(formOAuth2Params).push(this.buildRegistrationForm()); - this.domainInfos(formOAuth2Params).push(this.buildDomainInfoForm()); - } - - return formOAuth2Params; - } - - private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): UntypedFormGroup { - return this.fb.group({ - name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ - Validators.required, Validators.maxLength(255), - Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], - scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] - }, {validators: this.uniqueDomainValidator}); - } - - private buildMobileInfoForm(mobileInfo?: OAuth2MobileInfo): UntypedFormGroup { - return this.fb.group({ - pkgName: [mobileInfo?.pkgName, [Validators.required]], - appSecret: [mobileInfo?.appSecret, [Validators.required, this.base64Format]], - }, {validators: this.uniquePkgNameValidator}); - } - - private base64Format(control: FormControl): { [key: string]: boolean } | null { - if (control.value === '') { - return null; - } - try { - const value = atob(control.value); - if (value.length < 64) { - return {minLength: true}; - } - return null; - } catch (e) { - return {base64: true}; - } - } - - private buildRegistrationForm(registration?: OAuth2RegistrationInfo): UntypedFormGroup { - let additionalInfo = null; - if (isDefinedAndNotNull(registration?.additionalInfo)) { - additionalInfo = registration.additionalInfo; - if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { - additionalInfo.providerName = 'Custom'; - } - } - let defaultProviderName = 'Custom'; - if (this.templateProvider.indexOf('Google')) { - defaultProviderName = 'Google'; - } - - const clientRegistrationFormGroup = this.fb.group({ - additionalInfo: this.fb.group({ - providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] - }), - platforms: [registration?.platforms ? registration.platforms : []], - loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required], - loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null], - clientId: [registration?.clientId ? registration.clientId : '', [Validators.required, Validators.maxLength(255)]], - clientSecret: [registration?.clientSecret ? registration.clientSecret : '', [Validators.required, Validators.maxLength(2048)]], - accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '', - [Validators.required, - Validators.pattern(this.URL_REGEXP)]], - authorizationUri: [registration?.authorizationUri ? registration.authorizationUri : '', - [Validators.required, - Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(registration?.scope ? registration.scope : [], OAuth2SettingsComponent.validateScope), - jwkSetUri: [registration?.jwkSetUri ? registration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], - userInfoUri: [registration?.userInfoUri ? registration.userInfoUri : '', - [Validators.pattern(this.URL_REGEXP)]], - clientAuthenticationMethod: [ - registration?.clientAuthenticationMethod ? registration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, - Validators.required], - userNameAttributeName: [ - registration?.userNameAttributeName ? registration.userNameAttributeName : 'email', Validators.required], - mapperConfig: this.fb.group({ - allowUserCreation: [ - isDefinedAndNotNull(registration?.mapperConfig?.allowUserCreation) ? - registration.mapperConfig.allowUserCreation : true - ], - activateUser: [ - isDefinedAndNotNull(registration?.mapperConfig?.activateUser) ? registration.mapperConfig.activateUser : false - ], - type: [ - registration?.mapperConfig?.type ? registration.mapperConfig.type : MapperConfigType.BASIC, Validators.required - ] - } - ) - }); - - if (registration) { - this.changeMapperConfigType(clientRegistrationFormGroup, registration.mapperConfig.type, registration.mapperConfig); - } else { - this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC); - this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup); - } - - this.subscriptions.push(clientRegistrationFormGroup.get('mapperConfig.type').valueChanges.subscribe((value) => { - this.changeMapperConfigType(clientRegistrationFormGroup, value); - })); - - this.subscriptions.push(clientRegistrationFormGroup.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { - (clientRegistrationFormGroup.get('scope') as UntypedFormArray).clear(); - this.setProviderDefaultValue(provider, clientRegistrationFormGroup); - })); - - return clientRegistrationFormGroup; - } - - private setProviderDefaultValue(provider: string, clientRegistration: UntypedFormGroup) { - if (provider === 'Custom') { - clientRegistration.reset(this.defaultProvider, {emitEvent: false}); - clientRegistration.get('accessTokenUri').enable(); - clientRegistration.get('authorizationUri').enable(); - clientRegistration.get('jwkSetUri').enable(); - clientRegistration.get('userInfoUri').enable(); - } else { - const template = this.templates.get(provider); - template.clientId = ''; - template.clientSecret = ''; - template.scope.forEach(() => { - (clientRegistration.get('scope') as UntypedFormArray).push(this.fb.control('')); - }); - clientRegistration.get('accessTokenUri').disable(); - clientRegistration.get('authorizationUri').disable(); - clientRegistration.get('jwkSetUri').disable(); - clientRegistration.get('userInfoUri').disable(); - clientRegistration.patchValue(template); - } - } - - private changeMapperConfigType(control: AbstractControl, type: MapperConfigType, predefinedValue?: MapperConfig) { - const mapperConfig = control.get('mapperConfig') as UntypedFormGroup; - if (type === MapperConfigType.CUSTOM) { - mapperConfig.removeControl('basic'); - mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); - } else { - mapperConfig.removeControl('custom'); - if (!mapperConfig.get('basic')) { - mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); - } - if (type === MapperConfigType.GITHUB) { - mapperConfig.get('basic.emailAttributeKey').disable(); - mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); - } else { - mapperConfig.get('basic.emailAttributeKey').enable(); - } - } - } - - save(): void { - const setting = this.oauth2SettingsForm.getRawValue(); - this.oauth2Service.saveOAuth2Settings(setting).subscribe( - (oauth2Settings) => { - this.oauth2Info = oauth2Settings; - this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false}); - this.oauth2SettingsForm.markAsUntouched(); - this.oauth2SettingsForm.markAsPristine(); - } - ); - } - - confirmForm(): UntypedFormGroup { - return this.oauth2SettingsForm; - } - - addScope(event: MatChipInputEvent, control: AbstractControl): void { - const input = event.chipInput.inputElement; - const value = event.value; - const controller = control.get('scope') as UntypedFormArray; - if ((value.trim() !== '')) { - controller.push(this.fb.control(value.trim())); - controller.markAsDirty(); - } - - if (input) { - input.value = ''; - } - } - - removeScope(i: number, control: AbstractControl): void { - const controller = control.get('scope') as UntypedFormArray; - controller.removeAt(i); - controller.markAsTouched(); - controller.markAsDirty(); - } - - addOAuth2ParamsInfo(): void { - this.oauth2ParamsInfos.push(this.buildOAuth2ParamsInfoForm()); - } - - deleteOAuth2ParamsInfo($event: Event, index: number): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - - const domainName = this.domainListTittle(this.oauth2ParamsInfos.at(index)); - this.dialogService.confirm( - this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), - this.translate.instant('admin.oauth2.delete-domain-text'), null, - this.translate.instant('action.delete') - ).subscribe((data) => { - if (data) { - this.oauth2ParamsInfos.removeAt(index); - this.oauth2ParamsInfos.markAsTouched(); - this.oauth2ParamsInfos.markAsDirty(); - } - }); - } - - clientRegistrations(control: AbstractControl): UntypedFormArray { - return control.get('clientRegistrations') as UntypedFormArray; - } - - domainInfos(control: AbstractControl): UntypedFormArray { - return control.get('domainInfos') as UntypedFormArray; - } - - mobileInfos(control: AbstractControl): UntypedFormArray { - return control.get('mobileInfos') as UntypedFormArray; - } - - addRegistration(control: AbstractControl): void { - this.clientRegistrations(control).push(this.buildRegistrationForm()); - } - - deleteRegistration($event: Event, control: AbstractControl, index: number): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - - const providerName = this.clientRegistrations(control).at(index).get('additionalInfo.providerName').value; - this.dialogService.confirm( - this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), - this.translate.instant('admin.oauth2.delete-registration-text'), null, - this.translate.instant('action.delete') - ).subscribe((data) => { - if (data) { - this.clientRegistrations(control).removeAt(index); - this.clientRegistrations(control).markAsTouched(); - this.clientRegistrations(control).markAsDirty(); - } - }); - } - - toggleEditMode(control: AbstractControl, path: string) { - control.get(path).disabled ? control.get(path).enable() : control.get(path).disable(); - } - - getProviderName(controller: AbstractControl): string { - return controller.get('additionalInfo.providerName').value; - } - - isCustomProvider(controller: AbstractControl): boolean { - return this.getProviderName(controller) === 'Custom'; - } - - getHelpLink(controller: AbstractControl): string { - const provider = controller.get('additionalInfo.providerName').value; - if (provider === null || provider === 'Custom') { - return ''; - } - return this.templates.get(provider).helpLink; - } - - addDomainInfo(control: AbstractControl): void { - this.domainInfos(control).push(this.buildDomainInfoForm({ - name: '', - scheme: DomainSchema.HTTPS - })); - } - - removeDomainInfo($event: Event, control: AbstractControl, index: number): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - this.domainInfos(control).removeAt(index); - this.domainInfos(control).markAsTouched(); - this.domainInfos(control).markAsDirty(); - } - - addMobileInfo(control: AbstractControl): void { - this.mobileInfos(control).push(this.buildMobileInfoForm({ - pkgName: '', - appSecret: btoa(randomAlphanumeric(64)) - })); - } - - removeMobileInfo($event: Event, control: AbstractControl, index: number): void { - if ($event) { - $event.stopPropagation(); - $event.preventDefault(); - } - this.mobileInfos(control).removeAt(index); - this.mobileInfos(control).markAsTouched(); - this.mobileInfos(control).markAsDirty(); - } - - redirectURI(control: AbstractControl, schema?: DomainSchema): string { - const domainInfo = control.value as OAuth2DomainInfo; - if (domainInfo.name !== '') { - let protocol; - if (isDefined(schema)) { - protocol = schema.toLowerCase(); - } else { - protocol = domainInfo.scheme === DomainSchema.MIXED ? DomainSchema.HTTPS.toLowerCase() : domainInfo.scheme.toLowerCase(); - } - return `${protocol}://${domainInfo.name}${this.loginProcessingUrl}`; - } - return ''; - } - - redirectURIMixed(control: AbstractControl): string { - return this.redirectURI(control, DomainSchema.HTTP); - } - - trackByParams(index: number): number { - return index; - } - - trackByItem(i, item) { - return item; - } -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html new file mode 100644 index 0000000000..5a1c87b29e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html @@ -0,0 +1,22 @@ + + + + {{ client.title }} + diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss new file mode 100644 index 0000000000..6c489effd0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss @@ -0,0 +1,34 @@ +/** + * 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. + */ + +:host { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 4px 0; + a.tb-client-chip { + padding: 4px 8px; + border-radius: 12px; + line-height: 16px; + background: #e0e0e0; + color: #212121; + white-space: nowrap; + border: none; + &:hover { + background: #b7b7b7; + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts new file mode 100644 index 0000000000..24611fa581 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts @@ -0,0 +1,40 @@ +/// +/// 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. +/// + +import { Component, Input } from '@angular/core'; +import { HasOauth2Clients } from '@shared/models/oauth2.models'; + +@Component({ + selector: 'tb-client-chips', + templateUrl: './client-chips.component.html', + styleUrls: ['./client-chips.component.scss'] +}) +export class ClientChipsComponent { + + clientsEntityValue?: HasOauth2Clients; + + clientPrefixUrl = '/security-settings/oauth2/clients/details/'; + + @Input() + set clientsEntity(value: HasOauth2Clients) { + this.clientsEntityValue = value; + } + + get clientsEntity(): HasOauth2Clients { + return this.clientsEntityValue; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html new file mode 100644 index 0000000000..1a002020a1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html @@ -0,0 +1,49 @@ + +
+ +

{{ 'admin.oauth2.add-client' | translate }}

+ +
+ +
+ + +
+
+ +
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.ts new file mode 100644 index 0000000000..4bd0aa7f28 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.ts @@ -0,0 +1,80 @@ +/// +/// 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. +/// + +import { Component, OnDestroy, SkipSelf, ViewChild } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroupDirective, NgForm, UntypedFormControl } from '@angular/forms'; +import { getProviderHelpLink, OAuth2Client } from '@shared/models/oauth2.models'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { ClientComponent } from '@home/pages/admin/oauth2/clients/client.component'; +import { ErrorStateMatcher } from '@angular/material/core'; + +@Component({ + selector: 'tb-client-dialog', + templateUrl: './client-dialog.component.html', + providers: [{provide: ErrorStateMatcher, useExisting: ClientDialogComponent}], + styleUrls: [] +}) +export class ClientDialogComponent extends DialogComponent implements OnDestroy { + + submitted = false; + + @ViewChild('clientComponent', {static: true}) clientComponent: ClientComponent; + + constructor(protected store: Store, + protected router: Router, + protected dialogRef: MatDialogRef, + private fb: FormBuilder, + private oauth2Service: OAuth2Service, + @SkipSelf() private errorStateMatcher: ErrorStateMatcher) { + super(store, router, dialogRef); + } + + ngAfterViewInit(): void { + setTimeout(() => { + this.clientComponent.entityForm.markAsDirty(); + }, 0); + } + + isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { + const originalErrorState = this.errorStateMatcher.isErrorState(control, form); + const customErrorState = !!(control && control.invalid && this.submitted); + return originalErrorState || customErrorState; + } + + cancel(): void { + this.dialogRef.close(null); + } + + save() { + this.submitted = true; + if (this.clientComponent.entityForm.valid) { + this.oauth2Service.saveOAuth2Client(this.clientComponent.entityFormValue()).subscribe( + (client) => { + this.dialogRef.close(client); + } + ); + } + } + + helpLinkId() { + return getProviderHelpLink(this.clientComponent.entityForm.get('additionalInfo.providerName')?.value); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html new file mode 100644 index 0000000000..0de423aded --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html @@ -0,0 +1,53 @@ + + + admin.oauth2.clients + + + {{entity.title}} + close + + + + + + + + + + {{ translate.get('entity.no-entities-matching', {entity: searchText}) | async }} + + + +
+ +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts new file mode 100644 index 0000000000..94320a9d28 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts @@ -0,0 +1,182 @@ +/// +/// 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. +/// + +import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { filter, map, mergeMap, share, tap } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { MatAutocomplete } from '@angular/material/autocomplete'; +import { MatChipGrid } from '@angular/material/chips'; +import { SubscriptSizing } from '@angular/material/form-field'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; +import { PageLink } from '@app/shared/models/page/page-link'; + +@Component({ + selector: 'tb-client-list', + templateUrl: './client-list.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => ClientListComponent), + multi: true + } + ] +}) +export class ClientListComponent implements ControlValueAccessor, OnInit { + + entityListFormGroup: UntypedFormGroup; + + modelValue: Array | null; + + @Input() + disabled: boolean; + + @Input() + subscriptSizing: SubscriptSizing = 'fixed'; + + @ViewChild('entityInput') entityInput: ElementRef; + @ViewChild('entityAutocomplete') matAutocomplete: MatAutocomplete; + @ViewChild('chipList', {static: true}) chipList: MatChipGrid; + + entities: Array> = []; + filteredEntities: Observable>>; + + searchText = ''; + + private dirty = false; + + private propagateChange = (v: any) => { + }; + + constructor(private store: Store, + public translate: TranslateService, + private oauth2Service: OAuth2Service, + private fb: UntypedFormBuilder) { + this.entityListFormGroup = this.fb.group({ + entities: [this.entities], + entity: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredEntities = this.entityListFormGroup.get('entity').valueChanges + .pipe( + tap((value) => { + if (value && typeof value !== 'string') { + this.add(value); + } else if (value === null) { + this.clear(this.entityInput.nativeElement.value); + } + }), + filter((value) => typeof value === 'string'), + map((value) => value ? (typeof value === 'string' ? value : value.title) : ''), + mergeMap(name => this.fetchEntities(name)), + share() + ); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (isDisabled) { + this.entityListFormGroup.disable({emitEvent: false}); + } else { + this.entityListFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: Array | null): void { + this.searchText = ''; + if (value != null && value.length > 0) { + this.modelValue = [...value]; + this.entities = [...value]; + } else { + this.entities = []; + this.entityListFormGroup.get('entities').setValue(this.entities); + this.modelValue = null; + } + this.dirty = true; + } + + add(entity: OAuth2ClientInfo): void { + if (!this.modelValue || !this.modelValue.find(client => client.id.id === entity.id.id)) { + if (!this.modelValue) { + this.modelValue = []; + } + this.modelValue.push(entity); + this.entities.push(entity); + this.entityListFormGroup.get('entities').setValue(this.entities); + } + this.propagateChange(this.modelValue); + this.clear(); + } + + remove(entity: OAuth2ClientInfo) { + let index = this.entities.indexOf(entity); + if (index >= 0) { + this.entities.splice(index, 1); + this.entityListFormGroup.get('entities').setValue(this.entities); + index = this.modelValue.indexOf(entity); + this.modelValue.splice(index, 1); + if (!this.modelValue.length) { + this.modelValue = null; + } + this.propagateChange(this.modelValue); + this.clear(); + } + } + + displayEntityFn(entity?: OAuth2ClientInfo): string | undefined { + return entity ? entity.title : undefined; + } + + fetchEntities(searchText?: string): Observable> { + this.searchText = searchText; + + return this.oauth2Service.findTenantOAuth2ClientInfos(new PageLink(100, 0, searchText), {ignoreLoading: true}).pipe( + map((data) => data ? data.data : [])); + } + + onFocus() { + if (this.dirty) { + this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + clear(value: string = '') { + this.entityInput.nativeElement.value = value; + this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true}); + setTimeout(() => { + this.entityInput.nativeElement.blur(); + this.entityInput.nativeElement.focus(); + }, 0); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html new file mode 100644 index 0000000000..304d1fdec1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.html @@ -0,0 +1,22 @@ + +
+ {{ entitiesTableConfig.tableTitle }} +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.ts new file mode 100644 index 0000000000..891285d4fd --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-table-header.component.ts @@ -0,0 +1,34 @@ +/// +/// 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. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { OAuth2Client, OAuth2ClientInfo } from '@shared/models/oauth2.models'; +import { PageLink } from '@shared/models/page/page-link'; + +@Component({ + selector: 'tb-client-table-header', + templateUrl: './client-table-header.component.html', + styleUrls: [] +}) +export class ClientTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html new file mode 100644 index 0000000000..ccf88d4782 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.html @@ -0,0 +1,331 @@ + +
+
+ + admin.oauth2.title + + + {{ 'admin.oauth2.client-title-required' | translate }} + + +
+
+
+ + admin.oauth2.provider + + + {{ provider }} + + + +
+ + admin.oauth2.allowed-platforms + + + {{ platformTypeTranslations.get(platform) | translate }} + + + +
+
+ + admin.oauth2.client-id + + + {{ 'admin.oauth2.client-id-required' | translate }} + + + {{ 'admin.oauth2.client-id-max-length' | translate }} + + + + admin.oauth2.client-secret + + + {{ 'admin.oauth2.client-secret-required' | translate }} + + + {{ 'admin.oauth2.client-secret-max-length' | translate }} + + +
+ + + + admin.oauth2.advanced-settings + + + +
+
+ + + {{ "admin.oauth2.general" | translate }} + {{ 'admin.oauth2.mapper' | translate }} + +
+
+
+ + admin.oauth2.access-token-uri + + + {{ 'admin.oauth2.access-token-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + admin.oauth2.authorization-uri + + + {{ 'admin.oauth2.authorization-uri-required' | translate }} + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+
+ + admin.oauth2.jwk-set-uri + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + + + admin.oauth2.user-info-uri + + + {{ 'admin.oauth2.uri-pattern-error' | translate }} + + +
+
+ + admin.oauth2.client-authentication-method + + + {{ clientAuthenticationMethod | uppercase }} + + + +
+
+ + admin.oauth2.login-button-label + + + {{ 'admin.oauth2.login-button-label-required' | translate }} + + + + admin.oauth2.login-button-icon + + +
+
+
+ + {{ 'admin.oauth2.allow-user-creation' | translate }} + +
+
+ + {{ 'admin.oauth2.activate-user' | translate }} + +
+
+
+ + admin.oauth2.scope + + + {{scope}} + cancel + + + + + {{ 'admin.oauth2.scope-required' | translate }} + + +
+ + +
+
+
+ + admin.oauth2.user-name-attribute-name + + + {{ 'admin.oauth2.user-name-attribute-name-required' | translate }} + + +
+
+
+ + admin.oauth2.type + + + {{ mapperType }} + + + +
+
+
+ + admin.oauth2.email-attribute-key + + + {{ 'admin.oauth2.email-attribute-key-required' | translate }} + + + {{ 'admin.oauth2.email-attribute-key-max-length' | translate }} + + +
+
+ + admin.oauth2.first-name-attribute-key + + + {{ 'admin.oauth2.first-name-attribute-key-max-length' | translate }} + + + + admin.oauth2.last-name-attribute-key + + + {{ 'admin.oauth2.last-name-attribute-key-max-length' | translate }} + + +
+
+ + admin.oauth2.first-name-attribute-key + + + {{ tenantNameStrategy }} + + + + + admin.oauth2.tenant-name-pattern + + + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} + + + {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }} + + +
+
+ + admin.oauth2.customer-name-pattern + + + {{ 'admin.oauth2.customer-name-pattern-max-length' | translate }} + + +
+
+ + admin.oauth2.default-dashboard-name + + + {{ 'admin.oauth2.default-dashboard-name-max-length' | translate }} + + + + {{ 'admin.oauth2.always-fullscreen' | translate}} + +
+
+
+
+ + admin.oauth2.url + + + {{ 'admin.oauth2.url-required' | translate }} + + + {{ 'admin.oauth2.url-pattern' | translate }} + + + {{ 'admin.oauth2.url-max-length' | translate }} + + +
+
+ + common.username + + + {{ 'admin.oauth2.username-max-length' | translate }} + + + + common.password + + + {{ 'admin.oauth2.password-max-length' | translate }} + + +
+ + {{ 'admin.oauth2.send-token' | translate}} + +
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.scss new file mode 100644 index 0000000000..b7dcc9050d --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.scss @@ -0,0 +1,36 @@ +/** + * 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. + */ + +::ng-deep { + .mat-expansion-panel { + .mat-expansion-panel-header { + height: 48px; + padding: 0 12px; + + &.mat-expanded { + height: 48px; + } + } + + .mat-expansion-panel-body { + padding: 0 12px; + } + + &.configuration-panel { + border: 1px solid rgba(0, 0, 0, 0.2); + } + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts new file mode 100644 index 0000000000..ef95423be8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts @@ -0,0 +1,377 @@ +/// +/// 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. +/// + +import { ChangeDetectorRef, Component, Inject, Optional } from '@angular/core'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { + ClientAuthenticationMethod, + MapperType, + OAuth2BasicMapperConfig, + OAuth2Client, + OAuth2ClientInfo, + OAuth2ClientRegistrationTemplate, + OAuth2CustomMapperConfig, + OAuth2MapperConfig, + PlatformType, + platformTypeTranslations, + TenantNameStrategyType +} from '@shared/models/oauth2.models'; +import { AppState } from '@core/core.state'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { Store } from '@ngrx/store'; +import { + AbstractControl, + UntypedFormArray, + UntypedFormBuilder, + UntypedFormGroup, + ValidationErrors, + Validators +} from '@angular/forms'; +import { isDefinedAndNotNull } from '@core/utils'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { Subscription } from 'rxjs'; +import { MatChipInputEvent } from '@angular/material/chips'; +import { COMMA, ENTER } from '@angular/cdk/keycodes'; +import { PageLink } from '@shared/models/page/page-link'; + +@Component({ + selector: 'tb-client', + templateUrl: './client.component.html', + styleUrls: ['./client.component.scss'] +}) +export class ClientComponent extends EntityComponent { + + templateProvider = ['Custom']; + private templates = new Map(); + private defaultProvider = { + additionalInfo: { + providerName: 'Custom' + }, + clientAuthenticationMethod: ClientAuthenticationMethod.POST, + userNameAttributeName: 'email', + mapperConfig: { + allowUserCreation: true, + activateUser: false, + type: MapperType.BASIC, + basic: { + emailAttributeKey: 'email', + tenantNameStrategy: TenantNameStrategyType.DOMAIN, + alwaysFullScreen: false + } + } + }; + + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; + + private subscriptions: Array = []; + + public static validateScope(control: AbstractControl): ValidationErrors | null { + const scope: string[] = control.value; + if (!scope || !scope.length) { + return { + required: true + }; + } + return null; + } + + readonly separatorKeysCodes: number[] = [ENTER, COMMA]; + + clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); + mapperType = MapperType; + mapperTypes = Object.keys(MapperType); + tenantNameStrategies = Object.keys(TenantNameStrategyType); + platformTypes = Object.values(PlatformType); + platformTypeTranslations = platformTypeTranslations; + generalSettingsMode = true; + + readonly isDialog = false; + + constructor(protected store: Store, + protected translate: TranslateService, + private oauth2Service: OAuth2Service, + @Optional() @Inject('entity') protected entityValue: OAuth2Client, + @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + protected cd: ChangeDetectorRef, + public fb: UntypedFormBuilder) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + this.oauth2Service.getOAuth2Template().subscribe(templates => { + this.initTemplates(templates); + }); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.subscriptions.forEach((subscription) => { + subscription.unsubscribe(); + }); + } + + buildForm(entity: OAuth2Client): UntypedFormGroup { + return this.fb.group({ + title: [entity?.title ? entity.title : '', [Validators.required]], + additionalInfo: this.fb.group({ + providerName: [entity?.additionalInfo?.providerName ? entity?.additionalInfo?.providerName : '', Validators.required] + }), + platforms: [entity?.platforms ? entity.platforms : []], + clientId: [entity?.clientId ? entity.clientId : '', [Validators.required, Validators.maxLength(255)]], + clientSecret: [entity?.clientSecret ? entity.clientSecret : '', [Validators.required, Validators.maxLength(2048)]], + accessTokenUri: [entity?.accessTokenUri ? entity.accessTokenUri : '', + [Validators.required, Validators.pattern(this.URL_REGEXP)]], + authorizationUri: [entity?.authorizationUri ? entity.authorizationUri : '', + [Validators.required, Validators.pattern(this.URL_REGEXP)]], + jwkSetUri: [entity?.jwkSetUri ? entity.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [entity?.userInfoUri ? entity.userInfoUri : '', [Validators.pattern(this.URL_REGEXP)]], + clientAuthenticationMethod: [entity?.clientAuthenticationMethod ? + entity.clientAuthenticationMethod : ClientAuthenticationMethod.POST, Validators.required], + loginButtonLabel: [entity?.loginButtonLabel ? entity.loginButtonLabel : null, Validators.required], + loginButtonIcon: [entity?.loginButtonIcon ? entity.loginButtonIcon : null], + userNameAttributeName: [entity?.userNameAttributeName ? entity.userNameAttributeName : 'email', Validators.required], + scope: this.fb.array(entity?.scope ? entity.scope : [], ClientComponent.validateScope), + mapperConfig: this.fb.group({ + allowUserCreation: [isDefinedAndNotNull(entity?.mapperConfig?.allowUserCreation) ? + entity.mapperConfig.allowUserCreation : true], + activateUser: [isDefinedAndNotNull(entity?.mapperConfig?.activateUser) ? entity.mapperConfig.activateUser : false], + type: [entity?.mapperConfig?.type ? entity.mapperConfig.type : MapperType.BASIC, Validators.required] + }) + }); + } + + updateForm(entity: OAuth2Client) { + this.entityForm.patchValue({ + title: entity.title, + additionalInfo: { + providerName: entity.additionalInfo.providerName + }, + platforms: entity.platforms, + clientId: entity.clientId, + clientSecret: entity.clientSecret, + accessTokenUri: entity.accessTokenUri, + authorizationUri: entity.authorizationUri, + jwkSetUri: entity.jwkSetUri, + userInfoUri: entity.userInfoUri, + clientAuthenticationMethod: entity.clientAuthenticationMethod, + loginButtonLabel: entity.loginButtonLabel, + loginButtonIcon: entity.loginButtonIcon, + userNameAttributeName: entity.userNameAttributeName, + mapperConfig: { + allowUserCreation: entity.mapperConfig.allowUserCreation, + activateUser: entity.mapperConfig.activateUser, + type: entity.mapperConfig.type + } + }, {emitEvent: false}); + + const mapperConfig = this.entityForm.get('mapperConfig') as UntypedFormGroup; + if (entity.mapperConfig.basic) { + mapperConfig.removeControl('custom', {emitEvent: false}); + if (mapperConfig.get('basic')) { + this.entityForm.get('mapperConfig').patchValue({ + basic: entity.mapperConfig.basic + }); + } else { + mapperConfig.addControl('basic', this.formBasicGroup(entity.mapperConfig.basic)); + } + } else { + mapperConfig.removeControl('basic', {emitEvent: false}); + if (mapperConfig.get('custom')) { + this.entityForm.get('mapperConfig').patchValue({ + custom: entity.mapperConfig.custom + }); + } else { + mapperConfig.addControl('custom', this.formCustomGroup(entity.mapperConfig.custom)); + } + } + + const scopeControls = this.entityForm.get('scope') as UntypedFormArray; + if (entity.scope.length === scopeControls.length) { + scopeControls.patchValue(entity.scope, {emitEvent: false}); + } else { + const scopeControls: Array = []; + if (entity.scope) { + for (const scope of entity.scope) { + scopeControls.push(this.fb.control(scope, [Validators.required])); + } + } + this.entityForm.setControl('scope', this.fb.array(scopeControls)); + } + } + + getProviderName(): string { + return this.entityForm.get('additionalInfo.providerName').value; + } + + isCustomProvider(): boolean { + return this.getProviderName() === 'Custom'; + } + + trackByParams(index: number): number { + return index; + } + + trackByItem(i, item) { + return item; + } + + addScope(event: MatChipInputEvent, control: AbstractControl): void { + const input = event.chipInput.inputElement; + const value = event.value; + const controller = control.get('scope') as UntypedFormArray; + if ((value.trim() !== '')) { + controller.push(this.fb.control(value.trim())); + controller.markAsDirty(); + } + + if (input) { + input.value = ''; + } + } + + removeScope(i: number, control: AbstractControl): void { + const controller = control.get('scope') as UntypedFormArray; + controller.removeAt(i); + controller.markAsTouched(); + controller.markAsDirty(); + } + + private initTemplates(templates: OAuth2ClientRegistrationTemplate[]): void { + templates.map(provider => { + delete provider.additionalInfo; + this.templates.set(provider.name, provider); + }); + this.templateProvider.push(...Array.from(this.templates.keys())); + this.templateProvider.sort(); + + let additionalInfo = null; + if (isDefinedAndNotNull(this.entityValue?.additionalInfo)) { + additionalInfo = this.entityValue.additionalInfo; + if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { + additionalInfo.providerName = 'Custom'; + } + } + let defaultProviderName = 'Custom'; + if (this.templateProvider.indexOf('Google')) { + defaultProviderName = 'Google'; + } + + this.entityForm.get('additionalInfo.providerName').setValue(additionalInfo?.providerName ? + additionalInfo?.providerName : defaultProviderName); + + if (this.entityValue?.mapperConfig) { + this.changeMapperConfigType(this.entityForm, this.entityValue.mapperConfig.type, this.entityValue.mapperConfig); + } else { + this.changeMapperConfigType(this.entityForm, MapperType.BASIC); + this.setProviderDefaultValue(defaultProviderName, this.entityForm); + } + + this.subscriptions.push(this.entityForm.get('mapperConfig.type').valueChanges.subscribe((value) => { + this.changeMapperConfigType(this.entityForm, value); + })); + + this.subscriptions.push(this.entityForm.get('additionalInfo.providerName').valueChanges.subscribe((provider) => { + (this.entityForm.get('scope') as UntypedFormArray).clear(); + this.setProviderDefaultValue(provider, this.entityForm); + })); + } + + private changeMapperConfigType(control: AbstractControl, type: MapperType, predefinedValue?: OAuth2MapperConfig) { + const mapperConfig = control.get('mapperConfig') as UntypedFormGroup; + if (type === MapperType.CUSTOM) { + mapperConfig.removeControl('basic'); + mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); + } else { + mapperConfig.removeControl('custom'); + if (!mapperConfig.get('basic')) { + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); + } + if (type === MapperType.GITHUB) { + mapperConfig.get('basic.emailAttributeKey').disable(); + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false}); + } else { + if (this.isEdit) { + mapperConfig.get('basic.emailAttributeKey').enable(); + } + } + } + } + + private formCustomGroup(mapperConfigCustom?: OAuth2CustomMapperConfig): UntypedFormGroup { + const customGroup = this.fb.group({ + url: [mapperConfigCustom?.url ? mapperConfigCustom.url : null, + [Validators.required, Validators.pattern(this.URL_REGEXP), Validators.maxLength(255)]], + username: [mapperConfigCustom?.username ? mapperConfigCustom.username : null, Validators.maxLength(255)], + password: [mapperConfigCustom?.password ? mapperConfigCustom.password : null, Validators.maxLength(255)], + sendToken: [isDefinedAndNotNull(mapperConfigCustom?.sendToken) ? mapperConfigCustom.sendToken : false] + }); + if (!this.isEdit) { + customGroup.disable(); + } + return customGroup; + } + + private formBasicGroup(mapperConfigBasic?: OAuth2BasicMapperConfig): UntypedFormGroup { + let tenantNamePattern; + if (mapperConfigBasic?.tenantNamePattern) { + tenantNamePattern = mapperConfigBasic.tenantNamePattern; + } else { + tenantNamePattern = {value: null, disabled: true}; + } + const basicGroup = this.fb.group({ + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', + [Validators.required, Validators.maxLength(31)]], + firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : '', + Validators.maxLength(31)], + lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : '', + Validators.maxLength(31)], + tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategyType.DOMAIN], + tenantNamePattern: [tenantNamePattern, [Validators.required, Validators.maxLength(255)]], + customerNamePattern: [mapperConfigBasic?.customerNamePattern ? mapperConfigBasic.customerNamePattern : null, + Validators.maxLength(255)], + defaultDashboardName: [mapperConfigBasic?.defaultDashboardName ? mapperConfigBasic.defaultDashboardName : null, + Validators.maxLength(255)], + alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] + }); + + if (!this.isEdit) { + basicGroup.disable(); + } + + this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { + if (domain === 'CUSTOM') { + basicGroup.get('tenantNamePattern').enable(); + } else { + basicGroup.get('tenantNamePattern').disable(); + } + })); + + return basicGroup; + } + + private setProviderDefaultValue(provider: string, clientRegistration: UntypedFormGroup) { + if (provider === 'Custom') { + clientRegistration.reset(this.defaultProvider, {emitEvent: false}); + } else { + const template = this.templates.get(provider); + template.clientId = ''; + template.clientSecret = ''; + template.scope.forEach(() => { + (clientRegistration.get('scope') as UntypedFormArray).push(this.fb.control('')); + }); + clientRegistration.patchValue(template); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts new file mode 100644 index 0000000000..3f07ef82e1 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/clients-table-config.resolver.ts @@ -0,0 +1,94 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { + getProviderHelpLink, + OAuth2Client, + OAuth2ClientInfo, + platformTypeTranslations +} from '@shared/models/oauth2.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { ClientComponent } from '@home/pages/admin/oauth2/clients/client.component'; +import { ClientTableHeaderComponent } from '@home/pages/admin/oauth2/clients/client-table-header.component'; +import { Direction } from '@shared/models/page/sort-order'; +import { PageLink } from '@shared/models/page/page-link'; + +@Injectable() +export class ClientsTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private translate: TranslateService, + private datePipe: DatePipe, + private utilsService: UtilsService, + private oauth2Service: OAuth2Service) { + this.config.tableTitle = this.translate.instant('admin.oauth2.clients'); + this.config.hideTableTitle = true; + this.config.selectionEnabled = false; + this.config.entityType = EntityType.OAUTH2_CLIENT; + this.config.rowPointer = true; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.OAUTH2_CLIENT); + this.config.entityResources = { + helpLinkId: null, + helpLinkIdForEntity(entity: OAuth2Client): string { + return getProviderHelpLink(entity.additionalInfo.providerName); + } + }; + this.config.entityComponent = ClientComponent; + this.config.headerComponent = ClientTableHeaderComponent; + this.config.hideDetailsTabs = true; + this.config.addDialogStyle = {width: '850px', maxHeight: '100vh'}; + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.config.displayPagination = false; + this.config.pageMode = false; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('title', 'admin.oauth2.provider', '170px'), + new EntityTableColumn('platforms', 'admin.oauth2.allowed-platforms', '100%', + (clientInfo) => { + return clientInfo.platforms && clientInfo.platforms.length ? + clientInfo.platforms.map(platform => this.translate.instant(platformTypeTranslations.get(platform))).join(', ') : + this.translate.instant('admin.oauth2.all-platforms'); + }, () => ({}), false) + ); + + this.config.deleteEntityTitle = (client) => this.translate.instant('admin.oauth2.delete-client-title', {clientName: client.title}); + this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-client-text'); + this.config.entitiesFetchFunction = pageLink => this.oauth2Service.findTenantOAuth2ClientInfos(pageLink); + this.config.loadEntity = id => this.oauth2Service.getOAuth2ClientById(id.id); + this.config.saveEntity = client => { + return this.oauth2Service.saveOAuth2Client(client as OAuth2Client); + } + this.config.deleteEntity = id => this.oauth2Service.deleteOauth2Client(id.id); + } + + resolve(): EntityTableConfig { + return this.config; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts new file mode 100644 index 0000000000..4c59bf336f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-config.resolver.ts @@ -0,0 +1,142 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { + ClientChipsEntityTableColumn, + DateEntityTableColumn, + EntityActionTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { Domain, DomainInfo } from '@shared/models/oauth2.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { DomainService } from '@app/core/http/domain.service'; +import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.component'; +import { isEqual } from '@core/utils'; +import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component'; +import { Direction } from '@app/shared/models/page/sort-order'; + +@Injectable() +export class DomainTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private translate: TranslateService, + private datePipe: DatePipe, + private utilsService: UtilsService, + private domainService: DomainService) { + this.config.tableTitle = this.translate.instant('admin.oauth2.domains'); + this.config.hideTableTitle = true; + this.config.selectionEnabled = false; + this.config.entityType = EntityType.DOMAIN; + this.config.rowPointer = true; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.DOMAIN); + this.config.entityResources = entityTypeResources.get(EntityType.DOMAIN); + this.config.entityComponent = DomainComponent; + this.config.headerComponent = DomainTableHeaderComponent; + this.config.hideDetailsTabs = true; + this.config.addDialogStyle = {width: '850px', height: '688px'}; + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.config.displayPagination = false; + this.config.pageMode = false; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('name', 'admin.oauth2.domain-name', '170px'), + new ClientChipsEntityTableColumn('oauth2ClientInfos', 'admin.oauth2.clients', '40%'), + new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', + { + name: '', + nameFunction: (domain) => + this.translate.instant(domain.oauth2Enabled ? 'admin.oauth2.disable' : 'admin.oauth2.enable'), + icon: 'mdi:toggle-switch', + iconFunction: (entity) => entity.oauth2Enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline', + isEnabled: () => true, + onAction: ($event, entity) => this.toggleEnableOAuth($event, entity) + }), + new EntityActionTableColumn('propagateToEdge', 'admin.oauth2.edge', + { + name: '', + nameFunction: (domain) => + this.translate.instant(domain.propagateToEdge ? 'admin.oauth2.edge-disable' : 'admin.oauth2.edge-enable'), + icon: 'mdi:toggle-switch', + iconFunction: (entity) => entity.propagateToEdge ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline', + isEnabled: () => true, + onAction: ($event, entity) => this.togglePropagateToEdge($event, entity) + }) + ); + + this.config.deleteEntityTitle = (domain) => this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domain.name}); + this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-domain-text'); + this.config.entitiesFetchFunction = pageLink => this.domainService.getTenantDomainInfos(pageLink); + this.config.loadEntity = id => this.domainService.getDomainInfoById(id.id); + this.config.saveEntity = (domain, originalEntity) => { + const clientsIds = domain.oauth2ClientInfos ? domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id) : []; + if (domain.id && !isEqual(domain.oauth2ClientInfos, originalEntity.oauth2ClientInfos)) { + this.domainService.updateOauth2Clients(domain.id.id, clientsIds).subscribe(); + } + delete domain.oauth2ClientInfos; + return this.domainService.saveDomain(domain as Domain, domain.id ? [] : clientsIds); + } + this.config.deleteEntity = id => this.domainService.deleteDomain(id.id); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private toggleEnableOAuth($event: Event, domain: DomainInfo): void { + if ($event) { + $event.stopPropagation(); + } + + const modifiedDomain: DomainInfo = { + ...domain, + oauth2Enabled: !domain.oauth2Enabled + }; + + this.domainService.saveDomain(modifiedDomain, domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), + {ignoreLoading: true}) + .subscribe((result) => { + domain.oauth2Enabled = result.oauth2Enabled; + this.config.getTable().detectChanges(); + }); + } + + private togglePropagateToEdge($event: Event, domain: DomainInfo): void { + if ($event) { + $event.stopPropagation(); + } + + const modifiedDomain: DomainInfo = { + ...domain, + propagateToEdge: !domain.propagateToEdge + }; + + this.domainService.saveDomain(modifiedDomain, domain.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), + {ignoreLoading: true}) + .subscribe((result) => { + domain.propagateToEdge = result.propagateToEdge; + this.config.getTable().detectChanges(); + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html new file mode 100644 index 0000000000..f78a483eb0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.html @@ -0,0 +1,21 @@ + +
+ {{ entitiesTableConfig.tableTitle }} +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.ts new file mode 100644 index 0000000000..3b9f7c6a3c --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain-table-header.component.ts @@ -0,0 +1,33 @@ +/// +/// 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. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { DomainInfo } from '@shared/models/oauth2.models'; + +@Component({ + selector: 'tb-domain-table-header', + templateUrl: './domain-table-header.component.html', + styleUrls: [] +}) +export class DomainTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html new file mode 100644 index 0000000000..5b4bef660e --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html @@ -0,0 +1,68 @@ + +
+
+ + admin.oauth2.domain-name + + + {{ 'admin.oauth2.domain-name-required' | translate }} + + + {{ 'admin.domain-name-max-length' | translate }} + + + {{ 'admin.error-verification-url' | translate }} + + +
+
+ + admin.oauth2.redirect-url-template + + + + +
+
+ + {{ 'admin.oauth2.enable' | translate }} + +
+ + + +
+ + {{ 'admin.oauth2.edge' | translate }} + +
+
+ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts new file mode 100644 index 0000000000..d41e63fb11 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts @@ -0,0 +1,105 @@ +/// +/// 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. +/// + + +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { DomainInfo } from '@shared/models/oauth2.models'; +import { AppState } from '@core/core.state'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { Store } from '@ngrx/store'; +import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; +import { WINDOW } from '@core/services/window.service'; +import { isDefinedAndNotNull } from '@core/utils'; +import { MatButton } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; +import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; + +@Component({ + selector: 'tb-domain', + templateUrl: './domain.component.html', + styleUrls: [] +}) +export class DomainComponent extends EntityComponent { + + private loginProcessingUrl: string = ''; + + constructor(protected store: Store, + protected translate: TranslateService, + private oauth2Service: OAuth2Service, + @Inject('entity') protected entityValue: DomainInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + protected cd: ChangeDetectorRef, + public fb: UntypedFormBuilder, + @Inject(WINDOW) private window: Window, + private dialog: MatDialog) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + this.entityForm.get('name').setValue(this.window.location.hostname); + this.oauth2Service.getLoginProcessingUrl().subscribe(url => { + this.loginProcessingUrl = url; + }); + } + + buildForm(entity: DomainInfo): UntypedFormGroup { + return this.fb.group({ + name: [entity?.name ? entity.name : '', [ + Validators.required, Validators.maxLength(255), Validators.pattern('^(?:\\w+(?::\\w+)?@)?[^\\s/]+(?::\\d+)?$')]], + oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, + oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [], + propagateToEdge: isDefinedAndNotNull(entity?.propagateToEdge) ? entity.propagateToEdge : false + }); + } + + updateForm(entity: DomainInfo) { + this.entityForm.patchValue({ + name: entity.name, + oauth2Enabled: entity.oauth2Enabled, + oauth2ClientInfos: entity.oauth2ClientInfos, + propagateToEdge: entity.propagateToEdge + }) + } + + redirectURI(): string { + const domainName = this.entityForm.get('name').value; + return domainName !== '' ? `${domainName}${this.loginProcessingUrl}` : ''; + } + + createClient($event: Event, button: MatButton) { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + button._elementRef.nativeElement.blur(); + this.dialog.open(ClientDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed() + .subscribe((res) => { + if (res) { + const formValue = this.entityForm.get('oauth2ClientInfos').value ? + [...this.entityForm.get('oauth2ClientInfos').value] : []; + formValue.push({id: res.id, title: res.title}); + this.entityForm.get('oauth2ClientInfos').patchValue(formValue); + this.entityForm.get('oauth2ClientInfos').markAsDirty(); + this.entityForm.updateValueAndValidity(); + } + }); + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts new file mode 100644 index 0000000000..c7d5fa0be0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts @@ -0,0 +1,138 @@ +/// +/// 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. +/// + +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; +import { + CellActionDescriptorType, + ClientChipsEntityTableColumn, + DateEntityTableColumn, + EntityActionTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { MobileApp, MobileAppInfo } from '@shared/models/oauth2.models'; +import { UtilsService } from '@core/services/utils.service'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { isEqual } from '@core/utils'; +import { Direction } from '@app/shared/models/page/sort-order'; +import { MobileAppService } from '@core/http/mobile-app.service'; +import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component'; +import { MobileAppTableHeaderComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component'; + +@Injectable() +export class MobileAppTableConfigResolver implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private translate: TranslateService, + private datePipe: DatePipe, + private utilsService: UtilsService, + private mobileAppService: MobileAppService) { + this.config.tableTitle = this.translate.instant('admin.oauth2.mobile-apps'); + this.config.hideTableTitle = true; + this.config.selectionEnabled = false; + this.config.entityType = EntityType.MOBILE_APP; + this.config.rowPointer = true; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.MOBILE_APP); + this.config.entityResources = entityTypeResources.get(EntityType.MOBILE_APP); + this.config.entityComponent = MobileAppComponent; + this.config.headerComponent = MobileAppTableHeaderComponent; + this.config.hideDetailsTabs = true; + this.config.addDialogStyle = {width: '850px', height: '688px'}; + this.config.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.config.displayPagination = false; + this.config.pageMode = false; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '170px'), + new EntityTableColumn('pkgName', 'admin.oauth2.mobile-package', '170px'), + new EntityTableColumn('appSecret', 'admin.oauth2.mobile-app-secret', '350px', + (entity) => entity.appSecret ? this.appSecretText(entity) : '', () => ({}), + false, () => ({}), () => undefined, false, + { + name: this.translate.instant('admin.oauth2.copy-mobile-app-secret'), + icon: 'content_copy', + style: { + 'padding-top': '1px', + 'font-size': '16px', + color: 'rgba(0,0,0,.87)' + }, + isEnabled: (entity) => !!entity.appSecret, + onAction: ($event, entity) => entity.appSecret, + type: CellActionDescriptorType.COPY_BUTTON + }), + new ClientChipsEntityTableColumn('oauth2ClientInfos', 'admin.oauth2.clients', '20%'), + new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', + { + name: '', + nameFunction: (app) => + this.translate.instant(app.oauth2Enabled ? 'admin.oauth2.disable' : 'admin.oauth2.enable'), + icon: 'mdi:toggle-switch', + iconFunction: (entity) => entity.oauth2Enabled ? 'mdi:toggle-switch' : 'mdi:toggle-switch-off-outline', + isEnabled: () => true, + onAction: ($event, entity) => this.toggleEnableOAuth($event, entity) + }) + ); + + this.config.deleteEntityTitle = (app) => this.translate.instant('admin.oauth2.delete-mobile-app-title', {applicationName: app.pkgName}); + this.config.deleteEntityContent = () => this.translate.instant('admin.oauth2.delete-mobile-app-text'); + this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); + this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); + this.config.saveEntity = (mobileApp, originalEntity) => { + const clientsIds = mobileApp.oauth2ClientInfos ? mobileApp.oauth2ClientInfos.map(clientInfo => clientInfo.id.id) : []; + if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos, originalEntity.oauth2ClientInfos)) { + this.mobileAppService.updateOauth2Clients(mobileApp.id.id, clientsIds).subscribe(); + } + delete mobileApp.oauth2ClientInfos; + return this.mobileAppService.saveMobileApp(mobileApp as MobileApp, mobileApp.id ? [] : clientsIds); + } + this.config.deleteEntity = id => this.mobileAppService.deleteMobileApp(id.id); + } + + resolve(route: ActivatedRouteSnapshot): EntityTableConfig { + return this.config; + } + + private toggleEnableOAuth($event: Event, mobileApp: MobileAppInfo): void { + if ($event) { + $event.stopPropagation(); + } + + const modifiedMobileApp: MobileAppInfo = { + ...mobileApp, + oauth2Enabled: !mobileApp.oauth2Enabled + }; + + this.mobileAppService.saveMobileApp(modifiedMobileApp, mobileApp.oauth2ClientInfos.map(clientInfo => clientInfo.id.id), + {ignoreLoading: true}) + .subscribe((result) => { + mobileApp.oauth2Enabled = result.oauth2Enabled; + this.config.getTable().detectChanges(); + }); + } + + private appSecretText(entity): string { + let text = entity.appSecret; + if (text.length > 40) { + text = `${text.slice(0, 40)}…`; + } + return text; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html new file mode 100644 index 0000000000..f78a483eb0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html @@ -0,0 +1,21 @@ + +
+ {{ entitiesTableConfig.tableTitle }} +
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.ts new file mode 100644 index 0000000000..ca4bbaf74b --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.ts @@ -0,0 +1,33 @@ +/// +/// 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. +/// + +import { Component } from '@angular/core'; +import { EntityTableHeaderComponent } from '@home/components/entity/entity-table-header.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { MobileAppInfo } from '@shared/models/oauth2.models'; + +@Component({ + selector: 'tb-mobile-app-table-header', + templateUrl: './mobile-app-table-header.component.html', + styleUrls: [] +}) +export class MobileAppTableHeaderComponent extends EntityTableHeaderComponent { + + constructor(protected store: Store) { + super(store); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html new file mode 100644 index 0000000000..d5146098fa --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html @@ -0,0 +1,69 @@ + +
+
+ + admin.oauth2.mobile-package + + admin.oauth2.mobile-package-hint + + {{ 'admin.oauth2.mobile-package-unique' | translate }} + + +
+
+ + admin.oauth2.mobile-app-secret + + + + admin.oauth2.mobile-app-secret-hint + + {{ 'admin.oauth2.mobile-app-secret-required' | translate }} + + + {{ 'admin.oauth2.mobile-app-secret-min-length' | translate }} + + + {{ 'admin.oauth2.mobile-app-secret-base64' | translate }} + + +
+
+ + {{ 'admin.oauth2.enable' | translate }} + +
+ + + +
+ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts new file mode 100644 index 0000000000..d46ac8da57 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts @@ -0,0 +1,108 @@ +/// +/// 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. +/// + + +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { MobileAppInfo } from '@shared/models/oauth2.models'; +import { AppState } from '@core/core.state'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { Store } from '@ngrx/store'; +import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; +import { WINDOW } from '@core/services/window.service'; +import { isDefinedAndNotNull } from '@core/utils'; +import { MatButton } from '@angular/material/button'; +import { MatDialog } from '@angular/material/dialog'; +import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; + +@Component({ + selector: 'tb-mobile-app', + templateUrl: './mobile-app.component.html', + styleUrls: [] +}) +export class MobileAppComponent extends EntityComponent { + + constructor(protected store: Store, + protected translate: TranslateService, + private oauth2Service: OAuth2Service, + @Inject('entity') protected entityValue: MobileAppInfo, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + protected cd: ChangeDetectorRef, + public fb: UntypedFormBuilder, + @Inject(WINDOW) private window: Window, + private dialog: MatDialog) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); + } + + buildForm(entity: MobileAppInfo): UntypedFormGroup { + return this.fb.group({ + pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required]], + appSecret: [entity?.appSecret ? entity.appSecret : '', [Validators.required, this.base64Format]], + oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, + oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [] + }); + } + + updateForm(entity: MobileAppInfo) { + this.entityForm.patchValue({ + pkgName: entity.pkgName, + appSecret: entity.appSecret, + oauth2Enabled: entity.oauth2Enabled, + oauth2ClientInfos: entity.oauth2ClientInfos + }) + } + + createClient($event: Event, button: MatButton) { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + button._elementRef.nativeElement.blur(); + this.dialog.open(ClientDialogComponent, { + disableClose: true, + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], + data: {} + }).afterClosed() + .subscribe((res) => { + if (res) { + const formValue = this.entityForm.get('oauth2ClientInfos').value ? + [...this.entityForm.get('oauth2ClientInfos').value] : []; + formValue.push({id: res.id, title: res.title}); + this.entityForm.get('oauth2ClientInfos').patchValue(formValue); + this.entityForm.get('oauth2ClientInfos').markAsDirty(); + this.entityForm.updateValueAndValidity(); + } + }); + } + + private base64Format(control: UntypedFormControl): { [key: string]: boolean } | null { + if (control.value === '') { + return null; + } + try { + const value = atob(control.value); + if (value.length < 64) { + return {minLength: true}; + } + return null; + } catch (e) { + return {base64: true}; + } + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2-routing.module.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2-routing.module.ts new file mode 100644 index 0000000000..b1e18ed502 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2-routing.module.ts @@ -0,0 +1,152 @@ +/// +/// 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. +/// + +import { Injectable, NgModule } from '@angular/core'; +import { Resolve, RouterModule, Routes } from '@angular/router'; +import { RouterTabsComponent } from '@home/components/router-tabs.component'; +import { Authority } from '@shared/models/authority.enum'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { OAuth2Service } from '@core/http/oauth2.service'; +import { Observable } from 'rxjs'; +import { ClientsTableConfigResolver } from './clients/clients-table-config.resolver'; +import { DomainTableConfigResolver } from '@home/pages/admin/oauth2/domains/domain-table-config.resolver'; +import { EntityDetailsPageComponent } from '@home/components/entity/entity-details-page.component'; +import { entityDetailsPageBreadcrumbLabelFunction } from '@home/pages/home-pages.models'; +import { BreadCrumbConfig } from '@shared/components/breadcrumb'; +import { MobileAppTableConfigResolver } from '@home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver'; + +@Injectable() +export class OAuth2LoginProcessingUrlResolver implements Resolve { + + constructor(private oauth2Service: OAuth2Service) { + } + + resolve(): Observable { + return this.oauth2Service.getLoginProcessingUrl(); + } +} + +export const oAuth2Routes: Routes = [ + { + path: 'oauth2', + component: RouterTabsComponent, + data: { + auth: [Authority.SYS_ADMIN], + breadcrumb: { + label: 'admin.oauth2.oauth2', + icon: 'mdi:shield-account' + } + }, + children: [ + { + path: '', + children: [], + data: { + auth: [Authority.SYS_ADMIN], + redirectTo: '/security-settings/oauth2/domains' + } + }, + { + path: 'domains', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.domains', + breadcrumb: { + label: 'admin.oauth2.domains', + icon: 'domain' + } + }, + resolve: { + entitiesTableConfig: DomainTableConfigResolver + } + }, + { + path: 'mobile-applications', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.mobile-apps', + breadcrumb: { + label: 'admin.oauth2.mobile-apps', + icon: 'smartphone' + } + }, + resolve: { + entitiesTableConfig: MobileAppTableConfigResolver + } + }, + { + path: 'clients', + data: { + title: 'admin.oauth2.clients', + breadcrumb: { + label: 'admin.oauth2.clients', + icon: 'public' + } + }, + children: [ + { + path: '', + component: EntitiesTableComponent, + data: { + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.clients' + }, + resolve: { + entitiesTableConfig: ClientsTableConfigResolver + } + }, + { + path: 'details', + children: [ + { + path: ':entityId', + component: EntityDetailsPageComponent, + data: { + breadcrumb: { + labelFunction: entityDetailsPageBreadcrumbLabelFunction, + icon: 'public' + } as BreadCrumbConfig, + auth: [Authority.SYS_ADMIN], + title: 'admin.oauth2.clients', + hideTabs: true, + backNavigationCommands: ['../..'] + }, + resolve: { + entitiesTableConfig: ClientsTableConfigResolver + } + } + ] + } + ] + } + ] + } +]; + +@NgModule({ + providers: [ + OAuth2LoginProcessingUrlResolver, + ClientsTableConfigResolver, + DomainTableConfigResolver, + MobileAppTableConfigResolver + ], + imports: [RouterModule.forChild(oAuth2Routes)], + exports: [RouterModule] +}) +export class Oauth2RoutingModule { +} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts new file mode 100644 index 0000000000..8cd1083155 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts @@ -0,0 +1,50 @@ +/// +/// 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. +/// + +import { NgModule } from '@angular/core'; +import { ClientComponent } from '@home/pages/admin/oauth2/clients/client.component'; +import { Oauth2RoutingModule } from '@home/pages/admin/oauth2/oauth2-routing.module'; +import { SharedModule } from '@shared/shared.module'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { CommonModule } from '@angular/common'; +import { ClientTableHeaderComponent } from '@home/pages/admin/oauth2/clients/client-table-header.component'; +import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.component'; +import { ClientListComponent } from '@home/pages/admin/oauth2/clients/client-list.component'; +import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; +import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component'; +import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component'; +import { MobileAppTableHeaderComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component'; + +@NgModule({ + declarations: [ + ClientComponent, + ClientDialogComponent, + ClientListComponent, + ClientTableHeaderComponent, + DomainComponent, + DomainTableHeaderComponent, + MobileAppComponent, + MobileAppTableHeaderComponent + ], + imports: [ + Oauth2RoutingModule, + CommonModule, + SharedModule, + HomeComponentsModule + ] +}) +export class OAuth2Module { +} diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index 9b0a32baed..83ab1bbe4b 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -23,7 +23,7 @@ import { UntypedFormBuilder } from '@angular/forms'; import { HttpErrorResponse } from '@angular/common/http'; import { Constants } from '@shared/models/constants'; import { Router } from '@angular/router'; -import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; +import { OAuth2ClientLoginInfo } from '@shared/models/oauth2.models'; @Component({ selector: 'tb-login', @@ -38,7 +38,7 @@ export class LoginComponent extends PageComponent implements OnInit { username: '', password: '' }); - oauth2Clients: Array = null; + oauth2Clients: Array = null; constructor(protected store: Store, private authService: AuthService, @@ -73,7 +73,7 @@ export class LoginComponent extends PageComponent implements OnInit { } } - getOAuth2Uri(oauth2Client: OAuth2ClientInfo): string { + getOAuth2Uri(oauth2Client: OAuth2ClientLoginInfo): string { let result = ""; if (this.authService.redirectUrl) { result += "?prevUri=" + this.authService.redirectUrl; diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index ec6aa1c9e7..34d2d06dd2 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -91,6 +91,10 @@ export const HelpLinks = { slackSettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/slack-settings`, securitySettings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/ui/security-settings`, oauth2Settings: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/oauth-2-support/`, + oauth2Apple: 'https://developer.apple.com/sign-in-with-apple/get-started/', + oauth2Facebook: 'https://developers.facebook.com/docs/facebook-login/web#logindialog', + oauth2Github: 'https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app', + oauth2Google: 'https://developers.google.com/google-ads/api/docs/start', ruleEngine: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/overview/`, ruleNodeCheckRelation: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/filter-nodes/#check-relation-filter-node`, ruleNodeCheckExistenceFields: `${helpBaseUrl}/docs${docPlatformPrefix}/user-guide/rule-engine-2-0/filter-nodes/#check-existence-fields-node`, diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 045e95983c..80194ac843 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -44,7 +44,10 @@ export enum EntityType { NOTIFICATION_REQUEST = 'NOTIFICATION_REQUEST', NOTIFICATION_RULE = 'NOTIFICATION_RULE', NOTIFICATION_TARGET = 'NOTIFICATION_TARGET', - NOTIFICATION_TEMPLATE = 'NOTIFICATION_TEMPLATE' + NOTIFICATION_TEMPLATE = 'NOTIFICATION_TEMPLATE', + OAUTH2_CLIENT = 'OAUTH2_CLIENT', + DOMAIN = 'DOMAIN', + MOBILE_APP = 'MOBILE_APP' } export enum AliasEntityType { @@ -423,6 +426,42 @@ export const entityTypeTranslations = new Map([ [EntityType.OTA_PACKAGE, '/features/otaUpdates'], [EntityType.QUEUE, '/settings/queues'], [EntityType.WIDGETS_BUNDLE, '/resources/widgets-library/widgets-bundles/details'], - [EntityType.WIDGET_TYPE, '/resources/widgets-library/widget-types/details'] + [EntityType.WIDGET_TYPE, '/resources/widgets-library/widget-types/details'], + [EntityType.OAUTH2_CLIENT, '/security-settings/oauth2/clients/details'] ]); export interface EntitySubtype { diff --git a/ui-ngx/src/app/shared/models/id/domain-id.ts b/ui-ngx/src/app/shared/models/id/domain-id.ts new file mode 100644 index 0000000000..69d592d342 --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/domain-id.ts @@ -0,0 +1,27 @@ +/// +/// 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. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class DomainId implements EntityId { + entityType = EntityType.DOMAIN + id: string; + + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/mobile-app-id.ts b/ui-ngx/src/app/shared/models/id/mobile-app-id.ts new file mode 100644 index 0000000000..2148bc2f5c --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/mobile-app-id.ts @@ -0,0 +1,27 @@ +/// +/// 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. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class MobileAppId implements EntityId { + entityType = EntityType.MOBILE_APP + id: string; + + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/id/oauth2-client-id.ts b/ui-ngx/src/app/shared/models/id/oauth2-client-id.ts new file mode 100644 index 0000000000..6e69a663ac --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/oauth2-client-id.ts @@ -0,0 +1,27 @@ +/// +/// 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. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class OAuth2ClientId implements EntityId { + entityType = EntityType.OAUTH2_CLIENT + id: string; + + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index 9e3d3fa1c5..6ebd12be0e 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -14,31 +14,15 @@ /// limitations under the License. /// +import { OAuth2ClientId } from '@shared/models/id/oauth2-client-id'; +import { BaseData } from '@shared/models/base-data'; +import { TenantId } from '@shared/models/id/tenant-id'; +import { HasTenantId } from './entity.models'; +import { DomainId } from './id/domain-id'; import { HasUUID } from '@shared/models/id/has-uuid'; +import { MobileAppId } from '@shared/models/id/mobile-app-id'; -export interface OAuth2Info { - enabled: boolean; - edgeEnabled: boolean; - oauth2ParamsInfos: OAuth2ParamsInfo[]; -} - -export interface OAuth2ParamsInfo { - clientRegistrations: OAuth2RegistrationInfo[]; - domainInfos: OAuth2DomainInfo[]; - mobileInfos: OAuth2MobileInfo[]; -} - -export interface OAuth2DomainInfo { - name: string; - scheme: DomainSchema; -} - -export interface OAuth2MobileInfo { - pkgName: string; - appSecret: string; -} - -export enum DomainSchema{ +export enum DomainSchema { HTTP = 'HTTP', HTTPS = 'HTTPS', MIXED = 'MIXED' @@ -52,34 +36,13 @@ export const domainSchemaTranslations = new Map( ] ); -export enum MapperConfigType{ - BASIC = 'BASIC', - CUSTOM = 'CUSTOM', - GITHUB = 'GITHUB', - APPLE = 'APPLE' -} - -export enum TenantNameStrategy{ - DOMAIN = 'DOMAIN', - EMAIL = 'EMAIL', - CUSTOM = 'CUSTOM' -} - export enum PlatformType { WEB = 'WEB', ANDROID = 'ANDROID', IOS = 'IOS' } -export const platformTypeTranslations = new Map( - [ - [PlatformType.WEB, 'admin.oauth2.platform-web'], - [PlatformType.ANDROID, 'admin.oauth2.platform-android'], - [PlatformType.IOS, 'admin.oauth2.platform-ios'] - ] -); - -export interface OAuth2ClientRegistrationTemplate extends OAuth2RegistrationInfo{ +export interface OAuth2ClientRegistrationTemplate extends OAuth2RegistrationInfo { comment: string; createdTime: number; helpLink: string; @@ -101,7 +64,7 @@ export interface OAuth2RegistrationInfo { userInfoUri: string; clientAuthenticationMethod: ClientAuthenticationMethod; userNameAttributeName: string; - mapperConfig: MapperConfig; + mapperConfig: OAuth2MapperConfig; additionalInfo: string; } @@ -110,33 +73,132 @@ export enum ClientAuthenticationMethod { POST = 'POST' } -export interface MapperConfig { +export interface Domain extends BaseData, HasTenantId { + tenantId?: TenantId; + name: string; + oauth2Enabled: boolean; + propagateToEdge: boolean; +} + +export interface HasOauth2Clients { + oauth2ClientInfos?: Array; +} + +export interface DomainInfo extends Domain, HasOauth2Clients { + oauth2ClientInfos?: Array; +} + +export interface MobileApp extends BaseData, HasTenantId { + tenantId?: TenantId; + pkgName: string; + appSecret: string; + oauth2Enabled: boolean; +} + +export interface MobileAppInfo extends MobileApp, HasOauth2Clients { + oauth2ClientInfos?: Array; +} + +export interface OAuth2Client extends BaseData, HasTenantId { + tenantId?: TenantId; + title: string; + mapperConfig: OAuth2MapperConfig; + clientId: string; + clientSecret: string; + authorizationUri: string; + accessTokenUri: string; + scope: Array; + userInfoUri?: string; + userNameAttributeName: string; + jwkSetUri?: string; + clientAuthenticationMethod: ClientAuthenticationMethod; + loginButtonLabel: string; + loginButtonIcon?: string; + platforms?: Array; + additionalInfo: any; +} + +export interface OAuth2MapperConfig { allowUserCreation: boolean; activateUser: boolean; - type: MapperConfigType; - basic?: MapperConfigBasic; - custom?: MapperConfigCustom; + type: MapperType; + basic?: OAuth2BasicMapperConfig; + custom?: OAuth2CustomMapperConfig +} + +export enum MapperType { + BASIC = 'BASIC', + CUSTOM = 'CUSTOM', + GITHUB = 'GITHUB', + APPLE = 'APPLE' } -export interface MapperConfigBasic { - emailAttributeKey: string; +export interface OAuth2BasicMapperConfig { + emailAttributeKey?: string; firstNameAttributeKey?: string; lastNameAttributeKey?: string; - tenantNameStrategy: TenantNameStrategy; + tenantNameStrategy?: TenantNameStrategyType; tenantNamePattern?: string; customerNamePattern?: string; defaultDashboardName?: string; alwaysFullScreen?: boolean; } -export interface MapperConfigCustom { - url: string; +export enum TenantNameStrategyType { + DOMAIN = 'DOMAIN', + EMAIL = 'EMAIL', + CUSTOM = 'CUSTOM' +} + +export interface OAuth2CustomMapperConfig { + url?: string; username?: string; password?: string; + sendToken: boolean; +} + +export const platformTypeTranslations = new Map( + [ + [PlatformType.WEB, 'admin.oauth2.platform-web'], + [PlatformType.ANDROID, 'admin.oauth2.platform-android'], + [PlatformType.IOS, 'admin.oauth2.platform-ios'] + ] +); + +export interface OAuth2ClientInfo extends BaseData { + title: string; + platforms?: Array; } -export interface OAuth2ClientInfo { +export interface OAuth2ClientLoginInfo { name: string; - icon?: string; + icon: string; url: string; } + +export function getProviderHelpLink(provider: Provider): string { + if (provider) { + if (providerHelpLinkMap.has(provider)) { + return providerHelpLinkMap.get(provider); + } + } + return 'oauth2Settings'; +} + +export enum Provider { + CUSTOM = 'Custom', + FACEBOOK = 'Facebook', + GOOGLE = 'Google', + GITHUB = 'Github', + APPLE = 'Apple' +} + +const providerHelpLinkMap = new Map( + [ + [Provider.CUSTOM, 'oauth2Settings'], + [Provider.APPLE, 'oauth2Apple'], + [Provider.FACEBOOK, 'oauth2Facebook'], + [Provider.GITHUB, 'oauth2Github'], + [Provider.GOOGLE, 'oauth2Google'], + ] +) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 4eb0679b92..1c6a5248b3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -222,6 +222,27 @@ "always-fullscreen": "Always fullscreen", "authorization-uri": "Authorization URI", "authorization-uri-required": "Authorization URI is required.", + "add-client": "Add OAuth 2.0 client", + "client-details": "OAuth 2.0 details", + "client": "OAuth 2.0 client", + "clients": "OAuth 2.0 clients", + "no-oauth2-clients": "No OAuth 2.0 clients found", + "search-oauth2-clients": "Search OAuth 2.0 clients", + "delete-client-title": "Are you sure you want to delete the OAuth 2.0 client '{{clientName}}'?", + "delete-client-text": "Be careful, after the confirmation the client and all related data will become unrecoverable.", + "delete-mobile-app-title": "Are you sure you want to delete the mobile application '{{applicationName}}'?", + "delete-mobile-app-text": "Be careful, after the confirmation the mobile application and all related data will become unrecoverable.", + "title": "Title", + "client-title-required": "Title is required", + "advanced-settings": "Advanced settings", + "domain-details": "Domain details", + "no-domains": "No domains found", + "search-domains": "Search domains", + "mobile-app-details": "Mobile application details", + "add-mobile-app": "Add mobile application", + "no-mobile-apps": "Search mobile applications", + "search-mobile-apps": "No mobile applications found", + "send-token": "Send token", "client-authentication-method": "Client authentication method", "client-id": "Client ID", "client-id-required": "Client ID is required.", @@ -254,7 +275,7 @@ "login-provider": "Login provider", "mapper": "Mapper", "new-domain": "New domain", - "oauth2": "OAuth2", + "oauth2": "OAuth 2.0", "password-max-length": "Password should be less than 256", "redirect-uri-template": "Redirect URI template", "copy-redirect-uri": "Copy redirect URI", @@ -282,11 +303,13 @@ "domain-schema-http": "HTTP", "domain-schema-https": "HTTPS", "domain-schema-mixed": "HTTP+HTTPS", - "enable": "Enable OAuth2 settings", - "edge-enable": "Propagate to Edge", + "enable": "Enable OAuth 2.0 settings", + "disable": "Disable OAuth 2.0 settings", + "edge": "Propagate to Edge", + "edge-enable": "Enable propagation to Edge", + "edge-disable": "Disable propagation to Edge", "domains": "Domains", "mobile-apps": "Mobile applications", - "no-mobile-apps": "No applications configured", "mobile-package": "Application package", "mobile-package-placeholder": "Ex.: my.example.app", "mobile-package-hint": "For Android: your own unique Application ID. For iOS: Product bundle identifier.", @@ -298,7 +321,6 @@ "mobile-app-secret-base64": "Application secret must be base64 format.", "invalid-mobile-app-secret": "Application secret must contain only alphanumeric characters and must be between 16 and 2048 characters long.", "copy-mobile-app-secret": "Copy application secret", - "add-mobile-app": "Add application", "delete-mobile-app": "Delete application info", "providers": "Providers", "platform-web": "Web", @@ -312,6 +334,7 @@ "provider": "Provider", "redirect-url": "Redirect URI", "domain-name": "Domain name", + "domain-name-required": "Domain name is required", "redirect-url-template": "Redirect URI template", "microsoft-tenant-id": "Directory (tenant) Id", "microsoft-tenant-id-required": "Directory (tenant) Id is required", @@ -2300,7 +2323,16 @@ "type-notification-request": "Notification request", "type-notification-template": "Notification template", "type-notification-templates": "Notification templates", - "list-of-notification-templates": "{ count, plural, =1 {One notification template} other {List of # notification templates} }" + "list-of-notification-templates": "{ count, plural, =1 {One notification template} other {List of # notification templates} }", + "type-oauth2-client": "OAuth 2.0 client", + "type-oauth2-clients": "OAuth 2.0 clients", + "list-of-oauth2-clients": "{ count, plural, =1 {One OAuth 2.0 client} other {List of # OAuth 2.0 clients} }", + "type-domain": "Domain", + "type-domains": "Domains", + "list-of-domains": "{ count, plural, =1 {One Domain} other {List of # Domains} }", + "type-mobile-app": "Mobile application", + "type-mobile-apps": "Mobile applications", + "list-of-mobile-apps": "{ count, plural, =1 {One Mobile application} other {List of # Mobile applications} }" }, "entity-field": { "created-time": "Created time", From 8093049555c4051096d91b81ebbfecf099a36fcd Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 8 Aug 2024 19:18:58 +0300 Subject: [PATCH 07/29] added search for oauth2 clients, domains, mobile apps --- .../server/dao/domain/DomainServiceImpl.java | 4 ++-- .../server/dao/mobile/MobileAppServiceImpl.java | 5 ++--- .../server/dao/sql/domain/DomainRepository.java | 6 +++++- .../server/dao/sql/domain/JpaDomainDao.java | 2 +- .../server/dao/sql/mobile/JpaMobileAppDao.java | 2 +- .../server/dao/sql/mobile/MobileAppRepository.java | 6 +++++- .../server/dao/sql/oauth2/JpaOAuth2ClientDao.java | 2 +- .../server/dao/sql/oauth2/OAuth2ClientRepository.java | 10 +++++++--- 8 files changed, 24 insertions(+), 13 deletions(-) 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 4dd996d75c..196a9324e7 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 @@ -123,11 +123,11 @@ public class DomainServiceImpl extends AbstractEntityService implements DomainSe log.trace("Executing findDomainInfosByTenantId [{}]", tenantId); PageData pageData = domainDao.findByTenantId(tenantId, pageLink); List domainInfos = new ArrayList<>(); - pageData.getData().stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(domain -> { + for (Domain domain : pageData.getData()) { domainInfos.add(new DomainInfo(domain, oauth2ClientDao.findByDomainId(domain.getUuidId()).stream() .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); - }); + } return new PageData<>(domainInfos, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext()); } 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 5272d4e58d..aeac676903 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 @@ -20,7 +20,6 @@ import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; @@ -95,11 +94,11 @@ public class MobileAppServiceImpl extends AbstractEntityService implements Mobil log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId); PageData pageData = mobileAppDao.findByTenantId(tenantId, pageLink); List mobileAppInfos = new ArrayList<>(); - pageData.getData().stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(mobileApp -> { + for (MobileApp mobileApp : pageData.getData()) { mobileAppInfos.add(new MobileAppInfo(mobileApp, oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream() .map(OAuth2ClientInfo::new) .collect(Collectors.toList()))); - }); + } return new PageData<>(mobileAppInfos, pageData.getTotalPages(), pageData.getTotalElements(), pageData.hasNext()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java index baf4358e8a..d2cc957fe8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java @@ -28,7 +28,11 @@ import java.util.UUID; public interface DomainRepository extends JpaRepository { - Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); + @Query("SELECT d FROM DomainEntity d WHERE d.tenantId = :tenantId AND " + + "(:searchText is NULL OR ilike(d.name, concat('%', :searchText, '%')) = true)") + Page findByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); @Transactional @Modifying 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 4bcdeeec6b..7701483644 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 @@ -57,7 +57,7 @@ public class JpaDomainDao extends JpaAbstractDao implement @Override public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { - return DaoUtil.toPageData(domainRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); + return DaoUtil.toPageData(domainRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override 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 2e884d0d84..8f09c7267b 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 @@ -57,7 +57,7 @@ public class JpaMobileAppDao extends JpaAbstractDao @Override public PageData findByTenantId(TenantId tenantId, PageLink pageLink) { - return DaoUtil.toPageData(mobileAppRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); + return DaoUtil.toPageData(mobileAppRepository.findByTenantId(tenantId.getId(), pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java index 16ca927fbb..185a277644 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java @@ -28,7 +28,11 @@ import java.util.UUID; public interface MobileAppRepository extends JpaRepository { - Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); + @Query("SELECT a FROM MobileAppEntity a WHERE a.tenantId = :tenantId AND " + + "(:searchText is NULL OR ilike(a.pkgName, concat('%', :searchText, '%')) = true)") + Page findByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); @Transactional @Modifying 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 e6fd04da78..542578a028 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 @@ -52,7 +52,7 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao findByTenantId(UUID tenantId, PageLink pageLink) { - return DaoUtil.toPageData(repository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink))); + return DaoUtil.toPageData(repository.findByTenantId(tenantId, pageLink.getTextSearch(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java index 8c4e6efa15..51804f2c82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java @@ -29,13 +29,17 @@ import java.util.UUID; public interface OAuth2ClientRepository extends JpaRepository { - Page findByTenantId(@Param("tenantId") UUID tenantId, Pageable pageable); + @Query("SELECT с FROM OAuth2ClientEntity с WHERE с.tenantId = :tenantId AND " + + "(:searchText is NULL OR ilike(с.title, concat('%', :searchText, '%')) = true)") + Page findByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT c " + "FROM OAuth2ClientEntity c " + "LEFT JOIN DomainOauth2ClientEntity dc on c.id = dc.oauth2ClientId " + "LEFT JOIN DomainEntity domain on dc.domainId = domain.id " + - "AND domain.name = :domainName " + + "WHERE domain.name = :domainName " + "AND (:platformFilter IS NULL OR c.platforms IS NULL OR c.platforms = '' OR c.platforms LIKE :platformFilter)") List findEnabledByDomainNameAndPlatformType(@Param("domainName") String domainName, @Param("platformFilter") String platformFilter); @@ -44,7 +48,7 @@ public interface OAuth2ClientRepository extends JpaRepository findEnabledByPkgNameAndPlatformType(@Param("pkgName") String pkgName, @Param("platformFilter") String platformFilter); From 8d7acca4d8d823c98138166d3fe34a30107198f2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 12 Aug 2024 17:53:31 +0300 Subject: [PATCH 08/29] added "/oauth2/client/infos?clientIds=" api for UI --- .../server/controller/OAuth2Controller.java | 17 +++++++++++++++++ .../server/dao/oauth2/OAuth2ClientService.java | 4 +++- .../common/data/oauth2/OAuth2ClientInfo.java | 17 +++++++++++++++-- .../server/dao/oauth2/OAuth2ClientDao.java | 3 +++ .../dao/oauth2/OAuth2ClientServiceImpl.java | 14 +++++++++++++- .../dao/sql/oauth2/JpaOAuth2ClientDao.java | 8 ++++++++ .../dao/sql/oauth2/OAuth2ClientRepository.java | 2 ++ 7 files changed, 61 insertions(+), 4 deletions(-) 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 ce137be00e..ee46438569 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -16,6 +16,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.servlet.http.HttpServletRequest; import jakarta.validation.Valid; @@ -48,6 +49,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.utils.MiscUtils; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.UUID; @@ -132,6 +134,21 @@ public class OAuth2Controller extends BaseController { return oAuth2ClientService.findOAuth2ClientInfosByTenantId(getTenantId(), pageLink); } + @ApiOperation(value = "Get OAuth2 Client Registration infos By Ids (findTenantOAuth2ClientInfosByIds)", + notes = "Fetch OAuth2 Client Registration info objects based on the provided ids. ") + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") + @GetMapping(value = "/oauth2/client/infos", params = {"clientIds"}) + public List findTenantOAuth2ClientInfosByIds( + @Parameter(description = "A list of oauth2 ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")), required = true) + @RequestParam("clientIds") String[] strClientIds) throws ThingsboardException { + checkArrayParameter("clientIds", strClientIds); + List oAuth2ClientIds = new ArrayList<>(); + for (String oauth2ClientId : strClientIds) { + oAuth2ClientIds.add(new OAuth2ClientId(toUUID(oauth2ClientId))); + } + return oAuth2ClientService.findOAuth2ClientInfosByIds(getTenantId(), oAuth2ClientIds); + } + @ApiOperation(value = "Get OAuth2 Client Registration by id (getOAuth2ClientById)", notes = SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @GetMapping(value = "/oauth2/client/{id}") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java index 73fb8bafe7..a195e50917 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java @@ -17,9 +17,9 @@ package org.thingsboard.server.dao.oauth2; 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.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -47,4 +47,6 @@ public interface OAuth2ClientService extends EntityDaoService { void deleteOauth2ClientsByTenantId(TenantId tenantId); PageData findOAuth2ClientInfosByTenantId(TenantId tenantId, PageLink pageLink); + + List findOAuth2ClientInfosByIds(TenantId tenantId, List oAuth2ClientIds); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java index 1659855a2f..c2e9bce7be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java @@ -15,10 +15,12 @@ */ package org.thingsboard.server.common.data.oauth2; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.id.OAuth2ClientId; import java.util.List; @@ -26,10 +28,13 @@ import java.util.List; @Data @Schema @EqualsAndHashCode(callSuper = true) -public class OAuth2ClientInfo extends BaseData { +public class OAuth2ClientInfo extends BaseData implements HasName { - @Schema(description = "Oauth2 client registration title (e.g. Google)") + @Schema(description = "Oauth2 client registration title (e.g. My google)") private String title; + + @Schema(description = "Oauth2 client provider name (e.g. Google)") + private String providerName; @Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)") private List platforms; @@ -44,7 +49,15 @@ public class OAuth2ClientInfo extends BaseData { public OAuth2ClientInfo(OAuth2Client oAuth2Client) { super(oAuth2Client); this.title = oAuth2Client.getTitle(); + if (oAuth2Client.getAdditionalInfo() != null && oAuth2Client.getAdditionalInfo().has("providerName")) { + this.providerName = oAuth2Client.getAdditionalInfo().get("providerName").asText(); + } this.platforms = oAuth2Client.getPlatforms(); } + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getName() { + return title; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java index 673af18edb..c4c3b2e2ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.oauth2; +import org.thingsboard.server.common.data.id.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; @@ -40,4 +41,6 @@ public interface OAuth2ClientDao extends Dao { String findAppSecret(UUID id, String pkgName); void deleteByTenantId(TenantId tenantId); + + List findByIds(TenantId tenantId, List oAuth2ClientIds); } 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 4b97f28fb4..f7eeb20277 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 @@ -24,9 +24,9 @@ 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.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.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -42,6 +42,7 @@ import java.util.UUID; import java.util.stream.Collectors; import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validateString; @Slf4j @@ -138,6 +139,17 @@ public class OAuth2ClientServiceImpl extends AbstractEntityService implements OA return new PageData<>(oAuth2ClientInfos, clientInfos.getTotalPages(), clientInfos.getTotalPages(), clientInfos.hasNext()); } + @Override + public List findOAuth2ClientInfosByIds(TenantId tenantId, List oAuth2ClientIds) { + log.trace("Executing findQueueStatsByIds, tenantId [{}], queueStatsIds [{}]", tenantId, oAuth2ClientIds); + validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + validateIds(oAuth2ClientIds, ids -> "Incorrect clientIds " + ids); + return oauth2ClientDao.findByIds(tenantId, oAuth2ClientIds) + .stream() + .map(OAuth2ClientInfo::new) + .collect(Collectors.toList()); + } + @Override public void deleteByTenantId(TenantId tenantId) { deleteOauth2ClientsByTenantId(tenantId); 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 542578a028..d58b68ed7c 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 @@ -19,6 +19,7 @@ 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.OAuth2ClientId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Client; import org.thingsboard.server.common.data.oauth2.PlatformType; @@ -33,6 +34,8 @@ import org.thingsboard.server.dao.util.SqlDao; import java.util.List; import java.util.UUID; +import static org.thingsboard.server.dao.DaoUtil.toUUIDs; + @Component @RequiredArgsConstructor @SqlDao @@ -86,6 +89,11 @@ public class JpaOAuth2ClientDao extends JpaAbstractDao findByIds(TenantId tenantId, List oAuth2ClientIds) { + return DaoUtil.convertDataList(repository.findByTenantIdAndIdIn(tenantId.getId(), toUUIDs(oAuth2ClientIds))); + } + @Override public EntityType getEntityType() { return EntityType.OAUTH2_CLIENT; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java index 51804f2c82..8eaa2db010 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRepository.java @@ -79,4 +79,6 @@ public interface OAuth2ClientRepository extends JpaRepository findByTenantIdAndIdIn(UUID tenantId, List queueStatsIds); + } From 3c0253745408690c9eeb1ef6acbc853ed18cb236 Mon Sep 17 00:00:00 2001 From: rusikv Date: Tue, 13 Aug 2024 13:08:21 +0300 Subject: [PATCH 09/29] UI: oauth - switched to entity-list for clients, introduce entity-chips for tables, fixes improvements --- ui-ngx/src/app/core/http/domain.service.ts | 4 +- ui-ngx/src/app/core/http/entity.service.ts | 12 +- ui-ngx/src/app/core/http/oauth2.service.ts | 4 + .../entity/entities-table.component.html | 8 +- .../entity/entities-table.component.ts | 4 +- .../entity/entity-chips.component.html} | 6 +- .../entity/entity-chips.component.scss} | 2 +- .../entity/entity-chips.component.ts | 63 ++++++ .../entity/entity-details-panel.component.ts | 2 +- .../home/components/home-components.module.ts | 7 +- .../entity/entities-table-config.models.ts | 10 +- .../oauth2/clients/client-chips.component.ts | 40 ---- .../clients/client-dialog.component.html | 2 +- .../oauth2/clients/client-list.component.html | 53 ----- .../oauth2/clients/client-list.component.ts | 182 ------------------ .../client-table-header.component.html | 6 +- .../oauth2/clients/client.component.html | 4 +- .../admin/oauth2/clients/client.component.ts | 39 +--- .../clients/clients-table-config.resolver.ts | 5 +- .../domains/domain-table-config.resolver.ts | 16 +- .../domain-table-header.component.html | 5 +- .../oauth2/domains/domain.component.html | 8 +- .../admin/oauth2/domains/domain.component.ts | 17 +- .../mobile-app-table-config.resolver.ts | 14 +- .../mobile-app-table-header.component.html | 5 +- .../mobile-apps/mobile-app.component.html | 6 +- .../mobile-apps/mobile-app.component.ts | 20 +- .../home/pages/admin/oauth2/oauth2.module.ts | 2 - .../app/shared/models/entity-type.models.ts | 4 +- ui-ngx/src/app/shared/models/oauth2.models.ts | 7 +- .../assets/locale/locale.constant-en_US.json | 1 + 31 files changed, 164 insertions(+), 394 deletions(-) rename ui-ngx/src/app/modules/home/{pages/admin/oauth2/clients/client-chips.component.html => components/entity/entity-chips.component.html} (76%) rename ui-ngx/src/app/modules/home/{pages/admin/oauth2/clients/client-chips.component.scss => components/entity/entity-chips.component.scss} (97%) create mode 100644 ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.html delete mode 100644 ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-list.component.ts diff --git a/ui-ngx/src/app/core/http/domain.service.ts b/ui-ngx/src/app/core/http/domain.service.ts index 206748378e..bb6cd4a16b 100644 --- a/ui-ngx/src/app/core/http/domain.service.ts +++ b/ui-ngx/src/app/core/http/domain.service.ts @@ -37,8 +37,8 @@ export class DomainService { domain, defaultHttpOptionsFromConfig(config)); } - public updateOauth2Clients(id: string, oauth2ClientRegistrationIds: Array, config?: RequestConfig): Observable { - return this.http.put(`/api/domain/${id}/oauth2Clients`, oauth2ClientRegistrationIds, defaultHttpOptionsFromConfig(config)); + public updateOauth2Clients(id: string, oauth2ClientIds: Array, config?: RequestConfig): Observable { + return this.http.put(`/api/domain/${id}/oauth2Clients`, oauth2ClientIds, defaultHttpOptionsFromConfig(config)); } public getTenantDomainInfos(pageLink: PageLink, config?: RequestConfig): Observable> { diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index b5b2b0e796..bc0d810a05 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -96,6 +96,7 @@ import { NotificationType } from '@shared/models/notification.models'; import { UserId } from '@shared/models/id/user-id'; import { AlarmService } from '@core/http/alarm.service'; import { ResourceService } from '@core/http/resource.service'; +import { OAuth2Service } from '@core/http/oauth2.service'; @Injectable({ providedIn: 'root' @@ -125,7 +126,8 @@ export class EntityService { private queueService: QueueService, private notificationService: NotificationService, private alarmService: AlarmService, - private resourceService: ResourceService + private resourceService: ResourceService, + private oauth2Service: OAuth2Service ) { } private getEntityObservable(entityType: EntityType, entityId: string, @@ -272,6 +274,9 @@ export class EntityService { case EntityType.QUEUE_STATS: observable = this.queueService.getQueueStatisticsByIds(entityIds, config); break; + case EntityType.OAUTH2_CLIENT: + observable = this.oauth2Service.findTenantOAuth2ClientInfosByIds(entityIds, config); + break; } return observable; } @@ -452,6 +457,11 @@ export class EntityService { case EntityType.QUEUE_STATS: pageLink.sortOrder.property = 'createdTime'; entitiesObservable = this.queueService.getQueueStatistics(pageLink, config); + break; + case EntityType.OAUTH2_CLIENT: + pageLink.sortOrder.property = 'createdTime'; + entitiesObservable = this.oauth2Service.findTenantOAuth2ClientInfos(pageLink, config); + break; } return entitiesObservable; } diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts index 14b853eb93..73a8743a48 100644 --- a/ui-ngx/src/app/core/http/oauth2.service.ts +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -44,6 +44,10 @@ export class OAuth2Service { return this.http.get>(`/api/oauth2/client/infos${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); } + public findTenantOAuth2ClientInfosByIds(clientIds: Array, config?: RequestConfig): Observable> { + return this.http.get>(`/api/oauth2/client/infos?clientIds=${clientIds.join(',')}`, defaultHttpOptionsFromConfig(config)) + } + public getOAuth2ClientById(id: string, config?: RequestConfig): Observable { return this.http.get(`/api/oauth2/client/${id}`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html index 952e117539..9bf9443404 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.html @@ -38,9 +38,7 @@
- - {{ entitiesTableConfig.tableTitle }} - + {{ entitiesTableConfig.tableTitle }} - - + + diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index a37b22826b..3ba37d2564 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -46,8 +46,8 @@ import { ActivatedRoute, QueryParamsHandling, Router } from '@angular/router'; import { CellActionDescriptor, CellActionDescriptorType, - ClientChipsEntityTableColumn, EntityActionTableColumn, + EntityChipsEntityTableColumn, EntityColumn, EntityLinkTableColumn, EntityTableColumn, @@ -616,7 +616,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa columnsUpdated(resetData: boolean = false) { this.entityColumns = this.entitiesTableConfig.columns.filter( (column) => column instanceof EntityTableColumn || column instanceof EntityLinkTableColumn || - column instanceof ClientChipsEntityTableColumn) + column instanceof EntityChipsEntityTableColumn) .map(column => column as EntityTableColumn>); this.actionColumns = this.entitiesTableConfig.columns.filter( (column) => column instanceof EntityActionTableColumn) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html similarity index 76% rename from ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html rename to ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html index 5a1c87b29e..c81857689f 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.html +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.html @@ -16,7 +16,7 @@ --> - - {{ client.title }} + + {{ subEntity.name }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.scss similarity index 97% rename from ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss rename to ui-ngx/src/app/modules/home/components/entity/entity-chips.component.scss index 6c489effd0..72098fff51 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.scss +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.scss @@ -19,7 +19,7 @@ flex-wrap: wrap; gap: 8px; margin: 4px 0; - a.tb-client-chip { + a.tb-entity-chip { padding: 4px 8px; border-radius: 12px; line-height: 16px; diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts new file mode 100644 index 0000000000..cd9be9d79d --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/entity/entity-chips.component.ts @@ -0,0 +1,63 @@ +/// +/// 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. +/// + +import { Component, Input } from '@angular/core'; +import { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { baseDetailsPageByEntityType, EntityType } from '@app/shared/public-api'; + +const entityTypeEntitiesPropertyKeyMap = new Map([ + [EntityType.DOMAIN, 'oauth2ClientInfos'], + [EntityType.MOBILE_APP, 'oauth2ClientInfos'] +]); + +@Component({ + selector: 'tb-entity-chips', + templateUrl: './entity-chips.component.html', + styleUrls: ['./entity-chips.component.scss'] +}) +export class EntityChipsComponent { + + @Input() + set entity(value: BaseData) { + this.entityValue = value; + this.update(); + } + + get entity(): BaseData { + return this.entityValue; + } + + entityDetailsPrefixUrl: string; + + subEntities: Array>; + + private entityValue?: BaseData; + + private subEntitiesKey: string; + + update(): void { + if (this.entity && this.entity.id) { + const entityType = this.entity.id.entityType as EntityType; + this.subEntitiesKey = entityTypeEntitiesPropertyKeyMap.get(entityType); + this.subEntities = this.entity?.[this.subEntitiesKey]; + if (this.subEntities.length) { + this.entityDetailsPrefixUrl = baseDetailsPageByEntityType.get(this.subEntities[0].id.entityType as EntityType); + } + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index 7afbeafad4..ea1dfbf46d 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -228,7 +228,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV } hideDetailsTabs(): boolean { - return this.entitiesTableConfig.hideDetailsTabs || this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; + return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; } reloadEntity(): Observable> { diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 74fb826715..9cf8778710 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -171,7 +171,7 @@ import { import { WidgetConfigComponentsModule } from '@home/components/widget/config/widget-config-components.module'; import { BasicWidgetConfigModule } from '@home/components/widget/config/basic/basic-widget-config.module'; import { DeleteTimeseriesPanelComponent } from '@home/components/attribute/delete-timeseries-panel.component'; -import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-chips.component'; +import { EntityChipsComponent } from '@home/components/entity/entity-chips.component'; @NgModule({ declarations: @@ -310,7 +310,7 @@ import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-ch RateLimitsTextComponent, RateLimitsDetailsDialogComponent, SendNotificationButtonComponent, - ClientChipsComponent + EntityChipsComponent ], imports: [ CommonModule, @@ -441,7 +441,8 @@ import { ClientChipsComponent } from '@home/pages/admin/oauth2/clients/client-ch RateLimitsComponent, RateLimitsTextComponent, RateLimitsDetailsDialogComponent, - SendNotificationButtonComponent + SendNotificationButtonComponent, + EntityChipsComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index b342fd2e06..417c89434f 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -78,7 +78,7 @@ export interface HeaderActionDescriptor { onAction: ($event: MouseEvent) => void; } -export type EntityTableColumnType = 'content' | 'action' | 'link' | 'clients'; +export type EntityTableColumnType = 'content' | 'action' | 'link' | 'entityChips'; export class BaseEntityTableColumn> { constructor(public type: EntityTableColumnType, @@ -141,15 +141,15 @@ export class DateEntityTableColumn> extends EntityTabl } } -export class ClientChipsEntityTableColumn> extends BaseEntityTableColumn { +export class EntityChipsEntityTableColumn> extends BaseEntityTableColumn { constructor(public key: string, public title: string, public width: string = '0px') { - super('clients', key, title, width, false); + super('entityChips', key, title, width, false); } } -export type EntityColumn> = EntityTableColumn | EntityActionTableColumn | EntityLinkTableColumn | ClientChipsEntityTableColumn; +export type EntityColumn> = EntityTableColumn | EntityActionTableColumn | EntityLinkTableColumn | EntityChipsEntityTableColumn; export class EntityTableConfig, P extends PageLink = PageLink, L extends BaseData = T> { @@ -167,13 +167,11 @@ export class EntityTableConfig, P extends PageLink = P defaultTimewindowInterval = historyInterval(DAY); entityType: EntityType = null; tableTitle = ''; - hideTableTitle = false; selectionEnabled = true; searchEnabled = true; addEnabled = true; entitiesDeleteEnabled = true; detailsPanelEnabled = true; - hideDetailsTabs = false; hideDetailsTabsOnEdit = true; rowPointer = false; actionsColumnTitle = null; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts deleted file mode 100644 index 24611fa581..0000000000 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-chips.component.ts +++ /dev/null @@ -1,40 +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. -/// - -import { Component, Input } from '@angular/core'; -import { HasOauth2Clients } from '@shared/models/oauth2.models'; - -@Component({ - selector: 'tb-client-chips', - templateUrl: './client-chips.component.html', - styleUrls: ['./client-chips.component.scss'] -}) -export class ClientChipsComponent { - - clientsEntityValue?: HasOauth2Clients; - - clientPrefixUrl = '/security-settings/oauth2/clients/details/'; - - @Input() - set clientsEntity(value: HasOauth2Clients) { - this.clientsEntityValue = value; - } - - get clientsEntity(): HasOauth2Clients { - return this.clientsEntityValue; - } - -} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html index 1a002020a1..dcce273be2 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client-dialog.component.html @@ -30,7 +30,7 @@
- +
- +
{{ 'admin.oauth2.edge' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts index d41e63fb11..35895661ae 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.ts @@ -29,6 +29,7 @@ import { isDefinedAndNotNull } from '@core/utils'; import { MatButton } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; +import { EntityType } from '@shared/models/entity-type.models'; @Component({ selector: 'tb-domain', @@ -39,6 +40,8 @@ export class DomainComponent extends EntityComponent { private loginProcessingUrl: string = ''; + entityType = EntityType; + constructor(protected store: Store, protected translate: TranslateService, private oauth2Service: OAuth2Service, @@ -59,8 +62,8 @@ export class DomainComponent extends EntityComponent { return this.fb.group({ name: [entity?.name ? entity.name : '', [ Validators.required, Validators.maxLength(255), Validators.pattern('^(?:\\w+(?::\\w+)?@)?[^\\s/]+(?::\\d+)?$')]], - oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, - oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [], + oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : true, + oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos.map(info => info.id.id) : [], propagateToEdge: isDefinedAndNotNull(entity?.propagateToEdge) ? entity.propagateToEdge : false }); } @@ -69,7 +72,7 @@ export class DomainComponent extends EntityComponent { this.entityForm.patchValue({ name: entity.name, oauth2Enabled: entity.oauth2Enabled, - oauth2ClientInfos: entity.oauth2ClientInfos, + oauth2ClientInfos: entity.oauth2ClientInfos?.map(info => info.id.id), propagateToEdge: entity.propagateToEdge }) } @@ -84,20 +87,18 @@ export class DomainComponent extends EntityComponent { $event.stopPropagation(); $event.preventDefault(); } - button._elementRef.nativeElement.blur(); this.dialog.open(ClientDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: {} }).afterClosed() - .subscribe((res) => { - if (res) { + .subscribe((client) => { + if (client) { const formValue = this.entityForm.get('oauth2ClientInfos').value ? [...this.entityForm.get('oauth2ClientInfos').value] : []; - formValue.push({id: res.id, title: res.title}); + formValue.push(client.id.id); this.entityForm.get('oauth2ClientInfos').patchValue(formValue); this.entityForm.get('oauth2ClientInfos').markAsDirty(); - this.entityForm.updateValueAndValidity(); } }); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts index c7d5fa0be0..96bd222ed5 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-config.resolver.ts @@ -18,9 +18,9 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve } from '@angular/router'; import { CellActionDescriptorType, - ClientChipsEntityTableColumn, DateEntityTableColumn, EntityActionTableColumn, + EntityChipsEntityTableColumn, EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -45,7 +45,6 @@ export class MobileAppTableConfigResolver implements Resolve entity.appSecret, type: CellActionDescriptorType.COPY_BUTTON }), - new ClientChipsEntityTableColumn('oauth2ClientInfos', 'admin.oauth2.clients', '20%'), + new EntityChipsEntityTableColumn('oauth2ClientInfos', 'admin.oauth2.clients', '20%'), new EntityActionTableColumn('oauth2Enabled', 'admin.oauth2.enable', { name: '', @@ -94,9 +92,9 @@ export class MobileAppTableConfigResolver implements Resolve this.translate.instant('admin.oauth2.delete-mobile-app-text'); this.config.entitiesFetchFunction = pageLink => this.mobileAppService.getTenantMobileAppInfos(pageLink); this.config.loadEntity = id => this.mobileAppService.getMobileAppInfoById(id.id); - this.config.saveEntity = (mobileApp, originalEntity) => { - const clientsIds = mobileApp.oauth2ClientInfos ? mobileApp.oauth2ClientInfos.map(clientInfo => clientInfo.id.id) : []; - if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos, originalEntity.oauth2ClientInfos)) { + this.config.saveEntity = (mobileApp, originalMobileApp) => { + const clientsIds = mobileApp.oauth2ClientInfos as Array; + if (mobileApp.id && !isEqual(mobileApp.oauth2ClientInfos.sort(), originalMobileApp.oauth2ClientInfos.map(info => info.id.id).sort())) { this.mobileAppService.updateOauth2Clients(mobileApp.id.id, clientsIds).subscribe(); } delete mobileApp.oauth2ClientInfos; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html index f78a483eb0..fd66a026da 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app-table-header.component.html @@ -15,7 +15,4 @@ limitations under the License. --> -
- {{ entitiesTableConfig.tableTitle }} -
-
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html index d5146098fa..449d2c39b1 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.html @@ -57,13 +57,13 @@ {{ 'admin.oauth2.enable' | translate }}
- + - + diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts index d46ac8da57..07ee9b7bfc 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/mobile-apps/mobile-app.component.ts @@ -25,10 +25,11 @@ import { TranslateService } from '@ngx-translate/core'; import { Store } from '@ngrx/store'; import { UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; import { WINDOW } from '@core/services/window.service'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotNull, randomAlphanumeric } from '@core/utils'; import { MatButton } from '@angular/material/button'; import { MatDialog } from '@angular/material/dialog'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; +import { EntityType } from '@shared/models/entity-type.models'; @Component({ selector: 'tb-mobile-app', @@ -37,6 +38,8 @@ import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-d }) export class MobileAppComponent extends EntityComponent { + entityType = EntityType; + constructor(protected store: Store, protected translate: TranslateService, private oauth2Service: OAuth2Service, @@ -52,9 +55,10 @@ export class MobileAppComponent extends EntityComponent { buildForm(entity: MobileAppInfo): UntypedFormGroup { return this.fb.group({ pkgName: [entity?.pkgName ? entity.pkgName : '', [Validators.required]], - appSecret: [entity?.appSecret ? entity.appSecret : '', [Validators.required, this.base64Format]], + appSecret: [entity?.appSecret ? entity.appSecret : btoa(randomAlphanumeric(64)), + [Validators.required, this.base64Format]], oauth2Enabled: isDefinedAndNotNull(entity?.oauth2Enabled) ? entity.oauth2Enabled : false, - oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos : [] + oauth2ClientInfos: entity?.oauth2ClientInfos ? entity.oauth2ClientInfos.map(info => info.id.id) : [] }); } @@ -63,7 +67,7 @@ export class MobileAppComponent extends EntityComponent { pkgName: entity.pkgName, appSecret: entity.appSecret, oauth2Enabled: entity.oauth2Enabled, - oauth2ClientInfos: entity.oauth2ClientInfos + oauth2ClientInfos: entity.oauth2ClientInfos?.map(info => info.id.id) }) } @@ -72,20 +76,18 @@ export class MobileAppComponent extends EntityComponent { $event.stopPropagation(); $event.preventDefault(); } - button._elementRef.nativeElement.blur(); this.dialog.open(ClientDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: {} }).afterClosed() - .subscribe((res) => { - if (res) { + .subscribe((client) => { + if (client) { const formValue = this.entityForm.get('oauth2ClientInfos').value ? [...this.entityForm.get('oauth2ClientInfos').value] : []; - formValue.push({id: res.id, title: res.title}); + formValue.push(client.id.id); this.entityForm.get('oauth2ClientInfos').patchValue(formValue); this.entityForm.get('oauth2ClientInfos').markAsDirty(); - this.entityForm.updateValueAndValidity(); } }); } diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts index 8cd1083155..545b9a6a63 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/oauth2.module.ts @@ -22,7 +22,6 @@ import { HomeComponentsModule } from '@home/components/home-components.module'; import { CommonModule } from '@angular/common'; import { ClientTableHeaderComponent } from '@home/pages/admin/oauth2/clients/client-table-header.component'; import { DomainComponent } from '@home/pages/admin/oauth2/domains/domain.component'; -import { ClientListComponent } from '@home/pages/admin/oauth2/clients/client-list.component'; import { ClientDialogComponent } from '@home/pages/admin/oauth2/clients/client-dialog.component'; import { DomainTableHeaderComponent } from '@home/pages/admin/oauth2/domains/domain-table-header.component'; import { MobileAppComponent } from '@home/pages/admin/oauth2/mobile-apps/mobile-app.component'; @@ -32,7 +31,6 @@ import { MobileAppTableHeaderComponent } from '@home/pages/admin/oauth2/mobile-a declarations: [ ClientComponent, ClientDialogComponent, - ClientListComponent, ClientTableHeaderComponent, DomainComponent, DomainTableHeaderComponent, diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 80194ac843..51fefefdf2 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -609,7 +609,9 @@ export const baseDetailsPageByEntityType = new Map([ [EntityType.QUEUE, '/settings/queues'], [EntityType.WIDGETS_BUNDLE, '/resources/widgets-library/widgets-bundles/details'], [EntityType.WIDGET_TYPE, '/resources/widgets-library/widget-types/details'], - [EntityType.OAUTH2_CLIENT, '/security-settings/oauth2/clients/details'] + [EntityType.OAUTH2_CLIENT, '/security-settings/oauth2/clients/details'], + [EntityType.DOMAIN, '/security-settings/oauth2/clients/details'], + [EntityType.MOBILE_APP, '/security-settings/oauth2/clients/details'] ]); export interface EntitySubtype { diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index 6ebd12be0e..95453a8ecf 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -81,11 +81,11 @@ export interface Domain extends BaseData, HasTenantId { } export interface HasOauth2Clients { - oauth2ClientInfos?: Array; + oauth2ClientInfos?: Array | Array; } export interface DomainInfo extends Domain, HasOauth2Clients { - oauth2ClientInfos?: Array; + oauth2ClientInfos?: Array | Array; } export interface MobileApp extends BaseData, HasTenantId { @@ -96,7 +96,7 @@ export interface MobileApp extends BaseData, HasTenantId { } export interface MobileAppInfo extends MobileApp, HasOauth2Clients { - oauth2ClientInfos?: Array; + oauth2ClientInfos?: Array | Array; } export interface OAuth2Client extends BaseData, HasTenantId { @@ -167,6 +167,7 @@ export const platformTypeTranslations = new Map( export interface OAuth2ClientInfo extends BaseData { title: string; + providerName: string; platforms?: Array; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 1c6a5248b3..ea7bba2c21 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -243,6 +243,7 @@ "no-mobile-apps": "Search mobile applications", "search-mobile-apps": "No mobile applications found", "send-token": "Send token", + "create-new": "Create new", "client-authentication-method": "Client authentication method", "client-id": "Client ID", "client-id-required": "Client ID is required.", From 76476810e429c09687e9d26b6342780a46296925 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 14 Aug 2024 11:55:34 +0300 Subject: [PATCH 10/29] UI: removed redundant check statement --- ui-ngx/src/app/shared/models/oauth2.models.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index 95453a8ecf..dd024715d5 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -178,10 +178,8 @@ export interface OAuth2ClientLoginInfo { } export function getProviderHelpLink(provider: Provider): string { - if (provider) { - if (providerHelpLinkMap.has(provider)) { - return providerHelpLinkMap.get(provider); - } + if (providerHelpLinkMap.has(provider)) { + return providerHelpLinkMap.get(provider); } return 'oauth2Settings'; } From 60a8da1bd7be3229b0d04e30b86f61463a6d0e41 Mon Sep 17 00:00:00 2001 From: rusikv Date: Wed, 14 Aug 2024 15:31:13 +0300 Subject: [PATCH 11/29] UI: oauth2 client form fixed mapper section not updating, set client entity list label and placeholder text --- .../admin/oauth2/clients/client.component.ts | 15 ++++++++------- .../admin/oauth2/domains/domain.component.html | 4 +++- .../admin/oauth2/domains/domain.component.ts | 1 + .../oauth2/mobile-apps/mobile-app.component.html | 4 +++- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts index a371922057..90f0b17403 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts @@ -176,7 +176,9 @@ export class ClientComponent extends EntityComponent { this.changeMapperConfigType(this.entityForm, value); @@ -272,11 +270,14 @@ export class ClientComponent extends EntityComponent
- +
- +