Browse Source

Merge pull request #11231 from dashevchenko/oauth2

OAuth2 configuration redesign
pull/11504/head
Viacheslav Klimov 2 years ago
committed by GitHub
parent
commit
a77df2f90e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 97
      application/src/main/data/upgrade/3.7.0/schema_update.sql
  2. 15
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 18
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 15
      application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java
  5. 55
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  6. 132
      application/src/main/java/org/thingsboard/server/controller/DomainController.java
  7. 133
      application/src/main/java/org/thingsboard/server/controller/MobileAppController.java
  8. 133
      application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  10. 4
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  11. 13
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  12. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  14. 36
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java
  15. 29
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java
  16. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  17. 19
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java
  18. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java
  19. 94
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java
  20. 84
      application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java
  21. 32
      application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.java
  22. 83
      application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.java
  23. 32
      application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.java
  24. 62
      application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java
  25. 13
      application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java
  26. 18
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  27. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  28. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  29. 12
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  30. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java
  31. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  32. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  33. 8
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java
  34. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java
  35. 19
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  36. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  37. 4
      application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
  38. 6
      application/src/main/java/org/thingsboard/server/service/system/DefaultSystemInfoService.java
  39. 40
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  40. 139
      application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java
  41. 6
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  42. 73
      application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
  43. 141
      application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java
  44. 76
      application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java
  45. 19
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  46. 185
      application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java
  47. 2
      application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java
  48. 14
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  49. 4
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  50. 46
      common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java
  51. 44
      common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.java
  52. 55
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java
  53. 39
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java
  54. 5
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  55. 62
      common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java
  56. 38
      common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java
  57. 22
      common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Client.java
  58. 3
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  59. 14
      common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java
  60. 10
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  61. 14
      common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java
  62. 14
      common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java
  63. 72
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java
  64. 47
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java
  65. 32
      common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.java
  66. 82
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java
  67. 55
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java
  68. 17
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java
  69. 42
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java
  70. 2
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java
  71. 42
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java
  72. 46
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java
  73. 74
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java
  74. 2
      common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java
  75. 2
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  76. 19
      common/edge-api/src/main/proto/edge.proto
  77. 5
      common/proto/src/main/proto/queue.proto
  78. 41
      dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.java
  79. 160
      dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java
  80. 40
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java
  81. 154
      dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java
  82. 39
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  83. 78
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java
  84. 37
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientCompositeKey.java
  85. 66
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientEntity.java
  86. 48
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java
  87. 37
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientCompositeKey.java
  88. 65
      dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java
  89. 67
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java
  90. 61
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientInfoEntity.java
  91. 76
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java
  92. 70
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java
  93. 36
      dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java
  94. 49
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java
  95. 159
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java
  96. 34
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java
  97. 295
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java
  98. 107
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java
  99. 6
      dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java
  100. 80
      dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java

97
application/src/main/data/upgrade/3.7.0/schema_update.sql

@ -99,3 +99,100 @@ ALTER TABLE widgets_bundle ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1;
ALTER TABLE tenant ADD COLUMN IF NOT EXISTS version BIGINT DEFAULT 1;
-- ENTITIES VERSIONING UPDATE END
-- OAUTH2 UPDATE START
ALTER TABLE IF EXISTS oauth2_mobile RENAME TO mobile_app;
ALTER TABLE IF EXISTS oauth2_domain RENAME TO domain;
ALTER TABLE IF EXISTS oauth2_registration RENAME TO oauth2_client;
ALTER TABLE domain ADD COLUMN IF NOT EXISTS oauth2_enabled boolean,
ADD COLUMN IF NOT EXISTS edge_enabled boolean,
ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080',
DROP COLUMN IF EXISTS domain_scheme;
-- rename column domain_name to name
DO
$$
BEGIN
IF EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='domain' and column_name='domain_name') THEN
ALTER TABLE domain RENAME COLUMN domain_name TO name;
END IF;
END
$$;
-- delete duplicated domains
DELETE FROM domain d1 USING (
SELECT MIN(ctid) as ctid, name
FROM domain
GROUP BY name HAVING COUNT(*) > 1
) d2 WHERE d1.name = d2.name AND d1.ctid <> d2.ctid;
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';
-- delete duplicated apps
DELETE FROM mobile_app m1 USING (
SELECT MIN(ctid) as ctid, pkg_name
FROM mobile_app
GROUP BY pkg_name HAVING COUNT(*) > 1
) m2 WHERE m1.pkg_name = m2.pkg_name AND m1.ctid <> m2.ctid;
ALTER TABLE oauth2_client ADD COLUMN IF NOT EXISTS tenant_id uuid DEFAULT '13814000-1dd2-11b2-8080-808080808080',
ADD COLUMN IF NOT EXISTS title varchar(100);
UPDATE oauth2_client SET title = additional_info::jsonb->>'providerName' WHERE additional_info IS NOT NULL;
CREATE TABLE IF NOT EXISTS domain_oauth2_client (
domain_id uuid NOT NULL,
oauth2_client_id uuid NOT NULL,
CONSTRAINT fk_domain FOREIGN KEY (domain_id) REFERENCES domain(id) ON DELETE CASCADE,
CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS mobile_app_oauth2_client (
mobile_app_id uuid NOT NULL,
oauth2_client_id uuid NOT NULL,
CONSTRAINT fk_domain FOREIGN KEY (mobile_app_id) REFERENCES mobile_app(id) ON DELETE CASCADE,
CONSTRAINT fk_oauth2_client FOREIGN KEY (oauth2_client_id) REFERENCES oauth2_client(id) ON DELETE CASCADE
);
-- migrate oauth2_params table
DO
$$
BEGIN
IF EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'oauth2_params') THEN
UPDATE domain SET oauth2_enabled = p.enabled,
edge_enabled = p.edge_enabled
FROM oauth2_params p WHERE p.id = domain.oauth2_params_id;
UPDATE mobile_app SET oauth2_enabled = p.enabled
FROM oauth2_params p WHERE p.id = mobile_app.oauth2_params_id;
INSERT INTO domain_oauth2_client(domain_id, oauth2_client_id)
(SELECT d.id, r.id FROM domain d LEFT JOIN oauth2_client r on d.oauth2_params_id = r.oauth2_params_id
WHERE r.platforms IS NULL OR r.platforms IN ('','WEB'));
INSERT INTO mobile_app_oauth2_client(mobile_app_id, oauth2_client_id)
(SELECT m.id, r.id FROM mobile_app m LEFT JOIN oauth2_client r on m.oauth2_params_id = r.oauth2_params_id
WHERE r.platforms IS NULL OR r.platforms IN ('','ANDROID','IOS'));
ALTER TABLE mobile_app RENAME CONSTRAINT oauth2_mobile_pkey TO mobile_app_pkey;
ALTER TABLE domain RENAME CONSTRAINT oauth2_domain_pkey TO domain_pkey;
ALTER TABLE oauth2_client RENAME CONSTRAINT oauth2_registration_pkey TO oauth2_client_pkey;
ALTER TABLE domain DROP COLUMN oauth2_params_id;
ALTER TABLE mobile_app DROP COLUMN oauth2_params_id;
ALTER TABLE oauth2_client DROP COLUMN oauth2_params_id;
ALTER TABLE mobile_app ADD CONSTRAINT mobile_app_unq_key UNIQUE (pkg_name);
ALTER TABLE domain ADD CONSTRAINT domain_unq_key UNIQUE (name);
DROP TABLE IF EXISTS oauth2_params;
-- drop deprecated tables
DROP TABLE IF EXISTS oauth2_client_registration_info;
DROP TABLE IF EXISTS oauth2_client_registration;
END IF;
END
$$;
-- OAUTH2 UPDATE END

