Merge pull request #10239 from AndriiLandiak/feature/edge-oauth-support

Edge - OAuth2 support
This commit is contained in:
Volodymyr Babak 2024-03-27 14:52:01 +02:00 committed by GitHub
commit cb9236ff78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
34 changed files with 496 additions and 153 deletions

View File

@ -14,7 +14,6 @@
-- limitations under the License.
--
-- create new attribute_kv table schema
DO
$$
@ -105,4 +104,11 @@ EXCEPTION
ROLLBACK;
RAISE EXCEPTION 'Error during COPY: %', SQLERRM;
END
$$;
$$;
-- OAUTH2 PARAMS ALTER TABLE START
ALTER TABLE oauth2_params
ADD COLUMN IF NOT EXISTS edge_enabled boolean DEFAULT false;
-- OAUTH2 PARAMS ALTER TABLE END

View File

@ -42,6 +42,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProces
import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
@ -126,6 +127,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@Autowired
protected ApplicationEventPublisher eventPublisher;
@ -231,6 +235,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case TB_RESOURCE:
resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
break;
case OAUTH2:
oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg);
break;
default:
log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
}

View File

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.device.DeviceService;
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.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ResourceService;
@ -61,6 +62,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.profile.DevicePr
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
@ -151,6 +153,9 @@ public class EdgeContextComponent {
@Autowired
private ResourceService resourceService;
@Autowired
private OAuth2Service oAuth2Service;
@Autowired
private RateLimitService rateLimitService;
@ -220,6 +225,9 @@ public class EdgeContextComponent {
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@Autowired
private EdgeMsgConstructor edgeMsgConstructor;

View File

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.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;
@ -159,43 +160,41 @@ public class EdgeEventSourcingListener {
private boolean isValidSaveEntityEventForEdgeProcessing(SaveEntityEvent<?> event) {
Object entity = event.getEntity();
Object oldEntity = event.getOldEntity();
switch (event.getEntityId().getEntityType()) {
case RULE_CHAIN:
if (entity instanceof RuleChain) {
RuleChain ruleChain = (RuleChain) entity;
return RuleChainType.EDGE.equals(ruleChain.getType());
}
break;
case USER:
if (entity instanceof User) {
User user = (User) entity;
if (Authority.SYS_ADMIN.equals(user.getAuthority())) {
if (event.getEntityId() != null) {
switch (event.getEntityId().getEntityType()) {
case RULE_CHAIN:
if (entity instanceof RuleChain ruleChain) {
return RuleChainType.EDGE.equals(ruleChain.getType());
}
break;
case USER:
if (entity instanceof User user) {
if (Authority.SYS_ADMIN.equals(user.getAuthority())) {
return false;
}
if (oldEntity != null) {
User oldUser = (User) oldEntity;
cleanUpUserAdditionalInfo(oldUser);
cleanUpUserAdditionalInfo(user);
return !user.equals(oldUser);
}
}
break;
case OTA_PACKAGE:
if (entity instanceof OtaPackageInfo otaPackageInfo) {
return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData();
}
break;
case ALARM:
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm) {
return false;
}
if (oldEntity != null) {
User oldUser = (User) oldEntity;
cleanUpUserAdditionalInfo(oldUser);
cleanUpUserAdditionalInfo(user);
return !user.equals(oldUser);
}
}
break;
case OTA_PACKAGE:
if (entity instanceof OtaPackageInfo) {
OtaPackageInfo otaPackageInfo = (OtaPackageInfo) entity;
return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData();
}
break;
case ALARM:
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm) {
break;
case TENANT:
return !event.getCreated();
case API_USAGE_STATE, EDGE:
return false;
}
break;
case TENANT:
return !event.getCreated();
case API_USAGE_STATE:
case EDGE:
return false;
}
}
// Default: If the entity doesn't match any of the conditions, consider it as valid.
return true;
@ -206,8 +205,7 @@ public class EdgeEventSourcingListener {
if (user.getAdditionalInfo() instanceof NullNode) {
user.setAdditionalInfo(null);
}
if (user.getAdditionalInfo() instanceof ObjectNode) {
ObjectNode additionalInfo = ((ObjectNode) user.getAdditionalInfo());
if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) {
additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS);
additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS);
if (additionalInfo.isEmpty()) {
@ -221,6 +219,8 @@ public class EdgeEventSourcingListener {
private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) {
return EdgeEventType.ALARM_COMMENT;
} else if (entity instanceof OAuth2Info) {
return EdgeEventType.OAUTH2;
}
return null;
}
@ -228,6 +228,8 @@ 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;
}
@ -238,4 +240,5 @@ public class EdgeEventSourcingListener {
}
return isCreated ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED;
}
}

View File

@ -705,6 +705,8 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion);
case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion);
case OAUTH2:
return ctx.getOAuth2EdgeProcessor().convertOAuth2EventToDownlink(edgeEvent);
default:
log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent);
return null;

