Browse Source

oauth2 configuration breakdown: initial implementation

pull/11231/head
dashevchenko 2 years ago
parent
commit
f274c274e9
  1. 83
      application/src/main/data/upgrade/3.7.0/schema_update.sql
  2. 6
      application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
  3. 39
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  4. 142
      application/src/main/java/org/thingsboard/server/controller/DomainController.java
  5. 142
      application/src/main/java/org/thingsboard/server/controller/MobileAppController.java
  6. 73
      application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
  7. 2
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  8. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  9. 8
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  11. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  12. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java
  13. 12
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java
  14. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  15. 14
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java
  16. 77
      application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java
  17. 15
      application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java
  18. 74
      application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java
  19. 14
      application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java
  20. 63
      application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java
  21. 31
      application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java
  22. 8
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  23. 11
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  24. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  25. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  26. 6
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  27. 84
      application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
  28. 14
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  29. 74
      application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java
  30. 4
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  31. 44
      common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java
  32. 41
      common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java
  33. 21
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java
  34. 5
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  35. 3
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  36. 52
      common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java
  37. 24
      common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java
  38. 20
      common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Registration.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  40. 14
      common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java
  41. 6
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  42. 14
      common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java
  43. 8
      common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java
  44. 59
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java
  45. 25
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java
  46. 32
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Registration.java
  47. 12
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/HasOauth2Registrations.java
  48. 42
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java
  49. 38
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java
  50. 42
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java
  51. 46
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java
  52. 29
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java
  53. 58
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java
  54. 39
      dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java
  55. 167
      dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java
  56. 36
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java
  57. 163
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java
  58. 39
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  59. 81
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java
  60. 37
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationCompositeKey.java
  61. 66
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2RegistrationEntity.java
  62. 46
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java
  63. 37
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationCompositeKey.java
  64. 67
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2RegistrationEntity.java
  65. 76
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java
  66. 70
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java
  67. 16
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java
  68. 61
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationInfoEntity.java
  69. 6
      dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java
  70. 145
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java
  71. 14
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java
  72. 295
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java
  73. 99
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java
  74. 36
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DomainDataValidator.java
  75. 43
      dao/src/main/java/org/thingsboard/server/dao/service/validator/MobileAppDataValidator.java
  76. 114
      dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2RegistrationDataValidator.java
  77. 34
      dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainOauth2RegistrationRepository.java
  78. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/domain/DomainRepository.java
  79. 83
      dao/src/main/java/org/thingsboard/server/dao/sql/domain/JpaDomainDao.java
  80. 77
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/JpaMobileAppDao.java
  81. 32
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppOauth2RegistrationRepository.java
  82. 19
      dao/src/main/java/org/thingsboard/server/dao/sql/mobile/MobileAppRepository.java
  83. 54
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java
  84. 54
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java
  85. 49
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java
  86. 31
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java
  87. 69
      dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java
  88. 2
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  89. 49
      dao/src/main/resources/sql/schema-entities.sql
  90. 641
      dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java
  91. 668
      dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java
  92. 15
      rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java

83
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

6
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.");
}

39
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 extends EntityId> I emptyId(EntityType entityType) {
return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}

142
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<String> oauth2ClientIds = ids != null ? Arrays.asList(ids) : Collections.emptyList();
domain.setTenantId(getCurrentUser().getTenantId());
checkEntity(domain.getId(), domain, Resource.DOMAIN);
List<OAuth2RegistrationId> 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<OAuth2RegistrationId> 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<DomainInfo> 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);
}
}

142
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<OAuth2RegistrationId> 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<OAuth2RegistrationId> 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<MobileAppInfo> 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);
}
}

73
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<OAuth2ClientInfo> 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<OAuth2RegistrationInfo> 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() + "\"";
}

2
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) {

4
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;

8
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;

4
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;

2
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()));
}
}

6
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();
}
}

12
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<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = new ArrayList<>();
OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info();
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2,
EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info)));
List<OAuth2Registration> 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);
}

4
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

14
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<Void> 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);
}

77
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<OAuth2RegistrationId> 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;
}
}
}