15
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -68,17 +68,20 @@ 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.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.mobile.MobileAppService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor;
import org.thingsboard.server.dao.notification.NotificationRequestService;
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.OAuth2ClientService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
@ -370,6 +373,18 @@ public class ActorSystemContext {
@Getter
private NotificationRuleService notificationRuleService;
@Autowired
@Getter
private OAuth2ClientService oAuth2ClientService;
@Autowired
@Getter
private DomainService domainService;
@Autowired
@Getter
private MobileAppService mobileAppService;
@Autowired
@Getter
private SlackService slackService;

18
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -83,17 +83,20 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
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.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.mobile.MobileAppService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.notification.NotificationRequestService;
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.OAuth2ClientService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
@ -825,6 +828,21 @@ class DefaultTbContext implements TbContext {
return mainCtx.getNotificationRuleService();
}
@Override
public OAuth2ClientService getOAuth2ClientService() {
return mainCtx.getOAuth2ClientService();
}
@Override
public DomainService getDomainService() {
return mainCtx.getDomainService();
}
@Override
public MobileAppService getMobileAppService() {
return mainCtx.getMobileAppService();
}
@Override
public SlackService getSlackService() {
return mainCtx.getSlackService();

15
application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java

@ -37,8 +37,9 @@ import org.springframework.util.CollectionUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
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 +71,7 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private OAuth2Service oAuth2Service;
private OAuth2ClientService oAuth2ClientService;
@Autowired
private OAuth2AppTokenFactory oAuth2AppTokenFactory;
@ -115,14 +116,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza
return request.getParameter("appToken");
}
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage, String appToken) {
if (registrationId == null) {
private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String oauth2ClientId, String redirectUriAction, String appPackage, String appToken) {
if (oauth2ClientId == null) {
return null;
}
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(oauth2ClientId);
if (clientRegistration == null) {
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
throw new IllegalArgumentException("Invalid Client Registration with Id: " + oauth2ClientId);
}
Map<String, Object> attributes = new HashMap<>();
@ -131,7 +132,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(new OAuth2ClientId(UUID.fromString(oauth2ClientId)), appPackage);
if (StringUtils.isEmpty(appSecret)) {
throw new IllegalArgumentException("Invalid package: " + appPackage + ". No application secret found for Client Registration with given application package.");
}

55
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -63,6 +63,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.EntityVersionMismatchException;
@ -76,11 +77,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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RpcId;
@ -93,6 +97,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.OAuth2Client;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -119,13 +125,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;
@ -162,6 +170,9 @@ import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@ -236,7 +247,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;
@ -612,6 +629,15 @@ public abstract class BaseController {
case QUEUE:
checkQueueId(new QueueId(entityId.getId()), operation);
return;
case OAUTH2_CLIENT:
checkOauth2ClientId(new OAuth2ClientId(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);
}
@ -788,6 +814,18 @@ public abstract class BaseController {
return queue;
}
OAuth2Client checkOauth2ClientId(OAuth2ClientId oAuth2ClientId, Operation operation) throws ThingsboardException {
return checkEntityId(oAuth2ClientId, 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);
}
@ -874,4 +912,17 @@ public abstract class BaseController {
}
}
protected List<OAuth2ClientId> getOAuth2ClientIds(UUID[] ids) throws ThingsboardException {
if (ids == null) {
return Collections.emptyList();
}
List<OAuth2ClientId> oAuth2ClientIds = new ArrayList<>();
for (UUID id : ids) {
OAuth2ClientId oauth2ClientId = new OAuth2ClientId(id);
checkOauth2ClientId(oauth2ClientId, Operation.READ);
oAuth2ClientIds.add(oauth2ClientId);
}
return oAuth2ClientIds;
}
}

132
application/src/main/java/org/thingsboard/server/controller/DomainController.java

@ -0,0 +1,132 @@
/**
* 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 jakarta.validation.Valid;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.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.OAuth2ClientId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.domain.TbDomainService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.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" + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PostMapping(value = "/domain")
public Domain saveDomain(
@Parameter(description = "A JSON value representing the Domain.", required = true)
@RequestBody @Valid Domain domain,
@Parameter(description = "A list of oauth2 client registration ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception {
domain.setTenantId(getTenantId());
checkEntity(domain.getId(), domain, Resource.DOMAIN);
return tbDomainService.save(domain, getOAuth2ClientIds(ids), getCurrentUser());
}
@ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)",
notes = "Update oauth2 clients for the specified domain. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PutMapping(value = "/domain/{id}/oauth2Clients")
public void updateOauth2Clients(@PathVariable UUID id,
@RequestBody UUID[] clientIds) throws ThingsboardException {
DomainId domainId = new DomainId(id);
Domain domain = checkDomainId(domainId, Operation.WRITE);
List<OAuth2ClientId> oAuth2ClientIds = getOAuth2ClientIds(clientIds);
tbDomainService.updateOauth2Clients(domain, oAuth2ClientIds, getCurrentUser());
}
@ApiOperation(value = "Get Domain infos (getTenantDomainInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@GetMapping(value = "/domain/infos")
public PageData<DomainInfo> getTenantDomainInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@Parameter(description = "Case-insensitive 'substring' filter based on domain's name")
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.DOMAIN, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return domainService.findDomainInfosByTenantId(getTenantId(), pageLink);
}
@ApiOperation(value = "Get Domain info by Id (getDomainInfoById)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@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 domain Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@DeleteMapping(value = "/domain/{id}")
public void deleteDomain(@PathVariable UUID id) throws Exception {
DomainId domainId = new DomainId(id);
Domain domain = checkDomainId(domainId, Operation.DELETE);
tbDomainService.delete(domain, getCurrentUser());
}
}

133
application/src/main/java/org/thingsboard/server/controller/MobileAppController.java

@ -0,0 +1,133 @@
/**
* 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 jakarta.validation.Valid;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.mobile.TbMobileAppService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.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 mobile app. " +
"Referencing non-existing Mobile App Id will cause 'Not Found' error." +
"\n\nMobile app package name is unique for entire platform setup.\n\n" + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PostMapping(value = "/mobileApp")
public MobileApp saveMobileApp(
@Parameter(description = "A JSON value representing the Mobile Application.", required = true)
@RequestBody @Valid MobileApp mobileApp,
@Parameter(description = "A list of entity oauth2 client ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")))
@RequestParam(name = "oauth2ClientIds", required = false) UUID[] ids) throws Exception {
mobileApp.setTenantId(getTenantId());
checkEntity(mobileApp.getId(), mobileApp, Resource.MOBILE_APP);
return tbMobileAppService.save(mobileApp, getOAuth2ClientIds(ids), getCurrentUser());
}
@ApiOperation(value = "Update oauth2 clients (updateOauth2Clients)",
notes = "Update oauth2 clients of the specified mobile app. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PutMapping(value = "/mobileApp/{id}/oauth2Clients")
public void updateOauth2Clients(@PathVariable UUID id,
@RequestBody UUID[] clientIds) throws ThingsboardException {
MobileAppId mobileAppId = new MobileAppId(id);
MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.WRITE);
List<OAuth2ClientId> oAuth2ClientIds = getOAuth2ClientIds(clientIds);
tbMobileAppService.updateOauth2Clients(mobileApp, oAuth2ClientIds, getCurrentUser());
}
@ApiOperation(value = "Get mobile app infos (getTenantMobileAppInfos)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@GetMapping(value = "/mobileApp/infos")
public PageData<MobileAppInfo> getTenantMobileAppInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@Parameter(description = "Case-insensitive 'substring' filter based on app's name")
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.MOBILE_APP, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return mobileAppService.findMobileAppInfosByTenantId(getTenantId(), pageLink);
}
@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 mobile app Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@DeleteMapping(value = "/mobileApp/{id}")
public void deleteMobileApp(@PathVariable UUID id) throws Exception {
MobileAppId mobileAppId = new MobileAppId(id);
MobileApp mobileApp = checkMobileAppId(mobileAppId, Operation.DELETE);
tbMobileAppService.delete(mobileApp, getCurrentUser());
}
}

133
application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java

@ -16,60 +16,75 @@
package org.thingsboard.server.controller;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.config.annotations.ApiOperation;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.queue.util.TbCoreComponent;
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.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
@RestController
@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
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")
@RequestParam(required = false) String pkgName,
@Parameter(description = "Platform type to search OAuth2 clients for which " +
"the usage with this platform type is allowed in the settings. " +
"If platform type is not one of allowable values - it will just be ignored",
schema = @Schema(allowableValues = {"WEB", "ANDROID", "IOS"}))
@RequestParam(required = false) String platform) throws ThingsboardException {
@PostMapping(value = "/noauth/oauth2/client")
public List<OAuth2ClientLoginInfo> getOAuth2Clients(HttpServletRequest request,
@Parameter(description = "Mobile application package name, to find OAuth2 clients " +
"where there is configured mobile application with such package name")
@RequestParam(required = false) String pkgName,
@Parameter(description = "Platform type to search OAuth2 clients for which " +
"the usage with this platform type is allowed in the settings. " +
"If platform type is not one of allowable values - it will just be ignored",
schema = @Schema(allowableValues = {"WEB", "ANDROID", "IOS"}))
@RequestParam(required = false) String platform) {
if (log.isDebugEnabled()) {
log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort());
Enumeration<String> headerNames = request.getHeaderNames();
@ -80,31 +95,70 @@ public class OAuth2Controller extends BaseController {
}
PlatformType platformType = null;
if (StringUtils.isNotEmpty(platform)) {
try {
platformType = PlatformType.valueOf(platform);
} catch (Exception e) {
}
platformType = PlatformType.valueOf(platform);
}
if (StringUtils.isNotEmpty(pkgName)) {
return oAuth2ClientService.findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(pkgName, platformType);
} else {
return oAuth2ClientService.findOAuth2ClientLoginInfosByDomainName(MiscUtils.getDomainNameAndPort(request));
}
return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName, platformType);
}
@ApiOperation(value = "Get current OAuth2 settings (getCurrentOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@ApiOperation(value = "Save OAuth2 Client (saveOAuth2Client)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@PostMapping(value = "/oauth2/client")
public OAuth2Client saveOAuth2Client(@RequestBody @Valid OAuth2Client oAuth2Client) throws Exception {
TenantId tenantId = getTenantId();
oAuth2Client.setTenantId(tenantId);
checkEntity(oAuth2Client.getId(), oAuth2Client, Resource.OAUTH2_CLIENT);
return tbOauth2ClientService.save(oAuth2Client, getCurrentUser());
}
@ApiOperation(value = "Get OAuth2 Client infos (findTenantOAuth2ClientInfos)", 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();
@GetMapping(value = "/oauth2/client/infos")
public PageData<OAuth2ClientInfo> findTenantOAuth2ClientInfos(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@Parameter(description = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@Parameter(description = "Case-insensitive 'substring' filter based on client's title")
@RequestParam(required = false) String textSearch,
@Parameter(description = SORT_PROPERTY_DESCRIPTION)
@RequestParam(required = false) String sortProperty,
@Parameter(description = SORT_ORDER_DESCRIPTION)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CLIENT, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return oAuth2ClientService.findOAuth2ClientInfosByTenantId(getTenantId(), pageLink);
}
@ApiOperation(value = "Save OAuth2 settings (saveOAuth2Info)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@ApiOperation(value = "Get OAuth2 Client infos By Ids (findTenantOAuth2ClientInfosByIds)",
notes = "Fetch OAuth2 Client info objects based on the provided ids. ")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/oauth2/config", method = RequestMethod.POST)
@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();
@GetMapping(value = "/oauth2/client/infos", params = {"clientIds"})
public List<OAuth2ClientInfo> findTenantOAuth2ClientInfosByIds(
@Parameter(description = "A list of oauth2 ids, separated by comma ','", array = @ArraySchema(schema = @Schema(type = "string")), required = true)
@RequestParam("clientIds") UUID[] clientIds) throws ThingsboardException {
List<OAuth2ClientId> oAuth2ClientIds = getOAuth2ClientIds(clientIds);
return oAuth2ClientService.findOAuth2ClientInfosByIds(getTenantId(), oAuth2ClientIds);
}
@ApiOperation(value = "Get OAuth2 Client by id (getOAuth2ClientById)", notes = SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@GetMapping(value = "/oauth2/client/{id}")
public OAuth2Client getOAuth2ClientById(@PathVariable UUID id) throws ThingsboardException {
OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(id);
return checkEntityId(oAuth2ClientId, oAuth2ClientService::findOAuth2ClientById, Operation.READ);
}
@ApiOperation(value = "Delete oauth2 client (deleteOauth2Client)",
notes = "Deletes the oauth2 client. Referencing non-existing oauth2 client Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@DeleteMapping(value = "/oauth2/client/{id}")
public void deleteOauth2Client(@PathVariable UUID id) throws Exception {
OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(id);
OAuth2Client oAuth2Client = checkOauth2ClientId(oAuth2ClientId, Operation.DELETE);
tbOauth2ClientService.delete(oAuth2Client, getCurrentUser());
}
@ApiOperation(value = "Get OAuth2 log in processing URL (getLoginProcessingUrl)", notes = "Returns the URL enclosed in " +
@ -112,10 +166,9 @@ public class OAuth2Controller extends BaseController {
"further log in processing. This URL may be configured as 'security.oauth2.loginProcessingUrl' property in yml configuration file, or " +
"as 'SECURITY_OAUTH2_LOGIN_PROCESSING_URL' env variable. By default it is '/login/oauth2/code/'" + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/oauth2/loginProcessingUrl", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/oauth2/loginProcessingUrl")
public String getLoginProcessingUrl() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ);
accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CLIENT, Operation.READ);
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 DOMAIN, OAUTH2_CLIENT -> oAuth2EdgeProcessor.processEntityNotification(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

@ -29,13 +29,13 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
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.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
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.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 DomainService domainService;
@Autowired
private RateLimitService rateLimitService;

13
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -32,10 +32,10 @@ import org.thingsboard.server.common.data.alarm.AlarmApiCallResult;
import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.alarm.EntityAlarm;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.domain.Domain;
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.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -204,11 +204,12 @@ public class EdgeEventSourcingListener {
return !event.getCreated();
case API_USAGE_STATE, EDGE:
return false;
case DOMAIN:
if (entity instanceof Domain domain) {
return domain.isPropagateToEdge();
}
}
}
if (entity instanceof OAuth2Info oAuth2Info) {
return oAuth2Info.isEdgeEnabled();
}
// Default: If the entity doesn't match any of the conditions, consider it as valid.
return true;
}
@ -233,8 +234,6 @@ public class EdgeEventSourcingListener {
private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return EdgeEventType.ALARM_COMMENT;
} else if (entity instanceof OAuth2Info) {
return EdgeEventType.OAUTH2;
}
return null;
}
@ -242,8 +241,6 @@ public class EdgeEventSourcingListener {
private String getBodyMsgForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return JacksonUtil.toString(entity);
} else if (entity instanceof OAuth2Info) {
return JacksonUtil.toString(entity);
}
return null;
}

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -698,8 +698,10 @@ 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().convertOAuth2ClientEventToDownlink(edgeEvent, this.edgeVersion);
case DOMAIN:
return ctx.getOAuth2EdgeProcessor().convertOAuth2DomainEventToDownlink(edgeEvent, this.edgeVersion);
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

@ -88,7 +88,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.getDomainService()));
}
}

36
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java

@ -17,16 +17,44 @@ 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.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.common.data.domain.DomainInfo;
import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@TbCoreComponent
public class OAuth2MsgConstructor {
public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Info oAuth2Info) {
return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Info)).build();
public OAuth2ClientUpdateMsg constructOAuth2ClientUpdateMsg(UpdateMsgType msgType, OAuth2Client oAuth2Client) {
return OAuth2ClientUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(oAuth2Client))
.setIdMSB(oAuth2Client.getId().getId().getMostSignificantBits())
.setIdLSB(oAuth2Client.getId().getId().getLeastSignificantBits()).build();
}
public OAuth2ClientUpdateMsg constructOAuth2ClientDeleteMsg(OAuth2ClientId oAuth2ClientId) {
return OAuth2ClientUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(oAuth2ClientId.getId().getMostSignificantBits())
.setIdLSB(oAuth2ClientId.getId().getLeastSignificantBits()).build();
}
public OAuth2DomainUpdateMsg constructOAuth2DomainUpdateMsg(UpdateMsgType msgType, DomainInfo domainInfo) {
return OAuth2DomainUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(domainInfo))
.setIdMSB(domainInfo.getId().getId().getMostSignificantBits())
.setIdLSB(domainInfo.getId().getId().getLeastSignificantBits()).build();
}
public OAuth2DomainUpdateMsg constructOAuth2DomainDeleteMsg(DomainId domainId) {
return OAuth2DomainUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(domainId.getId().getMostSignificantBits())
.setIdLSB(domainId.getId().getLeastSignificantBits())
.build();
}
}

29
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java

@ -17,43 +17,32 @@ package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.domain.DomainInfo;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.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.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import java.util.ArrayList;
import java.util.List;
import org.thingsboard.server.dao.domain.DomainService;
@AllArgsConstructor
@Slf4j
public class OAuth2EdgeEventFetcher implements EdgeEventFetcher {
public class OAuth2EdgeEventFetcher extends BasePageableEdgeEventFetcher<DomainInfo> {
private final OAuth2Service oAuth2Service;
private final DomainService domainService;
@Override
public PageLink getPageLink(int pageSize) {
return null;
PageData<DomainInfo> fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return domainService.findDomainInfosByTenantId(TenantId.SYS_TENANT_ID, pageLink);
}
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info();
if (!oAuth2Info.isEdgeEnabled()) {
return new PageData<>();
}
List<EdgeEvent> result = new ArrayList<>();
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2,
EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info)));
// returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DomainInfo domainInfo) {
return EdgeUtils.constructEdgeEvent(TenantId.SYS_TENANT_ID, edge.getId(), EdgeEventType.DOMAIN,
EdgeEventActionType.ADDED, domainInfo.getId(), null);
}
}

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -69,6 +69,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
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.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
@ -76,7 +77,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 +241,10 @@ public abstract class BaseEdgeProcessor {
protected NotificationTemplateService notificationTemplateService;
@Autowired
protected OAuth2Service oAuth2Service;
protected OAuth2ClientService oAuth2ClientService;
@Autowired
protected DomainService domainService;
@Autowired
@Lazy

19
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/DeviceEdgeProcessor.java

@ -216,6 +216,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
var msgConstructor = (DeviceMsgConstructor) deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion);
switch (edgeEvent.getAction()) {
case ADDED:
case UPDATED:
@ -233,24 +234,20 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
.addDeviceUpdateMsg(deviceUpdateMsg);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId);
if (deviceCredentials != null) {
DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceCredentialsUpdatedMsg(deviceCredentials);
DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials);
builder.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsg).build();
}
if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId());
deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion);
builder.addDeviceProfileUpdateMsg(((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
.constructDeviceProfileUpdatedMsg(msgType, deviceProfile));
builder.addDeviceProfileUpdateMsg(msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile));
}
downlinkMsg = builder.build();
}
break;
case DELETED:
case UNASSIGNED_FROM_EDGE:
DeviceUpdateMsg deviceUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceDeleteMsg(deviceId);
DeviceUpdateMsg deviceUpdateMsg = msgConstructor.constructDeviceDeleteMsg(deviceId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceUpdateMsg(deviceUpdateMsg)
@ -259,8 +256,7 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
case CREDENTIALS_UPDATED:
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId);
if (deviceCredentials != null) {
DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceCredentialsUpdatedMsg(deviceCredentials);
DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = msgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsg)
@ -270,11 +266,10 @@ public abstract class DeviceEdgeProcessor extends BaseDeviceProcessor implements
case RPC_CALL:
return DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceRpcCallMsg(((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion))
.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody()))
.addDeviceRpcCallMsg(msgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody()))
.build();
}
return downlinkMsg;
}
}

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/device/profile/DeviceProfileEdgeProcessor.java

@ -96,14 +96,14 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces
public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent, EdgeId edgeId, EdgeVersion edgeVersion) {
DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
var msgConstructor = (DeviceMsgConstructor) deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion);
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {
DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId);
if (deviceProfile != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
deviceProfile = checkIfDeviceProfileDefaultFieldsAssignedToEdge(edgeEvent.getTenantId(), edgeId, deviceProfile, edgeVersion);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceProfileUpdatedMsg(msgType, deviceProfile);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceProfileUpdateMsg(deviceProfileUpdateMsg)
@ -111,8 +111,7 @@ public abstract class DeviceProfileEdgeProcessor extends BaseDeviceProfileProces
}
}
case DELETED -> {
DeviceProfileUpdateMsg deviceProfileUpdateMsg = ((DeviceMsgConstructor)
deviceMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructDeviceProfileDeleteMsg(deviceProfileId);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = msgConstructor.constructDeviceProfileDeleteMsg(deviceProfileId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addDeviceProfileUpdateMsg(deviceProfileUpdateMsg)

94
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java

@ -15,49 +15,95 @@
*/
package org.thingsboard.server.service.edge.rpc.processor.oauth2;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.domain.DomainInfo;
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.id.DomainId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils;
@Slf4j
@Component
@TbCoreComponent
public class OAuth2EdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertOAuth2EventToDownlink(EdgeEvent edgeEvent) {
public DownlinkMsg convertOAuth2DomainEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_7_1)) {
return null;
}
DomainId domainId = new DomainId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
OAuth2Info oAuth2Info = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Info.class);
if (oAuth2Info != null && oAuth2Info.isEdgeEnabled()) {
OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Info);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2UpdateMsg(oAuth2UpdateMsg)
.build();
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {
DomainInfo domainInfo = domainService.findDomainInfoById(edgeEvent.getTenantId(), domainId);
if (domainInfo != null && domainInfo.isPropagateToEdge()) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = oAuth2MsgConstructor.constructOAuth2DomainUpdateMsg(msgType, domainInfo);
DownlinkMsg.Builder builder = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg);
domainInfo.getOauth2ClientInfos().forEach(clientInfo -> {
OAuth2Client oauth2Client = oAuth2ClientService.findOAuth2ClientById(edgeEvent.getTenantId(), clientInfo.getId());
OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oauth2Client);
builder.addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg);
});
downlinkMsg = builder.build();
}
}
case DELETED -> {
OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = oAuth2MsgConstructor.constructOAuth2DomainDeleteMsg(domainId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2DomainUpdateMsg(oAuth2DomainUpdateMsg)
.build();
}
}
return downlinkMsg;
}
public ListenableFuture<Void> processOAuth2Notification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
OAuth2Info oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Info.class);
if (oAuth2Info == null) {
return Futures.immediateFuture(null);
public DownlinkMsg convertOAuth2ClientEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_7_1)) {
return null;
}
OAuth2ClientId oAuth2ClientId = new OAuth2ClientId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (edgeEvent.getAction()) {
case ADDED, UPDATED -> {
boolean isPropagateToEdge = oAuth2ClientService.isPropagateOAuth2ClientToEdge(edgeEvent.getTenantId(), oAuth2ClientId);
if (!isPropagateToEdge) {
return null;
}
OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(edgeEvent.getTenantId(), oAuth2ClientId);
if (oAuth2Client != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = oAuth2MsgConstructor.constructOAuth2ClientUpdateMsg(msgType, oAuth2Client);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2ClientUpdateMsg(oAuth2ClientUpdateMsg)
.build();
}
}
case DELETED -> {
OAuth2ClientUpdateMsg oAuth2ClientDeleteMsg = oAuth2MsgConstructor.constructOAuth2ClientDeleteMsg(oAuth2ClientId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2ClientUpdateMsg(oAuth2ClientDeleteMsg)
.build();
}
}
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
return processActionForAllEdges(tenantId, type, actionType, null, JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()), null);
return downlinkMsg;
}
}

