Code/SboxSuperuser/Api/PermissionExplanation.cs

A data container that describes why a permission decision was made. It stores the permission key, final allow/deny, a direct entry enum, contributing group contributions, policy and registry revision numbers, owner bypass flag, registration flag, and a textual reason.

using SboxSuperuser.Core.Contracts;

namespace SboxSuperuser;

/// <summary>A revisioned permission explanation with direct entries, inherited sources and an explicit diagnostic.</summary>
public sealed class PermissionExplanation
{
	/// <summary>Gets the permission being explained.</summary>
	public string Permission { get; }

	/// <summary>Gets the explicit final decision; false is deny.</summary>
	public bool IsAllowed { get; }

	/// <summary>Gets the direct user entry, or Unset when absent.</summary>
	public PermissionEffect DirectEntry { get; }

	/// <summary>Gets contributing inherited groups, including deny sources.</summary>
	public IReadOnlyList<PermissionContribution> Groups { get; }

	/// <summary>Gets the policy revision at which this decision was made.</summary>
	public long PolicyRevision { get; }

	/// <summary>Gets whether explicit owner authority determined this framework decision.</summary>
	public bool OwnerBypass { get; }

	/// <summary>Gets the permission registry revision used for this decision.</summary>
	public long RegistryRevision { get; }

	/// <summary>Gets whether this exact permission was registered; unknown keys always deny.</summary>
	public bool IsRegistered { get; }

	/// <summary>Gets a safe explanation of the winning rule or unknown-key denial.</summary>
	public string Reason { get; }

	internal PermissionExplanation( string permission, bool isAllowed, PermissionEffect directEntry,
		IEnumerable<PermissionContribution> groups, long policyRevision, bool ownerBypass,
		long registryRevision = 0, bool isRegistered = false, string reason = "No resolver diagnostic supplied." )
	{
		Permission = ContractGuard.Key( permission, nameof( permission ) );
		if ( policyRevision < 0 )
		{
			throw new ArgumentOutOfRangeException( nameof( policyRevision ) );
		}

		IsAllowed = isAllowed;
		DirectEntry = ContractGuard.EnumValue( directEntry, nameof( directEntry ) );
		Groups = ContractGuard.Copy( groups, nameof( groups ) );
		PolicyRevision = policyRevision;
		OwnerBypass = ownerBypass;
		RegistryRevision = registryRevision;
		IsRegistered = isRegistered;
		Reason = reason;
	}
}