15
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java → 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<OAuth2DomainEntity, UUID> {
public interface TbDomainService {
List<OAuth2DomainEntity> findByOauth2ParamsId(UUID oauth2ParamsId);
Domain save(Domain domain, List<OAuth2RegistrationId> oAuth2Clients, User user) throws Exception;
}
void delete(Domain domain, User user);
}

74
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<OAuth2RegistrationId> 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;
}
}
}

14
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java → 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<OAuth2Mobile> {
public interface TbMobileAppService {
List<OAuth2Mobile> findByOAuth2ParamsId(UUID oauth2ParamsId);
MobileApp save(MobileApp mobileApp, List<OAuth2RegistrationId> oauth2Clients, User user) throws Exception;
void delete(MobileApp mobileApp, User user);
}

63
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;
}
}
}

31
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);
}

8
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<OAuth2Mobile> mobiles = oAuth2MobileDao.find(TenantId.SYS_TENANT_ID);
List<MobileApp> mobiles = oAuth2MobileDao.findByTenantId(TenantId.SYS_TENANT_ID);
if (CollectionUtils.isNotEmpty(mobiles)) {
mobiles.stream()
.filter(config -> !validateKeyLength(config.getAppSecret()))

11
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());

4
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),

4
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);

6
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<Partiti
private final TelemetrySubscriptionService telemetryService;
private final TbApiUsageStateClient apiUsageStateClient;
private final AdminSettingsService adminSettingsService;
private final OAuth2Service oAuth2Service;
private final DomainService domainService;
private final MailService mailService;
private final SmsService smsService;
private volatile ScheduledExecutorService scheduler;
@ -143,7 +143,7 @@ public class DefaultSystemInfoService extends TbApplicationEventListener<Partiti
FeaturesInfo featuresInfo = new FeaturesInfo();
featuresInfo.setEmailEnabled(isEmailEnabled());
featuresInfo.setSmsEnabled(smsService.isConfigured(TenantId.SYS_TENANT_ID));
featuresInfo.setOauthEnabled(oAuth2Service.findOAuth2Info().isEnabled());
featuresInfo.setOauthEnabled(domainService.isOauth2Enabled(TenantId.SYS_TENANT_ID));
featuresInfo.setTwoFaEnabled(isTwoFaEnabled());
featuresInfo.setNotificationEnabled(isSlackEnabled());
return featuresInfo;

84
application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java

@ -17,7 +17,6 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
@ -39,15 +38,9 @@ import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.UsageInfo;
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.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.common.data.page.PageData;
import org.thingsboard.server.common.data.query.ApiUsageStateFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
@ -65,7 +58,6 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.EntityDataUpdate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
@ -369,9 +361,8 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertTrue(featuresInfo.isNotificationEnabled());
Assert.assertFalse(featuresInfo.isOauthEnabled());
OAuth2Info oAuth2Info = createDefaultOAuth2Info();
doPost("/api/oauth2/config", oAuth2Info).andExpect(status().isOk());
Domain domain = createDomain(TenantId.SYS_TENANT_ID, "mydomain", true);
doPost("/api/domain", domain).andExpect(status().isOk());
featuresInfo = doGet("/api/admin/featuresInfo", FeaturesInfo.class);
Assert.assertNotNull(featuresInfo);
@ -493,43 +484,38 @@ public class HomePageApiTest extends AbstractControllerTest {
return doPostWithResponse("/api/entitiesQuery/count", query, Long.class);
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build()
))
.mobileInfos(Collections.emptyList())
.clientRegistrations(Lists.newArrayList(
validRegistrationInfo()
))
.build()
));
private Domain createDomain(TenantId tenantId, String domainName, boolean oauth2Enabled) {
Domain domain = new Domain();
domain.setTenantId(tenantId);
domain.setName(domainName);
domain.setOauth2Enabled(oauth2Enabled);
return domain;
}
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(
OAuth2MapperConfig.builder()
.type(MapperType.CUSTOM)
.custom(
OAuth2CustomMapperConfig.builder()
.url(UUID.randomUUID().toString())
.build()
)
.build()
)
.build();
private OAuth2Registration validRegistration() {
OAuth2Registration oAuth2Registration = new OAuth2Registration();
oAuth2Registration.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 oAuth2Registration;
}
}

14
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.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
@ -544,13 +544,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
private void validateOAuth2() throws Exception {
Optional<OAuth2UpdateMsg> 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() {

74
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;
}
}