View File

@ -30,6 +30,7 @@ import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetc
import org.thingsboard.server.service.edge.rpc.fetch.DevicesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OAuth2EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher;
@ -79,6 +80,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()));
}
}

View File

@ -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.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.queue.util.TbCoreComponent;
@Component
@TbCoreComponent
public class OAuth2MsgConstructor {
public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Info oAuth2Info) {
return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Info)).build();
}
}

View File

@ -46,7 +46,7 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher {
List<EdgeEvent> result = new ArrayList<>();
result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(),
EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null));
// @voba - returns PageData object to be in sync with other fetchers
// returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
}
}

View File

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.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;
@AllArgsConstructor
@Slf4j
public class OAuth2EdgeEventFetcher implements EdgeEventFetcher {
private final OAuth2Service oAuth2Service;
@Override
public PageLink getPageLink(int pageSize) {
return null;
}
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = new ArrayList<>();
OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info();
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2,
EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info)));
// returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
}
}

View File

@ -51,9 +51,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -75,6 +74,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService;
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.relation.RelationService;
@ -101,6 +101,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMs
import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory;
@ -226,6 +227,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected ResourceService resourceService;
@Autowired
protected OAuth2Service oAuth2Service;
@Autowired
@Lazy
protected TbQueueProducerProvider producerProvider;
@ -257,6 +261,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor;
@Autowired
protected OAuth2MsgConstructor oAuth2MsgConstructor;
@Autowired
protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory;
@ -392,18 +399,13 @@ public abstract class BaseEdgeProcessor {
protected ListenableFuture<Void> processActionForAllEdges(TenantId tenantId, EdgeEventType type,
EdgeEventActionType actionType, EntityId entityId,
EdgeId sourceEdgeId) {
JsonNode body, EdgeId sourceEdgeId) {
List<ListenableFuture<Void>> futures = new ArrayList<>();
if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<TenantId> tenantsIds;
do {
tenantsIds = tenantService.findTenantsIds(pageLink);
for (TenantId tenantId1 : tenantsIds.getData()) {
futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, null, sourceEdgeId));
}
pageLink = pageLink.nextPageLink();
} while (tenantsIds.hasNext());
PageDataIterable<TenantId> tenantIds = new PageDataIterable<>(link -> tenantService.findTenantsIds(link), 1024);
for (TenantId tenantId1 : tenantIds) {
futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, body, sourceEdgeId));
}
} else {
futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId, null, sourceEdgeId);
}
@ -416,22 +418,13 @@ public abstract class BaseEdgeProcessor {
EntityId entityId,
JsonNode body,
EdgeId sourceEdgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = edgeService.findEdgesByTenantId(tenantId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (Edge edge : pageData.getData()) {
if (!edge.getId().equals(sourceEdgeId)) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body));
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantId(tenantId, link), 1024);
for (Edge edge : edges) {
if (!edge.getId().equals(sourceEdgeId)) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body));
}
} while (pageData != null && pageData.hasNext());
}
return futures;
}
@ -539,35 +532,24 @@ public abstract class BaseEdgeProcessor {
}
private ListenableFuture<Void> updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (RuleChain ruleChain : pageData.getData()) {
if (!ruleChain.getId().equals(processingRuleChainId)) {
List<RuleChainConnectionInfo> connectionInfos =
ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections();
if (connectionInfos != null && !connectionInfos.isEmpty()) {
for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
futures.add(saveEdgeEvent(tenantId,
edgeId,
EdgeEventType.RULE_CHAIN_METADATA,
EdgeEventActionType.UPDATED,
ruleChain.getId(),
null));
}
}
}
PageDataIterable<RuleChain> ruleChains = new PageDataIterable<>(link -> ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, link), 1024);
for (RuleChain ruleChain : ruleChains) {
List<RuleChainConnectionInfo> connectionInfos =
ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections();
if (connectionInfos != null && !connectionInfos.isEmpty()) {
for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
futures.add(saveEdgeEvent(tenantId,
edgeId,
EdgeEventType.RULE_CHAIN_METADATA,
EdgeEventActionType.UPDATED,
ruleChain.getId(),
null));
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
}
@ -577,7 +559,7 @@ public abstract class BaseEdgeProcessor {
case UPDATED:
case DELETED:
case CREDENTIALS_UPDATED: // used by USER entity
return processActionForAllEdges(tenantId, type, actionType, entityId, sourceEdgeId);
return processActionForAllEdges(tenantId, type, actionType, entityId, null, sourceEdgeId);
default:
return Futures.immediateFuture(null);
}

View File

@ -29,8 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
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.common.data.page.PageDataIterable;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@ -85,20 +84,11 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
switch (actionType) {
case UPDATED:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>();
do {
pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (Edge edge : pageData.getData()) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024);
for (Edge edge : edges) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
}
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));

