Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions roles/database/files/sql/creation/fworch-fill-config.sql
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ insert into config (config_key, config_value, config_user) VALUES ('modModelledM
insert into config (config_key, config_value, config_user) VALUES ('modModelledMarkerLocation', 'rulename', 0);
insert into config (config_key, config_value, config_user) VALUES ('ruleRecognitionOption', '{"nwRegardIp":true,"nwRegardName":false,"nwRegardGroupName":false,"nwResolveGroup":false,"svcRegardPortAndProt":true,"svcRegardName":false,"svcRegardGroupName":false,"svcResolveGroup":true,"svcSplitPortRanges":false}', 0);
insert into config (config_key, config_value, config_user) VALUES ('availableReportTypes', '[1,2,3,4,5,6,7,8,9,10,21,22,31,32]', 0);
insert into config (config_key, config_value, config_user) VALUES ('reportTypeVisibilityByRole', '{}', 0);
insert into config (config_key, config_value, config_user) VALUES ('varianceAnalysisSleepTime', '0', 0);
insert into config (config_key, config_value, config_user) VALUES ('varianceAnalysisStartAt', '00:00:00', 0);
insert into config (config_key, config_value, config_user) VALUES ('varianceAnalysisSync', 'false', 0);
Expand Down
14 changes: 14 additions & 0 deletions roles/database/files/sql/idempotent/fworch-texts.sql
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ INSERT INTO txt VALUES ('whats_new_facts', 'German', '
<li>Compliance-Diff-Berichte k&ouml;nnen optional auf Regeln beschr&auml;nkt werden, die im ausgew&auml;hlten Zeitraum neu nicht konform geworden sind.</li>
<li>In den allgemeinen Flow-Einstellungen kann &uuml;ber Namensmuster festgelegt werden, welche Flow-Netzwerkgruppen Zonen sind; der REST-Endpunkt <code>flow/getAddressGroups</code> kann diese Zonen getrennt ausliefern.</li>
<li>Flow-Netzwerkobjekte aus Antr&auml;gen speichern ihre Adressbereiche als Host-Adressen; ein Bereich aus IPv4 und IPv6 wird abgelehnt.</li>
<li>F&uuml;r einzelne Rollen kann die Sichtbarkeit jedes Reporttyps auf sichtbar, nicht sichtbar oder geerbt (vom Standardverhalten) gesetzt werden.</li>
<li>Details: siehe <a target="_blank" href="https://github.com/CactuseSecurity/firewall-orchestrator/releases">Release Notes.</a></li>
</ul>
');
Expand All @@ -560,6 +561,7 @@ INSERT INTO txt VALUES ('whats_new_facts', 'English', '
<li>Compliance diff reports can optionally be limited to rules that newly became non-compliant during the selected interval.</li>
<li>The general flow settings define through name patterns which flow network groups are zones; the REST endpoint <code>flow/getAddressGroups</code> can return those zones as a separate list.</li>
<li>Flow network objects created from requests store their address ranges as host addresses; a range mixing IPv4 and IPv6 is refused.</li>
<li>For individual roles, the visibility of each report type can be set to visible, not visible, or inherited (from the default behaviour).</li>
<li>Details: see <a target="_blank" href="https://github.com/CactuseSecurity/firewall-orchestrator/releases">release notes.</a></li>
</ul>
');
Expand Down Expand Up @@ -3302,6 +3304,16 @@ INSERT INTO txt VALUES ('select_file', 'German', 'Datei ausw&auml;hle
INSERT INTO txt VALUES ('select_file', 'English', 'Select file');
INSERT INTO txt VALUES ('availableReportTypes', 'German', 'Verf&uuml;gbare Reporttypen');
INSERT INTO txt VALUES ('availableReportTypes', 'English', 'Available Report Types');
INSERT INTO txt VALUES ('reportTypeVisibilityByRole', 'German', 'Reporttyp-Sichtbarkeit je Rolle');
INSERT INTO txt VALUES ('reportTypeVisibilityByRole', 'English', 'Report Type Visibility by Role');
INSERT INTO txt VALUES ('role', 'German', 'Rolle');
INSERT INTO txt VALUES ('role', 'English', 'Role');
INSERT INTO txt VALUES ('inherited', 'German', 'Geerbt');
INSERT INTO txt VALUES ('inherited', 'English', 'Inherited');
INSERT INTO txt VALUES ('visible', 'German', 'Sichtbar');
INSERT INTO txt VALUES ('visible', 'English', 'Visible');
INSERT INTO txt VALUES ('not_visible', 'German', 'Nicht sichtbar');
INSERT INTO txt VALUES ('not_visible', 'English', 'Not Visible');
INSERT INTO txt VALUES ('varianceAnalysisSync', 'German', 'Synchroner Soll-Ist-Abgleich');
INSERT INTO txt VALUES ('varianceAnalysisSync', 'English', 'Synchroneous Variance Analysis');
INSERT INTO txt VALUES ('varianceAnalysisRefresh','German', 'Soll-Ist-Abgleich auf Schaltfl&auml;che');
Expand Down Expand Up @@ -6535,6 +6547,8 @@ INSERT INTO txt VALUES ('H5458', 'German', 'Rezertifizierungsmodus: Methode der
INSERT INTO txt VALUES ('H5458', 'English', 'Recertification Mode: Type of recertification.');
INSERT INTO txt VALUES ('H5459', 'German', 'Rezert Check - Benachrichtigungen: Legt fest, wer in welchem Zeitintervall vor dem Rezert-Termin benachrichtigt wird.');
INSERT INTO txt VALUES ('H5459', 'English', 'Recert Check - Notifications: Defines who will be notified in which time interval before the recert deadline.');
INSERT INTO txt VALUES ('H5460', 'German', 'Reporttyp-Sichtbarkeit je Rolle: F&uuml;r eine ausgew&auml;hlte Rolle kann je Reporttyp festgelegt werden, ob dieser sichtbar, nicht sichtbar oder geerbt (vom Standardverhalten der verf&uuml;gbaren Reporttypen) sein soll. Diese Einstellung schr&auml;nkt die Liste der verf&uuml;gbaren Reporttypen zus&auml;tzlich f&uuml;r Nutzer der jeweiligen Rolle ein bzw. erweitert sie.');
INSERT INTO txt VALUES ('H5460', 'English', 'Report Type Visibility by Role: For a selected role, each report type can be set to visible, not visible or inherited (from the default behaviour of the available report types). This setting additionally restricts or extends the list of available report types for users of the respective role.');
INSERT INTO txt VALUES ('H5461', 'German', 'Jeder Nutzer kann seine eigene bevorzugte Sprache f&uuml;r die Anwendung einstellen.<br>
Alle Texte werden in dieser Sprache dargestellt, soweit verf&uuml;gbar. Wenn nicht, wird die Standardsprache verwendet. Wenn der Text auch dort nicht verf&uuml;gbar ist, wird Englisch genutzt.
Die Standardsprache beim ersten Anmelden kann vom Admin f&uuml;r alle Nutzer in den <a href="/help/settings/defaults">Standardeinstellungen</a> definiert werden.<br><br>
Expand Down
44 changes: 29 additions & 15 deletions roles/lib/files/FWO.Api.Client/ApiConnectionRoleScopeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,44 +99,58 @@ public static Task<TResult> RunWithReportingRole<TResult>(this ApiConnection api
}

/// <summary>
/// Sets the best available role for generating the given report type.
/// Sets the best available role for generating the given report type. Roles listed in
/// <paramref name="excludedRoles"/> (e.g. roles explicitly set to "Not Visible" for this
/// report type) are never selected, even if the user holds them.
/// </summary>
public static void SetBestRoleForReport(this ApiConnection apiConnection, ClaimsPrincipal user,
ReportType reportType)
ReportType reportType, IEnumerable<string>? excludedRoles = null)
{
apiConnection.SetBestRole(user, GetReportRoles(reportType));
apiConnection.SetBestRole(user, GetReportRoles(reportType, excludedRoles));
}

/// <summary>
/// Runs an API operation with the best available role for the given report type.
/// Runs an API operation with the best available role for the given report type. Roles listed
/// in <paramref name="excludedRoles"/> (e.g. roles explicitly set to "Not Visible" for this
/// report type) are never selected, even if the user holds them.
/// </summary>
public static Task<TResult> RunWithBestRoleForReport<TResult>(this ApiConnection apiConnection,
ClaimsPrincipal user, ReportType reportType, Func<Task<TResult>> action)
ClaimsPrincipal user, ReportType reportType, Func<Task<TResult>> action, IEnumerable<string>? excludedRoles = null)
{
return apiConnection.RunWithBestRole(user, GetReportRoles(reportType), action);
return apiConnection.RunWithBestRole(user, GetReportRoles(reportType, excludedRoles), action);
}

private static List<string> GetReportRoles(ReportType reportType)
private static List<string> GetReportRoles(ReportType reportType, IEnumerable<string>? excludedRoles = null)
{
List<string> roles;
if (reportType == ReportType.Owners || reportType.IsComplianceReport())
{
return [Roles.Admin, Roles.FwAdmin, Roles.Auditor];
roles = [Roles.Admin, Roles.FwAdmin, Roles.Auditor];
}
if (reportType.IsModellingReport())
else if (reportType.IsModellingReport())
{
return [Roles.Admin, Roles.Modeller, Roles.Recertifier, Roles.Auditor];
roles = [Roles.Admin, Roles.Modeller, Roles.Recertifier, Roles.Auditor];
}
if (reportType.IsWorkflowReport())
else if (reportType.IsWorkflowReport())
{
return [Roles.Admin, Roles.FwAdmin, Roles.Auditor, Roles.Requester,
roles = [Roles.Admin, Roles.FwAdmin, Roles.Auditor, Roles.Requester,
Roles.Approver, Roles.Planner, Roles.Implementer, Roles.Reviewer];
}
if (reportType.IsDeviceRelatedReport())
else if (reportType.IsDeviceRelatedReport())
{
return [Roles.Admin, Roles.FwAdmin, Roles.ReporterViewAll, Roles.Reporter,
roles = [Roles.Admin, Roles.FwAdmin, Roles.ReporterViewAll, Roles.Reporter,
Roles.Recertifier, Roles.Auditor];
}
return ReportingRoles;
else
{
roles = [.. ReportingRoles];
}

if (excludedRoles != null)
{
roles = [.. roles.Where(role => !excludedRoles.Contains(role, StringComparer.OrdinalIgnoreCase))];
}
return roles;
}

/// <summary>
Expand Down
110 changes: 106 additions & 4 deletions roles/lib/files/FWO.Basics/ReportType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,97 @@ public enum ReportType
Owners = 51
}

/// <summary>
/// Per-role override for whether a report type is shown/usable. "Inherited" falls back to the
/// standard role-category and global-availability rules; "Visible"/"NotVisible" override them.
/// </summary>
public enum ReportTypeVisibilityOption
{
Inherited = 0,
Visible = 1,
NotVisible = 2
}

/// <summary>
/// Parses and serializes the per-role report-type visibility overrides stored in
/// <c>ConfigData.ReportTypeVisibilityByRole</c>. Report types are keyed by their underlying
/// int value on the wire (not by enum name) to keep JSON dictionary-key serialization simple
/// and stable.
/// </summary>
public static class ReportTypeRoleVisibilityConfig
{
public static Dictionary<string, Dictionary<ReportType, ReportTypeVisibilityOption>> Parse(string? json)
{
Dictionary<string, Dictionary<ReportType, ReportTypeVisibilityOption>> result = [];
if (string.IsNullOrWhiteSpace(json))
{
return result;
}

Dictionary<string, Dictionary<int, ReportTypeVisibilityOption>>? raw;
try
{
raw = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, Dictionary<int, ReportTypeVisibilityOption>>>(json);
}
catch (System.Text.Json.JsonException)
{
return result;
}

if (raw == null)
{
return result;
}

foreach (KeyValuePair<string, Dictionary<int, ReportTypeVisibilityOption>> roleEntry in raw)
{
Dictionary<ReportType, ReportTypeVisibilityOption> byType = [];
foreach (KeyValuePair<int, ReportTypeVisibilityOption> typeEntry in roleEntry.Value)
{
byType[(ReportType)typeEntry.Key] = typeEntry.Value;
}
result[roleEntry.Key] = byType;
}
return result;
}

public static string Serialize(Dictionary<string, Dictionary<ReportType, ReportTypeVisibilityOption>> data)
{
Dictionary<string, Dictionary<int, ReportTypeVisibilityOption>> raw = [];
foreach (KeyValuePair<string, Dictionary<ReportType, ReportTypeVisibilityOption>> roleEntry in data)
{
Dictionary<int, ReportTypeVisibilityOption> byType = [];
foreach (KeyValuePair<ReportType, ReportTypeVisibilityOption> typeEntry in roleEntry.Value)
{
// Only persist explicit overrides - "Inherited" is the implicit default and need not be stored.
if (typeEntry.Value != ReportTypeVisibilityOption.Inherited)
{
byType[(int)typeEntry.Key] = typeEntry.Value;
}
}
if (byType.Count > 0)
{
raw[roleEntry.Key] = byType;
}
}
return System.Text.Json.JsonSerializer.Serialize(raw);
}

public static ReportTypeVisibilityOption GetOption(
Dictionary<string, Dictionary<ReportType, ReportTypeVisibilityOption>> data, string role, ReportType reportType)
{
// Role names come from LDAP both when the setting is saved and when it is looked up,
// but compare case-insensitively to stay consistent with ExecutionModeHelper's role matching.
KeyValuePair<string, Dictionary<ReportType, ReportTypeVisibilityOption>> roleEntry = data.FirstOrDefault(
entry => entry.Key.Equals(role, StringComparison.OrdinalIgnoreCase));
if (roleEntry.Value != null && roleEntry.Value.TryGetValue(reportType, out ReportTypeVisibilityOption option))
{
return option;
}
return ReportTypeVisibilityOption.Inherited;
}
}

public static class ReportTypeGroups
{
public static bool IsRuleReport(this ReportType reportType)
Expand Down Expand Up @@ -199,7 +290,18 @@ public static bool IsVisibleTemplateType(this ReportType reportType, ReportVisib
|| visibility.WorkflowRelated && reportType.IsWorkflowReport());
}

public static List<ReportType> CustomSortReportType(List<ReportType> ListIn, ReportVisibility visibility)
/// <summary>
/// Sorts <paramref name="ListIn"/> into the canonical report-type display order.
/// </summary>
/// <param name="filterByVisibility">
/// When true (the default), entries are additionally required to pass <see cref="IsVisibleTemplateType"/>
/// against <paramref name="visibility"/> - suitable when <paramref name="ListIn"/> has not yet been
/// filtered for visibility (e.g. <see cref="ReportTypeSelection"/>). Pass false when the caller has
/// already computed the visible set per item (e.g. via <c>UserConfig.CanUseReportType</c>, which also
/// honours per-role visibility overrides), so this call only sorts without discarding entries that the
/// coarse-grained <paramref name="visibility"/> alone would not have allowed.
/// </param>
public static List<ReportType> CustomSortReportType(List<ReportType> ListIn, ReportVisibility visibility, bool filterByVisibility = true)
{
List<ReportType> ListOut = [];
List<ReportType> orderedReportTypeList =
Expand All @@ -221,14 +323,14 @@ public static List<ReportType> CustomSortReportType(List<ReportType> ListIn, Rep
];
foreach (var reportType in orderedReportTypeList.Where(r => ListIn.Contains(r)))
{
if (reportType == ReportType.Undefined || reportType.IsVisibleTemplateType(visibility))
if (reportType == ReportType.Undefined || !filterByVisibility || reportType.IsVisibleTemplateType(visibility))
{
ListOut.Add(reportType);
}
ListIn.Remove(reportType);
}
// Finally add only remaining report types the current user may actually see.
ListOut.AddRange(ListIn.Where(reportType => reportType.IsVisibleTemplateType(visibility)));
// Finally add remaining report types, filtering by visibility unless the caller already did so.
ListOut.AddRange(filterByVisibility ? ListIn.Where(reportType => reportType.IsVisibleTemplateType(visibility)) : ListIn);
return ListOut;
}
}
Expand Down
27 changes: 27 additions & 0 deletions roles/lib/files/FWO.Basics/Roles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,31 @@ public static bool IsTechnicalOrAnonymous(string role)
}

public readonly record struct ReportVisibility(bool RuleRelated, bool ModellingRelated, bool ComplianceRelated, bool OwnerRelated, bool WorkflowRelated);

/// <summary>
/// Single source of truth for which roles fall into each report-category bucket used by
/// <see cref="ReportVisibility"/>. Shared by the aggregate (OR-across-held-roles) visibility
/// check and by the per-role visibility check used to resolve "Inherited" report-type overrides.
/// </summary>
public static class ReportVisibilityRoleSets
{
public static readonly string[] RuleRelated = [Roles.Reporter, Roles.ReporterViewAll, Roles.FwAdmin, Roles.Admin, Roles.Auditor, Roles.Recertifier];
public static readonly string[] ModellingRelated = [Roles.Modeller, Roles.Admin, Roles.Auditor, Roles.Recertifier];
public static readonly string[] ComplianceRelated = [Roles.Admin, Roles.FwAdmin, Roles.Auditor];
public static readonly string[] OwnerRelated = [Roles.Admin, Roles.FwAdmin, Roles.Auditor];
public static readonly string[] WorkflowRelated = [Roles.Admin, Roles.FwAdmin, Roles.Auditor, Roles.Requester, Roles.Approver, Roles.Planner, Roles.Implementer, Roles.Reviewer];

/// <summary>
/// Computes the report-category visibility for a single role (as opposed to the OR of every role a user holds).
/// </summary>
public static ReportVisibility ForRole(string role)
{
return new ReportVisibility(
RuleRelated: RuleRelated.Contains(role, StringComparer.OrdinalIgnoreCase),
ModellingRelated: ModellingRelated.Contains(role, StringComparer.OrdinalIgnoreCase),
ComplianceRelated: ComplianceRelated.Contains(role, StringComparer.OrdinalIgnoreCase),
OwnerRelated: OwnerRelated.Contains(role, StringComparer.OrdinalIgnoreCase),
WorkflowRelated: WorkflowRelated.Contains(role, StringComparer.OrdinalIgnoreCase));
}
}
}
3 changes: 3 additions & 0 deletions roles/lib/files/FWO.Config.Api/Data/ConfigData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ public class ConfigData : ICloneable
[JsonProperty("availableReportTypes"), JsonPropertyName("availableReportTypes")]
public string AvailableReportTypes { get; set; } = "[]";

[JsonProperty("reportTypeVisibilityByRole"), JsonPropertyName("reportTypeVisibilityByRole")]
public string ReportTypeVisibilityByRole { get; set; } = "{}";

[JsonProperty("dataRetentionTime"), JsonPropertyName("dataRetentionTime")]
public int DataRetentionTime { get; set; } = 731;

Expand Down
Loading
Loading