4
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

44
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<DomainInfo> findDomainInfosByTenantId(TenantId tenantId);
DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId);
boolean isOauth2Enabled(TenantId tenantId);
void updateOauth2Clients(TenantId tenantId, DomainId domainId, List<OAuth2RegistrationId> oAuth2ClientIds);
}

41
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<MobileAppInfo> findMobileAppInfosByTenantId(TenantId tenantId);
MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId);
void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List<OAuth2RegistrationId> oAuth2ClientIds);
}

21
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java → 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<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
List<OAuth2ClientInfo> getWebOAuth2Clients(String domainName, PlatformType platformType);
void saveOAuth2Info(OAuth2Info oauth2Info);
List<OAuth2ClientInfo> getMobileOAuth2Clients(String pkgName, PlatformType platformType);
OAuth2Info findOAuth2Info();
List<OAuth2RegistrationInfo> findOauth2ClientInfosByTenantId(TenantId tenantId);
OAuth2Registration findRegistration(UUID id);
List<OAuth2Registration> findOauth2ClientsByTenantId(TenantId tenantId);
List<OAuth2Registration> 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);
}

5
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

3
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;

52
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<DomainId> 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;
}
}

24
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java → 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<OAuth2RegistrationInfo> oauth2RegistrationInfos;
public DomainInfo(Domain domain, List<OAuth2RegistrationInfo> oauth2RegistrationInfos) {
super(domain);
this.oauth2RegistrationInfos = oauth2RegistrationInfos;
}
}

20
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

@ -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<OAuth2MobileEntity, UUID> {
List<OAuth2MobileEntity> findByOauth2ParamsId(UUID oauth2ParamsId);
private DomainId domainId;
private OAuth2RegistrationId oAuth2RegistrationId;
}

2
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;

14
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java → 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;
}
}

6
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!");
}

14
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java → 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;
}
}

8
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;
}
}

59
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<MobileAppId> 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;
}
}

25
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java → 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<OAuth2ParamsInfo> oauth2ParamsInfos;
public class MobileAppInfo extends MobileApp implements HasOauth2Registrations {
@Schema(description = "List of available oauth2 client registrations")
private List<OAuth2RegistrationInfo> oauth2RegistrationInfos;
public MobileAppInfo(MobileApp mobileApp, List<OAuth2RegistrationInfo> oauth2RegistrationInfos) {
super(mobileApp);
this.oauth2RegistrationInfos = oauth2RegistrationInfos;
}
}

32
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;
}

12
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java → 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<OAuth2Domain> {
public interface HasOauth2Registrations extends Serializable {
List<OAuth2Domain> findByOAuth2ParamsId(UUID oauth2ParamsId);
List<OAuth2RegistrationInfo> getOauth2RegistrationInfos();
}

42
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java

@ -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<OAuth2DomainId> {
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;
}
}

38
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java

@ -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;
}

42
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java

@ -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<OAuth2MobileId> {
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;
}
}

46
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java

@ -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<OAuth2DomainInfo> domainInfos;
@Schema(description = "Mobile applications settings. Application package name must be unique within the list", requiredMode = Schema.RequiredMode.REQUIRED)
private List<OAuth2MobileInfo> mobileInfos;
@Schema(description = "List of OAuth2 provider settings. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
private List<OAuth2RegistrationInfo> clientRegistrations;
}

29
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<OAuth2RegistrationId> implements HasName {
public class OAuth2Registration extends BaseDataWithAdditionalInfo<OAuth2RegistrationId> 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<String> 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<PlatformType> 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;

58
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<String> 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<OAuth2RegistrationId> {
@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<PlatformType> 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<PlatformType> platforms) {
super(id);
this.title = title;
this.platforms = platforms;
}
}

39
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<Domain> {
List<Domain> findByTenantId(TenantId tenantId);
int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean oauth2Enabled);
List<DomainOauth2Registration> findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId);
void saveOauth2Clients(DomainOauth2Registration domainOauth2Registration);
void removeOauth2Clients(DomainId domainId, OAuth2RegistrationId oAuth2RegistrationId);
}

167
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<Domain> 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<OAuth2RegistrationId> 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<DomainOauth2Registration> oauth2Clients = new ArrayList<>();
for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) {
oauth2Clients.add(new DomainOauth2Registration(domainId, oAuth2RegistrationId));
}
List<DomainOauth2Registration> existingClients = domainDao.findOauth2ClientsByDomainId(tenantId, domainId);
List<OAuth2RegistrationId> 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<DomainInfo> findDomainInfosByTenantId(TenantId tenantId) {
log.trace("Executing findDomainInfo [{}]", tenantId);
List<Domain> domains = domainDao.findByTenantId(tenantId);
List<DomainInfo> 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<HasId<?>> 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());
}
}

36
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<MobileApp> {
List<MobileApp> findByTenantId(TenantId tenantId);
List<MobileAppOauth2Registration> findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId);
void saveOauth2Clients(MobileAppOauth2Registration mobileAppOauth2Registration);
void removeOauth2Clients(MobileAppId mobileAppId, OAuth2RegistrationId oAuth2RegistrationId);
}

163
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<MobileApp> 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<MobileAppInfo> findMobileAppInfosByTenantId(TenantId tenantId) {
log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId);
List<MobileApp> mobileApps = mobileAppDao.findByTenantId(tenantId);
List<MobileAppInfo> 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<OAuth2RegistrationId> 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<MobileAppOauth2Registration> oauth2Clients = new ArrayList<>();
for (OAuth2RegistrationId oAuth2RegistrationId: oAuth2ClientIds) {
oauth2Clients.add(new MobileAppOauth2Registration(mobileAppId, oAuth2RegistrationId));
}
List<MobileAppOauth2Registration> existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId);
List<OAuth2RegistrationId> 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<HasId<?>> 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());
}
}

39
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";

81
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<Domain> {
@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;
}
}

37
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;
}

66
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<DomainOauth2Registration> {
@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;
}
}

46
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java → 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<OAuth2Mobile> {
@Table(name = ModelConstants.MOBILE_APP_TABLE_NAME)
public class MobileAppEntity extends BaseSqlEntity<MobileApp> {
@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;
}
}

37
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;
}

67
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<MobileAppOauth2Registration> {
@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;
}
}

76
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java

@ -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<OAuth2Domain> {
@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;
}
}

70
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java

@ -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<OAuth2Params> {
@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;
}
}

16
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<OAuth2Registration> {
@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<OAuth2Registration>
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>
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()

61
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<OAuth2RegistrationInfo> {
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;
}
}

6
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);
}

145
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<OAuth2Registration> oAuth2RegistrationDataValidator;
@Override
public List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2RegistrationInfo> findOauth2ClientInfosByTenantId(TenantId tenantId) {
log.trace("Executing findOauth2ClientInfosByTenantId");
return oauth2RegistrationDao.findInfosByTenantId(tenantId.getId());
}
@Override
public List<OAuth2Registration> 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<HasId<?>> 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;
}
}

14
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<OAuth2Registration> {
List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List<SchemeType> domainSchemes, String domainName, String pkgName, PlatformType platformType);
List<OAuth2RegistrationInfo> findInfosByTenantId(UUID tenantId);
List<OAuth2Registration> findByOAuth2ParamsId(UUID oauth2ParamsId);
List<OAuth2Registration> findByTenantId(UUID tenantId);
List<OAuth2Registration> findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType);
List<OAuth2Registration> findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType);
List<OAuth2RegistrationInfo> findInfosByDomainId(UUID domainId);
List<OAuth2RegistrationInfo> findInfosByMobileAppId(UUID domainId);
String findAppSecret(UUID id, String pkgName);

295
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java

@ -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<OAuth2ClientInfo> 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<OAuth2Params> oauth2ParamsList = oauth2ParamsDao.find(TenantId.SYS_TENANT_ID);
oauth2Info.setEnabled(oauth2ParamsList.stream().anyMatch(OAuth2Params::isEnabled));
oauth2Info.setEdgeEnabled(oauth2ParamsList.stream().anyMatch(OAuth2Params::isEdgeEnabled));
List<OAuth2ParamsInfo> oauth2ParamsInfos = new ArrayList<>();
oauth2Info.setOauth2ParamsInfos(oauth2ParamsInfos);
oauth2ParamsList.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(oauth2Params -> {
List<OAuth2Registration> registrations = oauth2RegistrationDao.findByOAuth2ParamsId(oauth2Params.getId().getId());
List<OAuth2Domain> domains = oauth2DomainDao.findByOAuth2ParamsId(oauth2Params.getId().getId());
List<OAuth2Mobile> 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<OAuth2Registration> findAllRegistrations() {
log.trace("Executing findAllRegistrations");
return oauth2RegistrationDao.find(TenantId.SYS_TENANT_ID);
}
private final Consumer<OAuth2Info> 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!");
}
}
}
}
};
}