84
application/src/main/java/org/thingsboard/server/service/entitiy/domain/DefaultTbDomainService.java

@ -0,0 +1,84 @@
/**
* 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.apache.commons.collections4.CollectionUtils;
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.domain.Domain;
import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.domain.DomainService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.List;
@Service
@AllArgsConstructor
public class DefaultTbDomainService extends AbstractTbEntityService implements TbDomainService {
private final DomainService domainService;
@Override
public Domain save(Domain domain, List<OAuth2ClientId> 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));
if (CollectionUtils.isNotEmpty(oAuth2Clients)) {
domainService.updateOauth2Clients(domain.getTenantId(), savedDomain.getId(), oAuth2Clients);
}
logEntityActionService.logEntityAction(tenantId, savedDomain.getId(), savedDomain, actionType, user, oAuth2Clients);
return savedDomain;
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.DOMAIN), domain, actionType, user, e, oAuth2Clients);
throw e;
}
}
@Override
public void updateOauth2Clients(Domain domain, List<OAuth2ClientId> oAuth2ClientIds, User user) {
ActionType actionType = ActionType.UPDATED;
TenantId tenantId = domain.getTenantId();
DomainId domainId = domain.getId();
try {
domainService.updateOauth2Clients(tenantId, domainId, oAuth2ClientIds);
logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, oAuth2ClientIds);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, e, oAuth2ClientIds);
throw e;
}
}
@Override
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);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, domainId, domain, actionType, user, e);
throw e;
}
}
}

32
application/src/main/java/org/thingsboard/server/service/entitiy/domain/TbDomainService.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.service.entitiy.domain;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import java.util.List;
public interface TbDomainService {
Domain save(Domain domain, List<OAuth2ClientId> oAuth2Clients, User user) throws Exception;
void updateOauth2Clients(Domain domain, List<OAuth2ClientId> oAuth2ClientIds, User user);
void delete(Domain domain, User user);
}

83
application/src/main/java/org/thingsboard/server/service/entitiy/mobile/DefaultTbMobileAppService.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.service.entitiy.mobile;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
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.MobileAppId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.dao.mobile.MobileAppService;
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<OAuth2ClientId> 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));
if (CollectionUtils.isNotEmpty(oauth2Clients)) {
mobileAppService.updateOauth2Clients(tenantId, savedMobileApp.getId(), oauth2Clients);
}
logEntityActionService.logEntityAction(tenantId, savedMobileApp.getId(), savedMobileApp, actionType, user);
return savedMobileApp;
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.MOBILE_APP), mobileApp, actionType, user, e);
throw e;
}
}
@Override
public void updateOauth2Clients(MobileApp mobileApp, List<OAuth2ClientId> oAuth2ClientIds, User user) {
ActionType actionType = ActionType.UPDATED;
TenantId tenantId = mobileApp.getTenantId();
MobileAppId mobileAppId = mobileApp.getId();
try {
mobileAppService.updateOauth2Clients(tenantId, mobileAppId, oAuth2ClientIds);
logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, oAuth2ClientIds);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, e, oAuth2ClientIds);
throw e;
}
}
@Override
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);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, mobileAppId, mobileApp, actionType, user, e);
throw e;
}
}
}

32
application/src/main/java/org/thingsboard/server/service/entitiy/mobile/TbMobileAppService.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.service.entitiy.mobile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import java.util.List;
public interface TbMobileAppService {
MobileApp save(MobileApp mobileApp, List<OAuth2ClientId> oauth2Clients, User user) throws Exception;
void updateOauth2Clients(MobileApp mobileApp, List<OAuth2ClientId> oAuth2ClientIds, User user);
void delete(MobileApp mobileApp, User user);
}

62
application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/DefaultTbOauth2ClientService.java

@ -0,0 +1,62 @@
/**
* 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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
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 OAuth2Client save(OAuth2Client oAuth2Client, User user) throws Exception {
ActionType actionType = oAuth2Client.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = oAuth2Client.getTenantId();
try {
OAuth2Client savedClient = checkNotNull(oAuth2ClientService.saveOAuth2Client(tenantId, oAuth2Client));
logEntityActionService.logEntityAction(tenantId, savedClient.getId(), savedClient, actionType, user);
return savedClient;
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, emptyId(EntityType.OAUTH2_CLIENT), oAuth2Client, actionType, user, e);
throw e;
}
}
@Override
public void delete(OAuth2Client oAuth2Client, User user) {
ActionType actionType = ActionType.DELETED;
TenantId tenantId = oAuth2Client.getTenantId();
OAuth2ClientId oAuth2ClientId = oAuth2Client.getId();
try {
oAuth2ClientService.deleteOAuth2ClientById(tenantId, oAuth2ClientId);
logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user);
} catch (Exception e) {
logEntityActionService.logEntityAction(tenantId, oAuth2ClientId, oAuth2Client, actionType, user, e);
throw e;
}
}
}

13
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java → application/src/main/java/org/thingsboard/server/service/entitiy/oauth2client/TbOauth2ClientService.java

@ -13,16 +13,15 @@
* 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.oauth2client;
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.oauth2.OAuth2Client;
import java.util.List;
import java.util.UUID;
public interface TbOauth2ClientService {
public interface OAuth2MobileDao extends Dao<OAuth2Mobile> {
OAuth2Client save(OAuth2Client oAuth2Client, User user) throws Exception;
List<OAuth2Mobile> findByOAuth2ParamsId(UUID oauth2ParamsId);
void delete(OAuth2Client oAuth2Client, User user);
}

18
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 mobileAppDao;
private final NotificationSettingsService notificationSettingsService;
private final NotificationTargetService notificationTargetService;
@ -308,17 +308,17 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
jwtSettingsService.saveJwtSettings(jwtSettings);
}
List<OAuth2Mobile> mobiles = oAuth2MobileDao.find(TenantId.SYS_TENANT_ID);
List<MobileApp> mobiles = mobileAppDao.findByTenantId(TenantId.SYS_TENANT_ID, new PageLink(Integer.MAX_VALUE,0)).getData();
if (CollectionUtils.isNotEmpty(mobiles)) {
mobiles.stream()
.filter(config -> !validateKeyLength(config.getAppSecret()))
.forEach(config -> {
.filter(mobileApp -> !validateKeyLength(mobileApp.getAppSecret()))
.forEach(mobileApp -> {
log.warn("WARNING: The App secret is shorter than 512 bits, which is a security risk. " +
"A new Application Secret has been added automatically for Mobile Application [{}]. " +
"You can change the Application Secret using the Web UI: " +
"Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", config.getPkgName());
config.setAppSecret(generateRandomKey());
oAuth2MobileDao.save(TenantId.SYS_TENANT_ID, config);
"Navigate to \"Security settings -> OAuth2 -> Mobile applications\" while logged in as a System Administrator.", mobileApp.getPkgName());
mobileApp.setAppSecret(generateRandomKey());
mobileAppDao.save(TenantId.SYS_TENANT_ID, mobileApp);
});
}
}

8
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -614,13 +614,11 @@ public class DefaultTbClusterService implements TbClusterService {
private void pushDeviceUpdateMessage(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) {
log.trace("{} Going to send edge update notification for device actor, device id {}, edge id {}", tenantId, entityId, edgeId);
switch (action) {
case ASSIGNED_TO_EDGE:
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), edgeId), null);
break;
case UNASSIGNED_FROM_EDGE:
case ASSIGNED_TO_EDGE -> pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), edgeId), null);
case UNASSIGNED_FROM_EDGE -> {
EdgeId relatedEdgeId = findRelatedEdgeIdIfAny(tenantId, entityId);
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), relatedEdgeId), null);
break;
}
}
}

8
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -108,7 +108,6 @@ import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpd
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -701,13 +700,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback));
}
private void forwardToAppActor(UUID id, Optional<TbActorMsg> actorMsg, TbCallback callback) {
if (actorMsg.isPresent()) {
forwardToAppActor(id, actorMsg.get());
}
callback.onSuccess();
}
private void forwardToAppActor(UUID id, TbActorMsg actorMsg) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
actorContext.tell(actorMsg);

12
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -97,9 +97,9 @@ public abstract class AbstractOAuth2ClientMapper {
private final Lock userCreationLock = new ReentrantLock();
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) {
protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Client oAuth2Client) {
OAuth2MapperConfig config = registration.getMapperConfig();
OAuth2MapperConfig config = oAuth2Client.getMapperConfig();
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail());
@ -143,9 +143,9 @@ public abstract class AbstractOAuth2ClientMapper {
}
}
if (registration.getAdditionalInfo() != null &&
registration.getAdditionalInfo().has("providerName")) {
additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText());
if (oAuth2Client.getAdditionalInfo() != null &&
oAuth2Client.getAdditionalInfo().has("providerName")) {
additionalInfo.put("authProviderName", oAuth2Client.getAdditionalInfo().get("providerName").asText());
}
user.setAdditionalInfo(additionalInfo);

8
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java

@ -25,7 +25,7 @@ import org.springframework.util.MultiValueMap;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -45,13 +45,13 @@ public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
private static final String EMAIL = "email";
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client oAuth2Client) {
OAuth2MapperConfig config = oAuth2Client.getMapperConfig();
Map<String, Object> attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes());
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, oAuth2Client);
}
private static Map<String, Object> updateAttributesFromRequestParams(HttpServletRequest request, Map<String, Object> attributes) {

8
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -20,7 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -33,12 +33,12 @@ import java.util.Map;
public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper {
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client oAuth2Client) {
OAuth2MapperConfig config = oAuth2Client.getMapperConfig();
Map<String, Object> attributes = token.getPrincipal().getAttributes();
String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey());
OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, oAuth2Client);
}
}

8
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -26,7 +26,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -41,10 +41,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder();
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client auth2Client) {
OAuth2MapperConfig config = auth2Client.getMapperConfig();
OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom());
return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration);
return getOrCreateSecurityUserFromOAuth2User(oauth2User, auth2Client);
}
private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) {

8
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java

@ -25,7 +25,7 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.dao.oauth2.OAuth2Configuration;
import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.queue.util.TbCoreComponent;
@ -49,13 +49,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
private OAuth2Configuration oAuth2Configuration;
@Override
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) {
OAuth2MapperConfig config = registration.getMapperConfig();
public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client oAuth2Client) {
OAuth2MapperConfig config = oAuth2Client.getMapperConfig();
Map<String, String> githubMapperConfig = oAuth2Configuration.getGithubMapper();
String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken);
Map<String, Object> attributes = token.getPrincipal().getAttributes();
OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config);
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration);
return getOrCreateSecurityUserFromOAuth2User(oAuth2User, oAuth2Client);
}
private synchronized String getEmail(String emailUrl, String oauth2Token) {

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java

@ -17,9 +17,9 @@ package org.thingsboard.server.service.security.auth.oauth2;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.service.security.model.SecurityUser;
public interface OAuth2ClientMapper {
SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration);
SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Client oAuth2Client);
}

19
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -31,10 +31,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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.dao.oauth2.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,19 +97,19 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
try {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId()));
OAuth2Client oauth2Client = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(token.getAuthorizedClientRegistrationId())));
OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(
token.getAuthorizedClientRegistrationId(),
token.getPrincipal().getName());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType());
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
registration);
oauth2Client);
clearAuthenticationAttributes(request, response);
JwtPair tokenPair = tokenFactory.createTokenPair(securityUser);
getRedirectStrategy().sendRedirect(request, response, getRedirectUrl(baseUrl, tokenPair));
systemSecurityService.logLoginAction(securityUser, new RestAuthenticationDetails(request), ActionType.LOGIN, registration.getName(), null);
systemSecurityService.logLoginAction(securityUser, new RestAuthenticationDetails(request), ActionType.LOGIN, oauth2Client.getName(), null);
} catch (Exception e) {
log.debug("Error occurred during processing authentication success result. " +
"request [{}], response [{}], authentication [{}]", request, response, authentication, e);

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

@ -40,7 +40,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;

40
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -99,6 +99,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
@ -1121,4 +1126,39 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
tbTenantProfileService.save(TenantId.SYS_TENANT_ID, tenantProfile, oldTenantProfile);
}
protected OAuth2Client createOauth2Client(TenantId tenantId, String title) {
return createOauth2Client(tenantId, title, null);
}
protected OAuth2Client createOauth2Client(TenantId tenantId, String title, List<PlatformType> platforms) {
OAuth2Client oAuth2Client = new OAuth2Client();
oAuth2Client.setTenantId(tenantId);
oAuth2Client.setTitle(title);
oAuth2Client.setClientId(UUID.randomUUID().toString());
oAuth2Client.setClientSecret(UUID.randomUUID().toString());
oAuth2Client.setAuthorizationUri(UUID.randomUUID().toString());
oAuth2Client.setAccessTokenUri(UUID.randomUUID().toString());
oAuth2Client.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
oAuth2Client.setPlatforms(platforms == null ? Collections.emptyList() : platforms);
oAuth2Client.setUserInfoUri(UUID.randomUUID().toString());
oAuth2Client.setUserNameAttributeName(UUID.randomUUID().toString());
oAuth2Client.setJwkSetUri(UUID.randomUUID().toString());
oAuth2Client.setClientAuthenticationMethod(UUID.randomUUID().toString());
oAuth2Client.setLoginButtonLabel(UUID.randomUUID().toString());
oAuth2Client.setLoginButtonIcon(UUID.randomUUID().toString());
oAuth2Client.setAdditionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
oAuth2Client.setMapperConfig(
OAuth2MapperConfig.builder()
.allowUserCreation(true)
.activateUser(true)
.type(MapperType.CUSTOM)
.custom(
OAuth2CustomMapperConfig.builder()
.url(UUID.randomUUID().toString())
.build()
)
.build());
return oAuth2Client;
}
}

139
application/src/test/java/org/thingsboard/server/controller/DomainControllerTest.java

@ -0,0 +1,139 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.domain.DomainInfo;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class DomainControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<DomainInfo>> PAGE_DATA_DOMAIN_TYPE_REF = new TypeReference<>() {
};
static final TypeReference<PageData<OAuth2ClientInfo>> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() {
};
@Before
public void setUp() throws Exception {
loginSysAdmin();
}
@After
public void tearDown() throws Exception {
PageData<DomainInfo> pageData = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0));
for (Domain domain : pageData.getData()) {
doDelete("/api/domain/" + domain.getId().getId())
.andExpect(status().isOk());
}
PageData<OAuth2ClientInfo> clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0));
for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) {
doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString())
.andExpect(status().isOk());
}
}
@Test
public void testSaveDomain() throws Exception {
PageData<DomainInfo> pageData = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0));
assertThat(pageData.getData()).isEmpty();
Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true);
Domain savedDomain = doPost("/api/domain", domain, Domain.class);
PageData<DomainInfo> pageData2 = doGetTypedWithPageLink("/api/domain/infos?", PAGE_DATA_DOMAIN_TYPE_REF, new PageLink(10, 0));
assertThat(pageData2.getData()).hasSize(1);
assertThat(pageData2.getData().get(0)).isEqualTo(new DomainInfo(savedDomain, Collections.emptyList()));
DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId());
assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, Collections.emptyList()));
doDelete("/api/domain/" + savedDomain.getId().getId());
doGet("/api/domain/info/{id}", savedDomain.getId().getId())
.andExpect(status().isNotFound());
}
@Test
public void testSaveDomainWithoutName() throws Exception {
Domain domain = constructDomain(TenantId.SYS_TENANT_ID, null, true, true);
doPost("/api/domain", domain)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("name must not be blank")));
}
@Test
public void testUpdateDomainOauth2Clients() throws Exception {
Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true);
Domain savedDomain = doPost("/api/domain", domain, Domain.class);
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
OAuth2Client oAuth2Client2 = createOauth2Client(TenantId.SYS_TENANT_ID, "test facebook client");
OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class);
doPut("/api/domain/" + savedDomain.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId()));
DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId());
assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client),
new OAuth2ClientInfo(savedOAuth2Client2))));
doPut("/api/domain/" + savedDomain.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId()));
DomainInfo retrievedDomainInfo2 = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId());
assertThat(retrievedDomainInfo2).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client2))));
}
@Test
public void testCreateDomainWithOauth2Clients() throws Exception {
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
Domain domain = constructDomain(TenantId.SYS_TENANT_ID, "my.test.domain", true, true);
Domain savedDomain = doPost("/api/domain?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), domain, Domain.class);
DomainInfo retrievedDomainInfo = doGet("/api/domain/info/{id}", DomainInfo.class, savedDomain.getId().getId());
assertThat(retrievedDomainInfo).isEqualTo(new DomainInfo(savedDomain, List.of(new OAuth2ClientInfo(savedOAuth2Client))));
}
private Domain constructDomain(TenantId tenantId, String domainName, boolean oauth2Enabled, boolean propagateToEdge) {
Domain domain = new Domain();
domain.setTenantId(tenantId);
domain.setName(domainName);
domain.setOauth2Enabled(oauth2Enabled);
domain.setPropagateToEdge(propagateToEdge);
return domain;
}
}

6
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -81,7 +81,8 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
@ -892,7 +893,8 @@ public class EdgeControllerTest extends AbstractControllerTest {
EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class);
edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class);
edgeImitator.expectMessageAmount(27);
edgeImitator.connect();

73
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.OAuth2Client;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.ApiUsageStateFilter;
import org.thingsboard.server.common.data.query.EntityCountQuery;
@ -57,6 +50,8 @@ import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.stats.TbApiUsageStateClient;
import org.thingsboard.server.dao.domain.DomainService;
import org.thingsboard.server.dao.oauth2.OAuth2ClientService;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -65,10 +60,8 @@ 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;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -86,6 +79,12 @@ public class HomePageApiTest extends AbstractControllerTest {
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
private DomainService domainService;
@Autowired
private OAuth2ClientService oAuth2ClientService;
@MockBean
private MailService mailService;
@ -369,9 +368,11 @@ public class HomePageApiTest extends AbstractControllerTest {
Assert.assertTrue(featuresInfo.isNotificationEnabled());
Assert.assertFalse(featuresInfo.isOauthEnabled());
OAuth2Info oAuth2Info = createDefaultOAuth2Info();
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
doPost("/api/oauth2/config", oAuth2Info).andExpect(status().isOk());
Domain domain = createDomain(TenantId.SYS_TENANT_ID, "my.home.domain", true, true);
doPost("/api/domain?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), domain, Domain.class);
featuresInfo = doGet("/api/admin/featuresInfo", FeaturesInfo.class);
Assert.assertNotNull(featuresInfo);
@ -384,6 +385,8 @@ public class HomePageApiTest extends AbstractControllerTest {
adminSettingsService.deleteAdminSettingsByTenantIdAndKey(TenantId.SYS_TENANT_ID, "notifications");
adminSettingsService.deleteAdminSettingsByTenantIdAndKey(TenantId.SYS_TENANT_ID, "twoFaSettings");
adminSettingsService.deleteAdminSettingsByTenantIdAndKey(TenantId.SYS_TENANT_ID, "sms");
oAuth2ClientService.deleteOauth2ClientsByTenantId(TenantId.SYS_TENANT_ID);
domainService.deleteDomainsByTenantId(TenantId.SYS_TENANT_ID);
}
@Test
@ -493,43 +496,13 @@ 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, boolean edgeEnabled) {
Domain domain = new Domain();
domain.setTenantId(tenantId);
domain.setName(domainName);
domain.setOauth2Enabled(oauth2Enabled);
domain.setPropagateToEdge(edgeEnabled);
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();
}
}

141
application/src/test/java/org/thingsboard/server/controller/MobileAppControllerTest.java

@ -0,0 +1,141 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class MobileAppControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<MobileAppInfo>> PAGE_DATA_MOBILE_APP_TYPE_REF = new TypeReference<>() {
};
static final TypeReference<PageData<OAuth2ClientInfo>> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() {
};
@Before
public void setUp() throws Exception {
loginSysAdmin();
}
@After
public void tearDown() throws Exception {
PageData<MobileAppInfo> pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
for (MobileApp mobileApp : pageData.getData()) {
doDelete("/api/mobileApp/" + mobileApp.getId().getId())
.andExpect(status().isOk());
}
PageData<OAuth2ClientInfo> clients = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0));
for (OAuth2ClientInfo oAuth2ClientInfo : clients.getData()) {
doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString())
.andExpect(status().isOk());
}
}
@Test
public void testSaveMobileApp() throws Exception {
PageData<MobileAppInfo> pageData = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
assertThat(pageData.getData()).isEmpty();
MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true);
MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class);
PageData<MobileAppInfo> pageData2 = doGetTypedWithPageLink("/api/mobileApp/infos?", PAGE_DATA_MOBILE_APP_TYPE_REF, new PageLink(10, 0));
assertThat(pageData2.getData()).hasSize(1);
assertThat(pageData2.getData().get(0)).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList()));
MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId());
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, Collections.emptyList()));
doDelete("/api/mobileApp/" + savedMobileApp.getId().getId());
doGet("/api/mobileApp/info/{id}", savedMobileApp.getId().getId())
.andExpect(status().isNotFound());
}
@Test
public void testSaveMobileAppWithShortAppSecret() throws Exception {
MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "mobileApp.ce", true);
mobileApp.setAppSecret("short");
doPost("/api/mobileApp", mobileApp)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("appSecret must be at least 16 and max 2048 characters")));
}
@Test
public void testUpdateMobileAppOauth2Clients() throws Exception {
MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true);
MobileApp savedMobileApp = doPost("/api/mobileApp", mobileApp, MobileApp.class);
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
OAuth2Client oAuth2Client2 = createOauth2Client(TenantId.SYS_TENANT_ID, "test facebook client");
OAuth2Client savedOAuth2Client2 = doPost("/api/oauth2/client", oAuth2Client2, OAuth2Client.class);
doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client.getId().getId(), savedOAuth2Client2.getId().getId()));
MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId());
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client),
new OAuth2ClientInfo(savedOAuth2Client2))));
doPut("/api/mobileApp/" + savedMobileApp.getId() + "/oauth2Clients", List.of(savedOAuth2Client2.getId().getId()));
MobileAppInfo retrievedMobileAppInfo2 = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId());
assertThat(retrievedMobileAppInfo2).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client2))));
}
@Test
public void testCreateMobileAppWithOauth2Clients() throws Exception {
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
MobileApp mobileApp = validMobileApp(TenantId.SYS_TENANT_ID, "my.test.package", true);
MobileApp savedMobileApp = doPost("/api/mobileApp?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), mobileApp, MobileApp.class);
MobileAppInfo retrievedMobileAppInfo = doGet("/api/mobileApp/info/{id}", MobileAppInfo.class, savedMobileApp.getId().getId());
assertThat(retrievedMobileAppInfo).isEqualTo(new MobileAppInfo(savedMobileApp, List.of(new OAuth2ClientInfo(savedOAuth2Client))));
}
private MobileApp validMobileApp(TenantId tenantId, String mobileAppName, boolean oauth2Enabled) {
MobileApp MobileApp = new MobileApp();
MobileApp.setTenantId(tenantId);
MobileApp.setPkgName(mobileAppName);
MobileApp.setAppSecret(StringUtils.randomAlphanumeric(24));
MobileApp.setOauth2Enabled(oauth2Enabled);
return MobileApp;
}
}

76
application/src/test/java/org/thingsboard/server/controller/Oauth2ClientControllerTest.java

@ -0,0 +1,76 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.service.DaoSqlTest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@DaoSqlTest
public class Oauth2ClientControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<OAuth2ClientInfo>> PAGE_DATA_OAUTH2_CLIENT_TYPE_REF = new TypeReference<>() {
};
@Before
public void setUp() throws Exception {
loginSysAdmin();
}
@After
public void tearDown() throws Exception {
PageData<OAuth2ClientInfo> pageData = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0));
for (OAuth2ClientInfo oAuth2ClientInfo : pageData.getData()) {
doDelete("/api/oauth2/client/" + oAuth2ClientInfo.getId().getId().toString())
.andExpect(status().isOk());
}
}
@Test
public void testSaveOauth2Client() throws Exception {
loginSysAdmin();
PageData<OAuth2ClientInfo> pageData = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0));
assertThat(pageData.getData()).isEmpty();
OAuth2Client oAuth2Client = createOauth2Client(TenantId.SYS_TENANT_ID, "test google client");
OAuth2Client savedOAuth2Client = doPost("/api/oauth2/client", oAuth2Client, OAuth2Client.class);
PageData<OAuth2ClientInfo> pageData2 = doGetTypedWithPageLink("/api/oauth2/client/infos?", PAGE_DATA_OAUTH2_CLIENT_TYPE_REF, new PageLink(10, 0));
assertThat(pageData2.getData()).hasSize(1);
assertThat(pageData2.getData().get(0)).isEqualTo(new OAuth2ClientInfo(savedOAuth2Client));
OAuth2Client retrievedOAuth2ClientInfo = doGet("/api/oauth2/client/{id}", OAuth2Client.class, savedOAuth2Client.getId().getId());
assertThat(retrievedOAuth2ClientInfo).isEqualTo(savedOAuth2Client);
doDelete("/api/oauth2/client/" + savedOAuth2Client.getId().getId());
doGet("/api/oauth2/client/{id}", savedOAuth2Client.getId().getId())
.andExpect(status().isNotFound());
}
}

19
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -63,7 +63,6 @@ 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.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
@ -87,7 +86,8 @@ import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
@ -144,7 +144,8 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class);
edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class);
edgeImitator.expectMessageAmount(24);
edgeImitator.connect();
@ -547,18 +548,6 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertTrue(customer.isPublic());
}
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);
Assert.assertNotNull(auth2Info);
Assert.assertEquals(oAuth2Info, auth2Info);
testAutoGeneratedCodeByProtobuf(oAuth2UpdateMsg);
}
private void validateSyncCompleted() {
Optional<SyncCompletedMsg> syncCompletedMsgOpt = edgeImitator.findMessageByType(SyncCompletedMsg.class);
Assert.assertTrue(syncCompletedMsgOpt.isPresent());

185
application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java

@ -15,94 +15,167 @@
*/
package org.thingsboard.server.edge;
import com.google.common.collect.Lists;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
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.OAuth2Client;
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.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.UUID;
@DaoSqlTest
public class OAuth2EdgeTest extends AbstractEdgeTest {
@Test
public void testOAuth2Support() throws Exception {
public void testOAuth2DomainSupport() throws Exception {
loginSysAdmin();
// enable oauth
// enable oauth and save domain
edgeImitator.allowIgnoredTypes();
edgeImitator.expectMessageAmount(1);
OAuth2Info oAuth2Info = createDefaultOAuth2Info();
oAuth2Info = doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Domain savedDomain = doPost("/api/domain", constructDomain(), Domain.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);
Assert.assertTrue(latestMessage instanceof OAuth2DomainUpdateMsg);
OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = (OAuth2DomainUpdateMsg) latestMessage;
Domain result = JacksonUtil.fromString(oAuth2DomainUpdateMsg.getEntity(), Domain.class, true);
Assert.assertEquals(savedDomain, result);
// disable oauth support
// disable oauth support: no update of domain events is sending to Edge
edgeImitator.expectMessageAmount(1);
oAuth2Info.setEnabled(false);
oAuth2Info.setEdgeEnabled(false);
doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
savedDomain.setPropagateToEdge(false);
doPost("/api/domain", savedDomain, Domain.class);
Assert.assertFalse(edgeImitator.waitForMessages(5));
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
// delete domain
edgeImitator.expectMessageAmount(1);
doDelete("/api/domain/" + savedDomain.getId().getId());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2DomainUpdateMsg);
oAuth2DomainUpdateMsg = (OAuth2DomainUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, oAuth2DomainUpdateMsg.getMsgType());
Assert.assertEquals(savedDomain.getUuidId().getMostSignificantBits(), oAuth2DomainUpdateMsg.getIdMSB());
Assert.assertEquals(savedDomain.getUuidId().getLeastSignificantBits(), oAuth2DomainUpdateMsg.getIdLSB());
edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class);
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class);
loginTenantAdmin();
}
@Test
public void testOAuth2ClientSupport() throws Exception {
loginSysAdmin();
// enable oauth and save domain
edgeImitator.allowIgnoredTypes();
edgeImitator.expectMessageAmount(2);
OAuth2Client savedOAuth2Client = validClientInfo(TenantId.SYS_TENANT_ID, "test edge google client");
savedOAuth2Client = doPost("/api/oauth2/client", savedOAuth2Client, OAuth2Client.class);
Domain savedDomain = doPost("/api/domain?oauth2ClientIds=" + savedOAuth2Client.getId().getId(), constructDomain(), Domain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<OAuth2DomainUpdateMsg> oAuth2DomainUpdateMsgOpt = edgeImitator.findMessageByType(OAuth2DomainUpdateMsg.class);
Assert.assertTrue(oAuth2DomainUpdateMsgOpt.isPresent());
Domain result = JacksonUtil.fromString(oAuth2DomainUpdateMsgOpt.get().getEntity(), Domain.class, true);
Assert.assertEquals(savedDomain, result);
Optional<OAuth2ClientUpdateMsg> oAuth2ClientUpdateMsgOpt = edgeImitator.findMessageByType(OAuth2ClientUpdateMsg.class);
Assert.assertTrue(oAuth2ClientUpdateMsgOpt.isPresent());
OAuth2Client clientResult = JacksonUtil.fromString(oAuth2ClientUpdateMsgOpt.get().getEntity(), OAuth2Client.class, true);
Assert.assertEquals(savedOAuth2Client, clientResult);
// disable oauth support: no update of domain events and client events are sending to Edge
edgeImitator.expectMessageAmount(1);
savedDomain.setPropagateToEdge(false);
doPost("/api/domain", savedDomain, Domain.class);
Assert.assertFalse(edgeImitator.waitForMessages(5));
edgeImitator.expectMessageAmount(1);
savedOAuth2Client.setTitle("Updated title");
doPost("/api/oauth2/client", savedOAuth2Client, OAuth2Client.class);
Assert.assertFalse(edgeImitator.waitForMessages(5));
// delete oauth2Client
edgeImitator.expectMessageAmount(1);
doDelete("/api/oauth2/client/" + savedOAuth2Client.getId().getId());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2ClientUpdateMsg);
OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = (OAuth2ClientUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, oAuth2ClientUpdateMsg.getMsgType());
Assert.assertEquals(savedOAuth2Client.getUuidId().getMostSignificantBits(), oAuth2ClientUpdateMsg.getIdMSB());
Assert.assertEquals(savedOAuth2Client.getUuidId().getLeastSignificantBits(), oAuth2ClientUpdateMsg.getIdLSB());
// delete domain
edgeImitator.expectMessageAmount(1);
doDelete("/api/domain/" + savedDomain.getId().getId());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2DomainUpdateMsg);
OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = (OAuth2DomainUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, oAuth2DomainUpdateMsg.getMsgType());
Assert.assertEquals(savedDomain.getUuidId().getMostSignificantBits(), oAuth2DomainUpdateMsg.getIdMSB());
Assert.assertEquals(savedDomain.getUuidId().getLeastSignificantBits(), oAuth2DomainUpdateMsg.getIdLSB());
edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class);
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.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 OAuth2Client validClientInfo(TenantId tenantId, String title) {
OAuth2Client oAuth2Client = new OAuth2Client();
oAuth2Client.setTenantId(tenantId);
oAuth2Client.setTitle(title);
oAuth2Client.setClientId(UUID.randomUUID().toString());
oAuth2Client.setClientSecret(UUID.randomUUID().toString());
oAuth2Client.setAuthorizationUri(UUID.randomUUID().toString());
oAuth2Client.setAccessTokenUri(UUID.randomUUID().toString());
oAuth2Client.setScope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
oAuth2Client.setPlatforms(Collections.emptyList());
oAuth2Client.setUserInfoUri(UUID.randomUUID().toString());
oAuth2Client.setUserNameAttributeName(UUID.randomUUID().toString());
oAuth2Client.setJwkSetUri(UUID.randomUUID().toString());
oAuth2Client.setClientAuthenticationMethod(UUID.randomUUID().toString());
oAuth2Client.setLoginButtonLabel(UUID.randomUUID().toString());
oAuth2Client.setLoginButtonIcon(UUID.randomUUID().toString());
oAuth2Client.setAdditionalInfo(JacksonUtil.newObjectNode().put(UUID.randomUUID().toString(), UUID.randomUUID().toString()));
oAuth2Client.setMapperConfig(
OAuth2MapperConfig.builder()
.allowUserCreation(true)
.activateUser(true)
.type(MapperType.CUSTOM)
.custom(
OAuth2CustomMapperConfig.builder()
.url(UUID.randomUUID().toString())
.build()
)
.build());
return oAuth2Client;
}
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 Domain constructDomain() {
Domain domain = new Domain();
domain.setTenantId(TenantId.SYS_TENANT_ID);
domain.setName("my.edge.domain");
domain.setOauth2Enabled(true);
domain.setPropagateToEdge(true);
return domain;
}
}

