diff --git a/src/PSProxmoxVE.Core/Services/UserService.cs b/src/PSProxmoxVE.Core/Services/UserService.cs index e0295ba..f223491 100644 --- a/src/PSProxmoxVE.Core/Services/UserService.cs +++ b/src/PSProxmoxVE.Core/Services/UserService.cs @@ -531,8 +531,8 @@ namespace PSProxmoxVE.Core.Services /// Comma-separated user IDs. /// Comma-separated group names. /// Comma-separated API token IDs (user@realm!tokenid). - /// Whether to propagate the permission to sub-paths. - /// If true, removes the specified ACL entries. + /// Whether to propagate the permission to sub-paths; omitted when unset. + /// If true, removes the specified ACL entries; omitted when unset. public void SetPermission( PveSession session, string path, @@ -540,8 +540,8 @@ namespace PSProxmoxVE.Core.Services string? users = null, string? groups = null, string? tokens = null, - bool propagate = true, - bool delete = false) + bool? propagate = null, + bool? delete = null) { if (session == null) throw new ArgumentNullException(nameof(session)); if (string.IsNullOrWhiteSpace(path)) throw new ArgumentNullException(nameof(path)); @@ -550,13 +550,13 @@ namespace PSProxmoxVE.Core.Services var formData = new Dictionary { ["path"] = path, - ["roles"] = roles, - ["propagate"] = propagate ? "1" : "0", - ["delete"] = delete ? "1" : "0" + ["roles"] = roles }; if (!string.IsNullOrEmpty(users)) formData["users"] = users!; if (!string.IsNullOrEmpty(groups)) formData["groups"] = groups!; if (!string.IsNullOrEmpty(tokens)) formData["tokens"] = tokens!; + if (propagate.HasValue) formData["propagate"] = propagate.Value ? "1" : "0"; + if (delete.HasValue) formData["delete"] = delete.Value ? "1" : "0"; Invoke(session, client => { diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs index 0e2e956..1a0fb65 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPveRoleCmdlet.cs @@ -1,7 +1,6 @@ using System.Management.Automation; -using Newtonsoft.Json.Linq; -using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Users; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Users { @@ -23,16 +22,13 @@ namespace PSProxmoxVE.Cmdlets.Users protected override void ProcessRecord() { var session = GetSession(); - using var client = new PveHttpClient(session); WriteVerbose("Getting roles..."); - var json = client.GetAsync("access/roles").GetAwaiter().GetResult(); - var root = JObject.Parse(json); - var data = root["data"] as JArray ?? new JArray(); + var service = new UserService(); + var roles = service.GetRoles(session); - foreach (var item in data) + foreach (var role in roles) { - var role = item.ToObject()!; if (!string.IsNullOrEmpty(RoleId) && !string.Equals(role.RoleId, RoleId, System.StringComparison.OrdinalIgnoreCase)) continue; diff --git a/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs index 3998fd3..0fe34ba 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/GetPveUserCmdlet.cs @@ -1,7 +1,6 @@ using System.Management.Automation; -using Newtonsoft.Json.Linq; -using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Users; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Users { @@ -31,16 +30,13 @@ namespace PSProxmoxVE.Cmdlets.Users protected override void ProcessRecord() { var session = GetSession(); - using var client = new PveHttpClient(session); WriteVerbose("Getting users..."); - var json = client.GetAsync("access/users").GetAwaiter().GetResult(); - var root = JObject.Parse(json); - var data = root["data"] as JArray ?? new JArray(); + var service = new UserService(); + var users = service.GetUsers(session); - foreach (var item in data) + foreach (var user in users) { - var user = item.ToObject()!; if (MatchesFilters(user)) WriteObject(user); } diff --git a/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs index cd3ea1f..81bad71 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/NewPveRoleCmdlet.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; using System.Management.Automation; -using PSProxmoxVE.Core.Client; using PSProxmoxVE.Core.Models.Users; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Users { @@ -32,16 +31,10 @@ namespace PSProxmoxVE.Cmdlets.Users return; var session = GetSession(); - using var client = new PveHttpClient(session); WriteVerbose($"Creating role '{RoleId}'..."); - var data = new Dictionary - { - ["roleid"] = RoleId - }; - if (!string.IsNullOrEmpty(Privileges)) data["privs"] = Privileges!; - - client.PostAsync("access/roles", data).GetAwaiter().GetResult(); + var service = new UserService(); + service.CreateRole(session, RoleId, Privileges); WriteObject(new PveRole { RoleId = RoleId, Privileges = Privileges }); } diff --git a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs index 0d54564..df21ffe 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs @@ -1,6 +1,5 @@ -using System.Collections.Generic; using System.Management.Automation; -using PSProxmoxVE.Core.Client; +using PSProxmoxVE.Core.Services; namespace PSProxmoxVE.Cmdlets.Users { @@ -48,26 +47,26 @@ namespace PSProxmoxVE.Cmdlets.Users return; var session = GetSession(); - using var client = new PveHttpClient(session); WriteVerbose($"Setting permission for '{UgId}' at '{Path}'..."); - var data = new Dictionary - { - ["path"] = Path, - ["roles"] = Role - }; - + string? users = null, groups = null, tokens = null; if (string.Equals(Type, "group", System.StringComparison.OrdinalIgnoreCase)) - data["groups"] = UgId; + groups = UgId; else if (string.Equals(Type, "token", System.StringComparison.OrdinalIgnoreCase) || UgId.Contains("!")) - data["tokens"] = UgId; + tokens = UgId; else - data["users"] = UgId; + users = UgId; - if (Propagate.IsPresent) data["propagate"] = "1"; - if (Delete.IsPresent) data["delete"] = "1"; - - client.PutAsync("access/acl", data).GetAwaiter().GetResult(); + var service = new UserService(); + service.SetPermission( + session, + Path, + Role, + users: users, + groups: groups, + tokens: tokens, + propagate: Propagate.IsPresent ? true : (bool?)null, + delete: Delete.IsPresent ? true : (bool?)null); } } } diff --git a/tests/PSProxmoxVE.Core.Tests/Services/UserServiceTests.cs b/tests/PSProxmoxVE.Core.Tests/Services/UserServiceTests.cs index 9468806..b6d2ab6 100644 --- a/tests/PSProxmoxVE.Core.Tests/Services/UserServiceTests.cs +++ b/tests/PSProxmoxVE.Core.Tests/Services/UserServiceTests.cs @@ -358,6 +358,7 @@ namespace PSProxmoxVE.Core.Tests.Services Assert.Equal(1, result[0].Special); Assert.Equal("CustomOps", result[2].RoleId); Assert.Equal(0, result[2].Special); + Assert.Equal("VM.PowerMgmt,VM.Console", result[2].Privileges); } [Fact] @@ -873,7 +874,10 @@ namespace PSProxmoxVE.Core.Tests.Services d["roles"] == "PVEVMAdmin" && d["users"] == "deploy@pve" && d["propagate"] == "1" && - d["delete"] == "0")), + !d.ContainsKey("delete") && + !d.ContainsKey("groups") && + !d.ContainsKey("tokens") && + d.Count == 4)), Times.Once); } @@ -894,7 +898,70 @@ namespace PSProxmoxVE.Core.Tests.Services d["roles"] == "Administrator" && d["groups"] == "admins" && d["propagate"] == "0" && - d["delete"] == "1")), + d["delete"] == "1" && + !d.ContainsKey("users") && + !d.ContainsKey("tokens") && + d.Count == 5)), + Times.Once); + } + + [Fact] + public void SetPermission_WithToken_SendsTokensKey() + { + // Arrange + _mockClient.Setup(c => c.PutAsync("access/acl", It.IsAny>())) + .ReturnsAsync(@"{""data"":null}"); + + // Act + _service.SetPermission(_session, "/vms/100", "PVEVMAdmin", tokens: "deploy@pve!ci"); + + // Assert + _mockClient.Verify(c => c.PutAsync("access/acl", + It.Is>(d => + d["tokens"] == "deploy@pve!ci" && + !d.ContainsKey("users") && + !d.ContainsKey("groups") && + !d.ContainsKey("propagate") && + !d.ContainsKey("delete") && + d.Count == 3)), + Times.Once); + } + + [Fact] + public void SetPermission_DeleteWithoutPropagate_SendsDeleteOmitsPropagate() + { + // Arrange + _mockClient.Setup(c => c.PutAsync("access/acl", It.IsAny>())) + .ReturnsAsync(@"{""data"":null}"); + + // Act + _service.SetPermission(_session, "/vms/100", "PVEVMAdmin", users: "deploy@pve", delete: true); + + // Assert + _mockClient.Verify(c => c.PutAsync("access/acl", + It.Is>(d => + d["delete"] == "1" && + !d.ContainsKey("propagate") && + d.Count == 4)), + Times.Once); + } + + [Fact] + public void SetPermission_NoPropagateOrDelete_OmitsBothFlags() + { + // Arrange + _mockClient.Setup(c => c.PutAsync("access/acl", It.IsAny>())) + .ReturnsAsync(@"{""data"":null}"); + + // Act + _service.SetPermission(_session, "/vms/100", "PVEVMAdmin", users: "deploy@pve"); + + // Assert + _mockClient.Verify(c => c.PutAsync("access/acl", + It.Is>(d => + !d.ContainsKey("propagate") && + !d.ContainsKey("delete") && + d.Count == 3)), Times.Once); }