99
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<OAuth2Registration> registrations, List<OAuth2Domain> domains, List<OAuth2Mobile> 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;
}
}

36
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<Domain> {
@Override
protected void validateDataImpl(TenantId tenantId, Domain domain) {
if (StringUtils.isEmpty(domain.getName())) {
throw new DataValidationException("Domain name should be specified!");
}
}
}

43
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<MobileApp> {
@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!");
}
}
}

114
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<OAuth2Registration> {
@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!");
}
}
}
}

34
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<DomainOauth2RegistrationEntity, DomainOauth2RegistrationCompositeKey> {
List<DomainOauth2RegistrationEntity> findAllByDomainId(@Param("domainId") UUID domainId);
}

39
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<DomainEntity, UUID> {
List<DomainEntity> 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);
}

83
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<DomainEntity, Domain> implements DomainDao {
private final DomainRepository domainRepository;
private final DomainOauth2RegistrationRepository domainOauth2RegistrationRepository;
@Override
protected Class<DomainEntity> getEntityClass() {
return DomainEntity.class;
}
@Override
protected JpaRepository<DomainEntity, UUID> getRepository() {
return domainRepository;
}
@Override
public List<Domain> 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<DomainOauth2Registration> 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()));
}
}

77
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<MobileAppEntity, MobileApp> implements MobileAppDao {
private final MobileAppRepository repository;
private final MobileAppOauth2RegistrationRepository mobileOauth2ProviderRepository;
@Override
protected Class<MobileAppEntity> getEntityClass() {
return MobileAppEntity.class;
}
@Override
protected JpaRepository<MobileAppEntity, UUID> getRepository() {
return repository;
}
@Override
public List<MobileApp> findByTenantId(TenantId tenantId) {
return DaoUtil.convertDataList(repository.findByTenantId(tenantId.getId()));
}
@Override
public List<MobileAppOauth2Registration> 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()));
}
}

32
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<MobileAppOauth2RegistrationEntity, MobileAppOauth2RegistrationCompositeKey> {
List<MobileAppOauth2RegistrationEntity> findAllByMobileAppId(@Param("mobileAppId") UUID mobileAppId);
}

19
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java → 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<OAuth2ParamsEntity, UUID> {
public interface MobileAppRepository extends JpaRepository<MobileAppEntity, UUID> {
List<MobileAppEntity> findByTenantId(@Param("tenantId") UUID tenantId);
@Transactional
@Modifying
@Query("DELETE FROM MobileAppEntity r WHERE r.tenantId = :tenantId")
void deleteByTenantId(@Param("tenantId") UUID tenantId);
}

54
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java

@ -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<OAuth2DomainEntity, OAuth2Domain> implements OAuth2DomainDao {
private final OAuth2DomainRepository repository;
@Override
protected Class<OAuth2DomainEntity> getEntityClass() {
return OAuth2DomainEntity.class;
}
@Override
protected JpaRepository<OAuth2DomainEntity, UUID> getRepository() {
return repository;
}
@Override
public List<OAuth2Domain> findByOAuth2ParamsId(UUID oauth2ParamsId) {
return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId));
}
}

54
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java

@ -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<OAuth2MobileEntity, OAuth2Mobile> implements OAuth2MobileDao {
private final OAuth2MobileRepository repository;
@Override
protected Class<OAuth2MobileEntity> getEntityClass() {
return OAuth2MobileEntity.class;
}
@Override
protected JpaRepository<OAuth2MobileEntity, UUID> getRepository() {
return repository;
}
@Override
public List<OAuth2Mobile> findByOAuth2ParamsId(UUID oauth2ParamsId) {
return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId));
}
}

49
dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java

