From dacf1071a88d22ad1ac37547ebf92f27096e23a0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Sep 2025 13:45:18 +0000 Subject: [PATCH 01/43] Initial plan From 65640847afcd47eea26e66450dc9a7ee3495732f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Sep 2025 13:55:21 +0000 Subject: [PATCH 02/43] Add status tracking to GitHubAppInstallation class to compare permissions and events Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- .../public/App/GitHubAppInstallation.ps1 | 98 +++++++++++++++++++ ...tHubAppInstallationForAuthenticatedApp.ps1 | 5 +- 2 files changed, 102 insertions(+), 1 deletion(-) diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 96fd2831c..0459560f8 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -41,8 +41,54 @@ # The URL to the target's profile based on the target type. [string] $Url + # The status indicating if the installation permissions and events match the app's configuration. + [string] $Status + GitHubAppInstallation() {} + # Helper method to compare installation permissions and events with app configuration + hidden [string] CompareWithAppConfiguration([pscustomobject] $AppPermissions, [string[]] $AppEvents) { + if ($null -eq $AppPermissions -or $null -eq $AppEvents) { + return 'Unknown' + } + + # Compare permissions - check if installation has all the permissions that the app requires + $permissionsMatch = $true + if ($AppPermissions.PSObject.Properties) { + foreach ($permission in $AppPermissions.PSObject.Properties) { + $appPermissionLevel = $permission.Value + $installationPermissionLevel = $this.Permissions.PSObject.Properties[$permission.Name]?.Value + + # If app requires a permission but installation doesn't have it, or has lower level + if ($appPermissionLevel -ne 'none' -and $installationPermissionLevel -ne $appPermissionLevel) { + $permissionsMatch = $false + break + } + } + } + + # Compare events - check if installation subscribes to all events that the app wants + $eventsMatch = $true + if ($AppEvents -and $AppEvents.Count -gt 0) { + foreach ($appEvent in $AppEvents) { + if ($appEvent -notin $this.Events) { + $eventsMatch = $false + break + } + } + } + + if ($permissionsMatch -and $eventsMatch) { + return 'UpToDate' + } elseif (-not $permissionsMatch -and -not $eventsMatch) { + return 'PermissionsAndEventsOutdated' + } elseif (-not $permissionsMatch) { + return 'PermissionsOutdated' + } else { + return 'EventsOutdated' + } + } + GitHubAppInstallation([PSCustomObject] $Object) { $this.ID = $Object.id $this.App = [GitHubApp]::new( @@ -63,6 +109,30 @@ $this.SuspendedAt = $Object.suspended_at $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) $this.Url = $Object.html_url + $this.Status = 'Unknown' + } + + GitHubAppInstallation([PSCustomObject] $Object, [GitHubApp] $App) { + $this.ID = $Object.id + $this.App = [GitHubApp]::new( + [PSCustomObject]@{ + client_id = $Object.client_id + app_id = $Object.app_id + app_slug = $Object.app_slug + } + ) + $this.Target = [GitHubOwner]::new($Object.account) + $this.Type = $Object.target_type + $this.RepositorySelection = $Object.repository_selection + $this.Permissions = $Object.permissions + $this.Events = , ($Object.events) + $this.FilePaths = $Object.single_file_paths + $this.CreatedAt = $Object.created_at + $this.UpdatedAt = $Object.updated_at + $this.SuspendedAt = $Object.suspended_at + $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) + $this.Url = $Object.html_url + $this.Status = $this.CompareWithAppConfiguration($App.Permissions, $App.Events) } GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context) { @@ -89,5 +159,33 @@ $this.SuspendedAt = $Object.suspended_at $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) $this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)" + $this.Status = 'Unknown' + } + + GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context, [GitHubApp] $App) { + $this.ID = $Object.id + $this.App = [GitHubApp]::new( + [PSCustomObject]@{ + client_id = $Object.client_id + app_id = $Object.app_id + app_slug = $Object.app_slug + } + ) + $this.Target = [GitHubOwner]@{ + Name = $Target + Type = $Type + Url = "https://$($Context.HostName)/$Target" + } + $this.Type = $Type + $this.RepositorySelection = $Object.repository_selection + $this.Permissions = $Object.permissions + $this.Events = , ($Object.events) + $this.FilePaths = $Object.single_file_paths + $this.CreatedAt = $Object.created_at + $this.UpdatedAt = $Object.updated_at + $this.SuspendedAt = $Object.suspended_at + $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) + $this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)" + $this.Status = $this.CompareWithAppConfiguration($App.Permissions, $App.Events) } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 index afbe9e03c..58f595659 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 @@ -46,9 +46,12 @@ Context = $Context } + # Get the authenticated app to compare permissions and events + $authenticatedApp = Get-GitHubAuthenticatedApp -Context $Context + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($installation in $_.Response) { - [GitHubAppInstallation]::new($installation) + [GitHubAppInstallation]::new($installation, $authenticatedApp) } } } From 439519d60ebd46acab835c9d6b36d243d1940bf8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 12 Sep 2025 13:58:18 +0000 Subject: [PATCH 03/43] Add Status property to GitHubAppInstallation display formats and update tests Co-authored-by: MariusStorhaug <17722253+MariusStorhaug@users.noreply.github.com> --- .../GitHubAppInstallation.Format.ps1xml | 17 +++++++++ tests/Apps.Tests.ps1 | 35 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/formats/GitHubAppInstallation.Format.ps1xml b/src/formats/GitHubAppInstallation.Format.ps1xml index 876cc43b7..b8f97eca3 100644 --- a/src/formats/GitHubAppInstallation.Format.ps1xml +++ b/src/formats/GitHubAppInstallation.Format.ps1xml @@ -20,6 +20,9 @@ + + + @@ -56,6 +59,17 @@ Type + + + switch ($_.Status) { + 'UpToDate' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Green)✓$($PSStyle.Reset)" } else { 'UpToDate' } } + 'PermissionsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Yellow)⚠P$($PSStyle.Reset)" } else { 'PermissionsOutdated' } } + 'EventsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Yellow)⚠E$($PSStyle.Reset)" } else { 'EventsOutdated' } } + 'PermissionsAndEventsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Red)✗$($PSStyle.Reset)" } else { 'PermissionsAndEventsOutdated' } } + default { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.BrightBlack)?$($PSStyle.Reset)" } else { $_.Status } } + } + + CreatedAt @@ -88,6 +102,9 @@ Type + + Status + Url diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index 44803729b..7f59d3c1f 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -93,6 +93,9 @@ Describe 'Apps' { $installation.SuspendedAt | Should -BeNullOrEmpty $installation.SuspendedBy | Should -BeOfType 'GitHubUser' $installation.SuspendedBy | Should -BeNullOrEmpty + # Validate the new Status property + $installation.Status | Should -Not -BeNullOrEmpty + $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') } } @@ -120,6 +123,38 @@ Describe 'Apps' { $installation.SuspendedAt | Should -BeNullOrEmpty $installation.SuspendedBy | Should -BeOfType 'GitHubUser' $installation.SuspendedBy | Should -BeNullOrEmpty + # Validate the new Status property + $installation.Status | Should -Not -BeNullOrEmpty + $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') + } + + It 'Get-GitHubAppInstallation - Status tracking functionality' { + $githubApp = Get-GitHubApp + $installations = Get-GitHubAppInstallation + LogGroup 'Status tracking test' { + Write-Host "Testing installation status tracking against app configuration" + Write-Host "App Permissions: $($githubApp.Permissions | ConvertTo-Json -Compress)" + Write-Host "App Events: $($githubApp.Events -join ', ')" + } + + foreach ($installation in $installations) { + LogGroup "Installation $($installation.ID) Status" { + Write-Host "Installation ID: $($installation.ID)" + Write-Host "Target: $($installation.Target.Name)" + Write-Host "Status: $($installation.Status)" + Write-Host "Installation Permissions: $($installation.Permissions | ConvertTo-Json -Compress)" + Write-Host "Installation Events: $($installation.Events -join ', ')" + } + + # The status should be calculated based on app vs installation comparison + $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') + + # When we have app information (which we do in this context), status should not be Unknown + # for authenticated app installations + if ($PSCmdlet.ParameterSetName -eq 'List installations for the authenticated app') { + $installation.Status | Should -Not -Be 'Unknown' + } + } } } From 4bffffe818a8e8165d3ff84693154c73b776b09b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 12 Sep 2025 18:14:57 +0200 Subject: [PATCH 04/43] Fix app link --- src/classes/public/App/GitHubAppInstallation.ps1 | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 0459560f8..7812e3140 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -114,13 +114,7 @@ GitHubAppInstallation([PSCustomObject] $Object, [GitHubApp] $App) { $this.ID = $Object.id - $this.App = [GitHubApp]::new( - [PSCustomObject]@{ - client_id = $Object.client_id - app_id = $Object.app_id - app_slug = $Object.app_slug - } - ) + $this.App = $App $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection @@ -164,13 +158,7 @@ GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context, [GitHubApp] $App) { $this.ID = $Object.id - $this.App = [GitHubApp]::new( - [PSCustomObject]@{ - client_id = $Object.client_id - app_id = $Object.app_id - app_slug = $Object.app_slug - } - ) + $this.App = $App $this.Target = [GitHubOwner]@{ Name = $Target Type = $Type From 896aaac6cf1a74156aab2e036d9cc3cb674faf4b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 12 Sep 2025 20:55:29 +0200 Subject: [PATCH 05/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Enhance=20argum?= =?UTF-8?q?ent=20completers=20for=20GitHub=20configuration=20and=20update?= =?UTF-8?q?=20authentication=20handling=20in=20API=20version=20retrieval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/public/Config/completers.ps1 | 35 ++++++++++++++++--- .../public/Meta/Get-GitHubApiVersion.ps1 | 5 ++- src/variables/private/GitHub.ps1 | 1 - 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/functions/public/Config/completers.ps1 b/src/functions/public/Config/completers.ps1 index 1165d3170..c498f0497 100644 --- a/src/functions/public/Config/completers.ps1 +++ b/src/functions/public/Config/completers.ps1 @@ -10,10 +10,37 @@ Register-ArgumentCompleter -CommandName Set-GitHubConfig -ParameterName Value -ScriptBlock { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters - if ($fakeBoundParameters.Name -eq 'CompletionMode') { - $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } - @('StartsWith', 'Contains') | Where-Object { $_ -like $pattern } | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } + switch ($fakeBoundParameters.Name) { + 'CompletionMode' { + @('StartsWith', 'Contains') | Where-Object { $_ -like $pattern } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } + 'HttpVersion' { + @('1.0', '1.1', '2.0', '3.0') | Where-Object { $_ -like $pattern } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } + 'EnvironmentType' { + @('Local', 'GitHubActions', 'FunctionApp', 'Unknown') | Where-Object { $_ -like $pattern } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } + 'ApiVersion' { + $params = @{ + Context = $fakeBoundParameters.Context + Debug = $false + Verbose = $false + } + Get-GitHubApiVersion @params | Where-Object { $_ -like $pattern } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } + } + 'DefaultContext' { + Get-GitHubContext -ListAvailable | Where-Object { $_.Name -like $pattern } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_.Name, $_.Name, 'ParameterValue', $_.Name) + } } } } diff --git a/src/functions/public/Meta/Get-GitHubApiVersion.ps1 b/src/functions/public/Meta/Get-GitHubApiVersion.ps1 index 7a618a104..cd9ac2b0a 100644 --- a/src/functions/public/Meta/Get-GitHubApiVersion.ps1 +++ b/src/functions/public/Meta/Get-GitHubApiVersion.ps1 @@ -34,10 +34,13 @@ $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" $Context = Resolve-GitHubContext -Context $Context -Anonymous $Anonymous - Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT, Anonymous + Assert-GitHubContext -Context $Context -AuthType App, IAT, PAT, UAT, Anonymous } process { + if ($Context.AuthType -eq 'APP') { + $Context = 'Anonymous' + } $apiParams = @{ Method = 'GET' ApiEndpoint = '/versions' diff --git a/src/variables/private/GitHub.ps1 b/src/variables/private/GitHub.ps1 index 190de6d2d..c5b04de1e 100644 --- a/src/variables/private/GitHub.ps1 +++ b/src/variables/private/GitHub.ps1 @@ -4,7 +4,6 @@ $script:IsLocal = -not ($script:IsGitHubActions -or $script:IsFunctionApp) $script:GitHub = [pscustomobject]@{ ContextVault = 'PSModule.GitHub' TokenPrefixPattern = '(?<=^(ghu|gho|ghs|github_pat|ghp)).*' - EnvironmentType = Get-GitHubEnvironmentType DefaultConfig = [GitHubConfig]@{ ID = 'Module' HostName = ($env:GITHUB_SERVER_URL ?? 'github.com') -replace '^https?://' From 5dfd0f5e3469abd5c206ae926c5503337aeddc46 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Fri, 12 Sep 2025 21:13:56 +0200 Subject: [PATCH 06/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Make=20the=20Co?= =?UTF-8?q?ntext=20parameter=20mandatory=20in=20Switch-GitHubContext=20fun?= =?UTF-8?q?ction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/public/Auth/Context/Switch-GitHubContext.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions/public/Auth/Context/Switch-GitHubContext.ps1 b/src/functions/public/Auth/Context/Switch-GitHubContext.ps1 index 0a6149a66..b4f7ed876 100644 --- a/src/functions/public/Auth/Context/Switch-GitHubContext.ps1 +++ b/src/functions/public/Auth/Context/Switch-GitHubContext.ps1 @@ -16,7 +16,7 @@ param( # The context to run the command in. Used to get the details for the API call. # Can be either a string or a GitHubContext object. - [Parameter(ValueFromPipeline)] + [Parameter(Mandatory, ValueFromPipeline, Position = 0)] [object] $Context ) From 6447faa8fba94e7fa8dbfb3d75c07ed8fd4fc12f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 13:20:32 +0200 Subject: [PATCH 07/43] Add initial test scripts for GitHub API interactions - Created TEMPLATE.ps1 for Pester testing framework setup. - Implemented Teams.Tests.ps1 to test GitHub Teams API functionalities including team creation, retrieval, updating, and deletion. - Added Users.Tests.ps1 to validate user-related API calls, including user retrieval and updates. - Developed Variables.Tests.ps1 to manage GitHub repository and organization variables, including creation, updating, and removal of variables. --- .github/PSModule.yml | 28 +- .../GitHubAppInstallationContext.ps1 | 4 +- src/classes/public/GitHubPermission.ps1 | 1285 +++++++++++++++++ .../public/GitHubPermissionDefinition.ps1 | 46 - src/variables/private/GitHub.ps1 | 1180 --------------- {tests => test2}/Artifacts.Tests.ps1 | 0 {tests => test2}/Emojis.Tests.ps1 | 0 {tests => test2}/Enterprise.Tests.ps1 | 0 {tests => test2}/Environments.Tests.ps1 | 0 {tests => test2}/GitHub.Tests.ps1 | 0 {tests => test2}/GitHubFormatter.Tests.ps1 | 0 {tests => test2}/Organizations.Tests.ps1 | 0 {tests => test2}/README.md | 0 {tests => test2}/Releases.Tests.ps1 | 0 {tests => test2}/Repositories.Tests.ps1 | 0 {tests => test2}/Secrets.Tests.ps1 | 0 {tests => test2}/TEMPLATE.ps1 | 0 {tests => test2}/Teams.Tests.ps1 | 0 {tests => test2}/Users.Tests.ps1 | 0 {tests => test2}/Variables.Tests.ps1 | 0 20 files changed, 1301 insertions(+), 1242 deletions(-) create mode 100644 src/classes/public/GitHubPermission.ps1 delete mode 100644 src/classes/public/GitHubPermissionDefinition.ps1 rename {tests => test2}/Artifacts.Tests.ps1 (100%) rename {tests => test2}/Emojis.Tests.ps1 (100%) rename {tests => test2}/Enterprise.Tests.ps1 (100%) rename {tests => test2}/Environments.Tests.ps1 (100%) rename {tests => test2}/GitHub.Tests.ps1 (100%) rename {tests => test2}/GitHubFormatter.Tests.ps1 (100%) rename {tests => test2}/Organizations.Tests.ps1 (100%) rename {tests => test2}/README.md (100%) rename {tests => test2}/Releases.Tests.ps1 (100%) rename {tests => test2}/Repositories.Tests.ps1 (100%) rename {tests => test2}/Secrets.Tests.ps1 (100%) rename {tests => test2}/TEMPLATE.ps1 (100%) rename {tests => test2}/Teams.Tests.ps1 (100%) rename {tests => test2}/Users.Tests.ps1 (100%) rename {tests => test2}/Variables.Tests.ps1 (100%) diff --git a/.github/PSModule.yml b/.github/PSModule.yml index 0e0770314..08dc7e549 100644 --- a/.github/PSModule.yml +++ b/.github/PSModule.yml @@ -1,17 +1,17 @@ Test: CodeCoverage: PercentTarget: 50 -# TestResults: -# Skip: true -# SourceCode: -# Skip: true -# PSModule: -# Skip: true -# Module: -# Windows: -# Skip: true -# MacOS: -# Skip: true -# Build: -# Docs: -# Skip: true + TestResults: + Skip: true + SourceCode: + Skip: true + PSModule: + Skip: true + Module: + Windows: + Skip: true + MacOS: + Skip: true +Build: + Docs: + Skip: true diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 index 2b817f3ec..d4a24882a 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -6,7 +6,7 @@ [System.Nullable[uint64]] $InstallationID # The permissions that the app is requesting on the target - [pscustomobject] $Permissions + [GitHubPermission[]] $Permissions # The events that the app is subscribing to once installed [string[]] $Events @@ -41,7 +41,7 @@ $this.PerPage = $Object.PerPage $this.ClientID = $Object.ClientID $this.InstallationID = $Object.InstallationID - $this.Permissions = $Object.Permissions + $this.Permissions = [GitHubPermission]::newFromObject($Object.Permissions) $this.Events = $Object.Events $this.InstallationType = $Object.InstallationType $this.InstallationName = $Object.InstallationName diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 new file mode 100644 index 000000000..330f40599 --- /dev/null +++ b/src/classes/public/GitHubPermission.ps1 @@ -0,0 +1,1285 @@ +class GitHubPermissionDefinition { + # The programmatic name of the permission as returned by the GitHub API + [string] $Name + + # The human-friendly name of the permission as shown in the GitHub UI + [string] $DisplayName + + # A brief description of what access the permission grants + [string] $Description + + # A link to the relevant documentation or GitHub UI page + [uri] $URL + + # The levels of access that can be granted for this permission + [string[]] $Options + + # The type of permission (Fine-grained, Classic) + [string] $Type + + # The scope at which this permission applies (Repository, Organization, User, Enterprise) + [string] $Scope + + static [GitHubPermissionDefinition[]] $List = @( + # ------------------------------ + # Repository Fine-Grained Permission Definitions + # ------------------------------ + [GitHubPermissionDefinition]::new( + 'actions', + 'Actions', + 'Workflows, workflow runs and artifacts.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-actions', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'administration', + 'Administration', + 'Repository creation, deletion, settings, teams, and collaborators.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-administration', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'attestations', + 'Attestations', + 'Create and retrieve attestations for a repository.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-attestations', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'checks', + 'Checks', + 'Checks on code.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-checks', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'security_events', + 'Code scanning alerts', + 'View and manage code scanning alerts.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-code-scanning-alerts', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'codespaces', + 'Codespaces', + 'Create, edit, delete and list Codespaces.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-codespaces', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'codespaces_lifecycle_admin', + 'Codespaces lifecycle admin', + 'Manage the lifecycle of Codespaces, including starting and stopping.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-codespaces-lifecycle-admin', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'codespaces_metadata', + 'Codespaces metadata', + 'Access Codespaces metadata including the devcontainers and machine type.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-codespaces-metadata', + @( + 'read' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'codespaces_secrets', + 'Codespaces secrets', + 'Restrict Codespaces user secrets modifications to specific repositories.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-codespaces-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'statuses', + 'Commit statuses', + 'Commit statuses.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-commit-statuses', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'contents', + 'Contents', + 'Repository contents, commits, branches, downloads, releases, and merges.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-contents', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'repository_custom_properties', + 'Custom properties', + 'Read and write repository custom properties values at the repository level, when allowed by the property.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-custom-properties', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'vulnerability_alerts', + 'Dependabot alerts', + 'Retrieve Dependabot alerts.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-dependabot-alerts', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'dependabot_secrets', + 'Dependabot secrets', + 'Manage Dependabot repository secrets.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-dependabot-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'deployments', + 'Deployments', + 'Deployments and deployment statuses.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-deployments', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'discussions', + 'Discussions', + 'Discussions and related comments and labels.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-discussions', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'environments', + 'Environments', + 'Manage repository environments.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-environments', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'issues', + 'Issues', + 'Issues and related comments, assignees, labels, and milestones.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-issues', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'merge_queues', + 'Merge queues', + "Manage a repository's merge queues", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-merge-queues', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( #Mandatory + 'metadata', + 'Metadata', + 'Search repositories, list collaborators, and access repository metadata.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-metadata', + @( + 'read' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'packages', + 'Packages', + 'Packages published to the GitHub Package Platform.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-packages', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'pages', + 'Pages', + 'Retrieve Pages statuses, configuration, and builds, as well as create new builds.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-pages', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'repository_projects', + 'Projects', + 'Manage classic projects within a repository.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-projects', + @( + 'read', + 'write', + 'admin' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'pull_requests', + 'Pull requests', + 'Pull requests and related comments, assignees, labels, milestones, and merges.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-pull-requests', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'repository_advisories', + 'Repository security advisories', + 'View and manage repository security advisories.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-repository-security-advisories', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'repo_secret_scanning_dismissal_requests', + 'Secret scanning alert dismissal requests', + 'View and manage secret scanning alert dismissal requests', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-secret-scanning-alert-dismissal-requests', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'secret_scanning_alerts', + 'Secret scanning alerts', + 'View and manage secret scanning alerts.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-secret-scanning-alerts', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'secret_scanning_bypass_requests', + 'Secret scanning push protection bypass requests', + 'Review and manage repository secret scanning push protection bypass requests.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-secret-scanning-push-protection-bypass-requests', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'secrets', + 'Secrets', + 'Manage Actions repository secrets.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'single_file', + 'Single file', + 'Manage just a single file.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-single-file', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'actions_variables', + 'Variables', + 'Manage Actions repository variables.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-variables', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'repository_hooks', + 'Webhooks', + 'Manage the post-receive hooks for a repository.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-webhooks', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + [GitHubPermissionDefinition]::new( + 'workflows', + 'Workflows', + 'Update GitHub Action workflow files.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#repository-permissions-for-workflows', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Repository' + ), + + # ------------------------------ + # Organization Fine-Grained Permission Definitions + # ------------------------------ + [GitHubPermissionDefinition]::new( + 'organization_api_insights', + 'API Insights', + 'View statistics on how the API is being used for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-api-insights', + @( + 'read' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_administration', + 'Administration', + 'Manage access to an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-administration', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_user_blocking', + 'Blocking users', + 'View and manage users blocked by the organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-blocking-users', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_campaigns', + 'Campaigns', + 'Manage campaigns.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-campaigns', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_custom_org_roles', + 'Custom organization roles', + 'Create, edit, delete and list custom organization roles. View system organization roles.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-custom-organization-roles', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_custom_properties', + 'Custom properties', + 'Read and write repository custom properties values and administer definitions at the organization level.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-custom-properties', + @( + 'read', + 'write', + 'admin' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_custom_roles', + 'Custom repository roles', + 'Create, edit, delete and list custom repository roles.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-custom-repository-roles', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_events', + 'Events', + 'View events triggered by an activity in an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-events', + @( + 'read' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_copilot_seat_management', + 'GitHub Copilot Business', + 'Manage Copilot Business seats and settings', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-github-copilot-business', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'issue_fields', + 'Issue Fields', + 'Manage issue fields for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-issue-fields', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'issue_types', + 'Issue Types', + 'Manage issue types for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-issue-types', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_knowledge_bases', + 'Knowledge bases', + 'View and manage knowledge bases for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-knowledge-bases', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'members', + 'Members', + 'Organization members and teams.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-members', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_models', + 'Models', + 'Manage model access for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-models', + @( + 'read' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_network_configurations', + 'Network configurations', + 'View and manage hosted compute network configurations available to an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-network-configurations', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_announcement_banners', + 'Organization announcement banners', + 'View and modify announcement banners for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-announcement-banners', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_secret_scanning_bypass_requests', + 'Organization bypass requests for secret scanning', + 'Review and manage secret scanning push protection bypass requests.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-bypass-requests-for-secret-scanning', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_codespaces', + 'Organization codespaces', + 'Manage Codespaces for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-codespaces', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_codespaces_secrets', + 'Organization codespaces secrets', + 'Manage Codespaces Secrets for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-codespaces-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_codespaces_settings', + 'Organization codespaces settings', + 'Manage Codespaces settings for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-codespaces-settings', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_dependabot_secrets', + 'Organization dependabot secrets', + 'Manage Dependabot organization secrets.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-dependabot-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_code_scanning_dismissal_requests', + 'Organization dismissal requests for code scanning', + 'Review and manage code scanning alert dismissal requests.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-dismissal-requests-for-code-scanning', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_private_registries', + 'Organization private registries', + 'Manage private registries for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-organization-private-registries', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_personal_access_token_requests', + 'Personal access token requests', + 'Manage personal access token requests from organization members.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-personal-access-token-requests', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_personal_access_tokens', + 'Personal access tokens', + 'View and revoke personal access tokens that have been granted access to an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-personal-access-tokens', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_plan', + 'Plan', + "View an organization's plan.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-plan', + @( + 'read' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_projects', + 'Projects', + 'Manage projects for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-projects', + @( + 'read', + 'write', + 'admin' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'secret_scanning_dismissal_requests', + 'Secret scanning alert dismissal requests', + 'Review and manage secret scanning alert dismissal requests', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-secret-scanning-alert-dismissal-requests', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_secrets', + 'Secrets', + 'Manage Actions organization secrets.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_self_hosted_runners', + 'Self-hosted runners', + 'View and manage Actions self-hosted runners available to an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-self-hosted-runners', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'team_discussions', + 'Team discussions', + 'Manage team discussions and related comments.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-team-discussions', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_actions_variables', + 'Variables', + 'Manage Actions organization variables.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-variables', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + [GitHubPermissionDefinition]::new( + 'organization_hooks', + 'Webhooks', + 'Manage the post-receive hooks for an organization.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#organization-permissions-for-webhooks', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Organization' + ), + + # ------------------------------ + # User (Account) Fine-Grained Permission Definitions + # ------------------------------ + [GitHubPermissionDefinition]::new( + 'blocking', + 'Block another user', + 'View and manage users blocked by the user.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-block-another-user', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'codespaces_user_secrets', + 'Codespaces user secrets', + 'Manage Codespaces user secrets.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-codespaces-user-secrets', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'copilot_messages', + 'Copilot Chat', + 'This application will receive your GitHub ID, your GitHub Copilot Chat session messages ' + + '(not including messages sent to another application), and timestamps of provided GitHub Copilot ' + + 'Chat session messages. This permission must be enabled for Copilot Extensions.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-copilot-chat', + @( + 'read' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'copilot_editor_context', + 'Copilot Editor Context', + 'This application will receive bits of Editor Context (e.g. currently opened file) whenever you send it a message through Copilot Chat.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-copilot-editor-context', + @( + 'read' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'emails', + 'Email addresses', + "Manage a user's email addresses.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-email-addresses', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'user_events', + 'Events', + "View events triggered by a user's activity.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-events', + @( + 'read' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'followers', + 'Followers', + "A user's followers", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-followers', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'gpg_keys', + 'GPG keys', + "View and manage a user's GPG keys.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-gpg-keys', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'gists', + 'Gists', + "Create and modify a user's gists and comments.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-gists', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'keys', + 'Git SSH keys', + 'Git SSH keys', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-git-ssh-keys', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'interaction_limits', + 'Interaction limits', + 'Interaction limits on repositories', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-interaction-limits', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'knowledge_bases', + 'Knowledge bases', + 'View knowledge bases for a user.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-knowledge-bases', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'user_models', + 'Models', + 'Allows access to GitHub Models.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-models', + @( + 'read' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'plan', + 'Plan', + "View a user's plan.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-plan', + @( + 'read' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'profile', + 'Profile', + "Manage a user's profile settings.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-profile', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'git_signing_ssh_public_keys', + 'SSH signing keys', + "View and manage a user's SSH signing keys.", + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-ssh-signing-keys', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'starring', + 'Starring', + 'List and manage repositories a user is starring.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-starring', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + [GitHubPermissionDefinition]::new( + 'watching', + 'Watching', + 'List and change repositories a user is subscribed to.', + 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + + '#user-permissions-for-watching', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'User' + ), + + # ------------------------------ + # Enterprise Fine-Grained Permission Definitions + # ------------------------------ + [GitHubPermissionDefinition]::new( + 'enterprise_custom_properties', + 'Custom properties', + 'View repository custom properties and administer definitions at the enterprise level.', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-custom-properties', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), + [GitHubPermissionDefinition]::new( + 'enterprise_organization_installation_repositories', + 'Enterprise organization installation repositories', + 'Manage repository access of GitHub Apps on Enterprise-owned organizations', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-organization-installation-repositories', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), + [GitHubPermissionDefinition]::new( + 'enterprise_organization_installations', + 'Enterprise organization installations', + 'Manage installation of GitHub Apps on Enterprise-owned organizations', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-organization-installations', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), + [GitHubPermissionDefinition]::new( + 'enterprise_organizations', + 'Enterprise organizations', + 'Create and remove enterprise organizations', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-organizations', + @( + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), + [GitHubPermissionDefinition]::new( + 'enterprise_people', + 'Enterprise people', + 'Manage user access to the enterprise', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-people', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), + [GitHubPermissionDefinition]::new( + 'enterprise_sso', + 'Enterprise single sign-on', + 'View and manage enterprise single sign-on configuration', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-single-sign-on', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ) + ) + + GitHubPermissionDefinition() {} + + GitHubPermissionDefinition( + [string]$Name, + [string]$DisplayName, + [string]$Description, + [string]$URL, + [string[]]$Options, + [string]$Type, + [string]$Scope + ) { + $this.Name = $Name + $this.DisplayName = $DisplayName + $this.Description = $Description + $this.URL = [uri]$URL + $this.Options = $Options + $this.Type = $Type + $this.Scope = $Scope + } + + [string] ToString() { + return $this.Name + } +} + +class GitHubPermission : GitHubPermissionDefinition { + # The value assigned to the permission. Must be one of the options defined in the parent class. + [string] $Value + + GitHubPermission() : base() {} + + GitHubPermission([string] $Permission, [string] $Value) : base() { + $definition = [GitHubPermissionDefinition]::List | Where-Object { $_.Name -eq $Permission } + if (-not $definition) { + throw "Invalid permission name: $Permission" + } + $this.Name = $definition.Name + $this.DisplayName = $definition.DisplayName + $this.Description = $definition.Description + $this.URL = $definition.URL + $this.Options = $definition.Options + $this.Type = $definition.Type + $this.Scope = $definition.Scope + + if ($Value -and ($definition.Options -notcontains $Value)) { + throw "Invalid permission value: $Value for permission: $Permission" + } + $this.Value = $Value + } + + static [GitHubPermission[]] newFromObject([pscustomobject] $Object) { + if (-not $Object) { return @() } + $result = @() + foreach ($name in ($Object | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name)) { + $tmpValue = $Object.$name + $definition = [GitHubPermissionDefinition]::List | Where-Object { $_.Name -eq $name } + if ($definition) { + if ($tmpValue -and ($definition.Options -notcontains $tmpValue)) { + # Skip invalid value for known permission + continue + } + $perm = [GitHubPermission]::new() + $perm.Name = $definition.Name + $perm.DisplayName = $definition.DisplayName + $perm.Description = $definition.Description + $perm.URL = $definition.URL + $perm.Options = $definition.Options + $perm.Type = $definition.Type + $perm.Scope = $definition.Scope + $perm.Value = $tmpValue + $result += $perm + } else { + # Unknown permission: create minimal object without metadata + $perm = [GitHubPermission]::new() + $perm.Name = $name + $perm.Value = $tmpValue + $result += $perm + } + } + return $result | Sort-Object Scope, DisplayName + } +} diff --git a/src/classes/public/GitHubPermissionDefinition.ps1 b/src/classes/public/GitHubPermissionDefinition.ps1 deleted file mode 100644 index 93ff6643f..000000000 --- a/src/classes/public/GitHubPermissionDefinition.ps1 +++ /dev/null @@ -1,46 +0,0 @@ -class GitHubPermissionDefinition { - # The programmatic name of the permission as returned by the GitHub API - [string] $Name - - # The human-friendly name of the permission as shown in the GitHub UI - [string] $DisplayName - - # A brief description of what access the permission grants - [string] $Description - - # A link to the relevant documentation or GitHub UI page - [uri] $URL - - # The levels of access that can be granted for this permission - [string[]] $Options - - # The type of permission (Fine-grained, Classic) - [string] $Type - - # The scope at which this permission applies (Repository, Organization, User, Enterprise) - [string] $Scope - - GitHubPermissionDefinition() {} - - GitHubPermissionDefinition( - [string]$Name, - [string]$DisplayName, - [string]$Description, - [string]$URL, - [string[]]$Options, - [string]$Type, - [string]$Scope - ) { - $this.Name = $Name - $this.DisplayName = $DisplayName - $this.Description = $Description - $this.URL = [uri]$URL - $this.Options = $Options - $this.Type = $Type - $this.Scope = $Scope - } - - [string] ToString() { - return $this.Name - } -} diff --git a/src/variables/private/GitHub.ps1 b/src/variables/private/GitHub.ps1 index c5b04de1e..67f82414e 100644 --- a/src/variables/private/GitHub.ps1 +++ b/src/variables/private/GitHub.ps1 @@ -23,1184 +23,4 @@ $script:GitHub = [pscustomobject]@{ Config = $null Event = $null Runner = $null - Permissions = @( - # ------------------------------ - # Repository Fine-Grained Permission Definitions - # ------------------------------ - [GitHubPermissionDefinition]::new( - 'actions', - 'Actions', - 'Workflows, workflow runs and artifacts.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-actions', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'administration', - 'Administration', - 'Repository creation, deletion, settings, teams, and collaborators.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-administration', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'attestations', - 'Attestations', - 'Create and retrieve attestations for a repository.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-attestations', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'checks', - 'Checks', - 'Checks on code.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-checks', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'security_events', - 'Code scanning alerts', - 'View and manage code scanning alerts.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-code-scanning-alerts', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'codespaces', - 'Codespaces', - 'Create, edit, delete and list Codespaces.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-codespaces', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'codespaces_lifecycle_admin', - 'Codespaces lifecycle admin', - 'Manage the lifecycle of Codespaces, including starting and stopping.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-codespaces-lifecycle-admin', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'codespaces_metadata', - 'Codespaces metadata', - 'Access Codespaces metadata including the devcontainers and machine type.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-codespaces-metadata', - @( - 'read' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'codespaces_secrets', - 'Codespaces secrets', - 'Restrict Codespaces user secrets modifications to specific repositories.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-codespaces-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'statuses', - 'Commit statuses', - 'Commit statuses.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-commit-statuses', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'contents', - 'Contents', - 'Repository contents, commits, branches, downloads, releases, and merges.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-contents', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'repository_custom_properties', - 'Custom properties', - 'Read and write repository custom properties values at the repository level, when allowed by the property.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-custom-properties', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'vulnerability_alerts', - 'Dependabot alerts', - 'Retrieve Dependabot alerts.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-dependabot-alerts', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'dependabot_secrets', - 'Dependabot secrets', - 'Manage Dependabot repository secrets.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-dependabot-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'deployments', - 'Deployments', - 'Deployments and deployment statuses.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-deployments', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'discussions', - 'Discussions', - 'Discussions and related comments and labels.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-discussions', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'environments', - 'Environments', - 'Manage repository environments.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-environments', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'issues', - 'Issues', - 'Issues and related comments, assignees, labels, and milestones.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-issues', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'merge_queues', - 'Merge queues', - "Manage a repository's merge queues", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-merge-queues', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( #Mandatory - 'metadata', - 'Metadata', - 'Search repositories, list collaborators, and access repository metadata.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-metadata', - @( - 'read' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'packages', - 'Packages', - 'Packages published to the GitHub Package Platform.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-packages', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'pages', - 'Pages', - 'Retrieve Pages statuses, configuration, and builds, as well as create new builds.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-pages', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'repository_projects', - 'Projects', - 'Manage classic projects within a repository.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-projects', - @( - 'read', - 'write', - 'admin' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'pull_requests', - 'Pull requests', - 'Pull requests and related comments, assignees, labels, milestones, and merges.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-pull-requests', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'repository_advisories', - 'Repository security advisories', - 'View and manage repository security advisories.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-repository-security-advisories', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'repo_secret_scanning_dismissal_requests', - 'Secret scanning alert dismissal requests', - 'View and manage secret scanning alert dismissal requests', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-secret-scanning-alert-dismissal-requests', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'secret_scanning_alerts', - 'Secret scanning alerts', - 'View and manage secret scanning alerts.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-secret-scanning-alerts', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'secret_scanning_bypass_requests', - 'Secret scanning push protection bypass requests', - 'Review and manage repository secret scanning push protection bypass requests.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-secret-scanning-push-protection-bypass-requests', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'secrets', - 'Secrets', - 'Manage Actions repository secrets.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'single_file', - 'Single file', - 'Manage just a single file.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-single-file', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'actions_variables', - 'Variables', - 'Manage Actions repository variables.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-variables', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'repository_hooks', - 'Webhooks', - 'Manage the post-receive hooks for a repository.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-webhooks', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - [GitHubPermissionDefinition]::new( - 'workflows', - 'Workflows', - 'Update GitHub Action workflow files.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#repository-permissions-for-workflows', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Repository' - ), - - # ------------------------------ - # Organization Fine-Grained Permission Definitions - # ------------------------------ - [GitHubPermissionDefinition]::new( - 'organization_api_insights', - 'API Insights', - 'View statistics on how the API is being used for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-api-insights', - @( - 'read' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_administration', - 'Administration', - 'Manage access to an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-administration', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_user_blocking', - 'Blocking users', - 'View and manage users blocked by the organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-blocking-users', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_campaigns', - 'Campaigns', - 'Manage campaigns.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-campaigns', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_custom_org_roles', - 'Custom organization roles', - 'Create, edit, delete and list custom organization roles. View system organization roles.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-custom-organization-roles', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_custom_properties', - 'Custom properties', - 'Read and write repository custom properties values and administer definitions at the organization level.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-custom-properties', - @( - 'read', - 'write', - 'admin' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_custom_roles', - 'Custom repository roles', - 'Create, edit, delete and list custom repository roles.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-custom-repository-roles', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_events', - 'Events', - 'View events triggered by an activity in an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-events', - @( - 'read' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_copilot_seat_management', - 'GitHub Copilot Business', - 'Manage Copilot Business seats and settings', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-github-copilot-business', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'issue_fields', - 'Issue Fields', - 'Manage issue fields for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-issue-fields', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'issue_types', - 'Issue Types', - 'Manage issue types for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-issue-types', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_knowledge_bases', - 'Knowledge bases', - 'View and manage knowledge bases for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-knowledge-bases', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'members', - 'Members', - 'Organization members and teams.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-members', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_models', - 'Models', - 'Manage model access for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-models', - @( - 'read' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_network_configurations', - 'Network configurations', - 'View and manage hosted compute network configurations available to an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-network-configurations', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_announcement_banners', - 'Organization announcement banners', - 'View and modify announcement banners for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-announcement-banners', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_secret_scanning_bypass_requests', - 'Organization bypass requests for secret scanning', - 'Review and manage secret scanning push protection bypass requests.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-bypass-requests-for-secret-scanning', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_codespaces', - 'Organization codespaces', - 'Manage Codespaces for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-codespaces', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_codespaces_secrets', - 'Organization codespaces secrets', - 'Manage Codespaces Secrets for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-codespaces-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_codespaces_settings', - 'Organization codespaces settings', - 'Manage Codespaces settings for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-codespaces-settings', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_dependabot_secrets', - 'Organization dependabot secrets', - 'Manage Dependabot organization secrets.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-dependabot-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_code_scanning_dismissal_requests', - 'Organization dismissal requests for code scanning', - 'Review and manage code scanning alert dismissal requests.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-dismissal-requests-for-code-scanning', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_private_registries', - 'Organization private registries', - 'Manage private registries for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-organization-private-registries', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_personal_access_token_requests', - 'Personal access token requests', - 'Manage personal access token requests from organization members.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-personal-access-token-requests', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_personal_access_tokens', - 'Personal access tokens', - 'View and revoke personal access tokens that have been granted access to an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-personal-access-tokens', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_plan', - 'Plan', - "View an organization's plan.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-plan', - @( - 'read' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_projects', - 'Projects', - 'Manage projects for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-projects', - @( - 'read', - 'write', - 'admin' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'secret_scanning_dismissal_requests', - 'Secret scanning alert dismissal requests', - 'Review and manage secret scanning alert dismissal requests', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-secret-scanning-alert-dismissal-requests', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_secrets', - 'Secrets', - 'Manage Actions organization secrets.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_self_hosted_runners', - 'Self-hosted runners', - 'View and manage Actions self-hosted runners available to an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-self-hosted-runners', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'team_discussions', - 'Team discussions', - 'Manage team discussions and related comments.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-team-discussions', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_actions_variables', - 'Variables', - 'Manage Actions organization variables.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-variables', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - [GitHubPermissionDefinition]::new( - 'organization_hooks', - 'Webhooks', - 'Manage the post-receive hooks for an organization.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#organization-permissions-for-webhooks', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Organization' - ), - - # ------------------------------ - # User (Account) Fine-Grained Permission Definitions - # ------------------------------ - [GitHubPermissionDefinition]::new( - 'blocking', - 'Block another user', - 'View and manage users blocked by the user.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-block-another-user', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'codespaces_user_secrets', - 'Codespaces user secrets', - 'Manage Codespaces user secrets.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-codespaces-user-secrets', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'copilot_messages', - 'Copilot Chat', - 'This application will receive your GitHub ID, your GitHub Copilot Chat session messages ' + - '(not including messages sent to another application), and timestamps of provided GitHub Copilot ' + - 'Chat session messages. This permission must be enabled for Copilot Extensions.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-copilot-chat', - @( - 'read' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'copilot_editor_context', - 'Copilot Editor Context', - 'This application will receive bits of Editor Context (e.g. currently opened file) whenever you send it a message through Copilot Chat.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-copilot-editor-context', - @( - 'read' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'emails', - 'Email addresses', - "Manage a user's email addresses.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-email-addresses', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'user_events', - 'Events', - "View events triggered by a user's activity.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-events', - @( - 'read' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'followers', - 'Followers', - "A user's followers", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-followers', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'gpg_keys', - 'GPG keys', - "View and manage a user's GPG keys.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-gpg-keys', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'gists', - 'Gists', - "Create and modify a user's gists and comments.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-gists', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'keys', - 'Git SSH keys', - 'Git SSH keys', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-git-ssh-keys', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'interaction_limits', - 'Interaction limits', - 'Interaction limits on repositories', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-interaction-limits', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'knowledge_bases', - 'Knowledge bases', - 'View knowledge bases for a user.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-knowledge-bases', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'user_models', - 'Models', - 'Allows access to GitHub Models.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-models', - @( - 'read' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'plan', - 'Plan', - "View a user's plan.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-plan', - @( - 'read' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'profile', - 'Profile', - "Manage a user's profile settings.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-profile', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'git_signing_ssh_public_keys', - 'SSH signing keys', - "View and manage a user's SSH signing keys.", - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-ssh-signing-keys', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'starring', - 'Starring', - 'List and manage repositories a user is starring.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-starring', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - [GitHubPermissionDefinition]::new( - 'watching', - 'Watching', - 'List and change repositories a user is subscribed to.', - 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + - '#user-permissions-for-watching', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'User' - ), - - # ------------------------------ - # Enterprise Fine-Grained Permission Definitions - # ------------------------------ - [GitHubPermissionDefinition]::new( - 'enterprise_custom_properties', - 'Custom properties', - 'View repository custom properties and administer definitions at the enterprise level.', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-custom-properties', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Enterprise' - ), - [GitHubPermissionDefinition]::new( - 'enterprise_organization_installation_repositories', - 'Enterprise organization installation repositories', - 'Manage repository access of GitHub Apps on Enterprise-owned organizations', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-enterprise-organization-installation-repositories', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Enterprise' - ), - [GitHubPermissionDefinition]::new( - 'enterprise_organization_installations', - 'Enterprise organization installations', - 'Manage installation of GitHub Apps on Enterprise-owned organizations', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-enterprise-organization-installations', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Enterprise' - ), - [GitHubPermissionDefinition]::new( - 'enterprise_organizations', - 'Enterprise organizations', - 'Create and remove enterprise organizations', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-enterprise-organizations', - @( - 'write' - ), - 'Fine-grained', - 'Enterprise' - ), - [GitHubPermissionDefinition]::new( - 'enterprise_people', - 'Enterprise people', - 'Manage user access to the enterprise', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-enterprise-people', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Enterprise' - ), - [GitHubPermissionDefinition]::new( - 'enterprise_sso', - 'Enterprise single sign-on', - 'View and manage enterprise single sign-on configuration', - 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + - '#enterprise-permissions-for-enterprise-single-sign-on', - @( - 'read', - 'write' - ), - 'Fine-grained', - 'Enterprise' - ) - ) } diff --git a/tests/Artifacts.Tests.ps1 b/test2/Artifacts.Tests.ps1 similarity index 100% rename from tests/Artifacts.Tests.ps1 rename to test2/Artifacts.Tests.ps1 diff --git a/tests/Emojis.Tests.ps1 b/test2/Emojis.Tests.ps1 similarity index 100% rename from tests/Emojis.Tests.ps1 rename to test2/Emojis.Tests.ps1 diff --git a/tests/Enterprise.Tests.ps1 b/test2/Enterprise.Tests.ps1 similarity index 100% rename from tests/Enterprise.Tests.ps1 rename to test2/Enterprise.Tests.ps1 diff --git a/tests/Environments.Tests.ps1 b/test2/Environments.Tests.ps1 similarity index 100% rename from tests/Environments.Tests.ps1 rename to test2/Environments.Tests.ps1 diff --git a/tests/GitHub.Tests.ps1 b/test2/GitHub.Tests.ps1 similarity index 100% rename from tests/GitHub.Tests.ps1 rename to test2/GitHub.Tests.ps1 diff --git a/tests/GitHubFormatter.Tests.ps1 b/test2/GitHubFormatter.Tests.ps1 similarity index 100% rename from tests/GitHubFormatter.Tests.ps1 rename to test2/GitHubFormatter.Tests.ps1 diff --git a/tests/Organizations.Tests.ps1 b/test2/Organizations.Tests.ps1 similarity index 100% rename from tests/Organizations.Tests.ps1 rename to test2/Organizations.Tests.ps1 diff --git a/tests/README.md b/test2/README.md similarity index 100% rename from tests/README.md rename to test2/README.md diff --git a/tests/Releases.Tests.ps1 b/test2/Releases.Tests.ps1 similarity index 100% rename from tests/Releases.Tests.ps1 rename to test2/Releases.Tests.ps1 diff --git a/tests/Repositories.Tests.ps1 b/test2/Repositories.Tests.ps1 similarity index 100% rename from tests/Repositories.Tests.ps1 rename to test2/Repositories.Tests.ps1 diff --git a/tests/Secrets.Tests.ps1 b/test2/Secrets.Tests.ps1 similarity index 100% rename from tests/Secrets.Tests.ps1 rename to test2/Secrets.Tests.ps1 diff --git a/tests/TEMPLATE.ps1 b/test2/TEMPLATE.ps1 similarity index 100% rename from tests/TEMPLATE.ps1 rename to test2/TEMPLATE.ps1 diff --git a/tests/Teams.Tests.ps1 b/test2/Teams.Tests.ps1 similarity index 100% rename from tests/Teams.Tests.ps1 rename to test2/Teams.Tests.ps1 diff --git a/tests/Users.Tests.ps1 b/test2/Users.Tests.ps1 similarity index 100% rename from tests/Users.Tests.ps1 rename to test2/Users.Tests.ps1 diff --git a/tests/Variables.Tests.ps1 b/test2/Variables.Tests.ps1 similarity index 100% rename from tests/Variables.Tests.ps1 rename to test2/Variables.Tests.ps1 From 6dc3cc6f591e8bb78cb5e526be012759faddf871 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 14:42:39 +0200 Subject: [PATCH 08/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20Permis?= =?UTF-8?q?sions=20property=20type=20to=20GitHubPermission=20array=20in=20?= =?UTF-8?q?GitHubApp=20and=20GitHubAppContext=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubApp.ps1 | 4 ++-- src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index 620f52af5..80b78bff8 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -36,7 +36,7 @@ [System.Nullable[datetime]] $UpdatedAt # The permissions that the app is requesting. - [pscustomobject] $Permissions + [GitHubPermission[]] $Permissions # The events that the app is subscribing to on its target. [string[]] $Events @@ -59,7 +59,7 @@ $this.Url = $Object.html_url $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Permissions = $Object.permissions + $this.Permissions = [GitHubPermission]::newFromObject($Object.permissions) $this.Events = , ($Object.events) $this.Installations = $Object.installations_count } diff --git a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 index 02cc68a67..815c5151a 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 @@ -15,7 +15,7 @@ [string] $OwnerType # The permissions that the app is requesting on the target - [pscustomobject] $Permissions + [GitHubPermission[]] $Permissions # The events that the app is subscribing to once installed [string[]] $Events @@ -47,7 +47,7 @@ $this.KeyVaultKeyReference = $Object.KeyVaultKeyReference $this.OwnerName = $Object.OwnerName $this.OwnerType = $Object.OwnerType - $this.Permissions = $Object.Permissions + $this.Permissions = [GitHubPermission]::newFromObject($Object.Permissions) $this.Events = $Object.Events } } From d3285a6dfb2d1045505abd53d6839c0b378b8d30 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 14:54:38 +0200 Subject: [PATCH 09/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Add=20GitHubPer?= =?UTF-8?q?mission.Format.ps1xml=20for=20GitHubPermission=20table=20format?= =?UTF-8?q?ting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/formats/GitHubPermission.Format.ps1xml | 52 ++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/formats/GitHubPermission.Format.ps1xml diff --git a/src/formats/GitHubPermission.Format.ps1xml b/src/formats/GitHubPermission.Format.ps1xml new file mode 100644 index 000000000..d917b0c3c --- /dev/null +++ b/src/formats/GitHubPermission.Format.ps1xml @@ -0,0 +1,52 @@ + + + + + GitHubPermissionTable + + GitHubPermission + + + + + + + + + + + + + + + + + + + + + Scope + + + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true') -and $_.Url) { + $PSStyle.FormatHyperlink($_.DisplayName, $_.Url) + } else { + $_.DisplayName + } + + + + Value + + + Description + + + + + + + + From c402d1f2faed7b1e5f44a1d12b39e706b31cfedd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 15:24:16 +0200 Subject: [PATCH 10/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Enhance=20GitHu?= =?UTF-8?q?bPermission=20initialization=20to=20handle=20unknown=20permissi?= =?UTF-8?q?ons=20and=20update=20permissions=20assignment=20in=20Set-GitHub?= =?UTF-8?q?Context=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 27 ++++++++++++------- .../Auth/Context/Set-GitHubContext.ps1 | 2 +- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 330f40599..432d85726 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1234,16 +1234,25 @@ class GitHubPermission : GitHubPermissionDefinition { GitHubPermission([string] $Permission, [string] $Value) : base() { $definition = [GitHubPermissionDefinition]::List | Where-Object { $_.Name -eq $Permission } - if (-not $definition) { - throw "Invalid permission name: $Permission" + if ($definition) { + $this.Name = $definition.Name + $this.DisplayName = $definition.DisplayName + $this.Description = $definition.Description + $this.URL = $definition.URL + $this.Options = $definition.Options + $this.Type = $definition.Type + $this.Scope = $definition.Scope + } else { + $this.Name = $Permission + $this.DisplayName = $Permission + $this.Description = 'Unknown permission - Open issue to add metadata' + $this.URL = $null + $this.Options = @() + $this.Type = 'Unknown' + $this.Scope = 'Unknown' + } - $this.Name = $definition.Name - $this.DisplayName = $definition.DisplayName - $this.Description = $definition.Description - $this.URL = $definition.URL - $this.Options = $definition.Options - $this.Type = $definition.Type - $this.Scope = $definition.Scope + if ($Value -and ($definition.Options -notcontains $Value)) { throw "Invalid permission value: $Value for permission: $Permission" diff --git a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 index c20ea4281..d3de2d165 100644 --- a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 @@ -127,7 +127,7 @@ $contextObj['Username'] = [string]$app.Slug $contextObj['NodeID'] = [string]$app.NodeID $contextObj['DatabaseID'] = [string]$app.ID - $contextObj['Permissions'] = [PSCustomObject]$app.Permissions + $contextObj['Permissions'] = [GitHubPermission]::newFromObject($app.Permissions) $contextObj['Events'] = [string[]]$app.Events $contextObj['OwnerName'] = [string]$app.Owner.Name $contextObj['OwnerType'] = [string]$app.Owner.Type From 1fc35cfb9c2b640d1b11411a768966878b5ffd5b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 16:42:38 +0200 Subject: [PATCH 11/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20perm?= =?UTF-8?q?ission=20handling=20in=20GitHubApp=20and=20GitHubAppInstallatio?= =?UTF-8?q?n=20classes=20to=20use=20NewFullCatalog=20for=20comprehensive?= =?UTF-8?q?=20permission=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubApp.ps1 | 2 +- .../public/App/GitHubAppInstallation.ps1 | 39 +++++++++-------- .../GitHubAppInstallationContext.ps1 | 2 +- src/classes/public/GitHubPermission.ps1 | 43 +++++++++++++++++++ .../Auth/Context/Set-GitHubContext.ps1 | 2 +- .../public/Auth/Connect-GitHubApp.ps1 | 6 +-- 6 files changed, 70 insertions(+), 24 deletions(-) diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index 80b78bff8..86eda5817 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -59,7 +59,7 @@ $this.Url = $Object.html_url $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Permissions = [GitHubPermission]::newFromObject($Object.permissions) + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $null) $this.Events = , ($Object.events) $this.Installations = $Object.installations_count } diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 7812e3140..7e02acf5a 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -14,8 +14,8 @@ # The type of repository selection. [string] $RepositorySelection - # The permissions that the app has on the target. - [pscustomobject] $Permissions + # The permissions that the app has on the target (full catalog for applicable scopes). + [GitHubPermission[]] $Permissions # The events that the app is subscribing to. [string[]] $Events @@ -47,23 +47,26 @@ GitHubAppInstallation() {} # Helper method to compare installation permissions and events with app configuration - hidden [string] CompareWithAppConfiguration([pscustomobject] $AppPermissions, [string[]] $AppEvents) { + hidden [string] CompareWithAppConfiguration([GitHubPermission[]] $AppPermissions, [string[]] $AppEvents) { if ($null -eq $AppPermissions -or $null -eq $AppEvents) { return 'Unknown' } - # Compare permissions - check if installation has all the permissions that the app requires + # Build lookup tables + $appLookup = @{} + foreach ($p in $AppPermissions) { $appLookup[$p.Name] = $p } + $instLookup = @{} + foreach ($p in $this.Permissions) { $instLookup[$p.Name] = $p } + + # Compare permissions - check if installation has all the permissions that the app requires (for its scope) $permissionsMatch = $true - if ($AppPermissions.PSObject.Properties) { - foreach ($permission in $AppPermissions.PSObject.Properties) { - $appPermissionLevel = $permission.Value - $installationPermissionLevel = $this.Permissions.PSObject.Properties[$permission.Name]?.Value - - # If app requires a permission but installation doesn't have it, or has lower level - if ($appPermissionLevel -ne 'none' -and $installationPermissionLevel -ne $appPermissionLevel) { - $permissionsMatch = $false - break - } + foreach ($appPerm in $AppPermissions) { + # Only evaluate permissions with a value (requested). Missing (null) == no access required. + if ([string]::IsNullOrEmpty($appPerm.Value)) { continue } + $instValue = $instLookup[$appPerm.Name].Value + if ($instValue -ne $appPerm.Value) { + $permissionsMatch = $false + break } } @@ -101,7 +104,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = $Object.permissions + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -118,7 +121,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = $Object.permissions + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -145,7 +148,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = $Object.permissions + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -166,7 +169,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = $Object.permissions + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 index d4a24882a..2ef4a142f 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -41,7 +41,7 @@ $this.PerPage = $Object.PerPage $this.ClientID = $Object.ClientID $this.InstallationID = $Object.InstallationID - $this.Permissions = [GitHubPermission]::newFromObject($Object.Permissions) + $this.Permissions = $Object.Permissions $this.Events = $Object.Events $this.InstallationType = $Object.InstallationType $this.InstallationName = $Object.InstallationName diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 432d85726..c277b58fa 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1291,4 +1291,47 @@ class GitHubPermission : GitHubPermissionDefinition { } return $result | Sort-Object Scope, DisplayName } + + # Returns a full catalog of permissions, ensuring all known permissions are present. + # If an object with granted permissions is provided, its values are merged. + static [GitHubPermission[]] NewFullCatalog([pscustomobject] $Object, [string] $InstallationType) { + $granted = [GitHubPermission]::newFromObject($Object) + $grantedLookup = @{} + foreach ($g in $granted) { $grantedLookup[$g.Name] = $g } + + $full = @() + foreach ($definition in [GitHubPermissionDefinition]::List) { + if ($grantedLookup.ContainsKey($definition.Name)) { + $full += $grantedLookup[$definition.Name] + } else { + $perm = [GitHubPermission]::new() + $perm.Name = $definition.Name + $perm.DisplayName = $definition.DisplayName + $perm.Description = $definition.Description + $perm.URL = $definition.URL + $perm.Options = $definition.Options + $perm.Type = $definition.Type + $perm.Scope = $definition.Scope + $perm.Value = $null + $full += $perm + } + } + + # Include unknown permissions (those not in catalog) as-is + foreach ($g in $granted) { + if (-not ([GitHubPermissionDefinition]::List.Name -contains $g.Name)) { + $full += $g + } + } + $full = $full | Sort-Object Scope, DisplayName + + if (-not $InstallationType) { return $full } + + switch ($InstallationType) { + 'Enterprise' { return $full | Where-Object { $_.Scope -eq 'Enterprise' } } + 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization','Repository') } } + 'User' { return $full | Where-Object { $_.Scope -in @('User','Repository') } } + default { return $full } + } + } } diff --git a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 index d3de2d165..91ba59e6f 100644 --- a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 @@ -127,7 +127,7 @@ $contextObj['Username'] = [string]$app.Slug $contextObj['NodeID'] = [string]$app.NodeID $contextObj['DatabaseID'] = [string]$app.ID - $contextObj['Permissions'] = [GitHubPermission]::newFromObject($app.Permissions) + $contextObj['Permissions'] = [GitHubPermission[]]$app.Permissions $contextObj['Events'] = [string[]]$app.Events $contextObj['OwnerName'] = [string]$app.Owner.Name $contextObj['OwnerType'] = [string]$app.Owner.Type diff --git a/src/functions/public/Auth/Connect-GitHubApp.ps1 b/src/functions/public/Auth/Connect-GitHubApp.ps1 index e69343465..7e208f006 100644 --- a/src/functions/public/Auth/Connect-GitHubApp.ps1 +++ b/src/functions/public/Auth/Connect-GitHubApp.ps1 @@ -134,9 +134,9 @@ HttpVersion = [string]$Context.HttpVersion PerPage = [int]$Context.PerPage ClientID = [string]$Context.ClientID - InstallationID = [string]$installation.id - Permissions = [pscustomobject]$installation.permissions - Events = [string[]]$installation.events + InstallationID = [string]$installation.ID + Permissions = [GitHubPermission[]]$installation.Permissions + Events = [string[]]$installation.Events InstallationType = [string]$installation.Type Token = [securestring]$token.Token TokenExpiresAt = [datetime]$token.ExpiresAt From bfe5279e2b49128368bed920ae92e9674c8b6727 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 16:43:19 +0200 Subject: [PATCH 12/43] Fix formatting in NewFullCatalog function for better readability of scope conditions --- src/classes/public/GitHubPermission.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index c277b58fa..f4d147e63 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1329,8 +1329,8 @@ class GitHubPermission : GitHubPermissionDefinition { switch ($InstallationType) { 'Enterprise' { return $full | Where-Object { $_.Scope -eq 'Enterprise' } } - 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization','Repository') } } - 'User' { return $full | Where-Object { $_.Scope -in @('User','Repository') } } + 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization', 'Repository') } } + 'User' { return $full | Where-Object { $_.Scope -in @('User', 'Repository') } } default { return $full } } } From 2c60136993f4fc978586f65c112ca7c5815eb41b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 16:47:44 +0200 Subject: [PATCH 13/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Simplify=20retu?= =?UTF-8?q?rn=20statement=20in=20NewFullCatalog=20function=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index f4d147e63..3ea2b16fb 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1331,7 +1331,7 @@ class GitHubPermission : GitHubPermissionDefinition { 'Enterprise' { return $full | Where-Object { $_.Scope -eq 'Enterprise' } } 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization', 'Repository') } } 'User' { return $full | Where-Object { $_.Scope -in @('User', 'Repository') } } - default { return $full } } + return $full } } From 32710e4b6e8bd16c38ca84217663608f59eb91f1 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 16:53:47 +0200 Subject: [PATCH 14/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20permis?= =?UTF-8?q?sions=20assignment=20in=20GitHubAppInstallationContext=20to=20u?= =?UTF-8?q?se=20NewFullCatalog=20for=20improved=20permission=20management?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Context/GitHubContext/GitHubAppInstallationContext.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 index 2ef4a142f..37fa9c5ba 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -41,7 +41,7 @@ $this.PerPage = $Object.PerPage $this.ClientID = $Object.ClientID $this.InstallationID = $Object.InstallationID - $this.Permissions = $Object.Permissions + $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.Permissions, $Object.InstallationType) $this.Events = $Object.Events $this.InstallationType = $Object.InstallationType $this.InstallationName = $Object.InstallationName From a8097a42d4d156d2d79effcce69968a514502d49 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 22:12:54 +0200 Subject: [PATCH 15/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20perm?= =?UTF-8?q?ission=20handling=20to=20use=20newPermissionList=20for=20consis?= =?UTF-8?q?tency=20across=20GitHubApp,=20GitHubAppInstallation,=20and=20Gi?= =?UTF-8?q?tHubAppContext=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubApp.ps1 | 2 +- .../public/App/GitHubAppInstallation.ps1 | 8 ++-- .../GitHubContext/GitHubAppContext.ps1 | 2 +- .../GitHubAppInstallationContext.ps1 | 2 +- src/classes/public/GitHubPermission.ps1 | 44 +++++-------------- 5 files changed, 19 insertions(+), 39 deletions(-) diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index 86eda5817..bf2cba325 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -59,7 +59,7 @@ $this.Url = $Object.html_url $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $null) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $null) $this.Events = , ($Object.events) $this.Installations = $Object.installations_count } diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 7e02acf5a..d23ea7186 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -104,7 +104,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -121,7 +121,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -148,7 +148,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -169,7 +169,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at diff --git a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 index 815c5151a..9975974ce 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 @@ -47,7 +47,7 @@ $this.KeyVaultKeyReference = $Object.KeyVaultKeyReference $this.OwnerName = $Object.OwnerName $this.OwnerType = $Object.OwnerType - $this.Permissions = [GitHubPermission]::newFromObject($Object.Permissions) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions, $null) $this.Events = $Object.Events } } diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 index 37fa9c5ba..ee6ee092d 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -41,7 +41,7 @@ $this.PerPage = $Object.PerPage $this.ClientID = $Object.ClientID $this.InstallationID = $Object.InstallationID - $this.Permissions = [GitHubPermission]::NewFullCatalog($Object.Permissions, $Object.InstallationType) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions, $Object.InstallationType) $this.Events = $Object.Events $this.InstallationType = $Object.InstallationType $this.InstallationName = $Object.InstallationName diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 3ea2b16fb..5bf75db69 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1242,7 +1242,11 @@ class GitHubPermission : GitHubPermissionDefinition { $this.Options = $definition.Options $this.Type = $definition.Type $this.Scope = $definition.Scope + if ($Value -and ($definition.Options -notcontains $Value)) { + throw "Invalid permission value: $Value for permission: $Permission" + } } else { + # Unknown permission: accept any value without validation $this.Name = $Permission $this.DisplayName = $Permission $this.Description = 'Unknown permission - Open issue to add metadata' @@ -1250,43 +1254,20 @@ class GitHubPermission : GitHubPermissionDefinition { $this.Options = @() $this.Type = 'Unknown' $this.Scope = 'Unknown' - - } - - - if ($Value -and ($definition.Options -notcontains $Value)) { - throw "Invalid permission value: $Value for permission: $Permission" } $this.Value = $Value } - static [GitHubPermission[]] newFromObject([pscustomobject] $Object) { + static [GitHubPermission[]] newPermission([pscustomobject] $Object) { if (-not $Object) { return @() } $result = @() foreach ($name in ($Object | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name)) { $tmpValue = $Object.$name - $definition = [GitHubPermissionDefinition]::List | Where-Object { $_.Name -eq $name } - if ($definition) { - if ($tmpValue -and ($definition.Options -notcontains $tmpValue)) { - # Skip invalid value for known permission - continue - } - $perm = [GitHubPermission]::new() - $perm.Name = $definition.Name - $perm.DisplayName = $definition.DisplayName - $perm.Description = $definition.Description - $perm.URL = $definition.URL - $perm.Options = $definition.Options - $perm.Type = $definition.Type - $perm.Scope = $definition.Scope - $perm.Value = $tmpValue - $result += $perm - } else { - # Unknown permission: create minimal object without metadata - $perm = [GitHubPermission]::new() - $perm.Name = $name - $perm.Value = $tmpValue - $result += $perm + try { + $result += [GitHubPermission]::new($name, $tmpValue) + } catch { + # Skip invalid value for known permission (constructor throws); preserves original behavior. + continue } } return $result | Sort-Object Scope, DisplayName @@ -1294,8 +1275,8 @@ class GitHubPermission : GitHubPermissionDefinition { # Returns a full catalog of permissions, ensuring all known permissions are present. # If an object with granted permissions is provided, its values are merged. - static [GitHubPermission[]] NewFullCatalog([pscustomobject] $Object, [string] $InstallationType) { - $granted = [GitHubPermission]::newFromObject($Object) + static [GitHubPermission[]] newPermissionList([pscustomobject] $Object, [string] $InstallationType) { + $granted = [GitHubPermission]::newPermission($Object) $grantedLookup = @{} foreach ($g in $granted) { $grantedLookup[$g.Name] = $g } @@ -1317,7 +1298,6 @@ class GitHubPermission : GitHubPermissionDefinition { } } - # Include unknown permissions (those not in catalog) as-is foreach ($g in $granted) { if (-not ([GitHubPermissionDefinition]::List.Name -contains $g.Name)) { $full += $g From 968dce5192fb09a364f9e2dd450ca2b30363a89b Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 23:35:04 +0200 Subject: [PATCH 16/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Add=20new=20per?= =?UTF-8?q?mission=20definition=20for=20enterprise=20custom=20organization?= =?UTF-8?q?=20roles=20with=20detailed=20description=20and=20access=20level?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 5bf75db69..0abbf2996 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1135,6 +1135,19 @@ class GitHubPermissionDefinition { 'Fine-grained', 'Enterprise' ), + [GitHubPermissionDefinition]::new( + 'enterprise_custom_org_roles', + 'Enterprise custom organization roles', + 'Create, edit, delete and list custom organization roles at the enterprise level. View system organization roles.', + 'https://docs.github.com/enterprise-cloud@latest/rest/overview/permissions-required-for-github-apps' + + '#enterprise-permissions-for-enterprise-custom-organization-roles', + @( + 'read', + 'write' + ), + 'Fine-grained', + 'Enterprise' + ), [GitHubPermissionDefinition]::new( 'enterprise_organization_installation_repositories', 'Enterprise organization installation repositories', From 6ce56a6945f20bbfed3678698bcbfa5a1b9fba64 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sat, 13 Sep 2025 23:45:12 +0200 Subject: [PATCH 17/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Remove=20'read'?= =?UTF-8?q?=20option=20from=20permission=20definition=20for=20improved=20c?= =?UTF-8?q?larity=20in=20access=20levels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 0abbf2996..5c8e10e8c 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1073,7 +1073,6 @@ class GitHubPermissionDefinition { 'https://docs.github.com/rest/overview/permissions-required-for-github-apps' + '#user-permissions-for-profile', @( - 'read', 'write' ), 'Fine-grained', From 8d245b69defa3e01ba650b8f67218403c56c635d Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 01:13:20 +0200 Subject: [PATCH 18/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Remove=20Compar?= =?UTF-8?q?eWithAppConfiguration=20method=20and=20replace=20with=20UpdateS?= =?UTF-8?q?tatus=20for=20improved=20permission=20status=20management=20in?= =?UTF-8?q?=20GitHubAppInstallation=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/App/GitHubAppInstallation.ps1 | 110 ++++++++++-------- src/classes/public/GitHubPermission.ps1 | 2 - 2 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index d23ea7186..327e89686 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -46,52 +46,6 @@ GitHubAppInstallation() {} - # Helper method to compare installation permissions and events with app configuration - hidden [string] CompareWithAppConfiguration([GitHubPermission[]] $AppPermissions, [string[]] $AppEvents) { - if ($null -eq $AppPermissions -or $null -eq $AppEvents) { - return 'Unknown' - } - - # Build lookup tables - $appLookup = @{} - foreach ($p in $AppPermissions) { $appLookup[$p.Name] = $p } - $instLookup = @{} - foreach ($p in $this.Permissions) { $instLookup[$p.Name] = $p } - - # Compare permissions - check if installation has all the permissions that the app requires (for its scope) - $permissionsMatch = $true - foreach ($appPerm in $AppPermissions) { - # Only evaluate permissions with a value (requested). Missing (null) == no access required. - if ([string]::IsNullOrEmpty($appPerm.Value)) { continue } - $instValue = $instLookup[$appPerm.Name].Value - if ($instValue -ne $appPerm.Value) { - $permissionsMatch = $false - break - } - } - - # Compare events - check if installation subscribes to all events that the app wants - $eventsMatch = $true - if ($AppEvents -and $AppEvents.Count -gt 0) { - foreach ($appEvent in $AppEvents) { - if ($appEvent -notin $this.Events) { - $eventsMatch = $false - break - } - } - } - - if ($permissionsMatch -and $eventsMatch) { - return 'UpToDate' - } elseif (-not $permissionsMatch -and -not $eventsMatch) { - return 'PermissionsAndEventsOutdated' - } elseif (-not $permissionsMatch) { - return 'PermissionsOutdated' - } else { - return 'EventsOutdated' - } - } - GitHubAppInstallation([PSCustomObject] $Object) { $this.ID = $Object.id $this.App = [GitHubApp]::new( @@ -129,7 +83,7 @@ $this.SuspendedAt = $Object.suspended_at $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) $this.Url = $Object.html_url - $this.Status = $this.CompareWithAppConfiguration($App.Permissions, $App.Events) + $this.UpdateStatus() } GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context) { @@ -177,6 +131,66 @@ $this.SuspendedAt = $Object.suspended_at $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) $this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)" - $this.Status = $this.CompareWithAppConfiguration($App.Permissions, $App.Events) + $this.UpdateStatus() + } + + # Updates the Status property by comparing installation permissions with app permissions + # filtered by the appropriate scope based on installation type + [void] UpdateStatus() { + if (-not $this.App -or -not $this.App.Permissions) { + $this.Status = 'Unknown' + return + } + + # Get app permissions filtered by installation type scope + $appPermissionsFiltered = switch ($this.Type) { + 'Enterprise' { + $this.App.Permissions | Where-Object { $_.Scope -eq 'Enterprise' } + } + 'Organization' { + $this.App.Permissions | Where-Object { $_.Scope -in @('Organization', 'Repository') } + } + 'User' { + $this.App.Permissions | Where-Object { $_.Scope -in @('User', 'Repository') } + } + default { + $this.App.Permissions + } + } + + # Compare permissions by creating lookup dictionaries + $appPermissionLookup = @{} + foreach ($perm in $appPermissionsFiltered) { + $appPermissionLookup[$perm.Name] = $perm.Value + } + + $installationPermissionLookup = @{} + foreach ($perm in $this.Permissions) { + $installationPermissionLookup[$perm.Name] = $perm.Value + } + + # Check if permissions match + $permissionsMatch = $true + + # Check if all app permissions exist in installation with same values + foreach ($name in $appPermissionLookup.Keys) { + if (-not $installationPermissionLookup.ContainsKey($name) -or + $installationPermissionLookup[$name] -ne $appPermissionLookup[$name]) { + $permissionsMatch = $false + break + } + } + + # Check if installation has any extra permissions not in the app + if ($permissionsMatch) { + foreach ($name in $installationPermissionLookup.Keys) { + if (-not $appPermissionLookup.ContainsKey($name)) { + $permissionsMatch = $false + break + } + } + } + + $this.Status = $permissionsMatch ? 'Ok' : 'Outdated' } } diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 5c8e10e8c..e2695e266 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1285,8 +1285,6 @@ class GitHubPermission : GitHubPermissionDefinition { return $result | Sort-Object Scope, DisplayName } - # Returns a full catalog of permissions, ensuring all known permissions are present. - # If an object with granted permissions is provided, its values are merged. static [GitHubPermission[]] newPermissionList([pscustomobject] $Object, [string] $InstallationType) { $granted = [GitHubPermission]::newPermission($Object) $grantedLookup = @{} From 4b901dd5de5e0207ec443287f23462da32f5f6fe Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 19:34:27 +0200 Subject: [PATCH 19/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20permis?= =?UTF-8?q?sion=20filtering=20for=20'User'=20scope=20to=20only=20include?= =?UTF-8?q?=20'Repository'=20in=20GitHubPermission=20and=20GitHubAppInstal?= =?UTF-8?q?lation=20classes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubAppInstallation.ps1 | 2 +- src/classes/public/GitHubPermission.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 327e89686..80beea68f 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -151,7 +151,7 @@ $this.App.Permissions | Where-Object { $_.Scope -in @('Organization', 'Repository') } } 'User' { - $this.App.Permissions | Where-Object { $_.Scope -in @('User', 'Repository') } + $this.App.Permissions | Where-Object { $_.Scope -in @('Repository') } } default { $this.App.Permissions diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index e2695e266..e9b9f527e 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1320,7 +1320,7 @@ class GitHubPermission : GitHubPermissionDefinition { switch ($InstallationType) { 'Enterprise' { return $full | Where-Object { $_.Scope -eq 'Enterprise' } } 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization', 'Repository') } } - 'User' { return $full | Where-Object { $_.Scope -in @('User', 'Repository') } } + 'User' { return $full | Where-Object { $_.Scope -in @('Repository') } } } return $full } From 4172baba39708079da63bb60feff691002f36b49 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 20:59:02 +0200 Subject: [PATCH 20/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20permis?= =?UTF-8?q?sion=20validation=20in=20tests=20to=20ensure=20correct=20types?= =?UTF-8?q?=20and=20counts=20for=20GitHubPermission=20objects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 62 +++++++------------------ tests/Apps.Tests.ps1 | 53 +++++++-------------- 2 files changed, 34 insertions(+), 81 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index e9b9f527e..174008304 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1270,58 +1270,30 @@ class GitHubPermission : GitHubPermissionDefinition { $this.Value = $Value } - static [GitHubPermission[]] newPermission([pscustomobject] $Object) { - if (-not $Object) { return @() } - $result = @() - foreach ($name in ($Object | Get-Member -MemberType NoteProperty | Select-Object -ExpandProperty Name)) { - $tmpValue = $Object.$name - try { - $result += [GitHubPermission]::new($name, $tmpValue) - } catch { - # Skip invalid value for known permission (constructor throws); preserves original behavior. - continue - } + static [GitHubPermission[]] newPermissionListTemplate ([string] $InstallationType) { + $all = [GitHubPermissionDefinition]::List + $expectedDefs = switch ($InstallationType) { + 'Enterprise' { $all | Where-Object { $_.Scope -eq 'Enterprise' } } + 'Organization' { $all | Where-Object { $_.Scope -in @('Organization', 'Repository') } } + 'User' { $all | Where-Object { $_.Scope -in @('Repository') } } + } + $full = foreach ($def in $expectedDefs) { + [GitHubPermission]::new($def.Name, $null) } - return $result | Sort-Object Scope, DisplayName + return $full } static [GitHubPermission[]] newPermissionList([pscustomobject] $Object, [string] $InstallationType) { - $granted = [GitHubPermission]::newPermission($Object) - $grantedLookup = @{} - foreach ($g in $granted) { $grantedLookup[$g.Name] = $g } + $permissions = [GitHubPermission]::newPermissionListTemplate($InstallationType) - $full = @() - foreach ($definition in [GitHubPermissionDefinition]::List) { - if ($grantedLookup.ContainsKey($definition.Name)) { - $full += $grantedLookup[$definition.Name] + foreach ($prop in $Object.PSObject.Properties) { + $knownPermission = $permissions | Where-Object { $_.Name -eq $prop.Name } + if ($knownPermission) { + $knownPermission.Value = $prop.Value } else { - $perm = [GitHubPermission]::new() - $perm.Name = $definition.Name - $perm.DisplayName = $definition.DisplayName - $perm.Description = $definition.Description - $perm.URL = $definition.URL - $perm.Options = $definition.Options - $perm.Type = $definition.Type - $perm.Scope = $definition.Scope - $perm.Value = $null - $full += $perm - } - } - - foreach ($g in $granted) { - if (-not ([GitHubPermissionDefinition]::List.Name -contains $g.Name)) { - $full += $g + $permissions += [GitHubPermission]::new($prop.Name, $prop.Value) } } - $full = $full | Sort-Object Scope, DisplayName - - if (-not $InstallationType) { return $full } - - switch ($InstallationType) { - 'Enterprise' { return $full | Where-Object { $_.Scope -eq 'Enterprise' } } - 'Organization' { return $full | Where-Object { $_.Scope -in @('Organization', 'Repository') } } - 'User' { return $full | Where-Object { $_.Scope -in @('Repository') } } - } - return $full + return $permissions | Sort-Object Scope, DisplayName } } diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index 7f59d3c1f..20f94afd5 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -56,7 +56,9 @@ Describe 'Apps' { $app.Url | Should -Not -BeNullOrEmpty $app.CreatedAt | Should -Not -BeNullOrEmpty $app.UpdatedAt | Should -Not -BeNullOrEmpty - $app.Permissions | Should -BeOfType 'PSCustomObject' + $app.Permissions.Count | Should -BeGreaterThan 0 + $app.Permissions | Should -BeOfType 'GitHubPermission' + $app.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name $app.Events | Should -BeOfType 'string' $app.Installations | Should -Not -BeNullOrEmpty } @@ -86,16 +88,17 @@ Describe 'Apps' { $installation.Target | Should -Not -BeNullOrEmpty $installation.Type | Should -BeIn @('Enterprise', 'Organization', 'User') $installation.RepositorySelection | Should -Not -BeNullOrEmpty - $installation.Permissions | Should -BeOfType 'PSCustomObject' + $installation.Permissions.Count | Should -BeGreaterThan 0 + $installation.Permissions | Should -BeOfType [GitHubPermission] + $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name $installation.Events | Should -BeOfType 'string' $installation.CreatedAt | Should -Not -BeNullOrEmpty $installation.UpdatedAt | Should -Not -BeNullOrEmpty $installation.SuspendedAt | Should -BeNullOrEmpty $installation.SuspendedBy | Should -BeOfType 'GitHubUser' $installation.SuspendedBy | Should -BeNullOrEmpty - # Validate the new Status property $installation.Status | Should -Not -BeNullOrEmpty - $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') + $installation.Status | Should -BeIn @('Ok', 'Outdated') } } @@ -116,45 +119,17 @@ Describe 'Apps' { $installation.Target | Should -Be $owner $installation.Type | Should -Be $ownerType $installation.RepositorySelection | Should -Not -BeNullOrEmpty - $installation.Permissions | Should -BeOfType 'PSCustomObject' + $installation.Permissions.Count | Should -BeGreaterThan 0 + $installation.Permissions | Should -BeOfType [GitHubPermission] + $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name $installation.Events | Should -BeOfType 'string' $installation.CreatedAt | Should -Not -BeNullOrEmpty $installation.UpdatedAt | Should -Not -BeNullOrEmpty $installation.SuspendedAt | Should -BeNullOrEmpty $installation.SuspendedBy | Should -BeOfType 'GitHubUser' $installation.SuspendedBy | Should -BeNullOrEmpty - # Validate the new Status property $installation.Status | Should -Not -BeNullOrEmpty - $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') - } - - It 'Get-GitHubAppInstallation - Status tracking functionality' { - $githubApp = Get-GitHubApp - $installations = Get-GitHubAppInstallation - LogGroup 'Status tracking test' { - Write-Host "Testing installation status tracking against app configuration" - Write-Host "App Permissions: $($githubApp.Permissions | ConvertTo-Json -Compress)" - Write-Host "App Events: $($githubApp.Events -join ', ')" - } - - foreach ($installation in $installations) { - LogGroup "Installation $($installation.ID) Status" { - Write-Host "Installation ID: $($installation.ID)" - Write-Host "Target: $($installation.Target.Name)" - Write-Host "Status: $($installation.Status)" - Write-Host "Installation Permissions: $($installation.Permissions | ConvertTo-Json -Compress)" - Write-Host "Installation Events: $($installation.Events -join ', ')" - } - - # The status should be calculated based on app vs installation comparison - $installation.Status | Should -BeIn @('Unknown', 'UpToDate', 'PermissionsOutdated', 'EventsOutdated', 'PermissionsAndEventsOutdated') - - # When we have app information (which we do in this context), status should not be Unknown - # for authenticated app installations - if ($PSCmdlet.ParameterSetName -eq 'List installations for the authenticated app') { - $installation.Status | Should -Not -Be 'Unknown' - } - } + $installation.Status | Should -BeIn @('Ok', 'Outdated') } } @@ -235,6 +210,12 @@ Describe 'Apps' { $context.TokenType | Should -Be 'ghs' $context.HttpVersion | Should -Be $config.HttpVersion $context.PerPage | Should -Be $config.PerPage + $context.Permissions.Count | Should -BeGreaterThan 0 + $context.Permissions | Should -BeOfType [GitHubPermission] + $context.Permissions.Name | Should -BeIn ([GitHubPermission]::List).Name + $context.Permissions.Value | Should -BeIn ([GitHubPermission]::List) + $context.Events | Should -BeOfType 'string' + } It 'Connect-GitHubApp - TokenExpiresIn property should be calculated correctly' { From 68c1bd788bd25328b4f4cea10e739c29d2060a4e Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 21:18:19 +0200 Subject: [PATCH 21/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20perm?= =?UTF-8?q?ission=20list=20creation=20in=20GitHubPermission=20class=20for?= =?UTF-8?q?=20improved=20clarity=20and=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 174008304..f7303d517 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1277,8 +1277,9 @@ class GitHubPermission : GitHubPermissionDefinition { 'Organization' { $all | Where-Object { $_.Scope -in @('Organization', 'Repository') } } 'User' { $all | Where-Object { $_.Scope -in @('Repository') } } } - $full = foreach ($def in $expectedDefs) { - [GitHubPermission]::new($def.Name, $null) + $full = @() + foreach ($def in $expectedDefs) { + $full += [GitHubPermission]::new($def.Name, $null) } return $full } @@ -1297,3 +1298,11 @@ class GitHubPermission : GitHubPermissionDefinition { return $permissions | Sort-Object Scope, DisplayName } } + +$Object = [pscustomobject]@{ + contents = 'read' + issues = 'write' + unknown_permission = 'custom_value' +} + +$InstallationType = 'Organization' From 4b67e3d5609e4b2016d7156f19d58761960ea2b7 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 21:52:46 +0200 Subject: [PATCH 22/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Remove=20unused?= =?UTF-8?q?=20variables=20related=20to=20permissions=20in=20GitHubPermissi?= =?UTF-8?q?on=20class=20for=20cleaner=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index f7303d517..6fc9b70a9 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1298,11 +1298,3 @@ class GitHubPermission : GitHubPermissionDefinition { return $permissions | Sort-Object Scope, DisplayName } } - -$Object = [pscustomobject]@{ - contents = 'read' - issues = 'write' - unknown_permission = 'custom_value' -} - -$InstallationType = 'Organization' From db204adbe3e3b9fdc5341157e2c47aa9584a00ea Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 22:16:04 +0200 Subject: [PATCH 23/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Add=20default?= =?UTF-8?q?=20case=20to=20permission=20filtering=20in=20GitHubPermission?= =?UTF-8?q?=20class=20for=20improved=20handling=20of=20unexpected=20scopes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 6fc9b70a9..4279e5c9e 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1276,6 +1276,7 @@ class GitHubPermission : GitHubPermissionDefinition { 'Enterprise' { $all | Where-Object { $_.Scope -eq 'Enterprise' } } 'Organization' { $all | Where-Object { $_.Scope -in @('Organization', 'Repository') } } 'User' { $all | Where-Object { $_.Scope -in @('Repository') } } + default { $all } } $full = @() foreach ($def in $expectedDefs) { From 51eed1f21e147c29f8985b41b97e46cdf9a322d0 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Sun, 14 Sep 2025 22:36:55 +0200 Subject: [PATCH 24/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Enhance=20loggi?= =?UTF-8?q?ng=20in=20Apps.Tests=20by=20adding=20permission=20context=20out?= =?UTF-8?q?put=20and=20refining=20app=20retrieval=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test2/GitHub.Tests.ps1 | 1 + tests/Apps.Tests.ps1 | 30 ++++++++++++++++-------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/test2/GitHub.Tests.ps1 b/test2/GitHub.Tests.ps1 index 53b9e347e..9a7c69a17 100644 --- a/test2/GitHub.Tests.ps1 +++ b/test2/GitHub.Tests.ps1 @@ -124,6 +124,7 @@ Describe 'Auth' { It 'Connect-GitHubAccount - Connects to GitHub CLI on runners' { [string]::IsNullOrEmpty($(gh auth token)) | Should -Be $false } + It 'Get-GitHubViewer - Gets the logged in context' { $viewer = Get-GitHubViewer LogGroup 'Viewer' { diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index 20f94afd5..1a9139cbc 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -26,7 +26,10 @@ Describe 'Apps' { BeforeAll { LogGroup 'Context' { $context = Connect-GitHubAccount @connectParams -PassThru -Silent - Write-Host ($context | Format-List | Out-String) + Write-Host "$($context | Format-List | Out-String)" + } + LogGroup 'Permissions' { + Write-Host "$($context.Permissions | Format-Table | Out-String)" } } @@ -35,6 +38,15 @@ Describe 'Apps' { Write-Host ('-' * 60) } + # Tests for IAT UAT and PAT goes here + It 'Get-GitHubApp - Get an app by slug' -Skip:($AuthType -eq 'APP') { + $app = Get-GitHubApp -Slug 'github-actions' + LogGroup 'App by slug' { + Write-Host ($app | Format-List | Out-String) + } + $app | Should -Not -BeNullOrEmpty + } + # Tests for APP goes here if ($AuthType -eq 'APP') { Context 'GitHub Apps' { @@ -73,16 +85,15 @@ Describe 'Apps' { It 'Get-GitHubAppInstallation - Can get app installations' { $githubApp = Get-GitHubApp $installations = Get-GitHubAppInstallation - LogGroup 'Installations' { - Write-Host ($installations | Format-List | Out-String) - } $installations | Should -Not -BeNullOrEmpty foreach ($installation in $installations) { + LogGroup "Installation - $($installation.Target.Name)" { + Write-Host "$($installations | Format-List | Out-String)" + } $installation | Should -BeOfType 'GitHubAppInstallation' $installation.ID | Should -Not -BeNullOrEmpty $installation.App | Should -BeOfType 'GitHubApp' $installation.App.ClientID | Should -Be $githubApp.ClientID - $installation.App.AppID | Should -Not -BeNullOrEmpty $installation.App.Slug | Should -Not -BeNullOrEmpty $installation.Target | Should -BeOfType 'GitHubOwner' $installation.Target | Should -Not -BeNullOrEmpty @@ -235,14 +246,5 @@ Describe 'Apps' { } } } - - # Tests for IAT UAT and PAT goes here - It 'Get-GitHubApp - Get an app by slug' -Skip:($AuthType -eq 'APP') { - $app = Get-GitHubApp -Slug 'github-actions' - LogGroup 'App by slug' { - Write-Host ($app | Format-List | Out-String) - } - $app | Should -Not -BeNullOrEmpty - } } } From 54f4957177c31b4dadedd4581a33f589b5a66375 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 09:35:40 +0200 Subject: [PATCH 25/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20GitH?= =?UTF-8?q?ub=20app=20tests=20for=20improved=20structure=20and=20clarity,?= =?UTF-8?q?=20consolidating=20test=20cases=20and=20enhancing=20readability?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Apps.Tests.ps1 | 311 +++++++++++++++++++++---------------------- 1 file changed, 154 insertions(+), 157 deletions(-) diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index 1a9139cbc..a6f64a4eb 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -38,7 +38,6 @@ Describe 'Apps' { Write-Host ('-' * 60) } - # Tests for IAT UAT and PAT goes here It 'Get-GitHubApp - Get an app by slug' -Skip:($AuthType -eq 'APP') { $app = Get-GitHubApp -Slug 'github-actions' LogGroup 'App by slug' { @@ -47,88 +46,55 @@ Describe 'Apps' { $app | Should -Not -BeNullOrEmpty } - # Tests for APP goes here - if ($AuthType -eq 'APP') { - Context 'GitHub Apps' { - It 'Get-GitHubApp - Can get app details' { - $app = Get-GitHubApp - LogGroup 'App' { - Write-Host ($app | Format-List | Out-String) - } - $app | Should -Not -BeNullOrEmpty - $app | Should -BeOfType 'GitHubApp' - $app.ID | Should -Not -BeNullOrEmpty - $app.ClientID | Should -Not -BeNullOrEmpty - $app.Slug | Should -Not -BeNullOrEmpty - $app.NodeID | Should -Not -BeNullOrEmpty - $app.Owner | Should -BeOfType 'GitHubOwner' - $app.Name | Should -Not -BeNullOrEmpty - $app.Description | Should -Not -BeNullOrEmpty - $app.ExternalUrl | Should -Not -BeNullOrEmpty - $app.Url | Should -Not -BeNullOrEmpty - $app.CreatedAt | Should -Not -BeNullOrEmpty - $app.UpdatedAt | Should -Not -BeNullOrEmpty - $app.Permissions.Count | Should -BeGreaterThan 0 - $app.Permissions | Should -BeOfType 'GitHubPermission' - $app.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name - $app.Events | Should -BeOfType 'string' - $app.Installations | Should -Not -BeNullOrEmpty - } - - It 'Get-GitHubAppInstallationRequest - Can get installation requests' { - $installationRequests = Get-GitHubAppInstallationRequest - LogGroup 'Installation requests' { - Write-Host ($installationRequests | Format-List | Out-String) - } + Context 'GitHub Apps' -Skip:($AuthType -ne 'APP') { + It 'Get-GitHubApp - Can get app details' { + $app = Get-GitHubApp + LogGroup 'App' { + Write-Host ($app | Format-List | Out-String) } + $app | Should -Not -BeNullOrEmpty + $app | Should -BeOfType 'GitHubApp' + $app.ID | Should -Not -BeNullOrEmpty + $app.ClientID | Should -Not -BeNullOrEmpty + $app.Slug | Should -Not -BeNullOrEmpty + $app.NodeID | Should -Not -BeNullOrEmpty + $app.Owner | Should -BeOfType 'GitHubOwner' + $app.Name | Should -Not -BeNullOrEmpty + $app.Description | Should -Not -BeNullOrEmpty + $app.ExternalUrl | Should -Not -BeNullOrEmpty + $app.Url | Should -Not -BeNullOrEmpty + $app.CreatedAt | Should -Not -BeNullOrEmpty + $app.UpdatedAt | Should -Not -BeNullOrEmpty + $app.Permissions.Count | Should -BeGreaterThan 0 + $app.Permissions | Should -BeOfType 'GitHubPermission' + $app.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name + $app.Events | Should -BeOfType 'string' + $app.Installations | Should -Not -BeNullOrEmpty + } - It 'Get-GitHubAppInstallation - Can get app installations' { - $githubApp = Get-GitHubApp - $installations = Get-GitHubAppInstallation - $installations | Should -Not -BeNullOrEmpty - foreach ($installation in $installations) { - LogGroup "Installation - $($installation.Target.Name)" { - Write-Host "$($installations | Format-List | Out-String)" - } - $installation | Should -BeOfType 'GitHubAppInstallation' - $installation.ID | Should -Not -BeNullOrEmpty - $installation.App | Should -BeOfType 'GitHubApp' - $installation.App.ClientID | Should -Be $githubApp.ClientID - $installation.App.Slug | Should -Not -BeNullOrEmpty - $installation.Target | Should -BeOfType 'GitHubOwner' - $installation.Target | Should -Not -BeNullOrEmpty - $installation.Type | Should -BeIn @('Enterprise', 'Organization', 'User') - $installation.RepositorySelection | Should -Not -BeNullOrEmpty - $installation.Permissions.Count | Should -BeGreaterThan 0 - $installation.Permissions | Should -BeOfType [GitHubPermission] - $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name - $installation.Events | Should -BeOfType 'string' - $installation.CreatedAt | Should -Not -BeNullOrEmpty - $installation.UpdatedAt | Should -Not -BeNullOrEmpty - $installation.SuspendedAt | Should -BeNullOrEmpty - $installation.SuspendedBy | Should -BeOfType 'GitHubUser' - $installation.SuspendedBy | Should -BeNullOrEmpty - $installation.Status | Should -Not -BeNullOrEmpty - $installation.Status | Should -BeIn @('Ok', 'Outdated') - } + It 'Get-GitHubAppInstallationRequest - Can get installation requests' { + $installationRequests = Get-GitHubAppInstallationRequest + LogGroup 'Installation requests' { + Write-Host ($installationRequests | Format-List | Out-String) } + } - It 'Get-GitHubAppInstallation - ' { - $githubApp = Get-GitHubApp - $installation = Get-GitHubAppInstallation | Where-Object { ($_.Target.Name -eq $owner) -and ($_.Type -eq $ownerType) } - LogGroup "Installation - $ownerType" { - Write-Host ($installation | Format-List | Out-String) + It 'Get-GitHubAppInstallation - Can get app installations' { + $githubApp = Get-GitHubApp + $installations = Get-GitHubAppInstallation + $installations | Should -Not -BeNullOrEmpty + foreach ($installation in $installations) { + LogGroup "Installation - $($installation.Target.Name)" { + Write-Host "$($installations | Format-List | Out-String)" } - $installation | Should -Not -BeNullOrEmpty $installation | Should -BeOfType 'GitHubAppInstallation' $installation.ID | Should -Not -BeNullOrEmpty $installation.App | Should -BeOfType 'GitHubApp' $installation.App.ClientID | Should -Be $githubApp.ClientID - $installation.App.AppID | Should -Not -BeNullOrEmpty $installation.App.Slug | Should -Not -BeNullOrEmpty $installation.Target | Should -BeOfType 'GitHubOwner' - $installation.Target | Should -Be $owner - $installation.Type | Should -Be $ownerType + $installation.Target | Should -Not -BeNullOrEmpty + $installation.Type | Should -BeIn @('Enterprise', 'Organization', 'User') $installation.RepositorySelection | Should -Not -BeNullOrEmpty $installation.Permissions.Count | Should -BeGreaterThan 0 $installation.Permissions | Should -BeOfType [GitHubPermission] @@ -144,106 +110,137 @@ Describe 'Apps' { } } - Context 'Webhooks' { - It 'Get-GitHubAppWebhookConfiguration - Can get the webhook configuration' { - $webhookConfig = Get-GitHubAppWebhookConfiguration - LogGroup 'Webhook config' { - Write-Host ($webhookConfig | Format-Table | Out-String) - } - $webhookConfig | Should -Not -BeNullOrEmpty + It 'Get-GitHubAppInstallation - ' { + $githubApp = Get-GitHubApp + $installation = Get-GitHubAppInstallation | Where-Object { ($_.Target.Name -eq $owner) -and ($_.Type -eq $ownerType) } + LogGroup "Installation - $ownerType" { + Write-Host ($installation | Format-List | Out-String) } + $installation | Should -Not -BeNullOrEmpty + $installation | Should -BeOfType 'GitHubAppInstallation' + $installation.ID | Should -Not -BeNullOrEmpty + $installation.App | Should -BeOfType 'GitHubApp' + $installation.App.ClientID | Should -Be $githubApp.ClientID + $installation.App.Slug | Should -Not -BeNullOrEmpty + $installation.Target | Should -BeOfType 'GitHubOwner' + $installation.Target | Should -Be $owner + $installation.Type | Should -Be $ownerType + $installation.RepositorySelection | Should -Not -BeNullOrEmpty + $installation.Permissions.Count | Should -BeGreaterThan 0 + $installation.Permissions | Should -BeOfType [GitHubPermission] + $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name + $installation.Events | Should -BeOfType 'string' + $installation.CreatedAt | Should -Not -BeNullOrEmpty + $installation.UpdatedAt | Should -Not -BeNullOrEmpty + $installation.SuspendedAt | Should -BeNullOrEmpty + $installation.SuspendedBy | Should -BeOfType 'GitHubUser' + $installation.SuspendedBy | Should -BeNullOrEmpty + $installation.Status | Should -Not -BeNullOrEmpty + $installation.Status | Should -BeIn @('Ok', 'Outdated') + } + } - It 'Update-GitHubAppWebhookConfiguration - Can update the webhook configuration' { - { Update-GitHubAppWebhookConfiguration -ContentType 'form' } | Should -Not -Throw - $webhookConfig = Get-GitHubAppWebhookConfiguration - LogGroup 'Webhook config - form' { - Write-Host ($webhookConfig | Format-Table | Out-String) - } - { Update-GitHubAppWebhookConfiguration -ContentType 'json' } | Should -Not -Throw - $webhookConfig = Get-GitHubAppWebhookConfiguration - LogGroup 'Webhook config - json' { - Write-Host ($webhookConfig | Format-Table | Out-String) - } + Context 'Webhooks' -Skip:($AuthType -ne 'APP') { + It 'Get-GitHubAppWebhookConfiguration - Can get the webhook configuration' { + $webhookConfig = Get-GitHubAppWebhookConfiguration + LogGroup 'Webhook config' { + Write-Host ($webhookConfig | Format-Table | Out-String) } + $webhookConfig | Should -Not -BeNullOrEmpty + } - It 'Get-GitHubAppWebhookDelivery - Can get webhook deliveries' { - $deliveries = Get-GitHubAppWebhookDelivery - LogGroup 'Deliveries' { - Write-Host ($deliveries | Format-Table | Out-String) - } - $deliveries | Should -Not -BeNullOrEmpty + It 'Update-GitHubAppWebhookConfiguration - Can update the webhook configuration' { + { Update-GitHubAppWebhookConfiguration -ContentType 'form' } | Should -Not -Throw + $webhookConfig = Get-GitHubAppWebhookConfiguration + LogGroup 'Webhook config - form' { + Write-Host ($webhookConfig | Format-Table | Out-String) } - - It 'Get-GitHubAppWebhookDelivery - Can redeliver a webhook delivery' { - $deliveries = Get-GitHubAppWebhookDelivery | Select-Object -First 1 - LogGroup 'Delivery - redeliver' { - Write-Host ($deliveries | Format-Table | Out-String) - } - { Invoke-GitHubAppWebhookReDelivery -ID $deliveries.id } | Should -Not -Throw - LogGroup 'Delivery - redeliver' { - Write-Host ($deliveries | Format-Table | Out-String) - } + { Update-GitHubAppWebhookConfiguration -ContentType 'json' } | Should -Not -Throw + $webhookConfig = Get-GitHubAppWebhookConfiguration + LogGroup 'Webhook config - json' { + Write-Host ($webhookConfig | Format-Table | Out-String) } } - Context 'Installation' { - BeforeAll { - $githubApp = Get-GitHubApp - $config = Get-GitHubConfig - $context = Connect-GitHubApp @connectAppParams -PassThru -Silent - LogGroup 'Context' { - Write-Host ($context | Format-List | Out-String) - } + It 'Get-GitHubAppWebhookDelivery - Can get webhook deliveries' { + $deliveries = Get-GitHubAppWebhookDelivery + LogGroup 'Deliveries' { + Write-Host ($deliveries | Format-Table | Out-String) } + $deliveries | Should -Not -BeNullOrEmpty + } - It 'Connect-GitHubApp - Connects as a GitHub App to ' { - $context | Should -BeOfType 'GitHubAppInstallationContext' - $context.ClientID | Should -Be $githubApp.ClientID - $context.TokenExpiresAt | Should -BeOfType [datetime] - $context.InstallationID | Should -BeOfType [uint64] - $context.InstallationID | Should -BeGreaterThan 0 - $context.Permissions | Should -BeOfType [PSCustomObject] - $context.Events | Should -BeOfType 'string' - $context.InstallationType | Should -Be $ownertype - $context.InstallationName | Should -Be $owner - $context.ID | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner" - $context.Name | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner" - $context.DisplayName | Should -Be $githubApp.Name - $context.Type | Should -Be 'Installation' - $context.HostName | Should -Be $config.HostName - $context.ApiBaseUri | Should -Be $config.ApiBaseUri - $context.ApiVersion | Should -Be $config.ApiVersion - $context.AuthType | Should -Be 'IAT' - $context.NodeID | Should -Not -BeNullOrEmpty - $context.DatabaseID | Should -Not -BeNullOrEmpty - $context.UserName | Should -Be $githubApp.Slug - $context.Token | Should -BeOfType [System.Security.SecureString] - $context.TokenType | Should -Be 'ghs' - $context.HttpVersion | Should -Be $config.HttpVersion - $context.PerPage | Should -Be $config.PerPage - $context.Permissions.Count | Should -BeGreaterThan 0 - $context.Permissions | Should -BeOfType [GitHubPermission] - $context.Permissions.Name | Should -BeIn ([GitHubPermission]::List).Name - $context.Permissions.Value | Should -BeIn ([GitHubPermission]::List) - $context.Events | Should -BeOfType 'string' - + It 'Get-GitHubAppWebhookDelivery - Can redeliver a webhook delivery' { + $deliveries = Get-GitHubAppWebhookDelivery | Select-Object -First 1 + LogGroup 'Delivery - redeliver' { + Write-Host ($deliveries | Format-Table | Out-String) } + { Invoke-GitHubAppWebhookReDelivery -ID $deliveries.id } | Should -Not -Throw + LogGroup 'Delivery - redeliver' { + Write-Host ($deliveries | Format-Table | Out-String) + } + } + } - It 'Connect-GitHubApp - TokenExpiresIn property should be calculated correctly' { - $context.TokenExpiresIn | Should -BeOfType [TimeSpan] - $context.TokenExpiresIn.TotalMinutes | Should -BeGreaterThan 0 - $context.TokenExpiresIn.TotalMinutes | Should -BeLessOrEqual 60 + Context 'Installation' -Skip:($AuthType -ne 'APP') { + BeforeAll { + $githubApp = Get-GitHubApp + $config = Get-GitHubConfig + $context = Connect-GitHubApp @connectAppParams -PassThru -Silent + LogGroup 'Context' { + Write-Host "$($context | Format-List | Out-String)" + } + LogGroup 'Permissions' { + Write-Host "$($context.Permissions | Format-Table | Out-String)" } + } - It 'Revoked GitHub App token should fail on API call' -Skip:($TokenType -eq 'GITHUB_TOKEN') { - $org = Get-GitHubOrganization -Name PSModule -Context $context - $org | Should -Not -BeNullOrEmpty - $context | Disconnect-GitHub + It 'Connect-GitHubApp - Connects as a GitHub App to ' { + $context | Should -BeOfType 'GitHubAppInstallationContext' + $context.ClientID | Should -Be $githubApp.ClientID + $context.TokenExpiresAt | Should -BeOfType [datetime] + $context.InstallationID | Should -BeOfType [uint64] + $context.InstallationID | Should -BeGreaterThan 0 + $context.Events | Should -BeOfType 'string' + $context.InstallationType | Should -Be $ownertype + $context.InstallationName | Should -Be $owner + $context.ID | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner" + $context.Name | Should -Be "$($config.HostName)/$($githubApp.Slug)/$ownertype/$owner" + $context.DisplayName | Should -Be $githubApp.Name + $context.Type | Should -Be 'Installation' + $context.HostName | Should -Be $config.HostName + $context.ApiBaseUri | Should -Be $config.ApiBaseUri + $context.ApiVersion | Should -Be $config.ApiVersion + $context.AuthType | Should -Be 'IAT' + $context.NodeID | Should -Not -BeNullOrEmpty + $context.DatabaseID | Should -Not -BeNullOrEmpty + $context.UserName | Should -Be $githubApp.Slug + $context.Token | Should -BeOfType [System.Security.SecureString] + $context.TokenType | Should -Be 'ghs' + $context.HttpVersion | Should -Be $config.HttpVersion + $context.PerPage | Should -Be $config.PerPage + $context.Permissions.Count | Should -BeGreaterThan 0 + $context.Permissions | Should -BeOfType [GitHubPermission] + $context.Permissions.Name | Should -BeIn ([GitHubPermission]::List).Name + $context.Permissions.Value | Should -BeIn ([GitHubPermission]::List) + $context.Events | Should -BeOfType 'string' - { - Invoke-RestMethod -Method Get -Uri "$($context.ApiBaseUri)/orgs/PSModule" -Authentication Bearer -Token $context.token - } | Should -Throw - } + } + + It 'Connect-GitHubApp - TokenExpiresIn property should be calculated correctly' { + $context.TokenExpiresIn | Should -BeOfType [TimeSpan] + $context.TokenExpiresIn.TotalMinutes | Should -BeGreaterThan 0 + $context.TokenExpiresIn.TotalMinutes | Should -BeLessOrEqual 60 + } + + It 'Revoked GitHub App token should fail on API call' -Skip:($TokenType -eq 'GITHUB_TOKEN') { + $org = Get-GitHubOrganization -Name PSModule -Context $context + $org | Should -Not -BeNullOrEmpty + $context | Disconnect-GitHub + + { + Invoke-RestMethod -Method Get -Uri "$($context.ApiBaseUri)/orgs/PSModule" -Authentication Bearer -Token $context.token + } | Should -Throw } } } From fa601ec439d02eb8be83fab8aeb780c2331718ae Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 09:54:49 +0200 Subject: [PATCH 26/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Fix=20logging?= =?UTF-8?q?=20in=20installation=20tests=20to=20output=20individual=20insta?= =?UTF-8?q?llation=20details=20instead=20of=20the=20entire=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Apps.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index a6f64a4eb..9ae2bce54 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -85,7 +85,7 @@ Describe 'Apps' { $installations | Should -Not -BeNullOrEmpty foreach ($installation in $installations) { LogGroup "Installation - $($installation.Target.Name)" { - Write-Host "$($installations | Format-List | Out-String)" + Write-Host "$($installation | Format-List | Out-String)" } $installation | Should -BeOfType 'GitHubAppInstallation' $installation.ID | Should -Not -BeNullOrEmpty From ee7477c4be40efc7b0c27f2b804cf1fef935850f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 10:49:29 +0200 Subject: [PATCH 27/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20perm?= =?UTF-8?q?ission=20checks=20in=20Apps.Tests=20to=20use=20a=20single=20var?= =?UTF-8?q?iable=20for=20GitHubPermissionDefinition=20list?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Apps.Tests.ps1 | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index 9ae2bce54..fc36ba570 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -24,6 +24,7 @@ Describe 'Apps' { Context 'As using on ' -ForEach $authCases { BeforeAll { + $permissionsDefinitions = [GitHubPermissionDefinition]::List LogGroup 'Context' { $context = Connect-GitHubAccount @connectParams -PassThru -Silent Write-Host "$($context | Format-List | Out-String)" @@ -67,7 +68,7 @@ Describe 'Apps' { $app.UpdatedAt | Should -Not -BeNullOrEmpty $app.Permissions.Count | Should -BeGreaterThan 0 $app.Permissions | Should -BeOfType 'GitHubPermission' - $app.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name + $app.Permissions.Name | Should -BeIn $permissionsDefinitions.Name $app.Events | Should -BeOfType 'string' $app.Installations | Should -Not -BeNullOrEmpty } @@ -98,7 +99,7 @@ Describe 'Apps' { $installation.RepositorySelection | Should -Not -BeNullOrEmpty $installation.Permissions.Count | Should -BeGreaterThan 0 $installation.Permissions | Should -BeOfType [GitHubPermission] - $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name + $installation.Permissions.Name | Should -BeIn $permissionsDefinitions.Name $installation.Events | Should -BeOfType 'string' $installation.CreatedAt | Should -Not -BeNullOrEmpty $installation.UpdatedAt | Should -Not -BeNullOrEmpty @@ -128,7 +129,7 @@ Describe 'Apps' { $installation.RepositorySelection | Should -Not -BeNullOrEmpty $installation.Permissions.Count | Should -BeGreaterThan 0 $installation.Permissions | Should -BeOfType [GitHubPermission] - $installation.Permissions.Name | Should -BeIn ([GitHubPermissionDefinition]::List).Name + $installation.Permissions.Name | Should -BeIn $permissionsDefinitions.Name $installation.Events | Should -BeOfType 'string' $installation.CreatedAt | Should -Not -BeNullOrEmpty $installation.UpdatedAt | Should -Not -BeNullOrEmpty @@ -186,6 +187,7 @@ Describe 'Apps' { BeforeAll { $githubApp = Get-GitHubApp $config = Get-GitHubConfig + $permissionsDefinitions = [GitHubPermissionDefinition]::List $context = Connect-GitHubApp @connectAppParams -PassThru -Silent LogGroup 'Context' { Write-Host "$($context | Format-List | Out-String)" @@ -221,8 +223,7 @@ Describe 'Apps' { $context.PerPage | Should -Be $config.PerPage $context.Permissions.Count | Should -BeGreaterThan 0 $context.Permissions | Should -BeOfType [GitHubPermission] - $context.Permissions.Name | Should -BeIn ([GitHubPermission]::List).Name - $context.Permissions.Value | Should -BeIn ([GitHubPermission]::List) + $context.Permissions.Name | Should -BeIn $permissionsDefinitions.Name $context.Events | Should -BeOfType 'string' } From fb83868e3201753b7d608eb9f00a179f0b8cd12f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 12:19:26 +0200 Subject: [PATCH 28/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20perm?= =?UTF-8?q?ission=20list=20creation=20in=20GitHubPermission=20class=20for?= =?UTF-8?q?=20improved=20handling=20of=20permissions=20from=20PSCustomObje?= =?UTF-8?q?ct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubApp.ps1 | 2 +- .../GitHubContext/GitHubAppContext.ps1 | 2 +- src/classes/public/GitHubPermission.ps1 | 77 +++++++++++++++---- 3 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index bf2cba325..9e66bf081 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -59,7 +59,7 @@ $this.Url = $Object.html_url $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $null) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions) $this.Events = , ($Object.events) $this.Installations = $Object.installations_count } diff --git a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 index 9975974ce..a94dc67a1 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 @@ -47,7 +47,7 @@ $this.KeyVaultKeyReference = $Object.KeyVaultKeyReference $this.OwnerName = $Object.OwnerName $this.OwnerType = $Object.OwnerType - $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions, $null) + $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions) $this.Events = $Object.Events } } diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 4279e5c9e..b8ddc8de7 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1270,32 +1270,83 @@ class GitHubPermission : GitHubPermissionDefinition { $this.Value = $Value } - static [GitHubPermission[]] newPermissionListTemplate ([string] $InstallationType) { - $all = [GitHubPermissionDefinition]::List - $expectedDefs = switch ($InstallationType) { + # Create a new list of all known permissions with null values + static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList() { + $tmpList = [System.Collections.Generic.List[GitHubPermission]]::new() + foreach ($def in [GitHubPermissionDefinition]::List) { + $tmpList.Add([GitHubPermission]@{ + Name = $def.Name + Value = $null + DisplayName = $def.DisplayName + Description = $def.Description + URL = $def.URL + Options = $def.Options + Type = $def.Type + Scope = $def.Scope + }) + } + return $tmpList | Sort-Object Scope, DisplayName + } + + # Create a new list of permissions filtered by installation typem with null values + static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([string] $InstallationType) { + $all = [GitHubPermission]::NewPermissionList() + $returned = switch ($InstallationType) { 'Enterprise' { $all | Where-Object { $_.Scope -eq 'Enterprise' } } 'Organization' { $all | Where-Object { $_.Scope -in @('Organization', 'Repository') } } 'User' { $all | Where-Object { $_.Scope -in @('Repository') } } default { $all } } - $full = @() - foreach ($def in $expectedDefs) { - $full += [GitHubPermission]::new($def.Name, $null) - } - return $full + return $returned | Sort-Object Scope, DisplayName } - static [GitHubPermission[]] newPermissionList([pscustomobject] $Object, [string] $InstallationType) { - $permissions = [GitHubPermission]::newPermissionListTemplate($InstallationType) + # Create a new list of all permissions with values from a PSCustomObject + static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object) { + $all = [GitHubPermission]::NewPermissionList() + foreach ($prop in $Object.PSObject.Properties) { + $knownPermission = $all | Where-Object { $_.Name -eq $prop.Name } + if ($knownPermission) { + $knownPermission.Value = $prop.Value + } else { + $all.Add( + [GitHubPermission]@{ + Name = $prop.Name + Value = $prop.Value + DisplayName = $prop.Name + Description = 'Unknown permission - Open issue to add metadata' + URL = $null + Options = @() + Type = 'Unknown' + Scope = 'Unknown' + } + ) + } + } + return $all | Sort-Object Scope, DisplayName + } + # Create a new list of permissions filtered by installation type with values from a PSCustomObject + static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { + $all = [GitHubPermission]::NewPermissionList() foreach ($prop in $Object.PSObject.Properties) { - $knownPermission = $permissions | Where-Object { $_.Name -eq $prop.Name } + $knownPermission = $all | Where-Object { $_.Name -eq $prop.Name } if ($knownPermission) { $knownPermission.Value = $prop.Value } else { - $permissions += [GitHubPermission]::new($prop.Name, $prop.Value) + $all.Add( + [GitHubPermission]@{ + Name = $prop.Name + Value = $prop.Value + DisplayName = $prop.Name + Description = 'Unknown permission - Open issue to add metadata' + URL = $null + Options = @() + Type = 'Unknown' + Scope = 'Unknown' + } + ) } } - return $permissions | Sort-Object Scope, DisplayName + return $all | Sort-Object Scope, DisplayName } } From 2e46e9dd716bc544aad51b441a999a6437720658 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 13:12:19 +0200 Subject: [PATCH 29/43] Refactor NewPermissionList methods to improve property handling from PSCustomObject --- src/classes/public/GitHubPermission.ps1 | 26 +++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index b8ddc8de7..c1bab63b1 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1303,16 +1303,17 @@ class GitHubPermission : GitHubPermissionDefinition { # Create a new list of all permissions with values from a PSCustomObject static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object) { $all = [GitHubPermission]::NewPermissionList() - foreach ($prop in $Object.PSObject.Properties) { - $knownPermission = $all | Where-Object { $_.Name -eq $prop.Name } + foreach ($name in $Object.PSObject.Properties.Name) { + $objectValue = $Object.$name + $knownPermission = $all | Where-Object { $_.Name -eq $name } if ($knownPermission) { - $knownPermission.Value = $prop.Value + $knownPermission.Value = $objectValue } else { $all.Add( [GitHubPermission]@{ - Name = $prop.Name - Value = $prop.Value - DisplayName = $prop.Name + Name = $name + Value = $objectValue + DisplayName = $name Description = 'Unknown permission - Open issue to add metadata' URL = $null Options = @() @@ -1328,16 +1329,17 @@ class GitHubPermission : GitHubPermissionDefinition { # Create a new list of permissions filtered by installation type with values from a PSCustomObject static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList() - foreach ($prop in $Object.PSObject.Properties) { - $knownPermission = $all | Where-Object { $_.Name -eq $prop.Name } + foreach ($name in $Object.PSObject.Properties.Name) { + $objectValue = $Object.$name + $knownPermission = $all | Where-Object { $_.Name -eq $name } if ($knownPermission) { - $knownPermission.Value = $prop.Value + $knownPermission.Value = $objectValue } else { $all.Add( [GitHubPermission]@{ - Name = $prop.Name - Value = $prop.Value - DisplayName = $prop.Name + Name = $name + Value = $objectValue + DisplayName = $name Description = 'Unknown permission - Open issue to add metadata' URL = $null Options = @() From 53e52490a87c58d24cc16656dc959773864a12b8 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 13:28:23 +0200 Subject: [PATCH 30/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Refactor=20NewP?= =?UTF-8?q?ermissionList=20method=20calls=20to=20use=20consistent=20casing?= =?UTF-8?q?=20across=20GitHubPermission=20class=20and=20related=20contexts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubApp.ps1 | 2 +- .../public/App/GitHubAppInstallation.ps1 | 8 ++--- .../GitHubContext/GitHubAppContext.ps1 | 2 +- .../GitHubAppInstallationContext.ps1 | 2 +- src/classes/public/GitHubPermission.ps1 | 31 ++++++++++--------- .../Get-GitHubAuthenticatedApp.ps1 | 3 +- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index 9e66bf081..9356e308c 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -59,7 +59,7 @@ $this.Url = $Object.html_url $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions) $this.Events = , ($Object.events) $this.Installations = $Object.installations_count } diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 80beea68f..ed3365543 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -58,7 +58,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -75,7 +75,7 @@ $this.Target = [GitHubOwner]::new($Object.account) $this.Type = $Object.target_type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -102,7 +102,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at @@ -123,7 +123,7 @@ } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection - $this.Permissions = [GitHubPermission]::newPermissionList($Object.permissions, $this.Type) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.permissions, $this.Type) $this.Events = , ($Object.events) $this.FilePaths = $Object.single_file_paths $this.CreatedAt = $Object.created_at diff --git a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 index a94dc67a1..856433681 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 @@ -47,7 +47,7 @@ $this.KeyVaultKeyReference = $Object.KeyVaultKeyReference $this.OwnerName = $Object.OwnerName $this.OwnerType = $Object.OwnerType - $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.Permissions) $this.Events = $Object.Events } } diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 index ee6ee092d..93b5a7934 100644 --- a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -41,7 +41,7 @@ $this.PerPage = $Object.PerPage $this.ClientID = $Object.ClientID $this.InstallationID = $Object.InstallationID - $this.Permissions = [GitHubPermission]::newPermissionList($Object.Permissions, $Object.InstallationType) + $this.Permissions = [GitHubPermission]::NewPermissionList($Object.Permissions, $Object.InstallationType) $this.Events = $Object.Events $this.InstallationType = $Object.InstallationType $this.InstallationName = $Object.InstallationName diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index c1bab63b1..119e0d822 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1271,25 +1271,26 @@ class GitHubPermission : GitHubPermissionDefinition { } # Create a new list of all known permissions with null values - static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList() { - $tmpList = [System.Collections.Generic.List[GitHubPermission]]::new() + static [System.Collections.ArrayList] NewPermissionList() { + $tmpList = [System.Collections.ArrayList]::new() foreach ($def in [GitHubPermissionDefinition]::List) { - $tmpList.Add([GitHubPermission]@{ - Name = $def.Name - Value = $null - DisplayName = $def.DisplayName - Description = $def.Description - URL = $def.URL - Options = $def.Options - Type = $def.Type - Scope = $def.Scope - }) + $object = [GitHubPermission]@{ + Name = $def.Name + Value = $null + DisplayName = $def.DisplayName + Description = $def.Description + URL = $def.URL + Options = $def.Options + Type = $def.Type + Scope = $def.Scope + } + $tmpList.Add($object) } return $tmpList | Sort-Object Scope, DisplayName } # Create a new list of permissions filtered by installation typem with null values - static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([string] $InstallationType) { + static [System.Collections.ArrayList] NewPermissionList([string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList() $returned = switch ($InstallationType) { 'Enterprise' { $all | Where-Object { $_.Scope -eq 'Enterprise' } } @@ -1301,7 +1302,7 @@ class GitHubPermission : GitHubPermissionDefinition { } # Create a new list of all permissions with values from a PSCustomObject - static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object) { + static [System.Collections.ArrayList] NewPermissionList([pscustomobject] $Object) { $all = [GitHubPermission]::NewPermissionList() foreach ($name in $Object.PSObject.Properties.Name) { $objectValue = $Object.$name @@ -1327,7 +1328,7 @@ class GitHubPermission : GitHubPermissionDefinition { } # Create a new list of permissions filtered by installation type with values from a PSCustomObject - static [System.Collections.Generic.List[GitHubPermission]] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { + static [System.Collections.ArrayList] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList() foreach ($name in $Object.PSObject.Properties.Name) { $objectValue = $Object.$name diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 index 9686c3cfa..44b2164d4 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 @@ -41,7 +41,8 @@ Context = $Context } - Invoke-GitHubAPI @apiParams | ForEach-Object { + $response = Invoke-GitHubAPI @apiParams + | ForEach-Object { [GitHubApp]::new($_.Response) } } From 2a1944bd5559f8fffbaef867bf44693fad6f2efe Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 13:37:02 +0200 Subject: [PATCH 31/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Simplify=20resp?= =?UTF-8?q?onse=20handling=20in=20Get-GitHubAuthenticatedApp=20by=20removi?= =?UTF-8?q?ng=20unnecessary=20variable=20assignment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 index 44b2164d4..9686c3cfa 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 @@ -41,8 +41,7 @@ Context = $Context } - $response = Invoke-GitHubAPI @apiParams - | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubApp]::new($_.Response) } } From 20f713f7a745d33f2178252b0c7768afbc6777e9 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 13:58:55 +0200 Subject: [PATCH 32/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Simplify=20GitH?= =?UTF-8?q?ubPermission=20constructor=20and=20NewPermissionList=20methods?= =?UTF-8?q?=20for=20improved=20clarity=20and=20performance?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 71 ++++++------------------- 1 file changed, 16 insertions(+), 55 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index 119e0d822..d39cdbbb1 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1245,36 +1245,20 @@ class GitHubPermission : GitHubPermissionDefinition { GitHubPermission() : base() {} GitHubPermission([string] $Permission, [string] $Value) : base() { - $definition = [GitHubPermissionDefinition]::List | Where-Object { $_.Name -eq $Permission } - if ($definition) { - $this.Name = $definition.Name - $this.DisplayName = $definition.DisplayName - $this.Description = $definition.Description - $this.URL = $definition.URL - $this.Options = $definition.Options - $this.Type = $definition.Type - $this.Scope = $definition.Scope - if ($Value -and ($definition.Options -notcontains $Value)) { - throw "Invalid permission value: $Value for permission: $Permission" - } - } else { - # Unknown permission: accept any value without validation - $this.Name = $Permission - $this.DisplayName = $Permission - $this.Description = 'Unknown permission - Open issue to add metadata' - $this.URL = $null - $this.Options = @() - $this.Type = 'Unknown' - $this.Scope = 'Unknown' - } + $this.Name = $Permission $this.Value = $Value + $this.DisplayName = $Permission + $this.Description = 'Unknown permission - Open issue to add metadata' + $this.URL = $null + $this.Options = @() + $this.Type = 'Unknown' + $this.Scope = 'Unknown' } # Create a new list of all known permissions with null values - static [System.Collections.ArrayList] NewPermissionList() { - $tmpList = [System.Collections.ArrayList]::new() - foreach ($def in [GitHubPermissionDefinition]::List) { - $object = [GitHubPermission]@{ + static [GitHubPermission[]] NewPermissionList() { + $tmpList = foreach ($def in [GitHubPermissionDefinition]::List) { + [GitHubPermission]@{ Name = $def.Name Value = $null DisplayName = $def.DisplayName @@ -1284,13 +1268,12 @@ class GitHubPermission : GitHubPermissionDefinition { Type = $def.Type Scope = $def.Scope } - $tmpList.Add($object) } return $tmpList | Sort-Object Scope, DisplayName } - # Create a new list of permissions filtered by installation typem with null values - static [System.Collections.ArrayList] NewPermissionList([string] $InstallationType) { + # Create a new list of permissions filtered by installation type with null values + static [GitHubPermission[]] NewPermissionList([string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList() $returned = switch ($InstallationType) { 'Enterprise' { $all | Where-Object { $_.Scope -eq 'Enterprise' } } @@ -1302,7 +1285,7 @@ class GitHubPermission : GitHubPermissionDefinition { } # Create a new list of all permissions with values from a PSCustomObject - static [System.Collections.ArrayList] NewPermissionList([pscustomobject] $Object) { + static [GitHubPermission[]] NewPermissionList([pscustomobject] $Object) { $all = [GitHubPermission]::NewPermissionList() foreach ($name in $Object.PSObject.Properties.Name) { $objectValue = $Object.$name @@ -1310,18 +1293,7 @@ class GitHubPermission : GitHubPermissionDefinition { if ($knownPermission) { $knownPermission.Value = $objectValue } else { - $all.Add( - [GitHubPermission]@{ - Name = $name - Value = $objectValue - DisplayName = $name - Description = 'Unknown permission - Open issue to add metadata' - URL = $null - Options = @() - Type = 'Unknown' - Scope = 'Unknown' - } - ) + $all += [GitHubPermission]::new($name, $objectValue) } } return $all | Sort-Object Scope, DisplayName @@ -1329,25 +1301,14 @@ class GitHubPermission : GitHubPermissionDefinition { # Create a new list of permissions filtered by installation type with values from a PSCustomObject static [System.Collections.ArrayList] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { - $all = [GitHubPermission]::NewPermissionList() + $all = [GitHubPermission]::NewPermissionList($InstallationType) foreach ($name in $Object.PSObject.Properties.Name) { $objectValue = $Object.$name $knownPermission = $all | Where-Object { $_.Name -eq $name } if ($knownPermission) { $knownPermission.Value = $objectValue } else { - $all.Add( - [GitHubPermission]@{ - Name = $name - Value = $objectValue - DisplayName = $name - Description = 'Unknown permission - Open issue to add metadata' - URL = $null - Options = @() - Type = 'Unknown' - Scope = 'Unknown' - } - ) + $all += [GitHubPermission]::new($name, $objectValue) } } return $all | Sort-Object Scope, DisplayName From ac1a3a7c6be83b3ae23395735ac4f381d338329f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 14:32:53 +0200 Subject: [PATCH 33/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20NewPer?= =?UTF-8?q?missionList=20methods=20to=20return=20GitHubPermission=20array?= =?UTF-8?q?=20and=20enhance=20import=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/GitHubPermission.ps1 | 34 ++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index d39cdbbb1..b8b57023a 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1300,7 +1300,7 @@ class GitHubPermission : GitHubPermissionDefinition { } # Create a new list of permissions filtered by installation type with values from a PSCustomObject - static [System.Collections.ArrayList] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { + static [GitHubPermission[]] NewPermissionList([pscustomobject] $Object, [string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList($InstallationType) foreach ($name in $Object.PSObject.Properties.Name) { $objectValue = $Object.$name @@ -1313,4 +1313,36 @@ class GitHubPermission : GitHubPermissionDefinition { } return $all | Sort-Object Scope, DisplayName } + + # Create a new list of permissions with values from an array of objects (import functionality) + static [GitHubPermission[]] NewPermissionList([object[]] $Objects) { + $all = [GitHubPermission]::NewPermissionList() + foreach ($obj in $Objects) { + $name = $obj.Name + $value = $obj.Value + $knownPermission = $all | Where-Object { $_.Name -eq $name } + if ($knownPermission) { + $knownPermission.Value = $value + } else { + $all += [GitHubPermission]::new($name, $value) + } + } + return $all | Sort-Object Scope, DisplayName + } + + # Create a new list of permissions filtered by installation type with values from an array of objects (import functionality) + static [GitHubPermission[]] NewPermissionList([object[]] $Objects, [string] $InstallationType) { + $all = [GitHubPermission]::NewPermissionList($InstallationType) + foreach ($obj in $Objects) { + $name = $obj.Name + $value = $obj.Value + $knownPermission = $all | Where-Object { $_.Name -eq $name } + if ($knownPermission) { + $knownPermission.Value = $value + } else { + $all += [GitHubPermission]::new($name, $value) + } + } + return $all | Sort-Object Scope, DisplayName + } } From 5f8a82ec7c59ed1820af0fac47a9d87a52dcd43f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 14:35:39 +0200 Subject: [PATCH 34/43] Refactor NewPermissionList methods to streamline object property access and improve readability --- src/classes/public/GitHubPermission.ps1 | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/classes/public/GitHubPermission.ps1 b/src/classes/public/GitHubPermission.ps1 index b8b57023a..db355a3f8 100644 --- a/src/classes/public/GitHubPermission.ps1 +++ b/src/classes/public/GitHubPermission.ps1 @@ -1318,13 +1318,11 @@ class GitHubPermission : GitHubPermissionDefinition { static [GitHubPermission[]] NewPermissionList([object[]] $Objects) { $all = [GitHubPermission]::NewPermissionList() foreach ($obj in $Objects) { - $name = $obj.Name - $value = $obj.Value - $knownPermission = $all | Where-Object { $_.Name -eq $name } + $knownPermission = $all | Where-Object { $_.Name -eq $obj.Name } if ($knownPermission) { - $knownPermission.Value = $value + $knownPermission.Value = $obj.Value } else { - $all += [GitHubPermission]::new($name, $value) + $all += [GitHubPermission]::new($obj.Name, $obj.Value) } } return $all | Sort-Object Scope, DisplayName @@ -1334,13 +1332,11 @@ class GitHubPermission : GitHubPermissionDefinition { static [GitHubPermission[]] NewPermissionList([object[]] $Objects, [string] $InstallationType) { $all = [GitHubPermission]::NewPermissionList($InstallationType) foreach ($obj in $Objects) { - $name = $obj.Name - $value = $obj.Value - $knownPermission = $all | Where-Object { $_.Name -eq $name } + $knownPermission = $all | Where-Object { $_.Name -eq $obj.Name } if ($knownPermission) { - $knownPermission.Value = $value + $knownPermission.Value = $obj.Value } else { - $all += [GitHubPermission]::new($name, $value) + $all += [GitHubPermission]::new($obj.Name, $obj.Value) } } return $all | Sort-Object Scope, DisplayName From fe0dae803a97ed51bc5a1a76b6ac45438164ff97 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 15:07:13 +0200 Subject: [PATCH 35/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20Get-Gi?= =?UTF-8?q?tHubPermissionDefinition=20to=20use=20List=20property=20for=20p?= =?UTF-8?q?ermissions=20and=20improve=20test=20assertions=20for=20clarity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../public/Permission/Get-GitHubPermissionDefinition.ps1 | 2 +- tests/Permissions.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 b/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 index 0019adf59..f7db75c69 100644 --- a/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 +++ b/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 @@ -86,7 +86,7 @@ function Get-GitHubPermissionDefinition { return $false } - $script:GitHub.Permissions | Where-Object { + [GitHubPermissionDefinition]::List | Where-Object { (& $test -Value $_.Name -Patterns $Name) -and (& $test -Value $_.DisplayName -Patterns $DisplayName) -and (& $test -Value $_.Type -Patterns $Type) -and diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 index 043358796..a3864fd98 100644 --- a/tests/Permissions.Tests.ps1 +++ b/tests/Permissions.Tests.ps1 @@ -93,7 +93,7 @@ Describe 'Permissions' { $result = Get-GitHubPermissionDefinition $result | Should -Not -BeNullOrEmpty $result | Should -BeOfType [GitHubPermissionDefinition] - ($result | Measure-Object).Count | Should -BeGreaterThan 0 + $result.Count | Should -BeGreaterThan 0 } It 'Should return only Fine-grained permissions when filtered by Type' { From 3529d91f9b47d03df1b8485c9f93f8b9bd80a006 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 15:26:22 +0200 Subject: [PATCH 36/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Enhance=20app?= =?UTF-8?q?=20context=20tests=20to=20verify=20Permissions=20property=20and?= =?UTF-8?q?=20ensure=20valid=20permission=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Permissions.Tests.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 index a3864fd98..fc5f72999 100644 --- a/tests/Permissions.Tests.ps1 +++ b/tests/Permissions.Tests.ps1 @@ -39,13 +39,17 @@ Describe 'Permissions' { BeforeAll { $installationContext = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent LogGroup 'Context - Installation' { - Write-Host ($installationContext | Format-List | Out-String) + Write-Host "$($installationContext | Format-List | Out-String)" + } + LogGroup 'Permissions' { + Write-Host "$($installationContext.Permissions | Format-List | Out-String)" } } It 'App context should have Permissions property populated' -Skip:($AuthType -ne 'APP') { - $installationContext.Permissions | Should -Not -BeNullOrEmpty - $installationContext.Permissions | Should -BeOfType [pscustomobject] + $installationContext.Permissions.Count | Should -BeGreaterThan 0 + $installationContext.Permissions | Should -BeOfType [GitHubPermission] + $installationContext.Permissions | Should -BeIn $permissionsDefinitions.Name } It 'All app installation permissions should exist in permission catalog and be valid options' -Skip:($AuthType -ne 'APP') { From b4cfbdad5c021278ab4deba46b39a88cdce3c6c4 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 15:27:02 +0200 Subject: [PATCH 37/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Remove=20skip?= =?UTF-8?q?=20condition=20from=20app=20context=20permission=20tests=20to?= =?UTF-8?q?=20ensure=20they=20always=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Permissions.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 index fc5f72999..cba8bf312 100644 --- a/tests/Permissions.Tests.ps1 +++ b/tests/Permissions.Tests.ps1 @@ -46,13 +46,13 @@ Describe 'Permissions' { } } - It 'App context should have Permissions property populated' -Skip:($AuthType -ne 'APP') { + It 'App context should have Permissions property populated' { $installationContext.Permissions.Count | Should -BeGreaterThan 0 $installationContext.Permissions | Should -BeOfType [GitHubPermission] $installationContext.Permissions | Should -BeIn $permissionsDefinitions.Name } - It 'All app installation permissions should exist in permission catalog and be valid options' -Skip:($AuthType -ne 'APP') { + It 'All app installation permissions should exist in permission catalog and be valid options' { $catalog = Get-GitHubPermissionDefinition $catalogNames = $catalog.Name @@ -77,7 +77,7 @@ Describe 'Permissions' { } } - It 'Permission catalog should contain all permissions granted to the app installation' -Skip:($AuthType -ne 'APP') { + It 'Permission catalog should contain all permissions granted to the app installation' { $catalog = Get-GitHubPermissionDefinition $missing = @() $installationContext.Permissions.PSObject.Properties | ForEach-Object { From f72dad007702aa74f71c5fab73fbd5565e088c17 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 15:43:22 +0200 Subject: [PATCH 38/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20app=20?= =?UTF-8?q?context=20tests=20to=20use=20Format-Table=20for=20permissions?= =?UTF-8?q?=20output=20and=20streamline=20permission=20validation=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Permissions.Tests.ps1 | 37 +++++-------------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 index cba8bf312..ba36b50dd 100644 --- a/tests/Permissions.Tests.ps1 +++ b/tests/Permissions.Tests.ps1 @@ -37,57 +37,30 @@ Describe 'Permissions' { Context 'For Apps' -Skip:($AuthType -ne 'APP') { BeforeAll { + $permissionsDefinitions = [GitHubPermissionDefinition]::List $installationContext = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent LogGroup 'Context - Installation' { Write-Host "$($installationContext | Format-List | Out-String)" } LogGroup 'Permissions' { - Write-Host "$($installationContext.Permissions | Format-List | Out-String)" + Write-Host "$($installationContext.Permissions | Format-Table | Out-String)" } } It 'App context should have Permissions property populated' { $installationContext.Permissions.Count | Should -BeGreaterThan 0 $installationContext.Permissions | Should -BeOfType [GitHubPermission] - $installationContext.Permissions | Should -BeIn $permissionsDefinitions.Name - } - - It 'All app installation permissions should exist in permission catalog and be valid options' { - $catalog = Get-GitHubPermissionDefinition - $catalogNames = $catalog.Name - - # Flatten context permission hashtable/object into name/value pairs (value is access level like read/write/admin) - $granted = @() - $installationContext.Permissions.PSObject.Properties | ForEach-Object { - if ($_.Name -eq 'metadata') { return } # metadata is mandatory; still in catalog but just proceed normally - $granted += [pscustomobject]@{ Name = $_.Name; Level = [string]$_.Value } - } - - # Unknown permissions (present in context but not in catalog) - $unknown = $granted | Where-Object { $_.Name -notin $catalogNames } - if ($unknown) { - throw "Unknown permission(s) detected in app installation: $($unknown.Name -join ', ')" - } - - # For each granted permission ensure level is one of the catalog options - foreach ($g in $granted) { - $def = $catalog | Where-Object Name -EQ $g.Name - $def | Should -Not -BeNullOrEmpty - $def.Options | Should -Contain $g.Level - } + $installationContext.Permissions.Name | Should -BeIn $permissionsDefinitions.Name } It 'Permission catalog should contain all permissions granted to the app installation' { - $catalog = Get-GitHubPermissionDefinition $missing = @() $installationContext.Permissions.PSObject.Properties | ForEach-Object { - if ($_.Name -notin $catalog.Name) { + if ($_.Name -notin $permissionsDefinitions.Name) { $missing += $_.Name } } - if ($missing.Count -gt 0) { - throw "Missing permission definitions for: $($missing -join ', ')" - } + $missing.Count | Should -Be 0 -Because "The following permissions are missing from the catalog: $($missing -join ', ')" } } } From 14499879db092b2b5cb92f19bda654288ac18973 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 16:16:44 +0200 Subject: [PATCH 39/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Simplify=20perm?= =?UTF-8?q?ission=20validation=20by=20directly=20iterating=20over=20Permis?= =?UTF-8?q?sions=20instead=20of=20PSObject.Properties?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/Permissions.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 index ba36b50dd..99662a3d3 100644 --- a/tests/Permissions.Tests.ps1 +++ b/tests/Permissions.Tests.ps1 @@ -55,7 +55,7 @@ Describe 'Permissions' { It 'Permission catalog should contain all permissions granted to the app installation' { $missing = @() - $installationContext.Permissions.PSObject.Properties | ForEach-Object { + $installationContext.Permissions | ForEach-Object { if ($_.Name -notin $permissionsDefinitions.Name) { $missing += $_.Name } From 058bdcee48d831a05ec398a2f49259fc1dc47686 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 17:20:34 +0200 Subject: [PATCH 40/43] Add initial test scripts for GitHub API interactions - Created TEMPLATE.ps1 for Pester testing framework setup. - Implemented Teams.Tests.ps1 to test GitHub Teams API functionalities including team creation, retrieval, updating, and deletion. - Developed Users.Tests.ps1 to validate user-related API calls such as fetching user details and updating user information. - Added Variables.Tests.ps1 to test GitHub repository and organization variable management, including setting, updating, and removing variables. --- .github/PSModule.yml | 28 +++++++++++----------- {test2 => tests}/Artifacts.Tests.ps1 | 0 {test2 => tests}/Emojis.Tests.ps1 | 0 {test2 => tests}/Enterprise.Tests.ps1 | 0 {test2 => tests}/Environments.Tests.ps1 | 0 {test2 => tests}/GitHub.Tests.ps1 | 0 {test2 => tests}/GitHubFormatter.Tests.ps1 | 0 {test2 => tests}/Organizations.Tests.ps1 | 0 {test2 => tests}/README.md | 0 {test2 => tests}/Releases.Tests.ps1 | 0 {test2 => tests}/Repositories.Tests.ps1 | 0 {test2 => tests}/Secrets.Tests.ps1 | 0 {test2 => tests}/TEMPLATE.ps1 | 0 {test2 => tests}/Teams.Tests.ps1 | 0 {test2 => tests}/Users.Tests.ps1 | 0 {test2 => tests}/Variables.Tests.ps1 | 0 16 files changed, 14 insertions(+), 14 deletions(-) rename {test2 => tests}/Artifacts.Tests.ps1 (100%) rename {test2 => tests}/Emojis.Tests.ps1 (100%) rename {test2 => tests}/Enterprise.Tests.ps1 (100%) rename {test2 => tests}/Environments.Tests.ps1 (100%) rename {test2 => tests}/GitHub.Tests.ps1 (100%) rename {test2 => tests}/GitHubFormatter.Tests.ps1 (100%) rename {test2 => tests}/Organizations.Tests.ps1 (100%) rename {test2 => tests}/README.md (100%) rename {test2 => tests}/Releases.Tests.ps1 (100%) rename {test2 => tests}/Repositories.Tests.ps1 (100%) rename {test2 => tests}/Secrets.Tests.ps1 (100%) rename {test2 => tests}/TEMPLATE.ps1 (100%) rename {test2 => tests}/Teams.Tests.ps1 (100%) rename {test2 => tests}/Users.Tests.ps1 (100%) rename {test2 => tests}/Variables.Tests.ps1 (100%) diff --git a/.github/PSModule.yml b/.github/PSModule.yml index 08dc7e549..0e0770314 100644 --- a/.github/PSModule.yml +++ b/.github/PSModule.yml @@ -1,17 +1,17 @@ Test: CodeCoverage: PercentTarget: 50 - TestResults: - Skip: true - SourceCode: - Skip: true - PSModule: - Skip: true - Module: - Windows: - Skip: true - MacOS: - Skip: true -Build: - Docs: - Skip: true +# TestResults: +# Skip: true +# SourceCode: +# Skip: true +# PSModule: +# Skip: true +# Module: +# Windows: +# Skip: true +# MacOS: +# Skip: true +# Build: +# Docs: +# Skip: true diff --git a/test2/Artifacts.Tests.ps1 b/tests/Artifacts.Tests.ps1 similarity index 100% rename from test2/Artifacts.Tests.ps1 rename to tests/Artifacts.Tests.ps1 diff --git a/test2/Emojis.Tests.ps1 b/tests/Emojis.Tests.ps1 similarity index 100% rename from test2/Emojis.Tests.ps1 rename to tests/Emojis.Tests.ps1 diff --git a/test2/Enterprise.Tests.ps1 b/tests/Enterprise.Tests.ps1 similarity index 100% rename from test2/Enterprise.Tests.ps1 rename to tests/Enterprise.Tests.ps1 diff --git a/test2/Environments.Tests.ps1 b/tests/Environments.Tests.ps1 similarity index 100% rename from test2/Environments.Tests.ps1 rename to tests/Environments.Tests.ps1 diff --git a/test2/GitHub.Tests.ps1 b/tests/GitHub.Tests.ps1 similarity index 100% rename from test2/GitHub.Tests.ps1 rename to tests/GitHub.Tests.ps1 diff --git a/test2/GitHubFormatter.Tests.ps1 b/tests/GitHubFormatter.Tests.ps1 similarity index 100% rename from test2/GitHubFormatter.Tests.ps1 rename to tests/GitHubFormatter.Tests.ps1 diff --git a/test2/Organizations.Tests.ps1 b/tests/Organizations.Tests.ps1 similarity index 100% rename from test2/Organizations.Tests.ps1 rename to tests/Organizations.Tests.ps1 diff --git a/test2/README.md b/tests/README.md similarity index 100% rename from test2/README.md rename to tests/README.md diff --git a/test2/Releases.Tests.ps1 b/tests/Releases.Tests.ps1 similarity index 100% rename from test2/Releases.Tests.ps1 rename to tests/Releases.Tests.ps1 diff --git a/test2/Repositories.Tests.ps1 b/tests/Repositories.Tests.ps1 similarity index 100% rename from test2/Repositories.Tests.ps1 rename to tests/Repositories.Tests.ps1 diff --git a/test2/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 similarity index 100% rename from test2/Secrets.Tests.ps1 rename to tests/Secrets.Tests.ps1 diff --git a/test2/TEMPLATE.ps1 b/tests/TEMPLATE.ps1 similarity index 100% rename from test2/TEMPLATE.ps1 rename to tests/TEMPLATE.ps1 diff --git a/test2/Teams.Tests.ps1 b/tests/Teams.Tests.ps1 similarity index 100% rename from test2/Teams.Tests.ps1 rename to tests/Teams.Tests.ps1 diff --git a/test2/Users.Tests.ps1 b/tests/Users.Tests.ps1 similarity index 100% rename from test2/Users.Tests.ps1 rename to tests/Users.Tests.ps1 diff --git a/test2/Variables.Tests.ps1 b/tests/Variables.Tests.ps1 similarity index 100% rename from test2/Variables.Tests.ps1 rename to tests/Variables.Tests.ps1 From c5d8ad08c757dcbefcf673485c5918b8dc613f31 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 17:37:16 +0200 Subject: [PATCH 41/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Clarify=20comme?= =?UTF-8?q?nt=20for=20Permissions=20property=20in=20GitHubAppInstallation?= =?UTF-8?q?=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/classes/public/App/GitHubAppInstallation.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index ed3365543..3fecc9140 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -14,7 +14,7 @@ # The type of repository selection. [string] $RepositorySelection - # The permissions that the app has on the target (full catalog for applicable scopes). + # The permissions that the app has on the target. [GitHubPermission[]] $Permissions # The events that the app is subscribing to. From c4021cc82726e8307ce9e387ffef30e54d873f8a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 17:37:24 +0200 Subject: [PATCH 42/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Replace=20Scrip?= =?UTF-8?q?tBlock=20with=20PropertyName=20for=20Status=20column=20in=20Git?= =?UTF-8?q?HubAppInstallation=20format?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/formats/GitHubAppInstallation.Format.ps1xml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/formats/GitHubAppInstallation.Format.ps1xml b/src/formats/GitHubAppInstallation.Format.ps1xml index b8f97eca3..d81a69fe6 100644 --- a/src/formats/GitHubAppInstallation.Format.ps1xml +++ b/src/formats/GitHubAppInstallation.Format.ps1xml @@ -60,15 +60,7 @@ Type - - switch ($_.Status) { - 'UpToDate' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Green)✓$($PSStyle.Reset)" } else { 'UpToDate' } } - 'PermissionsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Yellow)⚠P$($PSStyle.Reset)" } else { 'PermissionsOutdated' } } - 'EventsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Yellow)⚠E$($PSStyle.Reset)" } else { 'EventsOutdated' } } - 'PermissionsAndEventsOutdated' { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.Red)✗$($PSStyle.Reset)" } else { 'PermissionsAndEventsOutdated' } } - default { if ($Host.UI.SupportsVirtualTerminal -and ($env:GITHUB_ACTIONS -ne 'true')) { "$($PSStyle.Foreground.BrightBlack)?$($PSStyle.Reset)" } else { $_.Status } } - } - + Status CreatedAt From 90418145b5026e0a2791c10be5e78ad0c70d0314 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Mon, 15 Sep 2025 23:25:44 +0200 Subject: [PATCH 43/43] =?UTF-8?q?=F0=9F=A9=B9=20[Patch]:=20Update=20argume?= =?UTF-8?q?nt=20completers=20to=20use=20GitHubPermissionDefinition=20for?= =?UTF-8?q?=20consistent=20permission=20retrieval?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/public/Permission/completers.ps1 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/functions/public/Permission/completers.ps1 b/src/functions/public/Permission/completers.ps1 index 4586354f0..dc0be7453 100644 --- a/src/functions/public/Permission/completers.ps1 +++ b/src/functions/public/Permission/completers.ps1 @@ -2,7 +2,7 @@ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } - $script:GitHub.Permissions.Name | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { + [GitHubPermissionDefinition]::List.Name | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } @@ -11,7 +11,7 @@ Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -Paramete param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } - $script:GitHub.Permissions.DisplayName | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { + [GitHubPermissionDefinition]::List.DisplayName | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } @@ -20,7 +20,7 @@ Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -Paramete param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } - $script:GitHub.Permissions.Type | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { + [GitHubPermissionDefinition]::List.Type | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } } @@ -29,7 +29,7 @@ Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -Paramete param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters $pattern = switch (Get-GitHubConfig -Name CompletionMode) { 'Contains' { "*$wordToComplete*" } default { "$wordToComplete*" } } - $script:GitHub.Permissions.Scope | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { + [GitHubPermissionDefinition]::List.Scope | Sort-Object -Unique | Where-Object { $_ -like $pattern } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } }