diff --git a/src/PSProxmoxVE.Core/Models/Users/PveApiToken.cs b/src/PSProxmoxVE.Core/Models/Users/PveApiToken.cs index 44a0c8e..676f60a 100644 --- a/src/PSProxmoxVE.Core/Models/Users/PveApiToken.cs +++ b/src/PSProxmoxVE.Core/Models/Users/PveApiToken.cs @@ -23,10 +23,20 @@ public class PveApiToken /// /// The full token identifier in "user@realm!tokenid" format, e.g., "admin@pam!automation". - /// Only populated by New-PveApiToken; not returned by the list/get endpoints. + /// Computed from UserId and TokenId. The "full-tokenid" JSON field from New-PveApiToken + /// is captured by for deserialization, but this property + /// always returns a computed value so it works for list/get endpoints too. + /// + public string FullTokenId => + string.IsNullOrEmpty(UserId) || string.IsNullOrEmpty(TokenId) + ? RawFullTokenId ?? string.Empty + : $"{UserId}!{TokenId}"; + + /// + /// Raw "full-tokenid" value from the API (only present on token creation responses). /// [JsonProperty("full-tokenid")] - public string? FullTokenId { get; set; } + public string? RawFullTokenId { get; set; } /// /// The token secret UUID. Only present on creation — store it immediately, diff --git a/src/PSProxmoxVE.Core/Services/UserService.cs b/src/PSProxmoxVE.Core/Services/UserService.cs index f3b6af3..e677900 100644 --- a/src/PSProxmoxVE.Core/Services/UserService.cs +++ b/src/PSProxmoxVE.Core/Services/UserService.cs @@ -642,6 +642,7 @@ namespace PSProxmoxVE.Core.Services /// Comma-separated role IDs. /// 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. public void SetPermission( @@ -650,6 +651,7 @@ namespace PSProxmoxVE.Core.Services string roles, string? users = null, string? groups = null, + string? tokens = null, bool propagate = true, bool delete = false) { @@ -666,6 +668,7 @@ namespace PSProxmoxVE.Core.Services }; if (!string.IsNullOrEmpty(users)) formData["users"] = users!; if (!string.IsNullOrEmpty(groups)) formData["groups"] = groups!; + if (!string.IsNullOrEmpty(tokens)) formData["tokens"] = tokens!; IPveHttpClient client = _injectedClient ?? new PveHttpClient(session); try diff --git a/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs index 6cca5d3..f311b49 100644 --- a/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Connection/ConnectPveServerCmdlet.cs @@ -48,10 +48,14 @@ namespace PSProxmoxVE.Cmdlets.Connection [Parameter(Mandatory = false, HelpMessage = "Skip TLS certificate validation.")] public SwitchParameter SkipCertificateCheck { get; set; } - /// When specified, writes the resulting PveSession object to the pipeline. - [Parameter(Mandatory = false, HelpMessage = "Output the session object to the pipeline.")] + /// Deprecated — session is now always output. Kept for backwards compatibility. + [Parameter(Mandatory = false, DontShow = true)] public SwitchParameter PassThru { get; set; } + /// When specified, suppresses the session object from the pipeline output. + [Parameter(Mandatory = false, HelpMessage = "Do not output the session object to the pipeline.")] + public SwitchParameter Quiet { get; set; } + protected override void ProcessRecord() { PveSession session; @@ -122,7 +126,7 @@ namespace PSProxmoxVE.Cmdlets.Connection WriteVerbose($"Connected to {Server}:{Port} as {session.AuthMode} (PVE {session.ServerVersion})."); - if (PassThru.IsPresent) + if (!Quiet.IsPresent) WriteObject(session); } } diff --git a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs index 515ea7c..0d54564 100644 --- a/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs +++ b/src/PSProxmoxVE/Cmdlets/Users/SetPvePermissionCmdlet.cs @@ -27,9 +27,10 @@ namespace PSProxmoxVE.Cmdlets.Users [Parameter(Mandatory = true, Position = 2, HelpMessage = "The role to assign (e.g. Administrator).")] public string Role { get; set; } = string.Empty; - /// The ACL entry type: "user" or "group". - [Parameter(Mandatory = false, HelpMessage = "ACL entry type: user or group.")] - [ValidateSet("user", "group", IgnoreCase = true)] + /// The ACL entry type: "user", "token", or "group". When set to "user", API tokens + /// (UgId containing "!") are automatically detected and sent as the "tokens" parameter. + [Parameter(Mandatory = false, HelpMessage = "ACL entry type: user, token, or group.")] + [ValidateSet("user", "token", "group", IgnoreCase = true)] public string Type { get; set; } = "user"; /// Whether to propagate this ACL to child paths. @@ -58,6 +59,8 @@ namespace PSProxmoxVE.Cmdlets.Users if (string.Equals(Type, "group", System.StringComparison.OrdinalIgnoreCase)) data["groups"] = UgId; + else if (string.Equals(Type, "token", System.StringComparison.OrdinalIgnoreCase) || UgId.Contains("!")) + data["tokens"] = UgId; else data["users"] = UgId; diff --git a/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 b/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 index af46b4c..7c0c13e 100644 --- a/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 +++ b/tests/PSProxmoxVE.Tests/Connection/Connect-PveServer.Tests.ps1 @@ -94,12 +94,18 @@ Describe 'Connect-PveServer' { Should -Be ([System.Management.Automation.SwitchParameter]) } - It 'Should have a PassThru switch parameter' { + It 'Should have a PassThru switch parameter (deprecated, hidden)' { $script:Cmd.Parameters.ContainsKey('PassThru') | Should -BeTrue $script:Cmd.Parameters['PassThru'].ParameterType | Should -Be ([System.Management.Automation.SwitchParameter]) } + It 'Should have a Quiet switch parameter' { + $script:Cmd.Parameters.ContainsKey('Quiet') | Should -BeTrue + $script:Cmd.Parameters['Quiet'].ParameterType | + Should -Be ([System.Management.Automation.SwitchParameter]) + } + It 'Credential and ApiToken should belong to different parameter sets' { $credSets = $script:Cmd.Parameters['Credential'].ParameterSets.Keys $tokenSets = $script:Cmd.Parameters['ApiToken'].ParameterSets.Keys