@ -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<OAuth2ParamsEntity, OAuth2Params> implements OAuth2ParamsDao {
private final OAuth2ParamsRepository repository;
@Override
protected Class<OAuth2ParamsEntity> getEntityClass() {
return OAuth2ParamsEntity.class;
}
@Override
protected JpaRepository<OAuth2ParamsEntity, UUID> getRepository() {
return repository;
}
@Override
public void deleteAll() {
repository.deleteAll();
}
}

31
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<OAuth2RegistrationE
}
@Override
public List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List<SchemeType> domainSchemes, String domainName, String pkgName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(domainSchemes, domainName, pkgName,
public List<OAuth2RegistrationInfo> findInfosByTenantId(UUID tenantId) {
return DaoUtil.convertDataList(repository.findInfosByTenantId(tenantId));
}
@Override
public List<OAuth2Registration> findByTenantId(UUID tenantId) {
return DaoUtil.convertDataList(repository.findByTenantId(tenantId));
}
@Override
public List<OAuth2Registration> findEnabledByDomainNameAndPlatformType(String domainName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findEnabledByDomainNameAndPlatformType(domainName,
platformType != null ? "%" + platformType.name() + "%" : null));
}
@Override
public List<OAuth2Registration> findByOAuth2ParamsId(UUID oauth2ParamsId) {
return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId));
public List<OAuth2Registration> findEnabledByPckNameAndPlatformType(String pkgName, PlatformType platformType) {
return DaoUtil.convertDataList(repository.findEnabledByPkgNameAndPlatformType(pkgName,
platformType != null ? "%" + platformType.name() + "%" : null));
}
@Override
public List<OAuth2RegistrationInfo> findInfosByDomainId(UUID oauth2ParamsId) {
return DaoUtil.convertDataList(repository.findInfosByDomainId(oauth2ParamsId));
}
@Override
public List<OAuth2RegistrationInfo> findInfosByMobileAppId(UUID mobileAppId) {
return DaoUtil.convertDataList(repository.findInfosByMobileAppId(mobileAppId));
}
@Override

69
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<OAuth2RegistrationEntity, UUID> {
@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<OAuth2RegistrationEntity> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(@Param("domainSchemes") List<SchemeType> domainSchemes,
@Param("domainName") String domainName,
@Param("pkgName") String pkgName,
@Param("platformFilter") String platformFilter);
List<OAuth2RegistrationEntity> 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<OAuth2RegistrationEntity> 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<OAuth2RegistrationInfoEntity> 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<OAuth2RegistrationEntity> 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<OAuth2RegistrationEntity> 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<OAuth2RegistrationInfoEntity> 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<OAuth2RegistrationInfoEntity> 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);
}

2
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -177,7 +177,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_CHAIN, EntityType.API_USAGE_STATE,
EntityType.TB_RESOURCE, EntityType.OTA_PACKAGE, EntityType.RPC, EntityType.QUEUE,
EntityType.NOTIFICATION_REQUEST, EntityType.NOTIFICATION_RULE, EntityType.NOTIFICATION_TEMPLATE,
EntityType.NOTIFICATION_TARGET, EntityType.QUEUE_STATS, EntityType.CUSTOMER
EntityType.DOMAIN, EntityType.MOBILE_APP, EntityType.OAUTH2_CLIENT
);
}

49
dao/src/main/resources/sql/schema-entities.sql

