using Microsoft.AspNetCore.Components.Authorization; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; using ZB.MOM.WW.ScadaBridge.Communication; using ZB.MOM.WW.ScadaBridge.Security; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services; /// /// Default implementation — a thin facade over /// that enforces the /// CentralUI-side Design-role trust boundary and translates transport /// exceptions into a typed result. Mirrors /// . /// public sealed class BindingTester : IBindingTester { private readonly CommunicationService _communication; private readonly AuthenticationStateProvider _auth; /// /// Initializes a new instance of the . /// /// Central-side cluster communication service. /// Authentication state provider used for the Design-role guard. public BindingTester(CommunicationService communication, AuthenticationStateProvider auth) { _communication = communication ?? throw new ArgumentNullException(nameof(communication)); _auth = auth ?? throw new ArgumentNullException(nameof(auth)); } /// public async Task ReadAsync( string siteId, string connectionName, IReadOnlyList tagPaths, CancellationToken ct = default) { // CentralUI-side role guard — sites don't enforce envelope-level // roles, so the Design check must happen here before any cross-cluster // traffic. Use HasClaim against JwtTokenService.RoleClaimType (not // IsInRole, per c1e16cf). var state = await _auth.GetAuthenticationStateAsync(); if (!state.User.HasClaim(JwtTokenService.RoleClaimType, "Design")) { return new ReadTagValuesResult( Array.Empty(), new ReadTagValuesFailure(ReadTagValuesFailureKind.ServerError, "Not authorized.")); } try { return await _communication.ReadTagValuesAsync( siteId, new ReadTagValuesCommand(connectionName, tagPaths), ct); } catch (TimeoutException ex) { // Akka Ask timed out — the site (or its OPC UA session) didn't // answer within CommunicationOptions.QueryTimeout. Surface as a // typed Timeout failure so the dialog can render an inline banner. return new ReadTagValuesResult( Array.Empty(), new ReadTagValuesFailure(ReadTagValuesFailureKind.Timeout, ex.Message)); } catch (OperationCanceledException) { // Caller-initiated cancel — propagate so Blazor can drop the // response cleanly. Distinct from Timeout. throw; } catch (Exception ex) { // Any other transport / serialization failure: keep the dialog // alive with a typed banner. return new ReadTagValuesResult( Array.Empty(), new ReadTagValuesFailure(ReadTagValuesFailureKind.ServerError, ex.Message)); } } }