2
application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -45,6 +46,7 @@ public class UserEdgeTest extends AbstractEdgeTest {
private BCryptPasswordEncoder passwordEncoder;
@Test
@Ignore("Ignored until fix")
public void testCreateUpdateDeleteTenantUser() throws Exception {
// create user
edgeImitator.expectMessageAmount(4);

14
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -47,7 +47,8 @@ import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.NotificationRuleUpdateMsg;
import org.thingsboard.server.gen.edge.v1.NotificationTargetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.NotificationTemplateUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2ClientUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2DomainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -318,9 +319,14 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(resourceUpdateMsg));
}
}
if (downlinkMsg.getOAuth2UpdateMsgCount() > 0) {
for (OAuth2UpdateMsg oAuth2UpdateMsg : downlinkMsg.getOAuth2UpdateMsgList()) {
result.add(saveDownlinkMsg(oAuth2UpdateMsg));
if (downlinkMsg.getOAuth2ClientUpdateMsgCount() > 0) {
for (OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg : downlinkMsg.getOAuth2ClientUpdateMsgList()) {
result.add(saveDownlinkMsg(oAuth2ClientUpdateMsg));
}
}
if (downlinkMsg.getOAuth2DomainUpdateMsgCount() > 0) {
for (OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg : downlinkMsg.getOAuth2DomainUpdateMsgList()) {
result.add(saveDownlinkMsg(oAuth2DomainUpdateMsg));
}
}
if (downlinkMsg.getNotificationTemplateUpdateMsgCount() > 0) {

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

46
common/dao-api/src/main/java/org/thingsboard/server/dao/domain/DomainService.java

@ -0,0 +1,46 @@
/**
* 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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface DomainService extends EntityDaoService {
Domain saveDomain(TenantId tenantId, Domain domain);
void deleteDomainById(TenantId tenantId, DomainId domainId);
Domain findDomainById(TenantId tenantId, DomainId domainId);
PageData<DomainInfo> findDomainInfosByTenantId(TenantId tenantId, PageLink pageLink);
DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId);
boolean isOauth2Enabled(TenantId tenantId);
void updateOauth2Clients(TenantId tenantId, DomainId domainId, List<OAuth2ClientId> oAuth2ClientIds);
void deleteDomainsByTenantId(TenantId tenantId);
}

44
common/dao-api/src/main/java/org/thingsboard/server/dao/mobile/MobileAppService.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.mobile;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface MobileAppService extends EntityDaoService {
MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp);
void deleteMobileAppById(TenantId tenantId, MobileAppId mobileAppId);
MobileApp findMobileAppById(TenantId tenantId, MobileAppId mobileAppId);
PageData<MobileAppInfo> findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink);
MobileAppInfo findMobileAppInfoById(TenantId tenantId, MobileAppId mobileAppId);
void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List<OAuth2ClientId> oAuth2ClientIds);
void deleteMobileAppsByTenantId(TenantId tenantId);
}

55
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientService.java

@ -0,0 +1,55 @@
/**
* 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 org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
import java.util.UUID;
public interface OAuth2ClientService extends EntityDaoService {
List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByDomainName(String domainName);
List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType);
List<OAuth2Client> findOAuth2ClientsByTenantId(TenantId tenantId);
OAuth2Client saveOAuth2Client(TenantId tenantId, OAuth2Client oAuth2Client);
OAuth2Client findOAuth2ClientById(TenantId tenantId, OAuth2ClientId providerId);
String findAppSecret(OAuth2ClientId oAuth2ClientId, String pkgName);
void deleteOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId);
void deleteOauth2ClientsByTenantId(TenantId tenantId);
PageData<OAuth2ClientInfo> findOAuth2ClientInfosByTenantId(TenantId tenantId, PageLink pageLink);
List<OAuth2ClientInfo> findOAuth2ClientInfosByIds(TenantId tenantId, List<OAuth2ClientId> oAuth2ClientIds);
boolean isPropagateOAuth2ClientToEdge(TenantId tenantId, OAuth2ClientId oAuth2ClientId);
}

39
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java

@ -1,39 +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 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.PlatformType;
import java.util.List;
import java.util.UUID;
public interface OAuth2Service {
List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
void saveOAuth2Info(OAuth2Info oauth2Info);
OAuth2Info findOAuth2Info();
OAuth2Registration findRegistration(UUID id);
List<OAuth2Registration> findAllRegistrations();
String findAppSecret(UUID registrationId, String pkgName);
}

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

62
common/data/src/main/java/org/thingsboard/server/common/data/domain/Domain.java

@ -0,0 +1,62 @@
/**
* 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 jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.EqualsAndHashCode;
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;
import org.thingsboard.server.common.data.validation.Length;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString
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)
@NotBlank
@Length(fieldName = "name")
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() {
super();
}
public Domain(DomainId id) {
super(id);
}
public Domain(Domain domain) {
super(domain);
this.tenantId = domain.tenantId;
this.name = domain.name;
this.oauth2Enabled = domain.oauth2Enabled;
this.propagateToEdge = domain.propagateToEdge;
}
}

38
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java → common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainInfo.java

@ -13,30 +13,34 @@
* 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.id.DomainId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import java.util.List;
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
@Data
@ToString
@Builder(toBuilder = true)
@NoArgsConstructor
@AllArgsConstructor
@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 DomainInfo extends Domain {
@Schema(description = "List of available oauth2 clients")
private List<OAuth2ClientInfo> oauth2ClientInfos;
public DomainInfo(Domain domain, List<OAuth2ClientInfo> oauth2ClientInfos) {
super(domain);
this.oauth2ClientInfos = oauth2ClientInfos;
}
public DomainInfo() {
super();
}
public DomainInfo(DomainId domainId) {
super(domainId);
}
}

22
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/domain/DomainOauth2Client.java

@ -13,26 +13,22 @@
* 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.id.DomainId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
@EqualsAndHashCode
@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;
@EqualsAndHashCode
public class DomainOauth2Client {
private DomainId domainId;
private OAuth2ClientId oAuth2ClientId;
}

3
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java

@ -45,7 +45,8 @@ 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),
DOMAIN(true, EntityType.DOMAIN);
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;
}
}

10
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 OAuth2ClientId(uuid);
case MOBILE_APP:
return new MobileAppId(uuid);
case DOMAIN:
return new DomainId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}
@ -153,6 +159,10 @@ public class EntityIdFactory {
return new NotificationTargetId(uuid);
case NOTIFICATION_TEMPLATE:
return new NotificationTemplateId(uuid);
case OAUTH2_CLIENT:
return new OAuth2ClientId(uuid);
case DOMAIN:
return new DomainId(uuid);
}
throw new IllegalArgumentException("EdgeEventType " + edgeEventType + " 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;
}
}

14
common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java → common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.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 OAuth2RegistrationId extends UUIDBased {
public class OAuth2ClientId extends UUIDBased implements EntityId {
@JsonCreator
public OAuth2RegistrationId(@JsonProperty("id") UUID id) {
public OAuth2ClientId(@JsonProperty("id") UUID id) {
super(id);
}
public static OAuth2RegistrationId fromString(String oauth2RegistrationId) {
return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId));
public static OAuth2ClientId fromString(String oauth2ClientId) {
return new OAuth2ClientId(UUID.fromString(oauth2ClientId));
}
@Override
public EntityType getEntityType() {
return EntityType.OAUTH2_CLIENT;
}
}

72
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileApp.java

@ -0,0 +1,72 @@
/**
* 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 jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
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;
import org.thingsboard.server.common.data.validation.Length;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString
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)
@NotBlank
@Length(fieldName = "pkgName")
private String pkgName;
@Schema(description = "Application secret. The length must be at least 16 characters", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty
@Length(fieldName = "appSecret", min = 16, max = 2048, message = "must be at least 16 and max 2048 characters")
private String appSecret;
@Schema(description = "Whether OAuth2 settings are enabled or not")
private boolean oauth2Enabled;
public MobileApp() {
super();
}
public MobileApp(MobileAppId id) {
super(id);
}
public MobileApp(MobileApp mobile) {
super(mobile);
this.tenantId = mobile.tenantId;
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;
}
}

47
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppInfo.java

@ -0,0 +1,47 @@
/**
* 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@Schema
public class MobileAppInfo extends MobileApp {
@Schema(description = "List of available oauth2 clients")
private List<OAuth2ClientInfo> oauth2ClientInfos;
public MobileAppInfo(MobileApp mobileApp, List<OAuth2ClientInfo> oauth2ClientInfos) {
super(mobileApp);
this.oauth2ClientInfos = oauth2ClientInfos;
}
public MobileAppInfo() {
super();
}
public MobileAppInfo(MobileAppId mobileAppId) {
super(mobileAppId);
}
}

32
common/data/src/main/java/org/thingsboard/server/common/data/mobile/MobileAppOauth2Client.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.OAuth2ClientId;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MobileAppOauth2Client {
private MobileAppId mobileAppId;
private OAuth2ClientId oAuth2ClientId;
}

82
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Client.java

@ -15,51 +15,115 @@
*/
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.AllArgsConstructor;
import lombok.Builder;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import java.util.List;
@EqualsAndHashCode
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(exclude = {"clientSecret"})
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Schema
public class OAuth2RegistrationInfo {
public class OAuth2Client extends BaseDataWithAdditionalInfo<OAuth2ClientId> implements HasName, HasTenantId {
@Schema(description = "JSON object with Tenant Id")
private TenantId tenantId;
@Schema(description = "Oauth2 client title")
@NotBlank
@Length(fieldName = "title", max = 100, message = "cannot be longer than 100 chars")
private String title;
@Schema(description = "Config for mapping OAuth2 log in response to platform entities", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private OAuth2MapperConfig mapperConfig;
@Schema(description = "OAuth2 client ID. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "clientId")
private String clientId;
@Schema(description = "OAuth2 client secret. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "clientSecret", max = 2048)
private String clientSecret;
@Schema(description = "Authorization URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "authorizationUri")
private String authorizationUri;
@Schema(description = "Access token URI of the OAuth2 provider. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "accessTokenUri")
private String accessTokenUri;
@Schema(description = "OAuth scopes that will be requested from OAuth2 platform. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty
@Length(fieldName = "scope")
private List<String> scope;
@Schema(description = "User info URI of the OAuth2 provider")
@Length(fieldName = "userInfoUri")
private String userInfoUri;
@Schema(description = "Name of the username attribute in OAuth2 provider response. Cannot be empty")
@NotBlank
@Length(fieldName = "userNameAttributeName")
private String userNameAttributeName;
@Schema(description = "JSON Web Key URI of the OAuth2 provider")
@Length(fieldName = "jwkSetUri")
private String jwkSetUri;
@Schema(description = "Client authentication method to use: 'BASIC' or 'POST'. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "clientAuthenticationMethod")
private String clientAuthenticationMethod;
@Schema(description = "OAuth2 provider label. Cannot be empty", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank
@Length(fieldName = "loginButtonLabel")
private String loginButtonLabel;
@Schema(description = "Log in button icon for OAuth2 provider")
@Length(fieldName = "loginButtonIcon")
private String loginButtonIcon;
@Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)")
@Length(fieldName = "platforms")
private List<PlatformType> platforms;
@Schema(description = "Additional info of OAuth2 client (e.g. providerName)", requiredMode = Schema.RequiredMode.REQUIRED)
private JsonNode additionalInfo;
public OAuth2Client() {
super();
}
public OAuth2Client(OAuth2ClientId id) {
super(id);
}
public OAuth2Client(OAuth2Client oAuth2Client) {
super(oAuth2Client);
this.tenantId = oAuth2Client.tenantId;
this.title = oAuth2Client.title;
this.mapperConfig = oAuth2Client.mapperConfig;
this.clientId = oAuth2Client.clientId;
this.clientSecret = oAuth2Client.clientSecret;
this.authorizationUri = oAuth2Client.authorizationUri;
this.accessTokenUri = oAuth2Client.accessTokenUri;
this.scope = oAuth2Client.scope;
this.userInfoUri = oAuth2Client.userInfoUri;
this.userNameAttributeName = oAuth2Client.userNameAttributeName;
this.jwkSetUri = oAuth2Client.jwkSetUri;
this.clientAuthenticationMethod = oAuth2Client.clientAuthenticationMethod;
this.loginButtonLabel = oAuth2Client.loginButtonLabel;
this.loginButtonIcon = oAuth2Client.loginButtonIcon;
this.platforms = oAuth2Client.platforms;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return title;
}
}

55
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientInfo.java

@ -15,31 +15,48 @@
*/
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.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import java.util.List;
@EqualsAndHashCode
@Data
@NoArgsConstructor
@AllArgsConstructor
@Schema
public class OAuth2ClientInfo {
@Schema(description = "OAuth2 client name", example = "GitHub")
private String name;
@Schema(description = "Name of the icon, displayed on OAuth2 log in button", example = "github-logo")
private String icon;
@Schema(description = "URI for OAuth2 log in. On HTTP GET request to this URI, it redirects to the OAuth2 provider page",
example = "/oauth2/authorization/8352f191-2b4d-11ec-9ed1-cbf57c026ecc")
private String url;
public OAuth2ClientInfo(OAuth2ClientInfo oauth2ClientInfo) {
this.name = oauth2ClientInfo.getName();
this.icon = oauth2ClientInfo.getIcon();
this.url = oauth2ClientInfo.getUrl();
@EqualsAndHashCode(callSuper = true)
public class OAuth2ClientInfo extends BaseData<OAuth2ClientId> implements HasName {
@Schema(description = "Oauth2 client registration title (e.g. My google)")
private String title;
@Schema(description = "Oauth2 client provider name (e.g. Google)")
private String providerName;
@Schema(description = "List of platforms for which usage of the OAuth2 client is allowed (empty for all allowed)")
private List<PlatformType> platforms;
public OAuth2ClientInfo() {
super();
}
public OAuth2ClientInfo(OAuth2ClientId id) {
super(id);
}
public OAuth2ClientInfo(OAuth2Client oAuth2Client) {
super(oAuth2Client);
this.title = oAuth2Client.getTitle();
this.providerName = oAuth2Client.getAdditionalInfoField("providerName", JsonNode::asText,"");
this.platforms = oAuth2Client.getPlatforms();
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return title;
}
}

17
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java → common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientLoginInfo.java

@ -17,22 +17,23 @@ 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)
public class OAuth2ClientLoginInfo {
@Schema(description = "OAuth2 client name", example = "GitHub")
private String name;
@Schema(description = "Name of the icon, displayed on OAuth2 log in button", example = "github-logo")
private String icon;
@Schema(description = "URI for OAuth2 log in. On HTTP GET request to this URI, it redirects to the OAuth2 provider page",
example = "/oauth2/authorization/8352f191-2b4d-11ec-9ed1-cbf57c026ecc")
private String url;
}

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

2
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MapperConfig.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data.oauth2;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import lombok.Builder;
import lombok.Data;
@ -32,6 +33,7 @@ public class OAuth2MapperConfig {
@Schema(description = "Whether user credentials should be activated when user is created after successful authentication")
private boolean activateUser;
@Schema(description = "Type of OAuth2 mapper. Depending on this param, different mapper config fields must be specified", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull
private MapperType type;
@Valid
@Schema(description = "Mapper config for BASIC and GITHUB mapper types")

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

74
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java

@ -1,74 +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 com.fasterxml.jackson.annotation.JsonProperty;
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.id.OAuth2RegistrationId;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
@ToString(exclude = {"clientSecret"})
@NoArgsConstructor
public class OAuth2Registration extends BaseDataWithAdditionalInfo<OAuth2RegistrationId> implements HasName {
private OAuth2ParamsId oauth2ParamsId;
private OAuth2MapperConfig mapperConfig;
private String clientId;
private String clientSecret;
private String authorizationUri;
private String accessTokenUri;
private List<String> scope;
private String userInfoUri;
private String userNameAttributeName;
private String jwkSetUri;
private String clientAuthenticationMethod;
private String loginButtonLabel;
private String loginButtonIcon;
private List<PlatformType> platforms;
public OAuth2Registration(OAuth2Registration registration) {
super(registration);
this.oauth2ParamsId = registration.oauth2ParamsId;
this.mapperConfig = registration.mapperConfig;
this.clientId = registration.clientId;
this.clientSecret = registration.clientSecret;
this.authorizationUri = registration.authorizationUri;
this.accessTokenUri = registration.accessTokenUri;
this.scope = registration.scope;
this.userInfoUri = registration.userInfoUri;
this.userNameAttributeName = registration.userNameAttributeName;
this.jwkSetUri = registration.jwkSetUri;
this.clientAuthenticationMethod = registration.clientAuthenticationMethod;
this.loginButtonLabel = registration.loginButtonLabel;
this.loginButtonIcon = registration.loginButtonIcon;
this.platforms = registration.platforms;
}
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return loginButtonLabel;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/validation/Length.java

@ -33,6 +33,8 @@ public @interface Length {
int max() default 255;
int min() default 0;
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};

2
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -136,7 +136,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
.setConnectRequestMsg(ConnectRequestMsg.newBuilder()
.setEdgeRoutingKey(edgeKey)
.setEdgeSecret(edgeSecret)
.setEdgeVersion(EdgeVersion.V_3_7_0)
.setEdgeVersion(EdgeVersion.V_3_7_1)
.setMaxInboundMessageSize(maxInboundMessageSize)
.build())
.build());

19
common/edge-api/src/main/proto/edge.proto

@ -39,6 +39,7 @@ enum EdgeVersion {
V_3_6_2 = 5;
V_3_6_4 = 6;
V_3_7_0 = 7;
V_3_7_1 = 8;
}
/**
@ -469,8 +470,18 @@ message ResourceUpdateMsg {
string entity = 11;
}
message OAuth2UpdateMsg {
string entity = 1;
message OAuth2ClientUpdateMsg {
UpdateMsgType msgType = 1;
optional int64 idMSB = 2;
optional int64 idLSB = 3;
optional string entity = 4;
}
message OAuth2DomainUpdateMsg {
UpdateMsgType msgType = 1;
optional int64 idMSB = 2;
optional int64 idLSB = 3;
optional string entity = 4;
}
message NotificationRuleUpdateMsg {
@ -695,9 +706,9 @@ message DownlinkMsg {
repeated TenantProfileUpdateMsg tenantProfileUpdateMsg = 27;
repeated ResourceUpdateMsg resourceUpdateMsg = 28;
repeated AlarmCommentUpdateMsg alarmCommentUpdateMsg = 29;
repeated OAuth2UpdateMsg oAuth2UpdateMsg = 30;
repeated OAuth2ClientUpdateMsg oAuth2ClientUpdateMsg = 30;
repeated NotificationRuleUpdateMsg notificationRuleUpdateMsg = 31;
repeated NotificationTargetUpdateMsg notificationTargetUpdateMsg = 32;
repeated NotificationTemplateUpdateMsg notificationTemplateUpdateMsg = 33;
repeated OAuth2DomainUpdateMsg oAuth2DomainUpdateMsg = 34;
}

5
common/proto/src/main/proto/queue.proto

@ -54,7 +54,10 @@ enum EntityTypeProto {
NOTIFICATION_REQUEST = 31;
NOTIFICATION = 32;
NOTIFICATION_RULE = 33;
QUEUE_STATS = 34;
QUEUE_STATS = 34;
OAUTH2_CLIENT = 35;
DOMAIN = 36;
MOBILE_APP = 37;
}
/**

41
dao/src/main/java/org/thingsboard/server/dao/domain/DomainDao.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.domain;
import org.thingsboard.server.common.data.domain.Domain;
import org.thingsboard.server.common.data.domain.DomainOauth2Client;
import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface DomainDao extends Dao<Domain> {
PageData<Domain> findByTenantId(TenantId tenantId, PageLink pageLink);
int countDomainByTenantIdAndOauth2Enabled(TenantId tenantId, boolean oauth2Enabled);
List<DomainOauth2Client> findOauth2ClientsByDomainId(TenantId tenantId, DomainId domainId);
void addOauth2Client(DomainOauth2Client domainOauth2Client);
void removeOauth2Client(DomainOauth2Client domainOauth2Client);
void deleteByTenantId(TenantId tenantId);
}

160
dao/src/main/java/org/thingsboard/server/dao/domain/DomainServiceImpl.java

@ -0,0 +1,160 @@
/**
* 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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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.DomainOauth2Client;
import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.oauth2.OAuth2ClientDao;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Service
public class DomainServiceImpl extends AbstractEntityService implements DomainService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@Autowired
private OAuth2ClientDao oauth2ClientDao;
@Autowired
private DomainDao domainDao;
@Override
public Domain saveDomain(TenantId tenantId, Domain domain) {
log.trace("Executing saveDomain [{}]", domain);
try {
Domain savedDomain = domainDao.save(tenantId, domain);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(savedDomain.getId()).entity(savedDomain).build());
return savedDomain;
} catch (Exception e) {
checkConstraintViolation(e,
Map.of("domain_unq_key", "Domain with such name and scheme already exists!"));
throw e;
}
}
@Override
public void updateOauth2Clients(TenantId tenantId, DomainId domainId, List<OAuth2ClientId> oAuth2ClientIds) {
log.trace("Executing updateOauth2Clients, domainId [{}], oAuth2ClientIds [{}]", domainId, oAuth2ClientIds);
Set<DomainOauth2Client> newClientList = oAuth2ClientIds.stream()
.map(clientId -> new DomainOauth2Client(domainId, clientId))
.collect(Collectors.toSet());
List<DomainOauth2Client> existingClients = domainDao.findOauth2ClientsByDomainId(tenantId, domainId);
List<DomainOauth2Client> toRemoveList = existingClients.stream()
.filter(client -> !newClientList.contains(client))
.toList();
newClientList.removeIf(existingClients::contains);
for (DomainOauth2Client client : toRemoveList) {
domainDao.removeOauth2Client(client);
}
for (DomainOauth2Client client : newClientList) {
domainDao.addOauth2Client(client);
}
}
@Override
public void deleteDomainById(TenantId tenantId, DomainId domainId) {
log.trace("Executing deleteDomainById [{}]", 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 PageData<DomainInfo> findDomainInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findDomainInfosByTenantId [{}]", tenantId);
PageData<Domain> domains = domainDao.findByTenantId(tenantId, pageLink);
return domains.mapData(this::getDomainInfo);
}
@Override
public DomainInfo findDomainInfoById(TenantId tenantId, DomainId domainId) {
log.trace("Executing findDomainInfoById [{}] [{}]", tenantId, domainId);
Domain domain = domainDao.findById(tenantId, domainId.getId());
return getDomainInfo(domain);
}
@Override
public boolean isOauth2Enabled(TenantId tenantId) {
log.trace("Executing isOauth2Enabled [{}] ", tenantId);
return domainDao.countDomainByTenantIdAndOauth2Enabled(tenantId, true) > 0;
}
@Override
public void deleteDomainsByTenantId(TenantId tenantId) {
log.trace("Executing deleteDomainsByTenantId, tenantId [{}]", tenantId);
domainDao.deleteByTenantId(tenantId);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteDomainsByTenantId(tenantId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findDomainById(tenantId, new DomainId(entityId.getId())));
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
deleteDomainById(tenantId, (DomainId) id);
}
private DomainInfo getDomainInfo(Domain domain) {
if (domain == null) {
return null;
}
List<OAuth2ClientInfo> clients = oauth2ClientDao.findByDomainId(domain.getUuidId()).stream()
.map(OAuth2ClientInfo::new)
.collect(Collectors.toList());
return new DomainInfo(domain, clients);
}
@Override
public EntityType getEntityType() {
return EntityType.DOMAIN;
}
}

40
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppDao.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.mobile;
import org.thingsboard.server.common.data.id.MobileAppId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
public interface MobileAppDao extends Dao<MobileApp> {
PageData<MobileApp> findByTenantId(TenantId tenantId, PageLink pageLink);
List<MobileAppOauth2Client> findOauth2ClientsByMobileAppId(TenantId tenantId, MobileAppId mobileAppId);
void addOauth2Client(MobileAppOauth2Client mobileAppOauth2Client);
void removeOauth2Client(MobileAppOauth2Client mobileAppOauth2Client);
void deleteByTenantId(TenantId tenantId);
}

154
dao/src/main/java/org/thingsboard/server/dao/mobile/MobileAppServiceImpl.java

@ -0,0 +1,154 @@
/**
* 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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.mobile.MobileApp;
import org.thingsboard.server.common.data.mobile.MobileAppInfo;
import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.oauth2.OAuth2ClientDao;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@Slf4j
@Service
public class MobileAppServiceImpl extends AbstractEntityService implements MobileAppService {
@Autowired
private OAuth2ClientDao oauth2ClientDao;
@Autowired
private MobileAppDao mobileAppDao;
@Override
public MobileApp saveMobileApp(TenantId tenantId, MobileApp mobileApp) {
log.trace("Executing saveMobileApp [{}]", mobileApp);
try {
MobileApp savedMobileApp = mobileAppDao.save(tenantId, mobileApp);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entity(savedMobileApp).build());
return savedMobileApp;
} catch (Exception e) {
checkConstraintViolation(e,
Map.of("mobile_app_unq_key", "Mobile app with such package already exists!"));
throw e;
}
}
@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 PageData<MobileAppInfo> findMobileAppInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findMobileAppInfosByTenantId [{}]", tenantId);
PageData<MobileApp> mobiles = mobileAppDao.findByTenantId(tenantId, pageLink);
return mobiles.mapData(this::getMobileAppInfo);
}
@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 getMobileAppInfo(mobileApp);
}
@Override
public void updateOauth2Clients(TenantId tenantId, MobileAppId mobileAppId, List<OAuth2ClientId> oAuth2ClientIds) {
log.trace("Executing updateOauth2Clients, mobileAppId [{}], oAuth2ClientIds [{}]", mobileAppId, oAuth2ClientIds);
Set<MobileAppOauth2Client> newClientList = oAuth2ClientIds.stream()
.map(clientId -> new MobileAppOauth2Client(mobileAppId, clientId))
.collect(Collectors.toSet());
List<MobileAppOauth2Client> existingClients = mobileAppDao.findOauth2ClientsByMobileAppId(tenantId, mobileAppId);
List<MobileAppOauth2Client> toRemoveList = existingClients.stream()
.filter(client -> !newClientList.contains(client))
.toList();
newClientList.removeIf(existingClients::contains);
for (MobileAppOauth2Client client : toRemoveList) {
mobileAppDao.removeOauth2Client(client);
}
for (MobileAppOauth2Client client : newClientList) {
mobileAppDao.addOauth2Client(client);
}
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
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
deleteMobileAppById(tenantId, (MobileAppId) id);
}
@Override
public void deleteMobileAppsByTenantId(TenantId tenantId) {
log.trace("Executing deleteMobileAppsByTenantId, tenantId [{}]", tenantId);
mobileAppDao.deleteByTenantId(tenantId);
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteMobileAppsByTenantId(tenantId);
}
private MobileAppInfo getMobileAppInfo(MobileApp mobileApp) {
List<OAuth2ClientInfo> clients = oauth2ClientDao.findByMobileAppId(mobileApp.getUuidId()).stream()
.map(OAuth2ClientInfo::new)
.collect(Collectors.toList());
return new MobileAppInfo(mobileApp, clients);
}
@Override
public EntityType getEntityType() {
return EntityType.MOBILE_APP;
}
}

39
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -429,24 +429,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_NAME_PROPERTY = "name";
public static final String DOMAIN_OAUTH2_ENABLED_PROPERTY = "oauth2_enabled";
public static final String DOMAIN_PROPAGATE_TO_EDGE_PROPERTY = "edge_enabled";
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_CLIENT_TABLE_NAME = "domain_oauth2_client";
public static final String DOMAIN_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id";
public static final String DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY = "domain_id";
/**
* Mobile application constants.
*/
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_CLIENT_TABLE_NAME = "mobile_app_oauth2_client";
public static final String MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY = "oauth2_client_id";
public static final String MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY = "mobile_app_id";
/**
* OAuth2 client constants.
*/
public static final String OAUTH2_CLIENT_TABLE_NAME = "oauth2_client";
public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_TABLE_NAME = "oauth2_client_registration_template";
public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id";
public static final String OAUTH2_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";

78
dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainEntity.java

@ -0,0 +1,78 @@
/**
* 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_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) {
super(domain);
if (domain.getTenantId() != null) {
this.tenantId = domain.getTenantId().getId();
}
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/DomainOauth2ClientCompositeKey.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 DomainOauth2ClientCompositeKey implements Serializable {
@Transient
private static final long serialVersionUID = -245388185894468455L;
private UUID domainId;
private UUID oauth2ClientId;
}

66
dao/src/main/java/org/thingsboard/server/dao/model/sql/DomainOauth2ClientEntity.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.DomainOauth2Client;
import org.thingsboard.server.common.data.id.DomainId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.ToData;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.DOMAIN_OAUTH2_CLIENT_TABLE_NAME;
@Data
@Entity
@Table(name = DOMAIN_OAUTH2_CLIENT_TABLE_NAME)
@IdClass(DomainOauth2ClientCompositeKey.class)
public final class DomainOauth2ClientEntity implements ToData<DomainOauth2Client> {
@Id
@Column(name = DOMAIN_OAUTH2_CLIENT_DOMAIN_ID_PROPERTY, columnDefinition = "uuid")
private UUID domainId;
@Id
@Column(name = ModelConstants.DOMAIN_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid")
private UUID oauth2ClientId;
public DomainOauth2ClientEntity() {
super();
}
public DomainOauth2ClientEntity(DomainOauth2Client domainOauth2Client) {
domainId = domainOauth2Client.getDomainId().getId();
oauth2ClientId = domainOauth2Client.getOAuth2ClientId().getId();
}
@Override
public DomainOauth2Client toData() {
DomainOauth2Client result = new DomainOauth2Client();
result.setDomainId(new DomainId(domainId));
result.setOAuth2ClientId(new OAuth2ClientId(oauth2ClientId));
return result;
}
}

48
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java → dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppEntity.java

@ -20,53 +20,59 @@ import jakarta.persistence.Entity;
import jakarta.persistence.Table;
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 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) {
if (mobile.getId() != null) {
this.setUuid(mobile.getId().getId());
}
this.setCreatedTime(mobile.getCreatedTime());
if (mobile.getOauth2ParamsId() != null) {
this.oauth2ParamsId = mobile.getOauth2ParamsId().getId();
public MobileAppEntity(MobileApp mobile) {
super(mobile);
if (mobile.getTenantId() != null) {
this.tenantId = mobile.getTenantId().getId();
}
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/MobileAppOauth2ClientCompositeKey.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 MobileAppOauth2ClientCompositeKey implements Serializable {
@Transient
private static final long serialVersionUID = -245388185894468455L;
private UUID mobileAppId;
private UUID oauth2ClientId;
}

65
dao/src/main/java/org/thingsboard/server/dao/model/sql/MobileAppOauth2ClientEntity.java

@ -0,0 +1,65 @@
/**
* 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.OAuth2ClientId;
import org.thingsboard.server.common.data.mobile.MobileAppOauth2Client;
import org.thingsboard.server.dao.model.ToData;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME;
@Data
@Entity
@Table(name = MOBILE_APP_OAUTH2_CLIENT_TABLE_NAME)
@IdClass(MobileAppOauth2ClientCompositeKey.class)
public final class MobileAppOauth2ClientEntity implements ToData<MobileAppOauth2Client> {
@Id
@Column(name = MOBILE_APP_OAUTH2_CLIENT_MOBILE_APP_ID_PROPERTY, columnDefinition = "uuid")
private UUID mobileAppId;
@Id
@Column(name = MOBILE_APP_OAUTH2_CLIENT_CLIENT_ID_PROPERTY, columnDefinition = "uuid")
private UUID oauth2ClientId;
public MobileAppOauth2ClientEntity() {
super();
}
public MobileAppOauth2ClientEntity(MobileAppOauth2Client domainOauth2Provider) {
mobileAppId = domainOauth2Provider.getMobileAppId().getId();
oauth2ClientId = domainOauth2Provider.getOAuth2ClientId().getId();
}
@Override
public MobileAppOauth2Client toData() {
MobileAppOauth2Client result = new MobileAppOauth2Client();
result.setMobileAppId(new MobileAppId(mobileAppId));
result.setOAuth2ClientId(new OAuth2ClientId(oauth2ClientId));
return result;
}
}

67
dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java → dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientEntity.java

@ -25,13 +25,13 @@ import jakarta.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.OAuth2ParamsId;
import org.thingsboard.server.common.data.id.OAuth2RegistrationId;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType;
import org.thingsboard.server.dao.model.BaseSqlEntity;
@ -46,11 +46,13 @@ import java.util.stream.Collectors;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@Table(name = ModelConstants.OAUTH2_REGISTRATION_TABLE_NAME)
public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration> {
@Table(name = ModelConstants.OAUTH2_CLIENT_TABLE_NAME)
public class OAuth2ClientEntity extends BaseSqlEntity<OAuth2Client> {
@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)
@ -112,32 +114,30 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
@Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo;
public OAuth2RegistrationEntity() {
public OAuth2ClientEntity() {
super();
}
public OAuth2RegistrationEntity(OAuth2Registration registration) {
if (registration.getId() != null) {
this.setUuid(registration.getId().getId());
public OAuth2ClientEntity(OAuth2Client oAuth2Client) {
super(oAuth2Client);
if (oAuth2Client.getTenantId() != null) {
this.tenantId = oAuth2Client.getTenantId().getId();
}
this.setCreatedTime(registration.getCreatedTime());
if (registration.getOauth2ParamsId() != null) {
this.oauth2ParamsId = registration.getOauth2ParamsId().getId();
}
this.clientId = registration.getClientId();
this.clientSecret = registration.getClientSecret();
this.authorizationUri = registration.getAuthorizationUri();
this.tokenUri = registration.getAccessTokenUri();
this.scope = registration.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
this.platforms = registration.getPlatforms() != null ? registration.getPlatforms().stream().map(Enum::name).reduce((result, element) -> result + "," + element).orElse("") : "";
this.userInfoUri = registration.getUserInfoUri();
this.userNameAttributeName = registration.getUserNameAttributeName();
this.jwkSetUri = registration.getJwkSetUri();
this.clientAuthenticationMethod = registration.getClientAuthenticationMethod();
this.loginButtonLabel = registration.getLoginButtonLabel();
this.loginButtonIcon = registration.getLoginButtonIcon();
this.additionalInfo = registration.getAdditionalInfo();
OAuth2MapperConfig mapperConfig = registration.getMapperConfig();
this.title = oAuth2Client.getTitle();
this.clientId = oAuth2Client.getClientId();
this.clientSecret = oAuth2Client.getClientSecret();
this.authorizationUri = oAuth2Client.getAuthorizationUri();
this.tokenUri = oAuth2Client.getAccessTokenUri();
this.scope = oAuth2Client.getScope().stream().reduce((result, element) -> result + "," + element).orElse("");
this.platforms = oAuth2Client.getPlatforms() != null ? oAuth2Client.getPlatforms().stream().map(Enum::name).reduce((result, element) -> result + "," + element).orElse("") : "";
this.userInfoUri = oAuth2Client.getUserInfoUri();
this.userNameAttributeName = oAuth2Client.getUserNameAttributeName();
this.jwkSetUri = oAuth2Client.getJwkSetUri();
this.clientAuthenticationMethod = oAuth2Client.getClientAuthenticationMethod();
this.loginButtonLabel = oAuth2Client.getLoginButtonLabel();
this.loginButtonIcon = oAuth2Client.getLoginButtonIcon();
this.additionalInfo = oAuth2Client.getAdditionalInfo();
OAuth2MapperConfig mapperConfig = oAuth2Client.getMapperConfig();
if (mapperConfig != null) {
this.allowUserCreation = mapperConfig.isAllowUserCreation();
this.activateUser = mapperConfig.isActivateUser();
@ -164,11 +164,12 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity<OAuth2Registration>
}
@Override
public OAuth2Registration toData() {
OAuth2Registration registration = new OAuth2Registration();
registration.setId(new OAuth2RegistrationId(id));
public OAuth2Client toData() {
OAuth2Client registration = new OAuth2Client();
registration.setId(new OAuth2ClientId(id));
registration.setCreatedTime(createdTime);
registration.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/OAuth2ClientInfoEntity.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.OAuth2ClientId;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
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 OAuth2ClientInfoEntity extends BaseSqlEntity<OAuth2ClientInfo> {
private String platforms;
private String title;
public OAuth2ClientInfoEntity() {
super();
}
public OAuth2ClientInfoEntity(UUID id, long createdTime, String platforms, String title) {
this.id = id;
this.createdTime = createdTime;
this.platforms = platforms;
this.title = title;
}
@Override
public OAuth2ClientInfo toData() {
OAuth2ClientInfo oAuth2ClientInfo = new OAuth2ClientInfo();
oAuth2ClientInfo.setId(new OAuth2ClientId(id));
oAuth2ClientInfo.setCreatedTime(createdTime);
oAuth2ClientInfo.setTitle(title);
oAuth2ClientInfo.setPlatforms(StringUtils.isNotEmpty(platforms) ? Arrays.stream(platforms.split(","))
.map(str -> PlatformType.valueOf(str)).collect(Collectors.toList()) : Collections.emptyList());
return oAuth2ClientInfo;
}
}

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 jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.Table;
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 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 jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
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 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;
}
}

36
dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java

@ -21,7 +21,9 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.oauth2.OAuth2Registration;
import org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import java.util.UUID;
@ -30,29 +32,29 @@ 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));
return registration == null ?
null : toSpringClientRegistration(registration);
OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(registrationId)));
return oAuth2Client == null ?
null : toSpringClientRegistration(oAuth2Client);
}
private ClientRegistration toSpringClientRegistration(OAuth2Registration registration){
String registrationId = registration.getUuidId().toString();
private ClientRegistration toSpringClientRegistration(OAuth2Client oAuth2Client){
String registrationId = oAuth2Client.getUuidId().toString();
return ClientRegistration.withRegistrationId(registrationId)
.clientName(registration.getName())
.clientId(registration.getClientId())
.authorizationUri(registration.getAuthorizationUri())
.clientSecret(registration.getClientSecret())
.tokenUri(registration.getAccessTokenUri())
.scope(registration.getScope())
.clientName(oAuth2Client.getName())
.clientId(oAuth2Client.getClientId())
.authorizationUri(oAuth2Client.getAuthorizationUri())
.clientSecret(oAuth2Client.getClientSecret())
.tokenUri(oAuth2Client.getAccessTokenUri())
.scope(oAuth2Client.getScope())
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.userInfoUri(registration.getUserInfoUri())
.userNameAttributeName(registration.getUserNameAttributeName())
.jwkSetUri(registration.getJwkSetUri())
.clientAuthenticationMethod(registration.getClientAuthenticationMethod().equals("POST") ?
.userInfoUri(oAuth2Client.getUserInfoUri())
.userNameAttributeName(oAuth2Client.getUserNameAttributeName())
.jwkSetUri(oAuth2Client.getJwkSetUri())
.clientAuthenticationMethod(oAuth2Client.getClientAuthenticationMethod().equals("POST") ?
ClientAuthenticationMethod.CLIENT_SECRET_POST : ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
.redirectUri(defaultRedirectUriTemplate)
.build();

49
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientDao.java

@ -0,0 +1,49 @@
/**
* 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 org.thingsboard.server.common.data.id.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import java.util.List;
import java.util.UUID;
public interface OAuth2ClientDao extends Dao<OAuth2Client> {
PageData<OAuth2Client> findByTenantId(UUID tenantId, PageLink pageLink);
List<OAuth2Client> findEnabledByDomainName(String domainName);
List<OAuth2Client> findEnabledByPkgNameAndPlatformType(String pkgName, PlatformType platformType);
List<OAuth2Client> findByDomainId(UUID domainId);
List<OAuth2Client> findByMobileAppId(UUID mobileAppId);
String findAppSecret(UUID id, String pkgName);
void deleteByTenantId(UUID tenantId);
List<OAuth2Client> findByIds(UUID tenantId, List<OAuth2ClientId> oAuth2ClientIds);
boolean isPropagateToEdge(TenantId tenantId, UUID oAuth2ClientId);
}

159
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientServiceImpl.java

@ -0,0 +1,159 @@
/**
* 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.OAuth2ClientId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import org.thingsboard.server.common.data.oauth2.PlatformType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.service.DataValidator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@Service("OAuth2ClientService")
public class OAuth2ClientServiceImpl extends AbstractEntityService implements OAuth2ClientService {
@Autowired
private OAuth2ClientDao oauth2ClientDao;
@Autowired
private DataValidator<OAuth2Client> oAuth2ClientDataValidator;
@Override
public List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByDomainName(String domainName) {
log.trace("Executing findOAuth2ClientLoginInfosByDomainName [{}] ", domainName);
return oauth2ClientDao.findEnabledByDomainName(domainName)
.stream()
.map(OAuth2Utils::toClientLoginInfo)
.collect(Collectors.toList());
}
@Override
public List<OAuth2ClientLoginInfo> findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType(String pkgName, PlatformType platformType) {
log.trace("Executing findOAuth2ClientLoginInfosByMobilePkgNameAndPlatformType pkgName=[{}] platformType=[{}]",pkgName, platformType);
return oauth2ClientDao.findEnabledByPkgNameAndPlatformType(pkgName, platformType)
.stream()
.map(OAuth2Utils::toClientLoginInfo)
.collect(Collectors.toList());
}
@Override
@Transactional
public OAuth2Client saveOAuth2Client(TenantId tenantId, OAuth2Client oAuth2Client) {
log.trace("Executing saveOAuth2Client [{}]", oAuth2Client);
oAuth2ClientDataValidator.validate(oAuth2Client, OAuth2Client::getTenantId);
OAuth2Client savedOauth2Client = oauth2ClientDao.save(tenantId, oAuth2Client);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(tenantId).entityId(savedOauth2Client.getId()).entity(savedOauth2Client).build());
return savedOauth2Client;
}
@Override
public OAuth2Client findOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId) {
log.trace("Executing findOAuth2ClientById [{}]", oAuth2ClientId);
return oauth2ClientDao.findById(tenantId, oAuth2ClientId.getId());
}
@Override
public List<OAuth2Client> findOAuth2ClientsByTenantId(TenantId tenantId) {
log.trace("Executing findOAuth2ClientsByTenantId [{}]", tenantId);
return oauth2ClientDao.findByTenantId(tenantId.getId(), new PageLink(Integer.MAX_VALUE)).getData();
}
@Override
public String findAppSecret(OAuth2ClientId oAuth2ClientId, String pkgName) {
log.trace("Executing findAppSecret oAuth2ClientId = [{}] pkgName = [{}]", oAuth2ClientId, pkgName);
return oauth2ClientDao.findAppSecret(oAuth2ClientId.getId(), pkgName);
}
@Override
@Transactional
public void deleteOAuth2ClientById(TenantId tenantId, OAuth2ClientId oAuth2ClientId) {
log.trace("Executing deleteOAuth2ClientById [{}]", oAuth2ClientId);
oauth2ClientDao.removeById(tenantId, oAuth2ClientId.getId());
eventPublisher.publishEvent(DeleteEntityEvent.builder()
.tenantId(tenantId)
.entityId(oAuth2ClientId)
.build());
}
@Override
public void deleteOauth2ClientsByTenantId(TenantId tenantId) {
log.trace("Executing deleteOauth2ClientsByTenantId, tenantId [{}]", tenantId);
oauth2ClientDao.deleteByTenantId(tenantId.getId());
}
@Override
public PageData<OAuth2ClientInfo> findOAuth2ClientInfosByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findOAuth2ClientInfosByTenantId tenantId=[{}]", tenantId);
PageData<OAuth2Client> clients = oauth2ClientDao.findByTenantId(tenantId.getId(), pageLink);
return clients.mapData(OAuth2ClientInfo::new);
}
@Override
public List<OAuth2ClientInfo> findOAuth2ClientInfosByIds(TenantId tenantId, List<OAuth2ClientId> oAuth2ClientIds) {
log.trace("Executing findQueueStatsByIds, tenantId [{}], oAuth2ClientIds [{}]", tenantId, oAuth2ClientIds);
return oauth2ClientDao.findByIds(tenantId.getId(), oAuth2ClientIds)
.stream()
.map(OAuth2ClientInfo::new)
.collect(Collectors.toList());
}
@Override
public boolean isPropagateOAuth2ClientToEdge(TenantId tenantId, OAuth2ClientId oAuth2ClientId) {
log.trace("Executing isPropagateOAuth2ClientToEdge, tenantId [{}], oAuth2ClientId [{}]", tenantId, oAuth2ClientId);
return oauth2ClientDao.isPropagateToEdge(tenantId, oAuth2ClientId.getId());
}
@Override
public void deleteByTenantId(TenantId tenantId) {
deleteOauth2ClientsByTenantId(tenantId);
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(findOAuth2ClientById(tenantId, new OAuth2ClientId(entityId.getId())));
}
@Override
@Transactional
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
deleteOAuth2ClientById(tenantId, (OAuth2ClientId) id);
}
@Override
public EntityType getEntityType() {
return EntityType.OAUTH2_CLIENT;
}
}

34
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java

@ -1,34 +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 org.thingsboard.server.common.data.oauth2.OAuth2Registration;
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;
import java.util.UUID;
public interface OAuth2RegistrationDao extends Dao<OAuth2Registration> {
List<OAuth2Registration> findEnabledByDomainSchemesDomainNameAndPkgNameAndPlatformType(List<SchemeType> domainSchemes, String domainName, String pkgName, PlatformType platformType);
List<OAuth2Registration> findByOAuth2ParamsId(UUID oauth2ParamsId);
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 jakarta.transaction.Transactional;
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 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!");
}
}
}
}
};
}

107
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java

@ -15,117 +15,18 @@
*/
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;
import org.thingsboard.server.common.data.oauth2.OAuth2ClientLoginInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
public class OAuth2Utils {
public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s";
public static OAuth2ClientInfo toClientInfo(OAuth2Registration registration) {
OAuth2ClientInfo client = new OAuth2ClientInfo();
public static OAuth2ClientLoginInfo toClientLoginInfo(OAuth2Client registration) {
OAuth2ClientLoginInfo client = new OAuth2ClientLoginInfo();
client.setName(registration.getLoginButtonLabel());
client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, registration.getUuidId().toString()));
client.setIcon(registration.getLoginButtonIcon());
return client;
}
public static 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;
}
}

6
dao/src/main/java/org/thingsboard/server/dao/service/StringLengthValidator.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.validation.Length;
@Slf4j
public class StringLengthValidator implements ConstraintValidator<Length, Object> {
private int max;
private int min;
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
@ -34,14 +35,15 @@ public class StringLengthValidator implements ConstraintValidator<Length, Object
} else {
return true;
}
if (StringUtils.isEmpty(stringValue)) {
if (stringValue == null) {
return true;
}
return stringValue.length() <= max;
return stringValue.length() >= min && stringValue.length() <= max;
}
@Override
public void initialize(Length constraintAnnotation) {
this.max = constraintAnnotation.max();
this.min = constraintAnnotation.min();
}
}

80
dao/src/main/java/org/thingsboard/server/dao/service/validator/Oauth2ClientDataValidator.java

@ -0,0 +1,80 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service.validator;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.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.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Component
@AllArgsConstructor
public class Oauth2ClientDataValidator extends DataValidator<OAuth2Client> {
@Override
protected void validateDataImpl(TenantId tenantId, OAuth2Client oAuth2Client) {
OAuth2MapperConfig mapperConfig = oAuth2Client.getMapperConfig();
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!");
}
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save