@ -558,17 +558,9 @@ CREATE TABLE IF NOT EXISTS key_dictionary
CONSTRAINT key_dictionary_id_pkey PRIMARY KEY (key)
);
CREATE TABLE IF NOT EXISTS oauth2_params (
id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY,
enabled boolean,
edge_enabled boolean,
tenant_id uuid,
created_time bigint NOT NULL
);
CREATE TABLE IF NOT EXISTS oauth2_registration (
id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
tenant_id uuid NOT NULL,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
@ -597,28 +589,41 @@ CREATE TABLE IF NOT EXISTS oauth2_registration (
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean,
CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE
custom_send_token boolean
);
CREATE TABLE IF NOT EXISTS oauth2_domain (
id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
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),
domain_scheme varchar(31),
CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme)
oauth2_enabled boolean,
edge_enabled boolean,
CONSTRAINT domain_unq_key UNIQUE (domain_name)
);
CREATE TABLE IF NOT EXISTS oauth2_mobile (
id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
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),
app_secret varchar(2048),
CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name)
oauth2_enabled boolean,
CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name)
);
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
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (

641
dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ClientServiceTest.java

@ -0,0 +1,641 @@
/**
* 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.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.dao.oauth2.OAuth2ClientService;
@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<OAuth2RegistrationInfo> firstGroup = Lists.newArrayList(
// validRegistrationInfo(),
// validRegistrationInfo(),
// validRegistrationInfo(),
// validRegistrationInfo()
// );
// List<OAuth2RegistrationInfo> secondGroup = Lists.newArrayList(
// validRegistrationInfo(),
// validRegistrationInfo()
// );
// List<OAuth2RegistrationInfo> 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<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
// .map(registrationInfo -> new OAuth2ClientInfo(
// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
// .collect(Collectors.toList());
// List<OAuth2ClientInfo> secondGroupClientInfos = secondGroup.stream()
// .map(registrationInfo -> new OAuth2ClientInfo(
// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
// .collect(Collectors.toList());
// List<OAuth2ClientInfo> thirdGroupClientInfos = thirdGroup.stream()
// .map(registrationInfo -> new OAuth2ClientInfo(
// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
// .collect(Collectors.toList());
//
// List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null);
// Assert.assertTrue(nonExistentDomainClients.isEmpty());
//
// List<OAuth2ClientInfo> 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<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null);
// Assert.assertTrue(firstDomainHttpsClients.isEmpty());
//
// List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2RegistrationInfo> 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<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
// .map(registrationInfo -> new OAuth2ClientInfo(
// registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
// .collect(Collectors.toList());
//
// List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null);
// Assert.assertEquals(5, secondDomainHttpClients.size());
//
// oAuth2Info.setEnabled(false);
// oAuth2Service.saveOAuth2Info(oAuth2Info);
//
// List<OAuth2ClientInfo> 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<OAuth2Provider> 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<OAuth2Provider> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null);
// Assert.assertEquals(3, firstDomainHttpClients.size());
// List<OAuth2ClientInfo> pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null);
// Assert.assertEquals(3, pkg1Clients.size());
// List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<PlatformType> 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();
// }
}

668
dao/src/test/java/org/thingsboard/server/dao/service/OAuth2ServiceTest.java

@ -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<OAuth2RegistrationInfo> firstGroup = Lists.newArrayList(
validRegistrationInfo(),
validRegistrationInfo(),
validRegistrationInfo(),
validRegistrationInfo()
);
List<OAuth2RegistrationInfo> secondGroup = Lists.newArrayList(
validRegistrationInfo(),
validRegistrationInfo()
);
List<OAuth2RegistrationInfo> 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<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
.map(registrationInfo -> new OAuth2ClientInfo(
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> secondGroupClientInfos = secondGroup.stream()
.map(registrationInfo -> new OAuth2ClientInfo(
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> thirdGroupClientInfos = thirdGroup.stream()
.map(registrationInfo -> new OAuth2ClientInfo(
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null, null);
Assert.assertTrue(nonExistentDomainClients.isEmpty());
List<OAuth2ClientInfo> 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<OAuth2ClientInfo> firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null, null);
Assert.assertTrue(firstDomainHttpsClients.isEmpty());
List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2RegistrationInfo> 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<OAuth2ClientInfo> firstGroupClientInfos = firstGroup.stream()
.map(registrationInfo -> new OAuth2ClientInfo(
registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null))
.collect(Collectors.toList());
List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null, null);
Assert.assertEquals(5, secondDomainHttpClients.size());
oAuth2Info.setEnabled(false);
oAuth2Service.saveOAuth2Info(oAuth2Info);
List<OAuth2ClientInfo> 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<OAuth2Registration> 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<OAuth2Registration> 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<OAuth2ClientInfo> 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<OAuth2ClientInfo> firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null, null);
Assert.assertEquals(3, firstDomainHttpClients.size());
List<OAuth2ClientInfo> pkg1Clients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1", null);
Assert.assertEquals(3, pkg1Clients.size());
List<OAuth2ClientInfo> 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<OAuth2ClientInfo> 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<PlatformType> 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();
}
}

15
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();

Loading…
Cancel
Save