mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-09-03 18:55:33 +00:00
fix: populate Privileges on PvePermission from access/permissions (#163)
UserService.GetPermissions unwrapped the path-keyed /access/permissions response and discarded prop.Value, so every returned PvePermission had a path but no privilege data. Add a Privileges dictionary populated from the privilege map; a key's presence means the privilege is granted and its value is whether the grant propagates to sub-paths, matching PVE's documented "propagate boolean" contract for that endpoint. Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
4cc18f1cc2
commit
a7d2b877f5
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace PSProxmoxVE.Core.Models.Users;
|
||||
@@ -33,6 +34,16 @@ public class PvePermission
|
||||
[JsonProperty("ugid")]
|
||||
public string? UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The privileges granted on <see cref="Path"/>, keyed by privilege name (e.g. "VM.Audit").
|
||||
/// A key's presence means the privilege is granted; its value is whether that grant
|
||||
/// propagates to sub-paths, per the PVE /access/permissions contract ("propagate boolean").
|
||||
/// Populated only when this entry comes from the path-keyed /access/permissions response
|
||||
/// with a privilege map for the path; null for entries from the flat /access/acl array,
|
||||
/// or when PVE returned no privilege map for the path.
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, bool>? Privileges { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
|
||||
@@ -623,7 +623,15 @@ namespace PSProxmoxVE.Core.Services
|
||||
var result = new List<PvePermission>();
|
||||
foreach (var prop in ((JObject)data).Properties())
|
||||
{
|
||||
var perm = new PvePermission { Path = prop.Name };
|
||||
var privileges = prop.Value is JObject privMap
|
||||
? privMap.Properties().ToDictionary(
|
||||
p => p.Name,
|
||||
p => p.Value.Type == JTokenType.Boolean
|
||||
? p.Value.Value<bool>()
|
||||
: p.Value.Type == JTokenType.Integer && p.Value.Value<long>() != 0,
|
||||
StringComparer.OrdinalIgnoreCase)
|
||||
: null;
|
||||
var perm = new PvePermission { Path = prop.Name, Privileges = privileges };
|
||||
result.Add(perm);
|
||||
}
|
||||
return result.ToArray();
|
||||
|
||||
@@ -8,7 +8,8 @@ namespace PSProxmoxVE.Cmdlets.Users
|
||||
/// <summary>
|
||||
/// <para type="synopsis">Lists ACL entries (permissions) in Proxmox VE.</para>
|
||||
/// <para type="description">
|
||||
/// Returns Access Control List entries from the Proxmox VE access management system.
|
||||
/// Returns Access Control List entries from the Proxmox VE access management system,
|
||||
/// each carrying the privileges granted on its path in the Privileges property.
|
||||
/// Optionally filter by path or user/group ID.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
|
||||
@@ -768,16 +768,56 @@ namespace PSProxmoxVE.Core.Tests.Services
|
||||
_mockClient.Setup(c => c.GetAsync("access/permissions"))
|
||||
.ReturnsAsync(@"{""data"":{
|
||||
""/"":{ ""Datastore.Audit"":1, ""VM.Audit"":1 },
|
||||
""/nodes/pve1"":{ ""Sys.Console"":1 }
|
||||
""/vms/100"":{ ""VM.Audit"":1, ""VM.PowerMgmt"":0 }
|
||||
}}");
|
||||
|
||||
// Act
|
||||
var result = _service.GetPermissions(_session);
|
||||
|
||||
// Assert
|
||||
// Assert — a key's presence means the privilege is granted; its value is whether
|
||||
// that grant propagates to sub-paths (PVE's "propagate boolean" contract).
|
||||
Assert.Equal(2, result.Length);
|
||||
Assert.Contains(result, p => p.Path == "/");
|
||||
Assert.Contains(result, p => p.Path == "/nodes/pve1");
|
||||
var vmPerm = Assert.Single(result, p => p.Path == "/vms/100");
|
||||
Assert.NotNull(vmPerm.Privileges);
|
||||
Assert.True(vmPerm.Privileges!.ContainsKey("VM.Audit"));
|
||||
Assert.True(vmPerm.Privileges!["VM.Audit"]);
|
||||
Assert.True(vmPerm.Privileges!.ContainsKey("VM.PowerMgmt"));
|
||||
Assert.False(vmPerm.Privileges!["VM.PowerMgmt"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPermissions_ArrayResponse_LeavesPrivilegesNull()
|
||||
{
|
||||
// Arrange — the flat /access/acl-shaped array response carries no privilege map
|
||||
_mockClient.Setup(c => c.GetAsync("access/permissions"))
|
||||
.ReturnsAsync(@"{""data"":[
|
||||
{ ""path"":""/vms/100"", ""roleid"":""PVEVMAdmin"", ""ugid"":""deploy@pve"", ""propagate"":1 }
|
||||
]}");
|
||||
|
||||
// Act
|
||||
var result = _service.GetPermissions(_session);
|
||||
|
||||
// Assert
|
||||
var perm = Assert.Single(result);
|
||||
Assert.Equal("/vms/100", perm.Path);
|
||||
Assert.Null(perm.Privileges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPermissions_EmptyPrivilegeMap_YieldsNonNullEmptyDictionary()
|
||||
{
|
||||
// Arrange
|
||||
_mockClient.Setup(c => c.GetAsync("access/permissions"))
|
||||
.ReturnsAsync(@"{""data"":{""/"":{}}}");
|
||||
|
||||
// Act
|
||||
var result = _service.GetPermissions(_session);
|
||||
|
||||
// Assert
|
||||
var perm = Assert.Single(result);
|
||||
Assert.NotNull(perm.Privileges);
|
||||
Assert.Empty(perm.Privileges!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user