diff --git a/src/classes/public/Environment/GitHubEnvironment.ps1 b/src/classes/public/Environment/GitHubEnvironment.ps1 index 799addd97..9fb960fe1 100644 --- a/src/classes/public/Environment/GitHubEnvironment.ps1 +++ b/src/classes/public/Environment/GitHubEnvironment.ps1 @@ -8,7 +8,16 @@ # The owner of the environment. [string] $Owner + # URL to the owner/organization profile. + # Example: https://github.com/octocat + [string] $OwnerUrl + + # URL to the repository. + # Example: https://github.com/octocat/Hello-World + [string] $RepositoryUrl + # URL of the environment. + # Example: https://github.com/octocat/Hello-World/settings/environments/123/edit [string] $Url # The date and time the environment was created. @@ -34,6 +43,8 @@ $this.Name = $Object.name $this.Owner = $Owner $this.Repository = $Repository + $this.OwnerUrl = "https://$($Context.HostName)/$Owner" + $this.RepositoryUrl = "https://$($Context.HostName)/$Owner/$Repository" $this.Url = "https://$($Context.HostName)/$Owner/$Repository/settings/environments/$($Object.id)/edit" $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at diff --git a/src/classes/public/GitHubFormatter.ps1 b/src/classes/public/GitHubFormatter.ps1 index 1fa06d0c5..b69263dad 100644 --- a/src/classes/public/GitHubFormatter.ps1 +++ b/src/classes/public/GitHubFormatter.ps1 @@ -28,6 +28,6 @@ { $_ -ge 1MB } { return '{0:N2} MB' -f ($size / 1MB) } { $_ -ge 1KB } { return '{0:N2} KB' -f ($size / 1KB) } } - return "$size B" + return '{0:N2} B' -f $size } } diff --git a/src/classes/public/GitHubPermissionDefinition.ps1 b/src/classes/public/GitHubPermissionDefinition.ps1 new file mode 100644 index 000000000..93ff6643f --- /dev/null +++ b/src/classes/public/GitHubPermissionDefinition.ps1 @@ -0,0 +1,46 @@ +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/classes/public/Secrets/GitHubSecret.ps1 b/src/classes/public/Secrets/GitHubSecret.ps1 index 61b8c9160..27c73ead9 100644 --- a/src/classes/public/Secrets/GitHubSecret.ps1 +++ b/src/classes/public/Secrets/GitHubSecret.ps1 @@ -1,29 +1,29 @@ class GitHubSecret { - # The key ID of the public key. + # The name of the secret. [string] $Name - # The scope of the variable, organization, repository, or environment. + # The scope of the secret, organization, repository, or environment. [string] $Scope - # The name of the organization or user the Public Key is associated with. + # The name of the organization or user the secret is stored in. [string] $Owner - # The name of the repository the Public Key is associated with. + # The name of the repository the secret is stored in. [string] $Repository - # The name of the environment the Public Key is associated with. + # The name of the environment the secret is stored in. [string] $Environment - # The date and time the variable was created. + # The date and time the secret was created. [datetime] $CreatedAt - # The date and time the variable was last updated. + # The date and time the secret was last updated. [datetime] $UpdatedAt - # The visibility of the variable. + # The visibility of the secret. [string] $Visibility - # The ids of the repositories that the variable is visible to. + # The ids of the repositories that the secret is visible to. [GitHubRepository[]] $SelectedRepositories GitHubSecret() {} diff --git a/src/classes/public/Variables/GitHubVariable.ps1 b/src/classes/public/Variables/GitHubVariable.ps1 index 6af9e2c0a..412d31c15 100644 --- a/src/classes/public/Variables/GitHubVariable.ps1 +++ b/src/classes/public/Variables/GitHubVariable.ps1 @@ -8,13 +8,13 @@ # The scope of the variable, organization, repository, or environment. [string] $Scope - # The name of the organization or user the variable is associated with. + # The name of the organization or user the variable is stored in. [string] $Owner - # The name of the repository the variable is associated with. + # The name of the repository the variable is stored in. [string] $Repository - # The name of the environment the variable is associated with. + # The name of the environment the variable is stored in. [string] $Environment # The date and time the variable was created. diff --git a/src/formats/GitHubEnvironment.Format.ps1xml b/src/formats/GitHubEnvironment.Format.ps1xml index 1373b0c0f..6a8530fa0 100644 --- a/src/formats/GitHubEnvironment.Format.ps1xml +++ b/src/formats/GitHubEnvironment.Format.ps1xml @@ -32,10 +32,24 @@ - Repository + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true')) { + $PSStyle.FormatHyperlink($_.Repository,$_.RepositoryUrl) + } else { + $_.Repository + } + - Owner + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true')) { + $PSStyle.FormatHyperlink($_.Owner,$_.OwnerUrl) + } else { + $_.Owner + } + diff --git a/src/formats/GitHubPermissionDefinition.Format.ps1xml b/src/formats/GitHubPermissionDefinition.Format.ps1xml new file mode 100644 index 000000000..756b42381 --- /dev/null +++ b/src/formats/GitHubPermissionDefinition.Format.ps1xml @@ -0,0 +1,46 @@ + + + + + GitHubPermissionDefinitionTable + + GitHubPermissionDefinition + + + + + + + + + + + + + + + + + + Scope + + + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true') -and $_.Url) { + $PSStyle.FormatHyperlink($_.DisplayName, $_.Url) + } else { + $_.DisplayName + } + + + + Description + + + + + + + + diff --git a/src/functions/public/Auth/Context/Get-GitHubContext.ps1 b/src/functions/public/Auth/Context/Get-GitHubContext.ps1 index 9f0015551..6ff8aecc1 100644 --- a/src/functions/public/Auth/Context/Get-GitHubContext.ps1 +++ b/src/functions/public/Auth/Context/Get-GitHubContext.ps1 @@ -19,12 +19,12 @@ Justification = 'Encapsulated in a function. Never leaves as a plain text.' )] [OutputType([GitHubContext])] - [CmdletBinding(DefaultParameterSetName = '__AllParameterSets')] + [CmdletBinding(DefaultParameterSetName = 'Get default context')] param( # The name of the context. [Parameter( Mandatory, - ParameterSetName = 'NamedContext' + ParameterSetName = 'Get a named context' )] [Alias('Name')] [string] $Context, @@ -32,7 +32,7 @@ # List all available contexts. [Parameter( Mandatory, - ParameterSetName = 'ListAvailableContexts' + ParameterSetName = 'List all available contexts' )] [switch] $ListAvailable ) @@ -45,11 +45,11 @@ process { switch ($PSCmdlet.ParameterSetName) { - 'NamedContext' { - Write-Debug "NamedContext: [$Context]" + 'Get a named context' { + Write-Debug "Get a named context: [$Context]" $ID = $Context } - 'ListAvailableContexts' { + 'List all available contexts' { Write-Debug "ListAvailable: [$ListAvailable]" $ID = '*' } @@ -85,7 +85,7 @@ throw "Unknown context type: [$($contextObj.Type)]" } } - } + } | Sort-Object -Property Name } end { @@ -93,4 +93,3 @@ } } #Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } - diff --git a/src/functions/private/Auth/Context/completers.ps1 b/src/functions/public/Auth/Context/completers.ps1 similarity index 92% rename from src/functions/private/Auth/Context/completers.ps1 rename to src/functions/public/Auth/Context/completers.ps1 index 31f4d4436..fb204c67a 100644 --- a/src/functions/private/Auth/Context/completers.ps1 +++ b/src/functions/public/Auth/Context/completers.ps1 @@ -12,7 +12,8 @@ $contexts += 'Anonymous' } - $contexts += (Get-GitHubContext -ListAvailable -Verbose:$false).Name + $contexts += (Get-GitHubContext -ListAvailable -Verbose:$false -Debug:$false).Name + $contexts = $contexts | Sort-Object -Unique $contexts | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) } diff --git a/src/functions/public/Auth/Connect-GitHubApp_completer.ps1 b/src/functions/public/Auth/completer.ps1 similarity index 100% rename from src/functions/public/Auth/Connect-GitHubApp_completer.ps1 rename to src/functions/public/Auth/completer.ps1 diff --git a/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 b/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 new file mode 100644 index 000000000..0019adf59 --- /dev/null +++ b/src/functions/public/Permission/Get-GitHubPermissionDefinition.ps1 @@ -0,0 +1,104 @@ +function Get-GitHubPermissionDefinition { + <# + .SYNOPSIS + Retrieves GitHub permission definitions + + .DESCRIPTION + Gets the list of GitHub permission definitions from the module's internal data store. + This includes fine-grained permissions for repositories, organizations, and user accounts. + The function supports filtering by permission type and scope to help you find specific permissions. + + File path-specific permissions are excluded from this list as they are handled differently + by the GitHub API (they appear under the FilePaths property in installation data rather + than as named permissions). + + .EXAMPLE + Get-GitHubPermissionDefinition + + Gets all permission definitions. + + .EXAMPLE + Get-GitHubPermissionDefinition -Type Fine-grained + + Gets all fine-grained permission definitions. + + .EXAMPLE + Get-GitHubPermissionDefinition -Scope Repository + + Gets all permission definitions that apply to repository scope. + + .EXAMPLE + Get-GitHubPermissionDefinition -Type Fine-grained -Scope Organization + + Gets all fine-grained permission definitions that apply to organization scope. + + .EXAMPLE + Get-GitHubPermissionDefinition -Name contents + + Gets the specific permission definition for 'contents' permission. + + .NOTES + This function provides access to a curated list of GitHub permission definitions maintained within the module. + The data includes permission names, display names, descriptions, available options, and scopes. + + File path permissions are excluded from this list as they are handled differently by the GitHub API. + These permissions are user-specified paths with read/write access that appear in the FilePaths + property of GitHub App installation data, not as standard named permissions. + + .LINK + https://psmodule.io/GitHub/Functions/Permission/Get-GitHubPermissionDefinition + #> + [OutputType([GitHubPermissionDefinition[]])] + [CmdletBinding()] + param( + # Filter by permission name (supports multiple values & wildcards) + [Parameter()] + [string[]] $Name = '*', + + # Filter by permission display name (supports multiple values & wildcards) + [Parameter()] + [string[]] $DisplayName = '*', + + # Filter by permission type (supports multiple values & wildcards) + [Parameter()] + [string[]] $Type = '*', + + # Filter by permission scope (supports multiple values & wildcards) + [Parameter()] + [string[]] $Scope = '*' + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + try { + [scriptblock]$test = { + param( + [Parameter(Mandatory)][string] $Value, + [Parameter(Mandatory)][string[]] $Patterns + ) + foreach ($p in $Patterns) { + if ($Value -like $p) { return $true } + } + return $false + } + + $script:GitHub.Permissions | Where-Object { + (& $test -Value $_.Name -Patterns $Name) -and + (& $test -Value $_.DisplayName -Patterns $DisplayName) -and + (& $test -Value $_.Type -Patterns $Type) -and + (& $test -Value $_.Scope -Patterns $Scope) + } + } catch { + Write-Error "Failed to retrieve GitHub permission definitions: $($_.Exception.Message)" + throw + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/public/Permission/completers.ps1 b/src/functions/public/Permission/completers.ps1 new file mode 100644 index 000000000..139a72e5f --- /dev/null +++ b/src/functions/public/Permission/completers.ps1 @@ -0,0 +1,35 @@ +Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -ParameterName Name -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + $script:GitHub.Permissions.Name | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} + +Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -ParameterName DisplayName -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + $script:GitHub.Permissions.DisplayName | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} + +Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -ParameterName Type -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + $script:GitHub.Permissions.Type | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} + +Register-ArgumentCompleter -CommandName Get-GitHubPermissionDefinition -ParameterName Scope -ScriptBlock { + param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) + $null = $commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters + + $script:GitHub.Permissions.Scope | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } +} diff --git a/src/functions/public/Secrets/Remove-GitHubSecret.ps1 b/src/functions/public/Secrets/Remove-GitHubSecret.ps1 index 2d4f1a4b4..f5ea7b3eb 100644 --- a/src/functions/public/Secrets/Remove-GitHubSecret.ps1 +++ b/src/functions/public/Secrets/Remove-GitHubSecret.ps1 @@ -78,15 +78,38 @@ switch ($PSCmdlet.ParameterSetName) { 'ArrayInput' { foreach ($item in $InputObject) { - $params = @{ - Owner = $item.Owner - Repository = $item.Repository - Environment = $item.Environment - Name = $item.Name - Context = $item.Context + switch ($item.Scope) { + 'environment' { + $params = @{ + Owner = $item.Owner + Repository = $item.Repository + Environment = $item.Environment + Name = $item.Name + Context = $Context + } + Remove-GitHubSecretFromEnvironment @params + } + 'repository' { + $params = @{ + Owner = $item.Owner + Repository = $item.Repository + Name = $item.Name + Context = $Context + } + Remove-GitHubSecretFromRepository @params + } + 'organization' { + $params = @{ + Owner = $item.Owner + Name = $item.Name + Context = $Context + } + Remove-GitHubSecretFromOwner @params + } + default { + throw "Secret '$($item.Name)' has unsupported Scope value '$scope'." + } } - $params | Remove-HashtableEntry -NullOrEmptyValues - Remove-GitHubSecret @params } break } diff --git a/src/functions/public/Variables/Remove-GitHubVariable.ps1 b/src/functions/public/Variables/Remove-GitHubVariable.ps1 index ffe376cc3..578ad371c 100644 --- a/src/functions/public/Variables/Remove-GitHubVariable.ps1 +++ b/src/functions/public/Variables/Remove-GitHubVariable.ps1 @@ -84,15 +84,38 @@ switch ($PSCmdlet.ParameterSetName) { 'ArrayInput' { foreach ($item in $InputObject) { - $params = @{ - Owner = $item.Owner - Repository = $item.Repository - Environment = $item.Environment - Name = $item.Name - Context = $item.Context + switch ($item.Scope) { + 'environment' { + $params = @{ + Owner = $item.Owner + Repository = $item.Repository + Environment = $item.Environment + Name = $item.Name + Context = $Context + } + Remove-GitHubVariableFromEnvironment @params + } + 'repository' { + $params = @{ + Owner = $item.Owner + Repository = $item.Repository + Name = $item.Name + Context = $Context + } + Remove-GitHubVariableFromRepository @params + } + 'organization' { + $params = @{ + Owner = $item.Owner + Name = $item.Name + Context = $Context + } + Remove-GitHubVariableFromOwner @params + } + default { + throw "Variable '$($item.Name)' has unsupported Scope value '$($item.Scope)'." + } } - $params | Remove-HashtableEntry -NullOrEmptyValues - Remove-GitHubVariable @params } break } diff --git a/src/variables/private/GitHub.ps1 b/src/variables/private/GitHub.ps1 index 3891118b3..917d0be14 100644 --- a/src/variables/private/GitHub.ps1 +++ b/src/variables/private/GitHub.ps1 @@ -23,4 +23,1184 @@ $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/GitHubFormatter.Tests.ps1 b/tests/GitHubFormatter.Tests.ps1 index 63f55c7c3..7c9455acf 100644 --- a/tests/GitHubFormatter.Tests.ps1 +++ b/tests/GitHubFormatter.Tests.ps1 @@ -7,11 +7,7 @@ [CmdletBinding()] param() -# This test file validates size property standardization across GitHub classes -# It focuses on unit conversion and formatting expectations rather than live API calls - Describe 'Size Property Standardization Tests' { - Context 'Unit Conversion Logic' { It 'Validates KB to Bytes conversion formula' { # Test the conversion used in GitHubRepository and GitHubOrganization @@ -41,23 +37,19 @@ Describe 'Size Property Standardization Tests' { } Context 'Expected Format Output Patterns' { - It 'Validates expected format patterns for size display' { - # These tests verify the expected output patterns without requiring the actual formatter - # They document what the GitHubFormatter::FormatFileSize method should produce - - $testCases = @( - @{ Bytes = 0; ExpectedPattern = '\d+\s+B' } # "0 B" - @{ Bytes = 512; ExpectedPattern = '\d+\s+B' } # "512 B" - @{ Bytes = 1024; ExpectedPattern = '\d+\.\d{2} KB' } # "1.00 KB" - @{ Bytes = 1048576; ExpectedPattern = '\d+\.\d{2} MB' } # "1.00 MB" - @{ Bytes = 1073741824; ExpectedPattern = '\d+\.\d{2} GB' } # "1.00 GB" - @{ Bytes = 110592; ExpectedPattern = '\d+\.\d{2} KB' } # "108.00 KB" - ) - - foreach ($case in $testCases) { - # Document expected pattern - actual formatting tested in integration tests - $case.ExpectedPattern | Should -Match '\w+' # Verify pattern is non-empty - } + $testCases = @( + @{ Bytes = 0; ExpectedPattern = '\d+[.,]\d{2}\s{2}B' } # "0.00 B" + @{ Bytes = 512; ExpectedPattern = '\d+[.,]\d{2}\s{2}B' } # "512.00 B" + @{ Bytes = 1024; ExpectedPattern = '\d+[.,]\d{2} KB' } # "1.00 KB" + @{ Bytes = 1048576; ExpectedPattern = '\d+[.,]\d{2} MB' } # "1.00 MB" + @{ Bytes = 1073741824; ExpectedPattern = '\d+[.,]\d{2} GB' } # "1.00 GB" + @{ Bytes = 110592; ExpectedPattern = '\d+[.,]\d{2} KB' } # "108.00 KB" + ) + + It 'Validates formatter output pattern for bytes' -ForEach $testCases { + # Test the formatter against the expected pattern + $result = [GitHubFormatter]::FormatFileSize($Bytes) + $result | Should -Match $ExpectedPattern } } diff --git a/tests/Permissions.Tests.ps1 b/tests/Permissions.Tests.ps1 new file mode 100644 index 000000000..043358796 --- /dev/null +++ b/tests/Permissions.Tests.ps1 @@ -0,0 +1,211 @@ +#Requires -Modules @{ ModuleName = 'Pester'; RequiredVersion = '5.7.1' } + +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', '', + Justification = 'Pester grouping syntax: known issue.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Used to create a secure string for testing.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Log outputs to GitHub Actions logs.' +)] +[Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidLongLines', '', + Justification = 'Long test descriptions and skip switches' +)] +[CmdletBinding()] +param() + +Describe 'Permissions' { + $authCases = . "$PSScriptRoot/Data/AuthCases.ps1" + + Context 'As using on ' -ForEach $authCases { + BeforeAll { + $context = Connect-GitHubAccount @connectParams -PassThru -Silent + LogGroup 'Context' { + Write-Host ($context | Format-List | Out-String) + } + } + + AfterAll { + Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent + Write-Host ('-' * 60) + } + + Context 'For Apps' -Skip:($AuthType -ne 'APP') { + BeforeAll { + $installationContext = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + LogGroup 'Context - Installation' { + Write-Host ($installationContext | 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] + } + + It 'All app installation permissions should exist in permission catalog and be valid options' -Skip:($AuthType -ne 'APP') { + $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 + } + } + + It 'Permission catalog should contain all permissions granted to the app installation' -Skip:($AuthType -ne 'APP') { + $catalog = Get-GitHubPermissionDefinition + $missing = @() + $installationContext.Permissions.PSObject.Properties | ForEach-Object { + if ($_.Name -notin $catalog.Name) { + $missing += $_.Name + } + } + if ($missing.Count -gt 0) { + throw "Missing permission definitions for: $($missing -join ', ')" + } + } + } + } + + Context 'Get-GitHubPermissionDefinition' { + It 'Should return all permission definitions when called without parameters' { + $result = Get-GitHubPermissionDefinition + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + ($result | Measure-Object).Count | Should -BeGreaterThan 0 + } + + It 'Should return only Fine-grained permissions when filtered by Type' { + $result = Get-GitHubPermissionDefinition -Type Fine-grained + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + $result | ForEach-Object { $_.Type | Should -Be 'Fine-grained' } + } + + It 'Should return only Repository permissions when filtered by Scope' { + $result = Get-GitHubPermissionDefinition -Scope Repository + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + $result | ForEach-Object { $_.Scope | Should -Be 'Repository' } + } + + It 'Should return only Organization permissions when filtered by Scope' { + $result = Get-GitHubPermissionDefinition -Scope Organization + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + $result | ForEach-Object { $_.Scope | Should -Be 'Organization' } + } + + It 'Should return only User permissions when filtered by Scope' { + $result = Get-GitHubPermissionDefinition -Scope User + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + $result | ForEach-Object { $_.Scope | Should -Be 'User' } + } + + It 'Should filter by both Type and Scope when both are specified' { + $result = Get-GitHubPermissionDefinition -Type Fine-grained -Scope Repository + $result | Should -Not -BeNullOrEmpty + $result | Should -BeOfType [GitHubPermissionDefinition] + $result | ForEach-Object { + $_.Type | Should -Be 'Fine-grained' + $_.Scope | Should -Be 'Repository' + } + } + + It 'Should include expected properties for each permission' { + $result = Get-GitHubPermissionDefinition | Select-Object -First 1 + $result.Name | Should -Not -BeNullOrEmpty + $result.DisplayName | Should -Not -BeNullOrEmpty + $result.Description | Should -Not -BeNullOrEmpty + $result.URL | Should -Not -BeNullOrEmpty + $result.Options | Should -Not -BeNullOrEmpty + $result.Type | Should -Not -BeNullOrEmpty + $result.Scope | Should -Not -BeNullOrEmpty + } + + It 'Should include the contents permission for repositories' { + $result = Get-GitHubPermissionDefinition | Where-Object { $_.Name -eq 'contents' } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'contents' + $result.DisplayName | Should -Be 'Contents' + $result.Scope | Should -Be 'Repository' + $result.Type | Should -Be 'Fine-grained' + $result.Options | Should -Contain 'read' + $result.Options | Should -Contain 'write' + } + + It 'Should include the members permission for organizations' { + $result = Get-GitHubPermissionDefinition | Where-Object { $_.Name -eq 'members' } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'members' + $result.DisplayName | Should -Be 'Members' + $result.Scope | Should -Be 'Organization' + $result.Type | Should -Be 'Fine-grained' + $result.Options | Should -Contain 'read' + $result.Options | Should -Contain 'write' + } + + It 'Should include profile permission for users' { + $result = Get-GitHubPermissionDefinition | Where-Object { $_.Name -eq 'profile' } + $result | Should -Not -BeNullOrEmpty + $result.Name | Should -Be 'profile' + $result.DisplayName | Should -Be 'Profile' + $result.Scope | Should -Be 'User' + $result.Type | Should -Be 'Fine-grained' + $result.Options | Should -Contain 'write' + } + } + + Context 'GitHubPermission Class' { + BeforeAll { + $permission = [GitHubPermissionDefinition]@{ + Name = 'test' + DisplayName = 'Test Permission' + Description = 'A test permission' + URL = 'https://docs.github.com/test' + Options = @('read', 'write') + Type = 'Fine-grained' + Scope = 'Repository' + } + } + + It 'Should create a GitHubPermission object with all properties' { + $permission | Should -Not -BeNullOrEmpty + $permission.Name | Should -Be 'test' + $permission.DisplayName | Should -Be 'Test Permission' + $permission.Description | Should -Be 'A test permission' + $permission.URL | Should -Be 'https://docs.github.com/test' + $permission.Options | Should -Contain 'read' + $permission.Options | Should -Contain 'write' + $permission.Type | Should -Be 'Fine-grained' + $permission.Scope | Should -Be 'Repository' + } + + It 'Should have a meaningful ToString() method' { + $result = $permission.ToString() + $result | Should -Be 'test' + } + } +} diff --git a/tests/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 index 48bff8af7..48e06423d 100644 --- a/tests/Secrets.Tests.ps1 +++ b/tests/Secrets.Tests.ps1 @@ -215,14 +215,18 @@ Describe 'Secrets' { } } - It 'Remove-GitHubSecret' { + It 'Remove-GitHubSecret via pipeline - using pipeline' { $testSecretName = "$secretName`TestSecret*" + LogGroup 'Create secret(s) for pipeline removal' { + $create = Set-GitHubSecret @scope -Name "$secretName`TestSecretPipeline" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" + } LogGroup 'Before remove' { $before = Get-GitHubSecret @scope -Name $testSecretName Write-Host "$($before | Format-List | Out-String)" } LogGroup 'Remove' { - $before | Remove-GitHubSecret + Get-GitHubSecret @scope -Name $testSecretName | Remove-GitHubSecret } LogGroup 'After remove' { $after = Get-GitHubSecret @scope -Name $testSecretName @@ -231,6 +235,26 @@ Describe 'Secrets' { $after.Count | Should -Be 0 } + It 'Remove-GitHubSecret via pipeline - using variable' { + $pipelineTestSecretName = "$secretName`PipelineTest" + LogGroup 'Create test secret for pipeline removal' { + $createResult = Set-GitHubSecret @scope -Name $pipelineTestSecretName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get secret for pipeline removal' { + $secretToRemove = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($secretToRemove | Format-List | Out-String)" + } + LogGroup 'Remove via pipeline' { + { $secretToRemove | Remove-GitHubSecret } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } + Context 'SelectedRepository' { It 'Get-GitHubSecretSelectedRepository - gets a list of selected repositories' { LogGroup "SelectedRepositories - [$orgSecretName]" { @@ -402,18 +426,42 @@ Describe 'Secrets' { } } - It 'Remove-GitHubSecret' { + It 'Remove-GitHubSecret via pipeline - using pipeline' { + LogGroup 'Create secret(s) for pipeline removal' { + $create = Set-GitHubSecret @scope -Name "$secretName`PipelineRemoval" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" + } $before = Get-GitHubSecret @scope -Name "*$os*" LogGroup 'Secrets - Before' { Write-Host "$($before | Format-Table | Out-String)" } - $before | Remove-GitHubSecret + Get-GitHubSecret @scope -Name "*$os*" | Remove-GitHubSecret $after = Get-GitHubSecret @scope -Name "*$os*" LogGroup 'Secrets -After' { Write-Host "$($after | Format-Table | Out-String)" } $after.Count | Should -Be 0 } + + It 'Remove-GitHubSecret via pipeline - using variable' { + $pipelineTestSecretName = "$secretName`PipelineTest" + LogGroup 'Create test secret for pipeline removal' { + $createResult = Set-GitHubSecret @scope -Name $pipelineTestSecretName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get secret for pipeline removal' { + $secretToRemove = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($secretToRemove | Format-List | Out-String)" + } + LogGroup 'Remove via pipeline' { + { $secretToRemove | Remove-GitHubSecret } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } } Context 'Environment' -Skip:($OwnerType -in ('repository', 'enterprise')) { @@ -512,18 +560,43 @@ Describe 'Secrets' { } } - It 'Remove-GitHubSecret' { + It 'Remove-GitHubSecret via pipeline - using pipeline' { + LogGroup 'Create secret(s) for pipeline removal' { + $create = Set-GitHubSecret @scope -Name "$secretName`PipelineRemoval" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" + } LogGroup 'Secrets - Before' { $before = Get-GitHubSecret @scope -Name "*$os*" Write-Host "$($before | Format-Table | Out-String)" } - $before | Remove-GitHubSecret + Get-GitHubSecret @scope -Name "*$os*" | Remove-GitHubSecret LogGroup 'Secrets - After' { $after = Get-GitHubSecret @scope -Name "*$os*" Write-Host "$($after | Format-Table | Out-String)" } $after.Count | Should -Be 0 } + + It 'Remove-GitHubSecret via pipeline - using variable' { + $pipelineTestSecretName = "$secretName`PipelineTest" + LogGroup 'Create test secret for pipeline removal' { + $createResult = Set-GitHubSecret @scope -Name $pipelineTestSecretName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get secret and test whitespace handling' { + $secretToRemove = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($secretToRemove | Format-List | Out-String)" + Write-Host "Testing that environment secrets with valid Environment property work correctly" + } + LogGroup 'Remove via pipeline' { + { $secretToRemove | Remove-GitHubSecret } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubSecret @scope -Name $pipelineTestSecretName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } } } } diff --git a/tests/Variables.Tests.ps1 b/tests/Variables.Tests.ps1 index 2b039ce31..0599ac435 100644 --- a/tests/Variables.Tests.ps1 +++ b/tests/Variables.Tests.ps1 @@ -240,22 +240,43 @@ Describe 'Variables' { } } - It 'Remove-GitHubVariable' { - $testVarName = "$variableName`TestVariable*" - LogGroup 'Before remove' { - $before = Get-GitHubVariable @scope -Name $testVarName - Write-Host "$($before | Format-List | Out-String)" + It 'Remove-GitHubVariable via pipeline - using pipeline' { + LogGroup 'Create variable(s) for pipeline removal' { + $create = Set-GitHubVariable @scope -Name "$variableName`PipelineRemoval" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" } - LogGroup 'Remove' { - $before | Remove-GitHubVariable + $before = Get-GitHubVariable @scope -Name "*$os*" + LogGroup 'Variables - Before' { + Write-Host "$($before | Format-Table | Out-String)" } - LogGroup 'After remove' { - $after = Get-GitHubVariable @scope -Name $testVarName - Write-Host "$($after | Format-List | Out-String)" + Get-GitHubVariable @scope -Name "*$os*" | Remove-GitHubVariable + $after = Get-GitHubVariable @scope -Name "*$os*" + LogGroup 'Variables -After' { + Write-Host "$($after | Format-Table | Out-String)" } $after.Count | Should -Be 0 } + It 'Remove-GitHubVariable via pipeline - using variable' { + $pipelineTestVariableName = "$variableName`PipelineTest" + LogGroup 'Create test variable for pipeline removal' { + $createResult = Set-GitHubVariable @scope -Name $pipelineTestVariableName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get variable for pipeline removal' { + $variableToRemove = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($variableToRemove | Format-List | Out-String)" + } + LogGroup 'Remove via pipeline' { + { $variableToRemove | Remove-GitHubVariable } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } + Context 'SelectedRepository' -Tag 'Flaky' { It 'Get-GitHubVariableSelectedRepository - gets a list of selected repositories' { LogGroup "SelectedRepositories - [$orgVariableName]" { @@ -433,18 +454,42 @@ Describe 'Variables' { } } - It 'Remove-GitHubVariable' { + It 'Remove-GitHubVariable via pipeline - using pipeline' { + LogGroup 'Create variable(s) for pipeline removal' { + $create = Set-GitHubVariable @scope -Name "$variableName`PipelineRemoval" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" + } $before = Get-GitHubVariable @scope -Name "*$os*" LogGroup 'Variables - Before' { Write-Host "$($before | Format-Table | Out-String)" } - $before | Remove-GitHubVariable + Get-GitHubVariable @scope -Name "*$os*" | Remove-GitHubVariable $after = Get-GitHubVariable @scope -Name "*$os*" LogGroup 'Variables -After' { Write-Host "$($after | Format-Table | Out-String)" } $after.Count | Should -Be 0 } + + It 'Remove-GitHubVariable via pipeline - using variable' { + $pipelineTestVariableName = "$variableName`PipelineTest" + LogGroup 'Create test variable for pipeline removal' { + $createResult = Set-GitHubVariable @scope -Name $pipelineTestVariableName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get variable for pipeline removal' { + $variableToRemove = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($variableToRemove | Format-List | Out-String)" + } + LogGroup 'Remove via pipeline' { + { $variableToRemove | Remove-GitHubVariable } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } } Context 'Environment' -Skip:($OwnerType -in ('repository', 'enterprise')) { @@ -560,18 +605,43 @@ Describe 'Variables' { } } - It 'Remove-GitHubVariable' { + It 'Remove-GitHubVariable via pipeline - using pipeline' { + LogGroup 'Create variable(s) for pipeline removal' { + $create = Set-GitHubVariable @scope -Name "$variableName`PipelineRemoval" -Value 'PipelineTestValue' + Write-Host "$($create | Format-List | Out-String)" + } LogGroup 'Variables - Before' { $before = Get-GitHubVariable @scope -Name "*$os*" Write-Host "$($before | Format-Table | Out-String)" } - $before | Remove-GitHubVariable + Get-GitHubVariable @scope -Name "*$os*" | Remove-GitHubVariable LogGroup 'Variables - After' { $after = Get-GitHubVariable @scope -Name "*$os*" Write-Host "$($after | Format-Table | Out-String)" } $after.Count | Should -Be 0 } + + It 'Remove-GitHubVariable via pipeline - using variable' { + $pipelineTestVariableName = "$variableName`PipelineTest" + LogGroup 'Create test variable for pipeline removal' { + $createResult = Set-GitHubVariable @scope -Name $pipelineTestVariableName -Value 'PipelineTestValue' + Write-Host "$($createResult | Format-List | Out-String)" + } + LogGroup 'Get variable and test whitespace handling' { + $variableToRemove = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($variableToRemove | Format-List | Out-String)" + Write-Host "Testing that environment variables with valid Environment property work correctly" + } + LogGroup 'Remove via pipeline' { + { $variableToRemove | Remove-GitHubVariable } | Should -Not -Throw + } + LogGroup 'Verify removal' { + $after = Get-GitHubVariable @scope -Name $pipelineTestVariableName + Write-Host "$($after | Format-List | Out-String)" + $after | Should -BeNullOrEmpty + } + } } } }