fix(central-ui): resolve CentralUI-002/003/004 — site-scope enforcement, per-circuit console capture, cached auth state
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDeployment)]
|
||||
@inject ITemplateEngineRepository TemplateEngineRepository
|
||||
@inject ISiteRepository SiteRepository
|
||||
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
|
||||
@inject DebugStreamService DebugStreamService
|
||||
@inject IJSRuntime JS
|
||||
@implements IDisposable
|
||||
@@ -296,7 +297,9 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
||||
// Site scoping (CentralUI-002): a scoped Deployment user may only
|
||||
// debug sites they are permitted on.
|
||||
_sites = await SiteScope.FilterSitesAsync(await SiteRepository.GetAllSitesAsync());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -358,6 +361,14 @@
|
||||
_siteInstances.Clear();
|
||||
_selectedInstanceId = 0;
|
||||
if (_selectedSiteId == 0) return;
|
||||
// Site scoping (CentralUI-002): re-check the claim server-side — a query
|
||||
// string or stale localStorage value could name a site outside the grant.
|
||||
if (!await SiteScope.IsSiteAllowedAsync(_selectedSiteId))
|
||||
{
|
||||
_selectedSiteId = 0;
|
||||
_toast.ShowError("You are not permitted to debug instances on that site.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_siteInstances = (await TemplateEngineRepository.GetInstancesBySiteIdAsync(_selectedSiteId))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDeployment)]
|
||||
@inject IDeploymentManagerRepository DeploymentManagerRepository
|
||||
@inject ITemplateEngineRepository TemplateEngineRepository
|
||||
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
|
||||
@implements IDisposable
|
||||
|
||||
<div class="container-fluid mt-3">
|
||||
@@ -245,13 +246,23 @@
|
||||
_errorMessage = null;
|
||||
try
|
||||
{
|
||||
_records = (await DeploymentManagerRepository.GetAllDeploymentRecordsAsync())
|
||||
.OrderByDescending(r => r.DeployedAt)
|
||||
.ToList();
|
||||
|
||||
// Build instance name lookup
|
||||
// Build instance lookups first — site scoping (CentralUI-002) filters
|
||||
// deployment records by the site of their instance.
|
||||
var instances = await TemplateEngineRepository.GetAllInstancesAsync();
|
||||
_instanceNames = instances.ToDictionary(i => i.Id, i => i.UniqueName);
|
||||
var instanceSiteIds = instances.ToDictionary(i => i.Id, i => i.SiteId);
|
||||
|
||||
var systemWide = await SiteScope.IsSystemWideAsync();
|
||||
var permittedSiteIds = systemWide
|
||||
? null
|
||||
: await SiteScope.PermittedSiteIdsAsync();
|
||||
|
||||
_records = (await DeploymentManagerRepository.GetAllDeploymentRecordsAsync())
|
||||
.Where(r => permittedSiteIds == null
|
||||
|| (instanceSiteIds.TryGetValue(r.InstanceId, out var sid)
|
||||
&& permittedSiteIds.Contains(sid)))
|
||||
.OrderByDescending(r => r.DeployedAt)
|
||||
.ToList();
|
||||
|
||||
_totalPages = Math.Max(1, (int)Math.Ceiling(_records.Count / (double)PageSize));
|
||||
if (_currentPage > _totalPages) _currentPage = 1;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDeployment)]
|
||||
@inject ITemplateEngineRepository TemplateEngineRepository
|
||||
@inject ISiteRepository SiteRepository
|
||||
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
|
||||
@inject InstanceService InstanceService
|
||||
@inject IFlatteningPipeline FlatteningPipeline
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@@ -377,6 +378,17 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Site scoping (CentralUI-002): a scoped Deployment user must not be
|
||||
// able to configure or deploy an instance on a site outside their
|
||||
// grant by navigating straight to its URL.
|
||||
if (!await SiteScope.IsSiteAllowedAsync(_instance.SiteId))
|
||||
{
|
||||
_instance = null;
|
||||
_errorMessage = "You are not permitted to manage instances on this site.";
|
||||
_loading = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Identity
|
||||
var template = await TemplateEngineRepository.GetTemplateByIdAsync(_instance.TemplateId);
|
||||
_templateName = template?.Name ?? $"#{_instance.TemplateId}";
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDeployment)]
|
||||
@inject ITemplateEngineRepository TemplateEngineRepository
|
||||
@inject ISiteRepository SiteRepository
|
||||
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
|
||||
@inject InstanceService InstanceService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
@@ -93,7 +94,9 @@
|
||||
try
|
||||
{
|
||||
_templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
|
||||
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
||||
// Site scoping (CentralUI-002): a scoped Deployment user may only
|
||||
// create instances on sites they are permitted on.
|
||||
_sites = await SiteScope.FilterSitesAsync(await SiteRepository.GetAllSitesAsync());
|
||||
|
||||
_allAreas.Clear();
|
||||
foreach (var site in _sites)
|
||||
@@ -124,6 +127,13 @@
|
||||
if (string.IsNullOrWhiteSpace(_createName)) { _formError = "Instance name is required."; return; }
|
||||
if (_createTemplateId == 0) { _formError = "Select a template."; return; }
|
||||
if (_createSiteId == 0) { _formError = "Select a site."; return; }
|
||||
// Site scoping (CentralUI-002): re-check server-side before the mutating
|
||||
// command, independent of what the site dropdown was populated with.
|
||||
if (!await SiteScope.IsSiteAllowedAsync(_createSiteId))
|
||||
{
|
||||
_formError = "You are not permitted to create instances on the selected site.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
@inject AreaService AreaService
|
||||
@inject InstanceService InstanceService
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject IDialogService Dialog
|
||||
@@ -225,8 +226,13 @@
|
||||
_errorMessage = null;
|
||||
try
|
||||
{
|
||||
_allInstances = (await TemplateEngineRepository.GetAllInstancesAsync()).ToList();
|
||||
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
|
||||
// Site scoping (CentralUI-002): a scoped Deployment user only sees the
|
||||
// sites — and therefore the areas/instances — they are permitted on.
|
||||
_sites = await SiteScope.FilterSitesAsync(await SiteRepository.GetAllSitesAsync());
|
||||
var permittedSiteIds = _sites.Select(s => s.Id).ToHashSet();
|
||||
_allInstances = (await TemplateEngineRepository.GetAllInstancesAsync())
|
||||
.Where(i => permittedSiteIds.Contains(i.SiteId))
|
||||
.ToList();
|
||||
_templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
|
||||
|
||||
_allAreas.Clear();
|
||||
|
||||
Reference in New Issue
Block a user