diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java index bc5893c4a9..a2dd53bd82 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java @@ -60,6 +60,8 @@ import java.util.stream.Collectors; public final class PluginProcessingContext implements PluginContext { private static final Executor executor = Executors.newSingleThreadExecutor(); + public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; + public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; private final SharedPluginProcessingContext pluginCtx; private final Optional securityCtx; @@ -296,25 +298,25 @@ public final class PluginProcessingContext implements PluginContext { throw new IllegalStateException("Not Implemented!"); } } else { - callback.onSuccess(this, Boolean.TRUE); + callback.onSuccess(this, ValidationResult.ok()); } } private void validateDevice(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isSystemAdmin()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { ListenableFuture deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); Futures.addCallback(deviceFuture, getCallback(callback, device -> { if (device == null) { - return Boolean.FALSE; + return ValidationResult.entityNotFound("Device with requested id wasn't found!"); } else { if (!device.getTenantId().equals(ctx.getTenantId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); } else { - return Boolean.TRUE; + return ValidationResult.ok(); } } })); @@ -323,19 +325,19 @@ public final class PluginProcessingContext implements PluginContext { private void validateAsset(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isSystemAdmin()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { ListenableFuture assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId())); Futures.addCallback(assetFuture, getCallback(callback, asset -> { if (asset == null) { - return Boolean.FALSE; + return ValidationResult.entityNotFound("Asset with requested id wasn't found!"); } else { if (!asset.getTenantId().equals(ctx.getTenantId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!"); } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); } else { - return Boolean.TRUE; + return ValidationResult.ok(); } } })); @@ -344,19 +346,19 @@ public final class PluginProcessingContext implements PluginContext { private void validateRule(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isCustomerUser()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { ListenableFuture ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId())); Futures.addCallback(ruleFuture, getCallback(callback, rule -> { if (rule == null) { - return Boolean.FALSE; + return ValidationResult.entityNotFound("Rule with requested id wasn't found!"); } else { if (ctx.isTenantAdmin() && !rule.getTenantId().equals(ctx.getTenantId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Rule doesn't belong to the current Tenant!"); } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Rule is not in system scope!"); } else { - return Boolean.TRUE; + return ValidationResult.ok(); } } })); @@ -365,19 +367,19 @@ public final class PluginProcessingContext implements PluginContext { private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isCustomerUser()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { ListenableFuture pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId())); Futures.addCallback(pluginFuture, getCallback(callback, plugin -> { if (plugin == null) { - return Boolean.FALSE; + return ValidationResult.entityNotFound("Plugin with requested id wasn't found!"); } else { if (ctx.isTenantAdmin() && !plugin.getTenantId().equals(ctx.getTenantId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Plugin doesn't belong to the current Tenant!"); } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Plugin is not in system scope!"); } else { - return Boolean.TRUE; + return ValidationResult.ok(); } } })); @@ -386,19 +388,19 @@ public final class PluginProcessingContext implements PluginContext { private void validateCustomer(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isSystemAdmin()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { ListenableFuture customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId())); Futures.addCallback(customerFuture, getCallback(callback, customer -> { if (customer == null) { - return Boolean.FALSE; + return ValidationResult.entityNotFound("Customer with requested id wasn't found!"); } else { if (!customer.getTenantId().equals(ctx.getTenantId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!"); } else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) { - return Boolean.FALSE; + return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); } else { - return Boolean.TRUE; + return ValidationResult.ok(); } } })); @@ -407,12 +409,20 @@ public final class PluginProcessingContext implements PluginContext { private void validateTenant(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { if (ctx.isCustomerUser()) { - callback.onSuccess(this, Boolean.FALSE); + callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else if (ctx.isSystemAdmin()) { - callback.onSuccess(this, Boolean.TRUE); + callback.onSuccess(this, ValidationResult.ok()); } else { ListenableFuture tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); - Futures.addCallback(tenantFuture, getCallback(callback, tenant -> tenant != null && tenant.getId().equals(ctx.getTenantId()))); + Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { + if (tenant == null) { + return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); + } else if (!tenant.getId().equals(ctx.getTenantId())) { + return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); + } else { + return ValidationResult.ok(); + } + })); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java index c8d79b41b2..735ef167aa 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationCallback.java @@ -16,6 +16,9 @@ package org.thingsboard.server.actors.plugin; import com.hazelcast.util.function.Consumer; +import org.thingsboard.server.extensions.api.exception.AccessDeniedException; +import org.thingsboard.server.extensions.api.exception.EntityNotFoundException; +import org.thingsboard.server.extensions.api.exception.InternalErrorException; import org.thingsboard.server.extensions.api.exception.UnauthorizedException; import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginContext; @@ -23,7 +26,7 @@ import org.thingsboard.server.extensions.api.plugins.PluginContext; /** * Created by ashvayka on 21.02.17. */ -public class ValidationCallback implements PluginCallback { +public class ValidationCallback implements PluginCallback { private final PluginCallback callback; private final Consumer action; @@ -34,11 +37,30 @@ public class ValidationCallback implements PluginCallback { } @Override - public void onSuccess(PluginContext ctx, Boolean value) { - if (value) { + public void onSuccess(PluginContext ctx, ValidationResult result) { + ValidationResultCode resultCode = result.getResultCode(); + if (resultCode == ValidationResultCode.OK) { action.accept(ctx); } else { - onFailure(ctx, new UnauthorizedException("Permission denied.")); + Exception e; + switch (resultCode) { + case ENTITY_NOT_FOUND: + e = new EntityNotFoundException(result.getMessage()); + break; + case UNAUTHORIZED: + e = new UnauthorizedException(result.getMessage()); + break; + case ACCESS_DENIED: + e = new AccessDeniedException(result.getMessage()); + break; + case INTERNAL_ERROR: + e = new InternalErrorException(result.getMessage()); + break; + default: + e = new UnauthorizedException("Permission denied."); + break; + } + onFailure(ctx, e); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java new file mode 100644 index 0000000000..f0a59e19d2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2017 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.actors.plugin; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ValidationResult { + + private final ValidationResultCode resultCode; + private final String message; + + public static ValidationResult ok() { + return new ValidationResult(ValidationResultCode.OK, "Ok"); + } + + public static ValidationResult accessDenied(String message) { + return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message); + } + + public static ValidationResult entityNotFound(String message) { + return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message); + } + + public static ValidationResult unauthorized(String message) { + return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message); + } + + public static ValidationResult internalError(String message) { + return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResultCode.java b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResultCode.java new file mode 100644 index 0000000000..3a89c5462f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResultCode.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2017 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.actors.plugin; + +public enum ValidationResultCode { + OK, + UNAUTHORIZED, + ACCESS_DENIED, + ENTITY_NOT_FOUND, + INTERNAL_ERROR +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/AccessDeniedException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/AccessDeniedException.java new file mode 100644 index 0000000000..e94a3e5e94 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/AccessDeniedException.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 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.extensions.api.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class AccessDeniedException extends Exception implements ToErrorResponseEntity { + + public AccessDeniedException(String message) { + super(message); + } + + @Override + public ResponseEntity toErrorResponseEntity() { + return new ResponseEntity<>(getMessage(), HttpStatus.FORBIDDEN); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/EntityNotFoundException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/EntityNotFoundException.java new file mode 100644 index 0000000000..afa2aa7262 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/EntityNotFoundException.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 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.extensions.api.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class EntityNotFoundException extends Exception implements ToErrorResponseEntity { + + public EntityNotFoundException(String message) { + super(message); + } + + @Override + public ResponseEntity toErrorResponseEntity() { + return new ResponseEntity<>(getMessage(), HttpStatus.NOT_FOUND); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/InternalErrorException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/InternalErrorException.java new file mode 100644 index 0000000000..1f421a1272 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/InternalErrorException.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2017 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.extensions.api.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +public class InternalErrorException extends Exception implements ToErrorResponseEntity { + + public InternalErrorException(String message) { + super(message); + } + + @Override + public ResponseEntity toErrorResponseEntity() { + return new ResponseEntity<>(getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/ToErrorResponseEntity.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/ToErrorResponseEntity.java new file mode 100644 index 0000000000..11d5939d18 --- /dev/null +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/ToErrorResponseEntity.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2017 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.extensions.api.exception; + +import org.springframework.http.ResponseEntity; + +public interface ToErrorResponseEntity { + + ResponseEntity toErrorResponseEntity(); + +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java index b1288d93d4..f83cfa309c 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/exception/UnauthorizedException.java @@ -15,13 +15,20 @@ */ package org.thingsboard.server.extensions.api.exception; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + /** * Created by ashvayka on 21.02.17. */ -public class UnauthorizedException extends Exception { +public class UnauthorizedException extends Exception implements ToErrorResponseEntity { public UnauthorizedException(String message) { super(message); } + @Override + public ResponseEntity toErrorResponseEntity() { + return new ResponseEntity<>(getMessage(), HttpStatus.UNAUTHORIZED); + } } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java index c71b171871..f88b796bf4 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.extensions.core.plugin.rpc.handlers; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.Setter; @@ -26,6 +27,7 @@ import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity; import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginContext; @@ -65,7 +67,7 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { if (pathParams.length == 2) { String method = pathParams[0].toUpperCase(); if (DataConstants.ONEWAY.equals(method) || DataConstants.TWOWAY.equals(method)) { - DeviceId deviceId = DeviceId.fromString(pathParams[1]); + final TenantId tenantId = ctx.getSecurityCtx().orElseThrow(() -> new IllegalStateException("Security context is empty!")).getTenantId(); JsonNode rpcRequestBody = jsonMapper.readTree(request.getRequestBody()); RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), @@ -74,29 +76,10 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { cmd.setTimeout(rpcRequestBody.get("timeout").asLong()); } - final TenantId tenantId = ctx.getSecurityCtx().orElseThrow(() -> new IllegalStateException("Security context is empty!")).getTenantId(); - - ctx.checkAccess(deviceId, new PluginCallback() { - @Override - public void onSuccess(PluginContext ctx, Void value) { - long timeout = cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout; - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); - ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), - tenantId, - deviceId, - DataConstants.ONEWAY.equals(method), - System.currentTimeMillis() + timeout, - body - ); - rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder())); - } + boolean oneWay = DataConstants.ONEWAY.equals(method); - @Override - public void onFailure(PluginContext ctx, Exception e) { - msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); - } - }); - valid = true; + DeviceId deviceId = DeviceId.fromString(pathParams[1]); + valid = handleDeviceRPCRequest(ctx, msg, tenantId, deviceId, cmd, oneWay); } } } catch (IOException e) { @@ -109,6 +92,36 @@ public class RpcRestMsgHandler extends DefaultRestMsgHandler { } } + private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException { + long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout); + ctx.checkAccess(deviceId, new PluginCallback() { + @Override + public void onSuccess(PluginContext ctx, Void value) { + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); + ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), + tenantId, + deviceId, + oneWay, + timeout, + body + ); + rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder())); + } + + @Override + public void onFailure(PluginContext ctx, Exception e) { + ResponseEntity response; + if (e instanceof ToErrorResponseEntity) { + response = ((ToErrorResponseEntity)e).toErrorResponseEntity(); + } else { + response = new ResponseEntity(HttpStatus.UNAUTHORIZED); + } + msg.getResponseHolder().setResult(response); + } + }); + return true; + } + public void reply(PluginContext ctx, DeferredResult responseWriter, FromDeviceRpcResponse response) { Optional rpcError = response.getError(); if (rpcError.isPresent()) {