View File

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.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.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.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@Slf4j
@Component
@TbCoreComponent
public class OAuth2EdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertOAuth2EventToDownlink(EdgeEvent edgeEvent) {
DownlinkMsg downlinkMsg = null;
OAuth2Info oAuth2Info = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Info.class);
if (oAuth2Info != null) {
OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Info);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2UpdateMsg(oAuth2UpdateMsg)
.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);
}
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
return processActionForAllEdges(tenantId, type, actionType, null, JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()), null);
}
}

View File

@ -76,6 +76,7 @@ import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
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.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg;
@ -631,7 +632,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
List<Edge> loadedEdges = new ArrayList<>();
PageLink pageLink = new PageLink(23);
PageData<Edge> pageData = null;
PageData<Edge> pageData;
do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
new TypeReference<>() {
@ -885,6 +886,7 @@ 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.expectMessageAmount(24);
edgeImitator.connect();
@ -973,8 +975,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popQueueMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof QueueUpdateMsg) {
QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) message;
if (message instanceof QueueUpdateMsg queueUpdateMsg) {
Queue queue = JacksonUtil.fromString(queueUpdateMsg.getEntity(), Queue.class, true);
Assert.assertNotNull(queue);
if (msgType.equals(queueUpdateMsg.getMsgType()) && name.equals(queue.getName())) {
@ -988,8 +989,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popRuleChainMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof RuleChainUpdateMsg) {
RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) message;
if (message instanceof RuleChainUpdateMsg ruleChainUpdateMsg) {
RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true);
Assert.assertNotNull(ruleChain);
if (msgType.equals(ruleChainUpdateMsg.getMsgType())
@ -1005,8 +1005,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) {
for (AbstractMessage message : messages) {
if (message instanceof AdminSettingsUpdateMsg) {
AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message;
if (message instanceof AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
AdminSettings adminSettings = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true);
Assert.assertNotNull(adminSettings);
if (key.equals(adminSettings.getKey())) {
@ -1020,8 +1019,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popDeviceProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof DeviceProfileUpdateMsg) {
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) message;
if (message instanceof DeviceProfileUpdateMsg deviceProfileUpdateMsg) {
DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true);
Assert.assertNotNull(deviceProfile);
if (msgType.equals(deviceProfileUpdateMsg.getMsgType())
@ -1036,8 +1034,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popDeviceMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof DeviceUpdateMsg) {
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) message;
if (message instanceof DeviceUpdateMsg deviceUpdateMsg) {
Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true);
Assert.assertNotNull(device);
if (msgType.equals(deviceUpdateMsg.getMsgType())
@ -1052,8 +1049,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAssetProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof AssetProfileUpdateMsg) {
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) message;
if (message instanceof AssetProfileUpdateMsg assetProfileUpdateMsg) {
AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true);
Assert.assertNotNull(assetProfile);
if (msgType.equals(assetProfileUpdateMsg.getMsgType())
@ -1068,8 +1064,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAssetMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) {
if (message instanceof AssetUpdateMsg) {
AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) message;
if (message instanceof AssetUpdateMsg assetUpdateMsg) {
Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true);
Assert.assertNotNull(asset);
if (msgType.equals(assetUpdateMsg.getMsgType())
@ -1084,8 +1079,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popUserMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String email, Authority authority) {
for (AbstractMessage message : messages) {
if (message instanceof UserUpdateMsg) {
UserUpdateMsg userUpdateMsg = (UserUpdateMsg) message;
if (message instanceof UserUpdateMsg userUpdateMsg) {
User user = JacksonUtil.fromString(userUpdateMsg.getEntity(), User.class, true);
Assert.assertNotNull(user);
if (msgType.equals(userUpdateMsg.getMsgType())
@ -1101,8 +1095,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popCustomerMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String title) {
for (AbstractMessage message : messages) {
if (message instanceof CustomerUpdateMsg) {
CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) message;
if (message instanceof CustomerUpdateMsg customerUpdateMsg) {
Customer customer = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true);
Assert.assertNotNull(customer);
if (msgType.equals(customerUpdateMsg.getMsgType())
@ -1117,8 +1110,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popTenantMsg(List<AbstractMessage> messages, TenantId tenantId1) {
for (AbstractMessage message : messages) {
if (message instanceof TenantUpdateMsg) {
TenantUpdateMsg tenantUpdateMsg = (TenantUpdateMsg) message;
if (message instanceof TenantUpdateMsg tenantUpdateMsg) {
Tenant tenant = JacksonUtil.fromString(tenantUpdateMsg.getEntity(), Tenant.class, true);
Assert.assertNotNull(tenant);
if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantUpdateMsg.getMsgType())
@ -1133,8 +1125,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popTenantProfileMsg(List<AbstractMessage> messages, TenantProfileId tenantProfileId) {
for (AbstractMessage message : messages) {
if (message instanceof TenantProfileUpdateMsg) {
TenantProfileUpdateMsg tenantProfileUpdateMsg = (TenantProfileUpdateMsg) message;
if (message instanceof TenantProfileUpdateMsg tenantProfileUpdateMsg) {
TenantProfile tenantProfile = JacksonUtil.fromString(tenantProfileUpdateMsg.getEntity(), TenantProfile.class, true);
Assert.assertNotNull(tenantProfile);
if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantProfileUpdateMsg.getMsgType())
@ -1236,4 +1227,5 @@ public class EdgeControllerTest extends AbstractControllerTest {
edgeUpgradeInstructionsService.setAppVersion("3.6.2.6");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
}
}

View File

@ -494,7 +494,7 @@ public class HomePageApiTest extends AbstractControllerTest {
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList(
return new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build()

View File

@ -63,6 +63,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
@ -85,6 +86,7 @@ import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
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.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
@ -140,6 +142,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
edgeImitator.expectMessageAmount(21);
edgeImitator.connect();
@ -538,6 +541,18 @@ 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());

View File

@ -0,0 +1,113 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edge;
import com.google.common.collect.Lists;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
@DaoSqlTest
public class OAuth2EdgeTest extends AbstractEdgeTest {
@Test
public void testOAuth2Support() throws Exception {
loginSysAdmin();
// enable oauth
edgeImitator.allowIgnoredTypes();
edgeImitator.expectMessageAmount(1);
OAuth2Info oAuth2Info = createDefaultOAuth2Info();
oAuth2Info = doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.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);
// disable oauth support
edgeImitator.expectMessageAmount(1);
oAuth2Info.setEnabled(false);
oAuth2Info.setEdgeEnabled(false);
doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg);
oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage;
result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertEquals(oAuth2Info, result);
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
loginTenantAdmin();
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, true, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build()
))
.mobileInfos(Collections.emptyList())
.clientRegistrations(Lists.newArrayList(
validRegistrationInfo()
))
.build()
));
}
private 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();
}
}

View File

@ -44,6 +44,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -314,6 +315,11 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(resourceUpdateMsg));
}
}
if (downlinkMsg.getOAuth2UpdateMsgCount() > 0) {
for (OAuth2UpdateMsg oAuth2UpdateMsg : downlinkMsg.getOAuth2UpdateMsgList()) {
result.add(saveDownlinkMsg(oAuth2UpdateMsg));
}
}
if (downlinkMsg.hasEdgeConfiguration()) {
result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration()));
}

View File

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService;
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.relation.RelationService;
@ -81,6 +82,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstruct
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV1;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV2;
import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV1;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV2;
@ -128,6 +130,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.profile.DevicePr
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV1;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV2;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV1;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV2;
@ -239,6 +242,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean
protected ResourceService resourceService;
@MockBean
protected OAuth2Service oAuth2Service;
@MockBean
@Lazy
protected TbQueueProducerProvider producerProvider;
@ -360,6 +366,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean
protected WidgetMsgConstructorV2 widgetMsgConstructorV2;
@MockBean
protected OAuth2MsgConstructor oAuth2MsgConstructor;
@MockBean
protected AlarmEdgeProcessorV1 alarmProcessorV1;
@ -417,6 +426,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean
protected RelationEdgeProcessorV2 relationEdgeProcessorV2;
@MockBean
protected OAuth2EdgeProcessor oAuth2EdgeProcessor;
@SpyBean
protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory;

View File

@ -24,6 +24,7 @@ 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);

View File

@ -41,7 +41,8 @@ public enum EdgeEventType {
ADMIN_SETTINGS(true, null),
OTA_PACKAGE(true, EntityType.OTA_PACKAGE),
QUEUE(true, EntityType.QUEUE),
TB_RESOURCE(true, EntityType.TB_RESOURCE);
TB_RESOURCE(true, EntityType.TB_RESOURCE),
OAUTH2(true, null);
private final boolean allEdgesRelated;

View File

@ -35,6 +35,8 @@ import java.util.List;
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", required = true)
private List<OAuth2ParamsInfo> oauth2ParamsInfos;
}

View File

@ -30,11 +30,13 @@ import org.thingsboard.server.common.data.id.TenantId;
public class OAuth2Params extends BaseData<OAuth2ParamsId> {
private boolean enabled;
private boolean edgeEnabled;
private TenantId tenantId;
public OAuth2Params(OAuth2Params oauth2Params) {
super(oauth2Params);
this.enabled = oauth2Params.enabled;
this.edgeEnabled = oauth2Params.edgeEnabled;
this.tenantId = oauth2Params.tenantId;
}
}

View File

@ -467,6 +467,10 @@ message ResourceUpdateMsg {
string entity = 11;
}
message OAuth2UpdateMsg {
string entity = 1;
}
message RuleChainMetadataRequestMsg {
int64 ruleChainIdMSB = 1;
int64 ruleChainIdLSB = 2;
@ -668,5 +672,6 @@ message DownlinkMsg {
repeated TenantProfileUpdateMsg tenantProfileUpdateMsg = 27;
repeated ResourceUpdateMsg resourceUpdateMsg = 28;
repeated AlarmCommentUpdateMsg alarmCommentUpdateMsg = 29;
repeated OAuth2UpdateMsg oAuth2UpdateMsg = 30;
}

View File

@ -427,6 +427,7 @@ public class ModelConstants {
*/
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 OAUTH2_REGISTRATION_TABLE_NAME = "oauth2_registration";

View File

@ -39,6 +39,9 @@ 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;
@ -48,6 +51,7 @@ public class OAuth2ParamsEntity extends BaseSqlEntity<OAuth2Params> {
}
this.setCreatedTime(oauth2Params.getCreatedTime());
this.enabled = oauth2Params.isEnabled();
this.edgeEnabled = oauth2Params.isEdgeEnabled();
if (oauth2Params.getTenantId() != null) {
this.tenantId = oauth2Params.getTenantId().getId();
}
@ -60,6 +64,7 @@ public class OAuth2ParamsEntity extends BaseSqlEntity<OAuth2Params> {
oauth2Params.setCreatedTime(createdTime);
oauth2Params.setTenantId(TenantId.fromUUID(tenantId));
oauth2Params.setEnabled(enabled);
oauth2Params.setEdgeEnabled(edgeEnabled);
return oauth2Params;
}
}

View File

@ -40,6 +40,7 @@ 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;
@ -58,6 +59,7 @@ 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 ";
@ -116,6 +118,7 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se
});
}
});
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(TenantId.SYS_TENANT_ID).entity(oauth2Info).build());
}
@Override
@ -123,7 +126,8 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se
log.trace("Executing findOAuth2Info");
OAuth2Info oauth2Info = new OAuth2Info();
List<OAuth2Params> oauth2ParamsList = oauth2ParamsDao.find(TenantId.SYS_TENANT_ID);
oauth2Info.setEnabled(oauth2ParamsList.stream().anyMatch(param -> param.isEnabled()));
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 -> {

View File

@ -88,6 +88,7 @@ public class OAuth2Utils {
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;
}

View File

@ -559,6 +559,7 @@ CREATE TABLE IF NOT EXISTS key_dictionary
CREATE TABLE IF NOT EXISTS oauth2_params (
id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY,
enabled boolean,
edge_enabled boolean,
tenant_id uuid,
created_time bigint NOT NULL
);

View File

@ -47,7 +47,7 @@ import java.util.stream.Collectors;
@DaoSqlTest
public class OAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList());
private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, false, Collections.emptyList());
@Autowired
protected OAuth2Service oAuth2Service;
@ -66,7 +66,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testSaveHttpAndMixedDomainsTogether() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -87,7 +87,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testSaveHttpsAndMixedDomainsTogether() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(),
@ -153,7 +153,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
Assert.assertNotNull(foundOAuth2Info);
Assert.assertEquals(oAuth2Info, foundOAuth2Info);
OAuth2Info newOAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info newOAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build()
@ -194,7 +194,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
List<OAuth2RegistrationInfo> thirdGroup = Lists.newArrayList(
validRegistrationInfo()
);
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -318,7 +318,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
validRegistrationInfo(),
validRegistrationInfo()
);
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -363,7 +363,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testGetDisabledOAuth2Clients() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -402,7 +402,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testFindAllRegistrations() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -451,7 +451,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testFindRegistrationById() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -495,7 +495,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testFindAppSecret() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -547,7 +547,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
@Test
public void testFindClientsByPackageAndPlatform() {
OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList(
OAuth2Info oAuth2Info = new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -596,7 +596,7 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList(
return new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(),
@ -664,4 +664,5 @@ public class OAuth2ServiceTest extends AbstractServiceTest {
.appSecret(appSecret != null ? appSecret : StringUtils.randomAlphanumeric(24))
.build();
}
}

View File

@ -29,13 +29,18 @@
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<mat-card-content style="padding-top: 16px;">
<form [formGroup]="oauth2SettingsForm" (ngSubmit)="save()">
<fieldset [disabled]="isLoading$ | async">
<mat-checkbox formControlName="enabled">
{{ 'admin.oauth2.enable' | translate }}
</mat-checkbox>
<section *ngIf="oauth2SettingsForm.get('enabled').value" style="margin-top: 1em;">
<fieldset class="tb-form-panel stroked gap-xs-12" style="margin-bottom: 16px" [disabled]="isLoading$ | async">
<div class="tb-form-row no-border no-padding column-xs">
<mat-chip-listbox formControlName="enabled">
<mat-chip-option [value]="true">{{ 'admin.oauth2.enable' | translate }}</mat-chip-option>
</mat-chip-listbox>
<mat-chip-listbox formControlName="edgeEnabled">
<mat-chip-option [value]="true">{{ 'admin.oauth2.edge-enable' | translate }}</mat-chip-option>
</mat-chip-listbox>
</div>
<section *ngIf="oauth2SettingsForm.get('enabled').value && oauth2ParamsInfos.controls.length">
<ng-container formArrayName="oauth2ParamsInfos">
<div class="container">
<div>
<mat-accordion multi>
<ng-container *ngFor="let oauth2ParamsInfo of oauth2ParamsInfos.controls; let i = index; trackBy: trackByItem">
<mat-expansion-panel [formGroupName]="i">

View File

@ -13,7 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../../../scss/constants';
:host{
.gap-xs-12 {
@media #{$mat-xs} {
gap: 12px;
}
}
.mdc-evolution-chip-set .mdc-evolution-chip {
margin-top: 0;
margin-bottom: 0;
}
.checkbox-row {
margin-top: 1em;
}
@ -31,7 +44,7 @@
}
.container{
margin-bottom: 1em;
margin-bottom: 16px;
.tb-highlight{
margin: 0;

View File

@ -204,13 +204,23 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
private buildOAuth2SettingsForm(): void {
this.oauth2SettingsForm = this.fb.group({
oauth2ParamsInfos: this.fb.array([]),
enabled: [false]
enabled: [false],
edgeEnabled: [{value: false, disabled: true}]
});
this.subscriptions.push(this.oauth2SettingsForm.get('enabled').valueChanges.subscribe(enabled => {
if (enabled) {
this.oauth2SettingsForm.get('edgeEnabled').enable();
} else {
this.oauth2SettingsForm.get('edgeEnabled').patchValue(false);
this.oauth2SettingsForm.get('edgeEnabled').disable();
}
}))
}
private initOAuth2Settings(oauth2Info: OAuth2Info): void {
if (oauth2Info) {
this.oauth2SettingsForm.patchValue({enabled: oauth2Info.enabled}, {emitEvent: false});
this.oauth2SettingsForm.patchValue({enabled: oauth2Info.enabled, edgeEnabled: oauth2Info.edgeEnabled});
oauth2Info.oauth2ParamsInfos.forEach((oauth2ParamsInfo) => {
this.oauth2ParamsInfos.push(this.buildOAuth2ParamsInfoForm(oauth2ParamsInfo));
});

View File

@ -18,6 +18,7 @@ import { HasUUID } from '@shared/models/id/has-uuid';
export interface OAuth2Info {
enabled: boolean;
edgeEnabled: boolean;
oauth2ParamsInfos: OAuth2ParamsInfo[];
}

View File

@ -283,6 +283,7 @@
"domain-schema-https": "HTTPS",
"domain-schema-mixed": "HTTP+HTTPS",
"enable": "Enable OAuth2 settings",
"edge-enable": "Propagate to Edge",
"domains": "Domains",
"mobile-apps": "Mobile applications",
"no-mobile-apps": "No applications configured",