diff --git a/.github/PSModule.yml b/.github/PSModule.yml index 6d578178e..0e0770314 100644 --- a/.github/PSModule.yml +++ b/.github/PSModule.yml @@ -1,3 +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 diff --git a/.github/workflows/Linter.yml b/.github/workflows/Linter.yml index 6c163eb41..8017f6334 100644 --- a/.github/workflows/Linter.yml +++ b/.github/workflows/Linter.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/README.md b/README.md index f8286fb13..c84fbb0f4 100644 --- a/README.md +++ b/README.md @@ -70,34 +70,7 @@ Press Enter to open github.com in your browser...: #-> Press enter and paste th After this you will need to install the GitHub App on the repos you want to manage. You can do this by visiting the [PowerShell for GitHub](https://github.com/apps/powershell-for-github) app page. -> Info: We will be looking to include this as a check in the module in the future. So it becomes a part of the regular sign in process. -Consecutive runs of the `Connect-GitHubAccount` will not require you to paste the code again unless you revoke the token -or you change the type of authentication you want to use. Instead, it checks the remaining duration of the access token and -uses the refresh token to get a new access token if its less than 4 hours remaining. - -```powershell -Connect-GitHubAccount -✓ Access token is still valid for 05:30:41 ... -✓ Logged in as octocat! -``` - -This is also happening automatically when you run a command that requires authentication. The validity of the token is checked before the command is executed. -If it is no longer valid, the token is refreshed and the command is executed. - -```powershell -Connect-GitHubAccount -⚠ Access token remaining validity 01:22:31. Refreshing access token... -✓ Logged in as octocat! -``` - -If the timer has gone out, we still have your back. It will just refresh as long as the refresh token is valid. - -```powershell -Connect-GitHubAccount -⚠ Access token expired. Refreshing access token... -✓ Logged in as octocat! -``` #### Personal authentication - User access tokens with OAuth app @@ -154,6 +127,48 @@ Connect-GitHubAccount -ClientID $ClientID -PrivateKey $PrivateKey Using this approach, the module will autogenerate a JWT every time you run a command. I.e. Get-GitHubApp. +#### Using a GitHub App with Azure Key Vault + +For enhanced security, you can store your GitHub App's keys in Azure Key Vault and use that as way to signing the JWTs. +This approach requires a pre-authenticated session with either Azure CLI or Azure PowerShell. + +**Prerequisites:** +- Azure CLI authenticated session (`az login`) or Azure PowerShell authenticated session (`Connect-AzAccount`) +- GitHub App private key stored as a key in Azure Key Vault, with 'Sign' as a permitted operation +- Appropriate permissions to read keys from the Key Vault, like 'Key Vault Crypto User' + +**Using Azure CLI authentication:** + +```powershell +# Ensure you're authenticated with Azure CLI +az login + +# Connect using Key Vault key reference (URI with or without version) +Connect-GitHubAccount -ClientID $ClientID -KeyVaultKeyReference 'https://my-keyvault.vault.azure.net/keys/github-app-private-key' +✓ Logged in as my-github-app! +``` + +**Using Azure PowerShell authentication:** + +```powershell +# Ensure you're authenticated with Azure PowerShell +Connect-AzAccount + +# Connect using Key Vault key reference (URI with or without version) +Connect-GitHubAccount -ClientID $ClientID -KeyVaultKeyReference 'https://my-keyvault.vault.azure.net/keys/github-app-private-key' +✓ Logged in as my-github-app! +``` + +**Using Key Vault key reference with version:** + +```powershell +# Connect using Key Vault key reference with specific version +Connect-GitHubAccount -ClientID $ClientID -KeyVaultKeyReference 'https://my-keyvault.vault.azure.net/keys/github-app-private-key/abc123def456' +✓ Logged in as my-github-app! +``` + +This method ensures that your private key is securely stored in Azure Key Vault and never exposed in your scripts or configuration files. + #### Using a different host If you are using GitHub Enterprise, you can use the `-Host` (or `-HostName`) parameter to specify the host you want to connect to. @@ -181,6 +196,15 @@ Connect-GitHubAccount -Host 'https://msx.ghe.com' -ClientID 'lv123456789' ✓ Logged in as octocat! ``` +#### Automatic token renewal + +The module automatically manages short‑lived tokens for GitHub Apps: + +- User access tokens (when you authenticate via a GitHub App) are short‑lived and include a refresh token. The module refreshes them automatically before/when they expire—no extra steps are required. +- App JWTs (when the context is a GitHub App) are generated and rotated automatically per call as needed. You never need to create or renew the JWT yourself. + +Note: Long‑lived tokens like classic/fine‑grained PATs and provided installation tokens (GH_TOKEN/GITHUB_TOKEN) are not refreshed by the module. + ### Command Exploration Familiarize yourself with the available cmdlets using the module's comprehensive documentation or inline help. diff --git a/examples/Apps/AppManagement.ps1 b/examples/Apps/AppManagement.ps1 deleted file mode 100644 index 677f9e0c3..000000000 --- a/examples/Apps/AppManagement.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -# Install an app on the entire enterprise -$appIDs = @( - 'Iv1.f26b61bc99e69405' -) -$orgs = Get-GitHubOrganization -Enterprise 'msx' -foreach ($org in $orgs) { - foreach ($appID in $appIDs) { - Install-GitHubAppOnEnterpriseOrganization -Enterprise msx -Organization $org.login -ClientID $appID -RepositorySelection all - } -} diff --git a/examples/Apps/EnterpriseApps.ps1 b/examples/Apps/EnterpriseApps.ps1 index 2526b7b2d..eed2507df 100644 --- a/examples/Apps/EnterpriseApps.ps1 +++ b/examples/Apps/EnterpriseApps.ps1 @@ -1,40 +1,23 @@ -$appIDs = @( - 'qweqweqwe', - 'qweqweqweqwe' -) - -$organization = '*' -filter Install-GithubApp { - param( - [Parameter()] - [string] $Enterprise = 'msx', +$ClientID = '' +$PrivateKey = @' +-----BEGIN RSA PRIVATE KEY----- - [Parameter()] - [string] $Organization = '*', +-----END RSA PRIVATE KEY----- +'@ +Connect-GitHub -ClientID $ClientID -PrivateKey $PrivateKey +Connect-GitHubApp -Enterprise 'msx' - [Parameter( - Mandatory, - ValueFromPipeline - )] - [string] $AppID - ) +# The apps you want to install on orgs in the enterprise +$ClientIDs = @( + 'Iv1.f26b61bc99e69405' +) +$Enterprise = 'msx' +$Organization = '*' - process { - $installableOrgs = Get-GitHubOrganization -Enterprise $Enterprise - $orgs = $installableOrgs | Where-Object { $_.login -like $organization } - foreach ($org in $orgs) { - foreach ($appIDitem in $AppID) { - Install-GitHubApp -Enterprise $Enterprise -Organization $org.login -ClientID $appIDitem -RepositorySelection all | ForEach-Object { - [PSCustomObject]@{ - Organization = $org.login - AppID = $appIDitem - } - } - } - } +$installableOrgs = Get-GitHubOrganization -Enterprise $Enterprise +$orgs = $installableOrgs | Where-Object { $_.Name -like $Organization } +foreach ($org in $orgs) { + foreach ($ClientID in $ClientIDs) { + Install-GitHubApp -Enterprise $Enterprise -Organization $org.Name -ClientID $ClientID -RepositorySelection all } } - -$appIDs | Install-GitHubApp -Organization $organization - -$installation = Get-GitHubAppInstallation diff --git a/examples/CallingAPIs.ps1 b/examples/CallingAPIs.ps1 index e8c70c1a2..578f72bf8 100644 --- a/examples/CallingAPIs.ps1 +++ b/examples/CallingAPIs.ps1 @@ -26,12 +26,6 @@ Get-GitHubApp # More complex example - output is parts of the web response Invoke-GitHubAPI -ApiEndpoint /app -# Most complex example - output is the entire web response -$context = Get-GitHubContext -$jwt = Get-GitHubAppJSONWebToken -ClientId $context.ClientID -PrivateKey $context.Token -Invoke-RestMethod -Uri "$($context.ApiBaseUri)/app" -Token ($jwt.token) -Authentication Bearer -Invoke-WebRequest -Uri "$($context.ApiBaseUri)/app" -Token ($jwt.token) -Authentication Bearer - #endregion diff --git a/examples/Connecting.ps1 b/examples/Connecting.ps1 index 810b6d05a..005786ce0 100644 --- a/examples/Connecting.ps1 +++ b/examples/Connecting.ps1 @@ -7,22 +7,30 @@ Connect-GitHub # Log on to a specific instance of GitHub (enterprise) Connect-GitHub -Host 'msx.ghe.com' -Get-GitHubRepository -Context 'msx.ghe.com/MariusStorhaug' # Contexts should be selectable/overrideable on any call +Get-GitHubRepository -Context 'msx.ghe.com/MariusStorhaug' # Contexts are selectable/overrideable on any call -# Connect to GitHub interactively using OAuth App and Device Flow (should not use this, should we even support it?) +# Connect to GitHub interactively using OAuth App and Device Flow. Connect-GitHub -Mode 'OAuthApp' -Scope 'gist read:org repo workflow' -# Connect to GitHub interactively using less desired PAT flow +# Connect to GitHub interactively using less desired PAT flow, supports both fine-grained and classic PATs Connect-GitHub -UseAccessToken +# Connect to GitHub programatically (GitHub App Installation Access Token or PAT) +Connect-GitHub -Token *********** + # Connect to GitHub programatically (GitHub Actions) Connect-GitHub # Looks for the GITHUB_TOKEN variable -# Connect to GitHub programatically (GitHub App, for GitHub Actions or external applications, JWT login) +# Connect using a GitHub App and its private key (local signing of JWT) Connect-GitHub -ClientID '' -PrivateKey '' -# Connect to GitHub programatically (GitHub App Installation Access Token or PAT) -Connect-GitHub -Token *********** +# Connect using a GitHub App and the Key vault for signing the JWT. +# Prereq: The private key is stored in an Azure Key Vault and the shell has an authenticated Azure PowerShell or Azure CLI session +$ClientID = 'Iv23lieHcDQDwVV3alK1' +$KeyVaultKeyReference = 'https://psmodule-test-vault.vault.azure.net/keys/psmodule-ent-app' +Connect-GitHub -ClientID $ClientID -KeyVaultKeyReference $KeyVaultKeyReference +Connect-GitHubApp -Organization 'dnb-tooling' + ### ### Contexts / Profiles @@ -37,14 +45,12 @@ Get-GitHubContext -ListAvailable # Returns a specific context, autocomplete the name. Get-GitHubContext -Context 'msx.ghe.com/MariusStorhaug' -# Take a name dynamically from Get-GitHubContext? Autocomplete the name +# Take a name dynamically from Get-GitHubContext? tab-complete the name Switch-GitHubContext -Context 'msx.ghe.com/MariusStorhaug' # Set a specific context as the default context using pipeline 'msx.ghe.com/MariusStorhaug' | Switch-GitHubContext -Get-GitHubContext -Context 'github.com/MariusStorhaug' | Switch-GitHubContext - # Abstraction layers on GitHubContexts Get-GitHubContext -Context 'msx.ghe.com/MariusStorhaug' diff --git a/examples/Organizations/Organization.ps1 b/examples/Organizations/Organization.ps1 new file mode 100644 index 000000000..9804a45f2 --- /dev/null +++ b/examples/Organizations/Organization.ps1 @@ -0,0 +1,41 @@ +$orgParam = @{ + Enterprise = 'msx' + Name = 'Marius-Test7' + Owner = 'GitHub-Automation' + BillingEmail = 'post@msx.no' +} +$org = New-GitHubOrganization @orgParam + +$installAppParam = @{ + Enterprise = 'msx' + Organization = $org.login + ClientID = (Get-GitHubContext).ClientID + RepositorySelection = 'all' +} +Install-GitHubApp @installAppParam + +$updateOrgParam = @{ + Name = $org.login + Description = 'Test organization created by PowerShell script' + Location = 'Oslo, Norway' + BillingEmail = 'post@msx.no' + Company = 'MSX AS' + Email = 'info@msx.no' + Blog = 'https://msx.no/blog' + TwitterUsername = 'msx_no' + HasOrganizationProjects = $true + DefaultRepositoryPermission = 'read' + MembersCanCreateRepositories = $true + MembersCanCreatePublicRepositories = $true + MembersCanCreatePrivateRepositories = $true + MembersCanCreateInternalRepositories = $true + MembersCanCreatePages = $true + MembersCanCreatePublicPages = $true + MembersCanCreatePrivatePages = $true + MembersCanForkPrivateRepositories = $true + WebCommitSignoffRequired = $false + SecretScanningPushProtectionEnabledForNewRepositories = $true + SecretScanningPushProtectionCustomLinkEnabled = $false + SecretScanningPushProtectionCustomLink = '' +} +Update-GitHubOrganization @updateOrgParam diff --git a/examples/Repositories/Set-GitHubRepository.ps1 b/examples/Repositories/Set-GitHubRepository.ps1 index 2d82d9529..4e4d99def 100644 --- a/examples/Repositories/Set-GitHubRepository.ps1 +++ b/examples/Repositories/Set-GitHubRepository.ps1 @@ -1,4 +1,4 @@ -$params = @{ +$params = @{ Owner = 'octocat' Name = 'Hello-World' AllowSquashMergingWith = 'Pull request title and description' diff --git a/src/classes/public/App/GitHubApp.ps1 b/src/classes/public/App/GitHubApp.ps1 index 45dd6fff5..620f52af5 100644 --- a/src/classes/public/App/GitHubApp.ps1 +++ b/src/classes/public/App/GitHubApp.ps1 @@ -1,4 +1,4 @@ -class GitHubApp : GitHubNode { +class GitHubApp : GitHubNode { # The unique ID of the app [System.Nullable[UInt64]] $ID diff --git a/src/classes/public/App/GitHubAppInstallation.ps1 b/src/classes/public/App/GitHubAppInstallation.ps1 index 5335e4830..96fd2831c 100644 --- a/src/classes/public/App/GitHubAppInstallation.ps1 +++ b/src/classes/public/App/GitHubAppInstallation.ps1 @@ -65,7 +65,7 @@ $this.Url = $Object.html_url } - GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [string] $HostName) { + GitHubAppInstallation([PSCustomObject] $Object, [string] $Target, [string] $Type, [GitHubContext] $Context) { $this.ID = $Object.id $this.App = [GitHubApp]::new( [PSCustomObject]@{ @@ -77,7 +77,7 @@ $this.Target = [GitHubOwner]@{ Name = $Target Type = $Type - Url = "https://$HostName/$Target" + Url = "https://$($Context.HostName)/$Target" } $this.Type = $Type $this.RepositorySelection = $Object.repository_selection @@ -88,6 +88,6 @@ $this.UpdatedAt = $Object.updated_at $this.SuspendedAt = $Object.suspended_at $this.SuspendedBy = [GitHubUser]::new($Object.suspended_by) - $this.Url = "https://$HostName/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)" + $this.Url = "https://$($Context.HostName)/$($Type.ToLower())s/$Target/settings/installations/$($Object.id)" } } diff --git a/src/classes/public/Artifacts/GitHubArtifact.ps1 b/src/classes/public/Artifacts/GitHubArtifact.ps1 index 3e654860e..988c09931 100644 --- a/src/classes/public/Artifacts/GitHubArtifact.ps1 +++ b/src/classes/public/Artifacts/GitHubArtifact.ps1 @@ -37,14 +37,14 @@ GitHubArtifact() {} - GitHubArtifact([PSCustomObject]$Object, [string]$Owner, [string]$Repository, [string]$HostName) { + GitHubArtifact([PSCustomObject]$Object, [string]$Owner, [string]$Repository, [GitHubContext]$Context) { $this.ID = $Object.id $this.NodeID = $Object.node_id $this.Name = $Object.name $this.Owner = $Owner $this.Repository = $Repository $this.Size = $Object.size_in_bytes - $this.Url = "https://$($HostName)/$Owner/$Repository/actions/runs/$($Object.workflow_run.id)/artifacts/$($Object.id)" + $this.Url = "https://$($Context.HostName)/$Owner/$Repository/actions/runs/$($Object.workflow_run.id)/artifacts/$($Object.id)" $this.ArchiveDownloadUrl = $Object.archive_download_url $this.Expired = $Object.expired $this.Digest = $Object.digest diff --git a/src/classes/public/Config/GitHubConfig.ps1 b/src/classes/public/Config/GitHubConfig.ps1 index d28d66534..ac8ffd8b8 100644 --- a/src/classes/public/Config/GitHubConfig.ps1 +++ b/src/classes/public/Config/GitHubConfig.ps1 @@ -3,7 +3,7 @@ [string] $ID # The access token grace period in hours. - [System.Nullable[int]] $AccessTokenGracePeriodInHours + [System.Nullable[double]] $AccessTokenGracePeriodInHours # The default context. [string] $DefaultContext @@ -35,9 +35,6 @@ # The default value for retry interval in seconds. [System.Nullable[int]] $RetryInterval - # The tolerance time in seconds for JWT token validation. - [System.Nullable[int]] $JwtTimeTolerance - # The environment type, which is used to determine the context of the GitHub API calls. [string] $EnvironmentType diff --git a/src/classes/public/Context/GitHubContext.ps1 b/src/classes/public/Context/GitHubContext.ps1 index 8145217cd..7d437c6de 100644 --- a/src/classes/public/Context/GitHubContext.ps1 +++ b/src/classes/public/Context/GitHubContext.ps1 @@ -44,9 +44,13 @@ [securestring] $Token # The token type. - # ghu / gho / ghp / github_pat / PEM / ghs / + # ghu / gho / ghp / github_pat / JWT / ghs / [string] $TokenType + # The token expiration date. + # 2024-01-01-00:00:00 + [System.Nullable[datetime]] $TokenExpiresAt + # The default value for the Enterprise parameter. [string] $Enterprise @@ -62,9 +66,30 @@ # The default value for the 'per_page' API parameter used in 'GET' functions that support paging. [int] $PerPage - # Simple parameterless constructor GitHubContext() {} + GitHubContext([pscustomobject]$Object) { + $this.ID = $Object.ID + $this.Name = $Object.Name + $this.DisplayName = $Object.DisplayName + $this.Type = $Object.Type + $this.HostName = $Object.HostName + $this.ApiBaseUri = $Object.ApiBaseUri + $this.ApiVersion = $Object.ApiVersion + $this.AuthType = $Object.AuthType + $this.NodeID = $Object.NodeID + $this.DatabaseID = $Object.DatabaseID + $this.UserName = $Object.UserName + $this.Token = $Object.Token + $this.TokenType = $Object.TokenType + $this.TokenExpiresAt = $Object.TokenExpiresAt + $this.Enterprise = $Object.Enterprise + $this.Owner = $Object.Owner + $this.Repository = $Object.Repository + $this.HttpVersion = $Object.HttpVersion + $this.PerPage = $Object.PerPage + } + [string] ToString() { return $this.Name } diff --git a/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1 deleted file mode 100644 index 68a010afd..000000000 --- a/src/classes/public/Context/GitHubContext/AppGitHubContext.ps1 +++ /dev/null @@ -1,33 +0,0 @@ -class AppGitHubContext : GitHubContext { - # Client ID for GitHub Apps - [string] $ClientID - - # Owner of the GitHub App - [string] $OwnerName - - # Type of the owner of the GitHub App - [string] $OwnerType - - # The permissions that the app is requesting on the target - [pscustomobject] $Permissions - - # The events that the app is subscribing to once installed - [string[]] $Events - - # Simple parameterless constructor - AppGitHubContext() {} - - # Creates a context object from a hashtable of key-vaule pairs. - AppGitHubContext([hashtable]$Properties) { - foreach ($Property in $Properties.Keys) { - $this.$Property = $Properties.$Property - } - } - - # Creates a context object from a PSCustomObject. - AppGitHubContext([PSCustomObject]$Object) { - $Object.PSObject.Properties | ForEach-Object { - $this.($_.Name) = $_.Value - } - } -} diff --git a/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 new file mode 100644 index 000000000..02cc68a67 --- /dev/null +++ b/src/classes/public/Context/GitHubContext/GitHubAppContext.ps1 @@ -0,0 +1,53 @@ +class GitHubAppContext : GitHubContext { + # Client ID for GitHub Apps + [string] $ClientID + + # The private key for the app. + [securestring] $PrivateKey + + # Azure Key Vault key reference for JWT signing (alternative to PrivateKey). + [string] $KeyVaultKeyReference + + # Owner of the GitHub App + [string] $OwnerName + + # Type of the owner of the GitHub App + [string] $OwnerType + + # The permissions that the app is requesting on the target + [pscustomobject] $Permissions + + # The events that the app is subscribing to once installed + [string[]] $Events + + GitHubAppContext() {} + + GitHubAppContext([pscustomobject]$Object) { + $this.ID = $Object.ID + $this.Name = $Object.Name + $this.DisplayName = $Object.DisplayName + $this.Type = $Object.Type + $this.HostName = $Object.HostName + $this.ApiBaseUri = $Object.ApiBaseUri + $this.ApiVersion = $Object.ApiVersion + $this.AuthType = $Object.AuthType + $this.NodeID = $Object.NodeID + $this.DatabaseID = $Object.DatabaseID + $this.UserName = $Object.UserName + $this.Token = $Object.Token + $this.TokenType = $Object.TokenType + $this.TokenExpiresAt = $Object.TokenExpiresAt + $this.Enterprise = $Object.Enterprise + $this.Owner = $Object.Owner + $this.Repository = $Object.Repository + $this.HttpVersion = $Object.HttpVersion + $this.PerPage = $Object.PerPage + $this.ClientID = $Object.ClientID + $this.PrivateKey = $Object.PrivateKey + $this.KeyVaultKeyReference = $Object.KeyVaultKeyReference + $this.OwnerName = $Object.OwnerName + $this.OwnerType = $Object.OwnerType + $this.Permissions = $Object.Permissions + $this.Events = $Object.Events + } +} diff --git a/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 new file mode 100644 index 000000000..2b817f3ec --- /dev/null +++ b/src/classes/public/Context/GitHubContext/GitHubAppInstallationContext.ps1 @@ -0,0 +1,49 @@ +class GitHubAppInstallationContext : GitHubContext { + # Client ID for GitHub Apps + [string] $ClientID + + # The installation ID. + [System.Nullable[uint64]] $InstallationID + + # The permissions that the app is requesting on the target + [pscustomobject] $Permissions + + # The events that the app is subscribing to once installed + [string[]] $Events + + # The target type of the installation. + [string] $InstallationType + + # The target login or slug of the installation. + [string] $InstallationName + + GitHubAppInstallationContext() {} + + GitHubAppInstallationContext([pscustomobject]$Object) { + $this.ID = $Object.ID + $this.Name = $Object.Name + $this.DisplayName = $Object.DisplayName + $this.Type = $Object.Type + $this.HostName = $Object.HostName + $this.ApiBaseUri = $Object.ApiBaseUri + $this.ApiVersion = $Object.ApiVersion + $this.AuthType = $Object.AuthType + $this.NodeID = $Object.NodeID + $this.DatabaseID = $Object.DatabaseID + $this.UserName = $Object.UserName + $this.Token = $Object.Token + $this.TokenType = $Object.TokenType + $this.TokenExpiresAt = $Object.TokenExpiresAt + $this.Enterprise = $Object.Enterprise + $this.Owner = $Object.Owner + $this.Repository = $Object.Repository + $this.HttpVersion = $Object.HttpVersion + $this.PerPage = $Object.PerPage + $this.ClientID = $Object.ClientID + $this.InstallationID = $Object.InstallationID + $this.Permissions = $Object.Permissions + $this.Events = $Object.Events + $this.InstallationType = $Object.InstallationType + $this.InstallationName = $Object.InstallationName + } +} diff --git a/src/classes/public/Context/GitHubContext/GitHubUserContext.ps1 b/src/classes/public/Context/GitHubContext/GitHubUserContext.ps1 new file mode 100644 index 000000000..fa493984e --- /dev/null +++ b/src/classes/public/Context/GitHubContext/GitHubUserContext.ps1 @@ -0,0 +1,49 @@ +class GitHubUserContext : GitHubContext { + # The authentication client ID. + # Client ID for UAT + [string] $AuthClientID + + # The device flow type. + # GitHubApp / OAuthApp + [string] $DeviceFlowType + + # The scope when authenticating with OAuth. + # 'gist read:org repo workflow' + [string] $Scope + + # The refresh token. + [securestring] $RefreshToken + + # The refresh token expiration date. + # 2024-01-01-00:00:00 + [System.Nullable[datetime]] $RefreshTokenExpiresAt + + GitHubUserContext() {} + + GitHubUserContext([PSCustomObject]$Object) { + $this.ID = $Object.ID + $this.Name = $Object.Name + $this.DisplayName = $Object.DisplayName + $this.Type = $Object.Type + $this.HostName = $Object.HostName + $this.ApiBaseUri = $Object.ApiBaseUri + $this.ApiVersion = $Object.ApiVersion + $this.AuthType = $Object.AuthType + $this.NodeID = $Object.NodeID + $this.DatabaseID = $Object.DatabaseID + $this.UserName = $Object.UserName + $this.Token = $Object.Token + $this.TokenType = $Object.TokenType + $this.TokenExpiresAt = $Object.TokenExpiresAt + $this.Enterprise = $Object.Enterprise + $this.Owner = $Object.Owner + $this.Repository = $Object.Repository + $this.HttpVersion = $Object.HttpVersion + $this.PerPage = $Object.PerPage + $this.AuthClientID = $Object.AuthClientID + $this.DeviceFlowType = $Object.DeviceFlowType + $this.Scope = $Object.Scope + $this.RefreshToken = $Object.RefreshToken + $this.RefreshTokenExpiresAt = $Object.RefreshTokenExpiresAt + } +} diff --git a/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1 deleted file mode 100644 index b7dc45a15..000000000 --- a/src/classes/public/Context/GitHubContext/InstallationGitHubContext.ps1 +++ /dev/null @@ -1,40 +0,0 @@ -class InstallationGitHubContext : GitHubContext { - # Client ID for GitHub Apps - [string] $ClientID - - # The token expiration date. - # 2024-01-01-00:00:00 - [datetime] $TokenExpirationDate - - # The installation ID. - [uint64] $InstallationID - - # The permissions that the app is requesting on the target - [pscustomobject] $Permissions - - # The events that the app is subscribing to once installed - [string[]] $Events - - # The target type of the installation. - [string] $InstallationType - - # The target login or slug of the installation. - [string] $InstallationName - - # Simple parameterless constructor - InstallationGitHubContext() {} - - # Creates a context object from a hashtable of key-vaule pairs. - InstallationGitHubContext([hashtable]$Properties) { - foreach ($Property in $Properties.Keys) { - $this.$Property = $Properties.$Property - } - } - - # Creates a context object from a PSCustomObject. - InstallationGitHubContext([PSCustomObject]$Object) { - $Object.PSObject.Properties | ForEach-Object { - $this.($_.Name) = $_.Value - } - } -} diff --git a/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1 b/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1 deleted file mode 100644 index 345aaf867..000000000 --- a/src/classes/public/Context/GitHubContext/UserGitHubContext.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -class UserGitHubContext : GitHubContext { - # The authentication client ID. - # Client ID for UAT - [string] $AuthClientID - - # The device flow type. - # GitHubApp / OAuthApp - [string] $DeviceFlowType - - # The scope when authenticating with OAuth. - # 'gist read:org repo workflow' - [string] $Scope - - # The token expiration date. - # 2024-01-01-00:00:00 - [datetime] $TokenExpirationDate - - # The refresh token. - [securestring] $RefreshToken - - # The refresh token expiration date. - # 2024-01-01-00:00:00 - [datetime] $RefreshTokenExpirationDate - - # Simple parameterless constructor - UserGitHubContext() {} - - # Creates a context object from a hashtable of key-vaule pairs. - UserGitHubContext([hashtable]$Properties) { - foreach ($Property in $Properties.Keys) { - $this.$Property = $Properties.$Property - } - } - - # Creates a context object from a PSCustomObject. - UserGitHubContext([PSCustomObject]$Object) { - $Object.PSObject.Properties | ForEach-Object { - $this.($_.Name) = $_.Value - } - } -} diff --git a/src/classes/public/Environment/GitHubEnvironment.ps1 b/src/classes/public/Environment/GitHubEnvironment.ps1 index 4da7639c7..799addd97 100644 --- a/src/classes/public/Environment/GitHubEnvironment.ps1 +++ b/src/classes/public/Environment/GitHubEnvironment.ps1 @@ -28,12 +28,13 @@ GitHubEnvironment() {} - GitHubEnvironment([PSCustomObject]$Object, [string]$Owner, [string]$Repository) { + GitHubEnvironment([PSCustomObject]$Object, [string]$Owner, [string]$Repository, [GitHubContext]$Context) { $this.ID = $Object.id $this.NodeID = $Object.node_id $this.Name = $Object.name $this.Owner = $Owner $this.Repository = $Repository + $this.Url = "https://$($Context.HostName)/$Owner/$Repository/settings/environments/$($Object.id)/edit" $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at $this.AdminsCanBypass = $Object.can_admins_bypass diff --git a/src/classes/public/GitHubBillingInfo.ps1 b/src/classes/public/GitHubBillingInfo.ps1 new file mode 100644 index 000000000..64195a37a --- /dev/null +++ b/src/classes/public/GitHubBillingInfo.ps1 @@ -0,0 +1,27 @@ +class GitHubBillingInfo { + [int]$AllLicensableUsersCount + [int]$AssetPacks + [int]$BandwidthQuota + [int]$BandwidthUsage + [int]$BandwidthUsagePercentage + [int]$StorageQuota + [int]$StorageUsage + [int]$StorageUsagePercentage + [int]$TotalAvailableLicenses + [int]$TotalLicenses + + GitHubBillingInfo() {} + + GitHubBillingInfo([PSCustomObject] $Object) { + $this.AllLicensableUsersCount = $Object.allLicensableUsersCount + $this.AssetPacks = $Object.assetPacks + $this.BandwidthQuota = $Object.bandwidthQuota + $this.BandwidthUsage = $Object.bandwidthUsage + $this.BandwidthUsagePercentage = $Object.bandwidthUsagePercentage + $this.StorageQuota = $Object.storageQuota + $this.StorageUsage = $Object.storageUsage + $this.StorageUsagePercentage = $Object.storageUsagePercentage + $this.TotalAvailableLicenses = $Object.totalAvailableLicenses + $this.TotalLicenses = $Object.totalLicenses + } +} diff --git a/src/classes/public/GitHubFormatter.ps1 b/src/classes/public/GitHubFormatter.ps1 new file mode 100644 index 000000000..ce5f49087 --- /dev/null +++ b/src/classes/public/GitHubFormatter.ps1 @@ -0,0 +1,32 @@ +class GitHubFormatter { + static [string] FormatColorByRatio([double]$Ratio, [string]$Text) { + $Ratio = [Math]::Min([Math]::Max($Ratio, 0), 1) + + if ($Ratio -ge 1) { + $r = 0 + $g = 255 + } elseif ($Ratio -le 0) { + $r = 255 + $g = 0 + } elseif ($Ratio -ge 0.5) { + $r = [Math]::Round(255 * (2 - 2 * $Ratio)) + $g = 255 + } else { + $r = 255 + $g = [Math]::Round(255 * (2 * $Ratio)) + } + $color = "`e[38;2;$r;$g;0m" + $reset = "`e[0m" + return "$color$Text$reset" + } + + static [string] FormatFileSize([long]$size) { + switch ($size) { + { $_ -ge 1TB } { return '{0:N2} TB' -f ($size / 1TB) } + { $_ -ge 1GB } { return '{0:N2} GB' -f ($size / 1GB) } + { $_ -ge 1MB } { return '{0:N2} MB' -f ($size / 1MB) } + { $_ -ge 1KB } { return '{0:N2} KB' -f ($size / 1KB) } + } + return "$size B" + } +} diff --git a/src/classes/public/GitHubJWTComponent.ps1 b/src/classes/public/GitHubJWTComponent.ps1 new file mode 100644 index 000000000..2f24a1e55 --- /dev/null +++ b/src/classes/public/GitHubJWTComponent.ps1 @@ -0,0 +1,15 @@ +class GitHubJWTComponent { + static [string] ToBase64UrlString([hashtable] $Data) { + return [GitHubJWTComponent]::ConvertToBase64UrlFormat( + [System.Convert]::ToBase64String( + [System.Text.Encoding]::UTF8.GetBytes( + (ConvertTo-Json -InputObject $Data) + ) + ) + ) + } + + static [string] ConvertToBase64UrlFormat([string] $Base64String) { + return $Base64String.TrimEnd('=').Replace('+', '-').Replace('/', '_') + } +} diff --git a/src/classes/public/GitHubJsonWebToken.ps1 b/src/classes/public/GitHubJsonWebToken.ps1 deleted file mode 100644 index b98b9d99c..000000000 --- a/src/classes/public/GitHubJsonWebToken.ps1 +++ /dev/null @@ -1,15 +0,0 @@ -class GitHubJsonWebToken { - # The secure JWT string used for GitHub API authentication - [securestring] $Token - - # The timestamp when this token was issued (in UTC) - [DateTime] $IssuedAt - - # The timestamp when this token will expire (in UTC) - [DateTime] $ExpiresAt - - # The ClientID for the GitHub App - [string] $Issuer - - GitHubJsonWebToken() {} -} diff --git a/src/classes/public/Owner/GitHubOwner.ps1 b/src/classes/public/Owner/GitHubOwner.ps1 index cc932ce1a..81efa24b0 100644 --- a/src/classes/public/Owner/GitHubOwner.ps1 +++ b/src/classes/public/Owner/GitHubOwner.ps1 @@ -19,41 +19,17 @@ # Example: User [string] $Type - # The company the account is affiliated with. - # Example: GitHub - [string] $Company - - # The blog URL of the account. - # Example: https://github.com/blog - [string] $Blog - # The location of the account. # Example: San Francisco [string] $Location - # The email of the account. - # Example: octocat@github.com - [string] $Email - - # The Twitter username. - # Example: monalisa - [string] $TwitterUsername - - # The number of public repositories. - # Example: 2 - [System.Nullable[uint]] $PublicRepos + # The description of the organization. + # Example: A great organization + [string] $Description - # The number of public gists. - # Example: 1 - [System.Nullable[uint]] $PublicGists - - # The number of followers. - # Example: 20 - [System.Nullable[uint]] $Followers - - # The number of accounts this account is following. - # Example: 0 - [System.Nullable[uint]] $Following + # The website URL of the account. + # Example: https://github.com/blog + [string] $Website # The creation date of the account. # Example: 2008-01-14T04:33:35Z @@ -63,10 +39,6 @@ # Example: 2008-01-14T04:33:35Z [System.Nullable[datetime]] $UpdatedAt - # The user's plan. - # Includes: Name, Collaborators, PrivateRepos, Space - [GitHubPlan] $Plan - GitHubOwner() {} GitHubOwner([PSCustomObject]$Object) { @@ -78,20 +50,13 @@ $this.Name = $Object.slug ?? $Object.login $this.DisplayName = $Object.name $this.AvatarUrl = $Object.avatar_url - $this.Url = $Object.html_url + $this.Url = $Object.html_url ?? $Object.url $this.Type = $Object.type - $this.Company = $Object.company - $this.Blog = $Object.website_url ?? $Object.blog $this.Location = $Object.location - $this.Email = $Object.email - $this.TwitterUsername = $Object.twitter_username - $this.PublicRepos = $Object.public_repos - $this.PublicGists = $Object.public_gists - $this.Followers = $Object.followers - $this.Following = $Object.following + $this.Description = $Object.description ?? $Object.bio + $this.Website = $Object.websiteUrl ?? $Object.blog $this.CreatedAt = $Object.created_at $this.UpdatedAt = $Object.updated_at - $this.Plan = [GitHubPlan]::New($Object.plan) } [string] ToString() { diff --git a/src/classes/public/Owner/GitHubOwner/GitHubEnterprise.ps1 b/src/classes/public/Owner/GitHubOwner/GitHubEnterprise.ps1 new file mode 100644 index 000000000..66fea40c4 --- /dev/null +++ b/src/classes/public/Owner/GitHubOwner/GitHubEnterprise.ps1 @@ -0,0 +1,53 @@ +class GitHubEnterprise : GitHubOwner { + # The readme of the enterprise. + # Example: This is the readme for the enterprise + [string] $Readme + + # The readme of the enterprise, as HTML. + # Example:

This is the readme for the enterprise

+ [string] $ReadmeHTML + + static [hashtable] $PropertyToGraphQLMap = @{ + ID = 'databaseId' + NodeID = 'id' + Name = 'slug' + DisplayName = 'name' + AvatarUrl = 'avatarUrl' + Url = 'url' + Website = 'websiteUrl' + Location = 'location' + CreatedAt = 'createdAt' + UpdatedAt = 'updatedAt' + Description = 'description' + Readme = 'readme' + ReadmeHTML = 'readmeHTML' + } + + GitHubEnterprise() {} + + GitHubEnterprise([PSCustomObject] $Object) { + # From GitHubNode + $this.ID = $Object.databaseId + $this.NodeID = $Object.id + + # From GitHubOwner + $this.Name = $Object.slug + $this.DisplayName = $Object.name + $this.AvatarUrl = $Object.avatarUrl + $this.Url = $Object.url + $this.Type = $Object.type ?? 'Enterprise' + $this.Location = $Object.location + $this.Description = $Object.description + $this.Website = $Object.websiteUrl + $this.CreatedAt = $Object.createdAt + $this.UpdatedAt = $Object.updatedAt + + # From GitHubEnterprise + $this.Readme = $Object.readme + $this.ReadmeHTML = $Object.readmeHTML + } + + [string] ToString() { + return $this.Name + } +} diff --git a/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 b/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 index bbc1bae4f..a99927110 100644 --- a/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 +++ b/src/classes/public/Owner/GitHubOwner/GitHubOrganization.ps1 @@ -1,7 +1,31 @@ class GitHubOrganization : GitHubOwner { - # The description of the organization. - # Example: A great organization - [string] $Description + # The email of the account. + # Example: octocat@github.com + [string] $Email + + # The Twitter username. + # Example: monalisa + [string] $TwitterUsername + + # The user's plan. + # Includes: Name, Collaborators, PrivateRepos, Space + [GitHubPlan] $Plan + + # The number of public repositories. + # Example: 2 + [System.Nullable[uint]] $PublicRepos + + # The number of public gists. + # Example: 1 + [System.Nullable[uint]] $PublicGists + + # The number of followers. + # Example: 20 + [System.Nullable[uint]] $Followers + + # The number of accounts this account is following. + # Example: 0 + [System.Nullable[uint]] $Following # The number of private gists. # Example: 81 @@ -49,7 +73,7 @@ # Whether two-factor authentication is required for members. # Example: $true - [System.Nullable[bool]] $TwoFactorRequirementEnabled + [System.Nullable[bool]] $RequiresTwoFactorAuthentication # The type of repositories members can create. # Example: all @@ -134,54 +158,74 @@ # The date and time when the organization was archived, if applicable. [System.Nullable[datetime]] $ArchivedAt + static [hashtable] $PropertyToGraphQLMap = @{ + ArchivedAt = 'archivedAt' + AvatarUrl = 'avatarUrl' + CreatedAt = 'createdAt' + Description = 'description' + DisplayName = 'name' + Email = 'email' + ID = 'databaseId' + Location = 'location' + Name = 'login' + NodeID = 'id' + IsVerified = 'isVerified' + # MembersCanForkPrivateRepositories = 'membersCanForkPrivateRepositories' + # RequiresTwoFactorAuthentication = 'requiresTwoFactorAuthentication' + TwitterUsername = 'twitterUsername' + UpdatedAt = 'updatedAt' + Url = 'url' + # RequireWebCommitSignoff = 'webCommitSignoffRequired' + Website = 'websiteUrl' + } + GitHubOrganization() {} - GitHubOrganization([PSCustomObject]$Object) { + GitHubOrganization([PSCustomObject] $Object, [GitHubContext] $Context) { # From GitHubNode - $this.ID = $Object.id - $this.NodeID = $Object.node_id + $this.ID = $Object.databaseId ?? $Object.id + $this.NodeID = $Object.node_id ?? $Object.id # From GitHubOwner $this.Name = $Object.login $this.DisplayName = $Object.name - $this.AvatarUrl = $Object.avatar_url - $this.Url = $Object.html_url - $this.Type = $Object.type - $this.Company = $Object.company - $this.Blog = $Object.blog + $this.AvatarUrl = $Object.avatar_url ?? $Object.avatarUrl + $this.Url = $Object.html_url ?? $Object.url ?? "https://$($Context.HostName)/$($Object.login)" + $this.Type = $Object.type ?? 'Organization' $this.Location = $Object.location + $this.Description = $Object.description + $this.Website = $Object.website ?? $Object.blog + $this.CreatedAt = $Object.created_at ?? $Object.createdAt + $this.UpdatedAt = $Object.updated_at ?? $Object.updatedAt + + # From GitHubOrganization $this.Email = $Object.email - $this.TwitterUsername = $Object.twitter_username + $this.TwitterUsername = $Object.twitter_username ?? $Object.twitterUsername + $this.Plan = [GitHubPlan]::New($Object.plan) $this.PublicRepos = $Object.public_repos $this.PublicGists = $Object.public_gists $this.Followers = $Object.followers $this.Following = $Object.following - $this.CreatedAt = $Object.created_at - $this.UpdatedAt = $Object.updated_at - $this.Plan = [GitHubPlan]::New($Object.plan) - - # From GitHubOrganization - $this.Description = $Object.description $this.PrivateGists = $Object.total_private_gists $this.TotalPrivateRepos = $Object.total_private_repos $this.OwnedPrivateRepos = $Object.owned_private_repos $this.DiskUsage = $Object.disk_usage $this.Collaborators = $Object.collaborators - $this.IsVerified = $Object.is_verified + $this.IsVerified = $Object.is_verified ?? $Object.isVerified $this.HasOrganizationProjects = $Object.has_organization_projects $this.HasRepositoryProjects = $Object.has_repository_projects $this.BillingEmail = $Object.billing_email $this.DefaultRepositoryPermission = $Object.default_repository_permission $this.MembersCanCreateRepositories = $Object.members_can_create_repositories - $this.TwoFactorRequirementEnabled = $Object.two_factor_requirement_enabled + $this.RequiresTwoFactorAuthentication = $Object.two_factor_requirement_enabled ?? $Object.requiresTwoFactorAuthentication $this.MembersAllowedRepositoryCreationType = $Object.members_allowed_repository_creation_type $this.MembersCanCreatePublicRepositories = $Object.members_can_create_public_repositories $this.MembersCanCreatePrivateRepositories = $Object.members_can_create_private_repositories $this.MembersCanCreateInternalRepositories = $Object.members_can_create_internal_repositories $this.MembersCanInviteCollaborators = $Object.members_can_invite_collaborators $this.MembersCanCreatePages = $Object.members_can_create_pages - $this.MembersCanForkPrivateRepositories = $Object.members_can_fork_private_repositories - $this.RequireWebCommitSignoff = $Object.web_commit_signoff_required + $this.MembersCanForkPrivateRepositories = $Object.members_can_fork_private_repositories ?? $Object.membersCanForkPrivateRepositories + $this.RequireWebCommitSignoff = $Object.web_commit_signoff_required ?? $Object.webCommitSignoffRequired $this.DeployKeysEnabledForRepositories = $Object.deploy_keys_enabled_for_repositories $this.MembersCanCreatePublicPages = $Object.members_can_create_public_pages $this.MembersCanCreatePrivatePages = $Object.members_can_create_private_pages @@ -194,7 +238,7 @@ $this.SecretScanningPushProtectionCustomLinkEnabled = $Object.secret_scanning_push_protection_custom_link_enabled $this.SecretScanningPushProtectionCustomLink = $Object.secret_scanning_push_protection_custom_link $this.SecretScanningValidityChecksEnabled = $Object.secret_scanning_validity_checks_enabled - $this.ArchivedAt = $Object.archived_at + $this.ArchivedAt = $Object.archived_at ?? $Object.archivedAt } [string] ToString() { diff --git a/src/classes/public/Owner/GitHubOwner/GitHubUser.ps1 b/src/classes/public/Owner/GitHubOwner/GitHubUser.ps1 index b6b1d4664..a9e4e4abe 100644 --- a/src/classes/public/Owner/GitHubOwner/GitHubUser.ps1 +++ b/src/classes/public/Owner/GitHubOwner/GitHubUser.ps1 @@ -1,15 +1,43 @@ class GitHubUser : GitHubOwner { + # The email of the account. + # Example: octocat@github.com + [string] $Email + # Whether the user is hireable. [System.Nullable[bool]] $Hireable - # The user's biography. - # Example: There once was... - [string] $Bio + # The company the account is affiliated with. + # Example: GitHub + [string] $Company + + # The Twitter username. + # Example: monalisa + [string] $TwitterUsername + + # The number of public repositories. + # Example: 2 + [System.Nullable[uint]] $PublicRepos + + # The number of public gists. + # Example: 1 + [System.Nullable[uint]] $PublicGists + + # The number of followers. + # Example: 20 + [System.Nullable[uint]] $Followers + + # The number of accounts this account is following. + # Example: 0 + [System.Nullable[uint]] $Following # The notification email address of the user. # Example: octocat@github.com [string] $NotificationEmail + # The user's plan. + # Includes: Name, Collaborators, PrivateRepos, Space + [GitHubPlan] $Plan + GitHubUser() {} GitHubUser([PSCustomObject]$Object) { @@ -23,23 +51,23 @@ $this.AvatarUrl = $Object.avatar_url $this.Url = $Object.html_url $this.Type = $Object.type - $this.Company = $Object.company - $this.Blog = $Object.blog $this.Location = $Object.location + $this.Description = $Object.bio + $this.Website = $Object.blog + $this.CreatedAt = $Object.created_at + $this.UpdatedAt = $Object.updated_at + + # From GitHubUser $this.Email = $Object.email + $this.Hireable = $Object.hireable + $this.Company = $Object.company $this.TwitterUsername = $Object.twitter_username $this.PublicRepos = $Object.public_repos $this.PublicGists = $Object.public_gists $this.Followers = $Object.followers $this.Following = $Object.following - $this.CreatedAt = $Object.created_at - $this.UpdatedAt = $Object.updated_at - $this.Plan = [GitHubPlan]::New($Object.plan) - - # From GitHubUser - $this.Hireable = $Object.hireable - $this.Bio = $Object.bio $this.NotificationEmail = $Object.notification_email + $this.Plan = [GitHubPlan]::New($Object.plan) } [string] ToString() { diff --git a/src/classes/public/RateLimit/GithubRateLimitResource.ps1 b/src/classes/public/RateLimit/GithubRateLimitResource.ps1 index 00ae7a91f..b53bbdceb 100644 --- a/src/classes/public/RateLimit/GithubRateLimitResource.ps1 +++ b/src/classes/public/RateLimit/GithubRateLimitResource.ps1 @@ -12,22 +12,17 @@ [UInt64] $Remaining # The time when the rate limit will reset. - [DateTime] $Reset + [DateTime] $ResetsAt # Simple parameterless constructor GitHubRateLimitResource() {} - # Constructor that initializes the class from a hashtable - GitHubRateLimitResource([hashtable]$Properties) { - foreach ($Property in $Properties.Keys) { - $this.$Property = $Properties.$Property - } - } - # Constructor that initializes the class from a PSCustomObject - GitHubRateLimitResource([PSCustomObject]$Object) { - $Object.PSObject.Properties | ForEach-Object { - $this.($_.Name) = $_.Value - } + GitHubRateLimitResource([pscustomobject]$Object) { + $this.Name = $Object.name + $this.Limit = $Object.limit + $this.Used = $Object.used + $this.Remaining = $Object.remaining + $this.ResetsAt = [DateTime]::UnixEpoch.AddSeconds($Object.reset).ToLocalTime() } } diff --git a/src/classes/public/Releases/GitHubReleaseAsset.ps1 b/src/classes/public/Releases/GitHubReleaseAsset.ps1 index c0ae6d74b..fac713cfe 100644 --- a/src/classes/public/Releases/GitHubReleaseAsset.ps1 +++ b/src/classes/public/Releases/GitHubReleaseAsset.ps1 @@ -52,8 +52,8 @@ $this.ContentType = $Object.content_type $this.Size = $Object.size $this.Downloads = $Object.download_count - $this.CreatedAt = [datetime]::Parse($Object.created_at) - $this.UpdatedAt = [datetime]::Parse($Object.updated_at) + $this.CreatedAt = $Object.created_at + $this.UpdatedAt = $Object.updated_at $this.UploadedBy = [GitHubUser]::new($Object.uploader) } else { $this.NodeID = $Object.id @@ -62,8 +62,8 @@ $this.ContentType = $Object.contentType $this.Size = $Object.size $this.Downloads = $Object.downloadCount - $this.CreatedAt = [datetime]::Parse($Object.createdAt) - $this.UpdatedAt = [datetime]::Parse($Object.updatedAt) + $this.CreatedAt = $Object.createdAt + $this.UpdatedAt = $Object.updatedAt $this.UploadedBy = [GitHubUser]::new($Object.uploadedBy) } } diff --git a/src/classes/public/Workflows/GitHubWorkflowRun.ps1 b/src/classes/public/Workflows/GitHubWorkflowRun.ps1 index 6a6e60e18..223434062 100644 --- a/src/classes/public/Workflows/GitHubWorkflowRun.ps1 +++ b/src/classes/public/Workflows/GitHubWorkflowRun.ps1 @@ -85,7 +85,7 @@ # The start time of the latest run. Resets on re-run. # Example: "2023-01-01T12:01:00Z" - [System.Nullable[datetime]] $RunStartedAt + [System.Nullable[datetime]] $StartedAt # The head commit details. # Example: (nullable-simple-commit object) @@ -126,7 +126,7 @@ $this.PullRequests = $_.pull_requests $this.CreatedAt = $_.created_at $this.UpdatedAt = $_.updated_at - $this.RunStartedAt = $_.run_started_at + $this.StartedAt = $_.run_started_at $this.Actor = [GitHubUser]::new($_.actor) $this.TriggeringActor = [GitHubUser]::new($_.triggering_actor) $this.HeadCommit = $_.head_commit diff --git a/src/formats/GitHubConfig.Format.ps1xml b/src/formats/GitHubConfig.Format.ps1xml index 51fb2c86b..cd556ceb4 100644 --- a/src/formats/GitHubConfig.Format.ps1xml +++ b/src/formats/GitHubConfig.Format.ps1xml @@ -31,9 +31,6 @@ AccessTokenGracePeriodInHours - - JwtTimeTolerance - ApiVersion diff --git a/src/formats/GitHubContext.Format.ps1xml b/src/formats/GitHubContext.Format.ps1xml index 3e3337abe..45756fbb5 100644 --- a/src/formats/GitHubContext.Format.ps1xml +++ b/src/formats/GitHubContext.Format.ps1xml @@ -4,9 +4,9 @@ GitHubContextTableView - AppGitHubContext - InstallationGitHubContext - UserGitHubContext + GitHubAppContext + GitHubAppInstallationContext + GitHubUserContext GitHubContext @@ -24,7 +24,10 @@ - + + + + @@ -47,7 +50,39 @@ TokenType - TokenExpirationDate + TokenExpiresAt + + + + if ($null -eq $_.TokenExpiresIn) { + return + } + + if ($_.TokenExpiresIn -le 0) { + $text = 'Expired' + } else { + $text = $_.TokenExpiresIn.ToString('hh\:mm\:ss') + } + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true')) { + switch ($_.AuthType) { + 'UAT' { + $maxValue = [TimeSpan]::FromHours(8) + } + 'IAT' { + $maxValue = [TimeSpan]::FromHours(1) + } + 'APP' { + $maxValue = [TimeSpan]::FromMinutes(10) + } + } + $ratio = [Math]::Min(($_.TokenExpiresIn / $maxValue), 1) + [GitHubFormatter]::FormatColorByRatio($ratio, $text) + } else { + $text + } + @@ -120,9 +155,9 @@ - AppGitHubContextListView + GitHubAppContextListView - AppGitHubContext + GitHubAppContext @@ -140,6 +175,12 @@ TokenType + + TokenExpiresAt + + + TokenExpiresIn + HostName @@ -185,9 +226,9 @@ - InstallationGitHubContextListView + GitHubAppInstallationContextListView - InstallationGitHubContext + GitHubAppInstallationContext @@ -205,6 +246,12 @@ TokenType + + TokenExpiresAt + + + TokenExpiresIn + HostName @@ -217,9 +264,6 @@ InstallationID - - TokenExpirationDate - InstallationType @@ -256,9 +300,9 @@ - UserGitHubContextListView + GitHubUserContextListView - UserGitHubContext + GitHubUserContext @@ -276,6 +320,12 @@ TokenType + + TokenExpiresAt + + + TokenExpiresIn + HostName @@ -292,10 +342,10 @@ Scope - TokenExpirationDate + RefreshTokenExpiresAt - RefreshTokenExpirationDate + RefreshTokenExpiresIn ApiBaseUri diff --git a/src/formats/GitHubOwner.Format.ps1xml b/src/formats/GitHubOwner.Format.ps1xml index 34d050cd5..c1d8dcb3f 100644 --- a/src/formats/GitHubOwner.Format.ps1xml +++ b/src/formats/GitHubOwner.Format.ps1xml @@ -7,9 +7,13 @@ GitHubOwner GitHubUser GitHubOrganization + GitHubEnterprise + + + @@ -20,15 +24,15 @@ - - - - + + + DisplayName + if ($Host.UI.SupportsVirtualTerminal -and @@ -46,10 +50,7 @@ Type - Company - - - Plan + CreatedAt @@ -65,6 +66,9 @@ + + DisplayName + Name @@ -74,9 +78,6 @@ NodeID - - DisplayName - AvatarUrl @@ -86,9 +87,6 @@ Type - - Company - Location @@ -96,10 +94,10 @@ Email - TwitterUsername + Description - Blog + Website CreatedAt @@ -107,9 +105,6 @@ UpdatedAt - - Plan - diff --git a/src/formats/GitHubRateLimitResource.Format.ps1xml b/src/formats/GitHubRateLimitResource.Format.ps1xml index 0e4c7c898..51b413d3f 100644 --- a/src/formats/GitHubRateLimitResource.Format.ps1xml +++ b/src/formats/GitHubRateLimitResource.Format.ps1xml @@ -21,7 +21,10 @@ - + + + + @@ -40,7 +43,36 @@ Remaining - Reset + ResetsAt + + + + if ($null -eq $_.ResetsIn) { + return + } + + if ($_.ResetsIn -lt 0) { + $text = 'Expired' + } else { + $text = $_.ResetsIn.ToString('hh\:mm\:ss') + } + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true')) { + if ($_.Name -in @('source_import', + 'dependency_snapshots', 'code_scanning_autofix', 'search', + 'dependency_sbom', 'code_search')) { + $maxValue = [TimeSpan]::FromMinutes(1) + } else { + $maxValue = [TimeSpan]::FromHours(1) + } + + $ratio = [Math]::Min(($_.ResetsIn / $maxValue), 1) + [GitHubFormatter]::FormatColorByRatio($ratio, $text) + } else { + $text + } + diff --git a/src/formats/GitHubReleaseAsset.Format.ps1xml b/src/formats/GitHubReleaseAsset.Format.ps1xml new file mode 100644 index 000000000..d1e9978d5 --- /dev/null +++ b/src/formats/GitHubReleaseAsset.Format.ps1xml @@ -0,0 +1,68 @@ + + + + + GitHubReleaseAssetTable + + GitHubReleaseAsset + + + + + + + + + + + + + + + + + + + + + + + + + + + + if ($Host.UI.SupportsVirtualTerminal -and + ($env:GITHUB_ACTIONS -ne 'true')) { + $PSStyle.FormatHyperlink($_.Name,$_.Url) + } else { + $_.Name + } + + + + State + + + + [GitHubFormatter]::FormatFileSize($_.Size) + + Right + + + Downloads + Right + + + CreatedAt + + + UpdatedAt + + + + + + + + diff --git a/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByRepo.ps1 b/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByRepo.ps1 index f6f8dd9b1..9f756123c 100644 --- a/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByRepo.ps1 +++ b/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByRepo.ps1 @@ -1,4 +1,4 @@ -filter Get-GitHubWorkflowRunByRepo { +filter Get-GitHubWorkflowRunByRepo { <# .SYNOPSIS List workflow runs for a repository. @@ -108,7 +108,7 @@ filter Get-GitHubWorkflowRunByRepo { } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/runs" Body = $body @@ -116,7 +116,7 @@ filter Get-GitHubWorkflowRunByRepo { Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.workflow_runs | ForEach-Object { [GitHubWorkflowRun]::new($_) } diff --git a/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByWorkflow.ps1 b/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByWorkflow.ps1 index 9b362463c..918fd069d 100644 --- a/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByWorkflow.ps1 +++ b/src/functions/private/Actions/Workflow Run/Get-GitHubWorkflowRunByWorkflow.ps1 @@ -113,7 +113,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Context = $Context APIEndpoint = "/repos/$Owner/$Repository/actions/workflows/$ID/runs" Method = 'GET' @@ -121,7 +121,7 @@ Body = $body } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.workflow_runs | ForEach-Object { [GitHubWorkflowRun]::new($_) } diff --git a/src/functions/private/Apps/GitHub Apps/Add-GitHubKeyVaultJWTSignature.ps1 b/src/functions/private/Apps/GitHub Apps/Add-GitHubKeyVaultJWTSignature.ps1 new file mode 100644 index 000000000..1665a309c --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/Add-GitHubKeyVaultJWTSignature.ps1 @@ -0,0 +1,101 @@ +function Add-GitHubKeyVaultJWTSignature { + <# + .SYNOPSIS + Adds a JWT signature using Azure Key Vault. + + .DESCRIPTION + Signs an unsigned JWT (header.payload) using a key stored in Azure Key Vault. + The function supports authentication via Azure CLI or Az PowerShell module and returns the signed JWT as a secure string. + + .EXAMPLE + Add-GitHubKeyVaultJWTSignature -UnsignedJWT 'header.payload' -KeyVaultKeyReference 'https://myvault.vault.azure.net/keys/mykey' + + Output: + ```powershell + System.Security.SecureString + ``` + + Signs the provided JWT (`header.payload`) using the specified Azure Key Vault key, returning a secure string containing the signed JWT. + + .OUTPUTS + System.Security.SecureString + + .NOTES + The function returns a secure string containing the fully signed JWT (header.payload.signature). + Ensure Azure CLI or Az PowerShell is installed and authenticated before running this function. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Used to handle secure string private keys.' + )] + [CmdletBinding()] + param ( + # The unsigned JWT (header.payload) to sign. + [Parameter(Mandatory)] + [string] $UnsignedJWT, + + # The Azure Key Vault key URL used for signing. + [Parameter(Mandatory)] + [string] $KeyVaultKeyReference + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + if (Test-GitHubAzureCLI) { + try { + $accessToken = (az account get-access-token --resource 'https://vault.azure.net/' --output json | ConvertFrom-Json).accessToken + } catch { + Write-Error "Failed to get access token from Azure CLI: $_" + return + } + } elseif (Test-GitHubAzPowerShell) { + try { + $accessToken = (Get-AzAccessToken -ResourceUrl 'https://vault.azure.net/').Token + } catch { + Write-Error "Failed to get access token from Az PowerShell: $_" + return + } + } else { + Write-Error 'Azure authentication is required. Please ensure you are logged in using either Azure CLI or Az PowerShell.' + return + } + + if ($accessToken -isnot [securestring]) { + $accessToken = ConvertTo-SecureString -String $accessToken -AsPlainText + } + + $hash64url = [GitHubJWTComponent]::ConvertToBase64UrlFormat( + [System.Convert]::ToBase64String( + [System.Security.Cryptography.SHA256]::Create().ComputeHash( + [System.Text.Encoding]::UTF8.GetBytes($UnsignedJWT) + ) + ) + ) + + $KeyVaultKeyReference = $KeyVaultKeyReference.TrimEnd('/') + + $params = @{ + Method = 'POST' + URI = "$KeyVaultKeyReference/sign?api-version=7.4" + Body = @{ + alg = 'RS256' + value = $hash64url + } | ConvertTo-Json + ContentType = 'application/json' + Authentication = 'Bearer' + Token = $accessToken + } + + $result = Invoke-RestMethod @params + $signature = $result.value + return (ConvertTo-SecureString -String "$UnsignedJWT.$signature" -AsPlainText) + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Apps/GitHub Apps/Add-GitHubLocalJWTSignature.ps1 b/src/functions/private/Apps/GitHub Apps/Add-GitHubLocalJWTSignature.ps1 new file mode 100644 index 000000000..1c14e89f3 --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/Add-GitHubLocalJWTSignature.ps1 @@ -0,0 +1,74 @@ +function Add-GitHubLocalJWTSignature { + <# + .SYNOPSIS + Signs a JSON Web Token (JWT) using a local RSA private key. + + .DESCRIPTION + Takes an unsigned JWT (header.payload) and adds a signature using the provided RSA private key. + This function handles the RSA signing process and returns the complete signed JWT. + + .EXAMPLE + Add-GitHubLocalJWTSignature -UnsignedJWT 'eyJ0eXAiOi...' -PrivateKey '--- BEGIN RSA PRIVATE KEY --- ... --- END RSA PRIVATE KEY ---' + + Adds a signature to the unsigned JWT using the provided private key. + + .OUTPUTS + securestring + + .NOTES + This function isolates the signing logic to enable support for multiple signing methods. + + .LINK + https://psmodule.io/GitHub/Functions/Apps/GitHub%20App/Add-GitHubLocalJWTSignature + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Used to handle secure string private keys.' + )] + [CmdletBinding()] + [OutputType([securestring])] + param( + # The unsigned JWT (header.payload) to sign. + [Parameter(Mandatory)] + [string] $UnsignedJWT, + + # The private key of the GitHub App. + [Parameter(Mandatory)] + [object] $PrivateKey + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + if ($PrivateKey -is [securestring]) { + $PrivateKey = $PrivateKey | ConvertFrom-SecureString -AsPlainText + } + + $rsa = [System.Security.Cryptography.RSA]::Create() + $rsa.ImportFromPem($PrivateKey) + + try { + $signature = [GitHubJWTComponent]::ConvertToBase64UrlFormat( + [System.Convert]::ToBase64String( + $rsa.SignData( + [System.Text.Encoding]::UTF8.GetBytes($UnsignedJWT), + [System.Security.Cryptography.HashAlgorithmName]::SHA256, + [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 + ) + ) + ) + return (ConvertTo-SecureString -String "$UnsignedJWT.$signature" -AsPlainText) + } finally { + if ($rsa) { + $rsa.Dispose() + } + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppBySlug.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppBySlug.ps1 index 7709015e3..ce921c706 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppBySlug.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppBySlug.ps1 @@ -35,13 +35,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/apps/$Slug" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubApp]::new($_.Response) } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallableOrganization.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallableOrganization.ps1 index 568e120f4..06e229ebd 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallableOrganization.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallableOrganization.ps1 @@ -43,16 +43,16 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/enterprises/$Enterprise/apps/installable_organizations" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($organization in $_.Response) { - [GitHubOrganization]::new($organization) + [GitHubOrganization]::new($organization, $Context) } } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 index f4dfcb18e..afbe9e03c 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAppInstallationForAuthenticatedApp.ps1 @@ -39,16 +39,16 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/app/installations' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { - [GitHubAppInstallation]::new($_) + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($installation in $_.Response) { + [GitHubAppInstallation]::new($installation) } } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 index bfa58d061..9686c3cfa 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubAuthenticatedApp.ps1 @@ -35,13 +35,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/app' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubApp]::new($_.Response) } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubEnterpriseOrganizationAppInstallation.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubEnterpriseOrganizationAppInstallation.ps1 index 4609f1fe1..014e1bcfa 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubEnterpriseOrganizationAppInstallation.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubEnterpriseOrganizationAppInstallation.ps1 @@ -53,16 +53,16 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($installation in $_.Response) { - [GitHubAppInstallation]::new($installation, $Organization, 'Organization', $context.HostName) + [GitHubAppInstallation]::new($installation, $Organization, 'Organization', $Context) } } } diff --git a/src/functions/private/Apps/GitHub Apps/Get-GitHubOrganizationAppInstallation.ps1 b/src/functions/private/Apps/GitHub Apps/Get-GitHubOrganizationAppInstallation.ps1 index 5df95b3fd..dafc037a7 100644 --- a/src/functions/private/Apps/GitHub Apps/Get-GitHubOrganizationAppInstallation.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Get-GitHubOrganizationAppInstallation.ps1 @@ -45,14 +45,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/installations" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($installation in $_.Response.installations) { [GitHubAppInstallation]::new($installation) } diff --git a/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 b/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 index b66c98e66..d417c0628 100644 --- a/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Install-GitHubAppOnEnterpriseOrganization.ps1 @@ -11,6 +11,7 @@ .EXAMPLE Install-GitHubAppOnEnterpriseOrganization -Enterprise 'msx' -Organization 'org' -ClientID '123456' #> + [OutputType([GitHubAppInstallation])] [CmdletBinding()] param( # The enterprise slug or ID. @@ -29,7 +30,7 @@ # - all - all repositories that the authenticated GitHub App installation can access. # - selected - select specific repositories. [Parameter(Mandatory)] - [ValidateSet('all', 'selected')] + [ValidateSet('All', 'Selected')] [string] $RepositorySelection, # The names of the repositories to which the installation will be granted access. @@ -49,6 +50,9 @@ } process { + if ($RepositorySelection) { + $RepositorySelection = $RepositorySelection.ToLower() + } $body = @{ client_id = $ClientID repository_selection = $RepositorySelection @@ -56,15 +60,15 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations" Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - Write-Output $_.Response + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubAppInstallation]::new($_.Response) } } diff --git a/src/functions/public/Apps/GitHub App/New-GitHubAppInstallationAccessToken.ps1 b/src/functions/private/Apps/GitHub Apps/New-GitHubAppInstallationAccessToken.ps1 similarity index 92% rename from src/functions/public/Apps/GitHub App/New-GitHubAppInstallationAccessToken.ps1 rename to src/functions/private/Apps/GitHub Apps/New-GitHubAppInstallationAccessToken.ps1 index 2291149ad..142f8370a 100644 --- a/src/functions/public/Apps/GitHub App/New-GitHubAppInstallationAccessToken.ps1 +++ b/src/functions/private/Apps/GitHub Apps/New-GitHubAppInstallationAccessToken.ps1 @@ -48,18 +48,15 @@ 'PSUseShouldProcessForStateChangingFunctions', '', Justification = 'No state is changed.' )] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', - Justification = 'The tokens are received as clear text. Mitigating exposure by removing variables and performing garbage collection.')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'The tokens are received as clear text. Mitigating exposure by removing variables and performing garbage collection.' + )] [CmdletBinding()] param( # The unique identifier of the installation. # Example: '12345678' - [Parameter( - Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName - )] - [Alias('installation_id', 'InstallationID')] + [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [int] $ID, # The context to run the command in. Used to get the details for the API call. @@ -76,13 +73,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/app/installations/$ID/access_tokens" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [pscustomobject]@{ Token = $_.Response.token | ConvertTo-SecureString -AsPlainText -Force ExpiresAt = $_.Response.expires_at.ToLocalTime() diff --git a/src/functions/private/Apps/GitHub Apps/New-GitHubUnsignedJWT.ps1 b/src/functions/private/Apps/GitHub Apps/New-GitHubUnsignedJWT.ps1 new file mode 100644 index 000000000..16d3264cc --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/New-GitHubUnsignedJWT.ps1 @@ -0,0 +1,68 @@ +function New-GitHubUnsignedJWT { + <# + .SYNOPSIS + Creates an unsigned JSON Web Token (JWT) for a GitHub App. + + .DESCRIPTION + Creates the header and payload portions of a JSON Web Token (JWT) for a GitHub App. + This function does not sign the JWT - it returns the unsigned token (header.payload) + that can be passed to a signing function. + + .EXAMPLE + New-GitHubUnsignedJWT -ClientId 'Iv987654321' + + Creates an unsigned JWT for a GitHub App using the specified client ID. + + .OUTPUTS + String + + .NOTES + This function generates a JWT for a GitHub App that can be signed using a private key. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', + Justification = 'Function creates an unsigned JWT without modifying system state' + )] + [CmdletBinding()] + [OutputType([pscustomobject])] + param( + # The client ID of the GitHub App. + # Can use the GitHub App ID or the client ID. + [Parameter(Mandatory)] + [string] $ClientID + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + $header = [GitHubJWTComponent]::ToBase64UrlString( + @{ + alg = 'RS256' + typ = 'JWT' + } + ) + $nowUtc = [System.DateTimeOffset]::UtcNow + $iat = $nowUtc.AddMinutes(-10) + $exp = $nowUtc.AddMinutes(10) + $payload = [GitHubJWTComponent]::ToBase64UrlString( + @{ + iat = $iat.ToUnixTimeSeconds() + exp = $exp.ToUnixTimeSeconds() + iss = $ClientID + } + ) + [pscustomobject]@{ + Base = "$header.$payload" + IssuedAt = $iat.LocalDateTime + ExpiresAt = $exp.LocalDateTime + Issuer = $ClientID + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Apps/GitHub Apps/Revoke-GitHubAppInstallationAccessToken.ps1 b/src/functions/private/Apps/GitHub Apps/Revoke-GitHubAppInstallationAccessToken.ps1 new file mode 100644 index 000000000..d6f845b8b --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/Revoke-GitHubAppInstallationAccessToken.ps1 @@ -0,0 +1,46 @@ +function Revoke-GitHubAppInstallationAccessToken { + <# + .SYNOPSIS + Revoke an installation access token. + + .DESCRIPTION + Revokes the installation token you're using to authenticate as an installation and access this endpoint. + Once an installation token is revoked, the token is invalidated and cannot be used. Other endpoints that require the revoked installation + token must have a new installation token to work. You can create a new token using the `Connect-GitHubApp` function. + + .LINK + https://psmodule.io/GitHub/Functions/Apps/GitHub%20App/Revoke-GitHubAppInstallationAccessToken + + .NOTES + [Revoke an installation access token](https://docs.github.com/rest/apps/installations#revoke-an-installation-access-token) + #> + [CmdletBinding(SupportsShouldProcess)] + 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(Mandatory)] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + Assert-GitHubContext -Context $Context -AuthType IAT + } + + process { + $InputObject = @{ + Method = 'DELETE' + APIEndpoint = '/installation/token' + Context = $Context + } + + if ($PSCmdlet.ShouldProcess('GitHub App installation access token', 'Revoke')) { + $null = Invoke-GitHubAPI @InputObject + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Apps/GitHub Apps/Test-GitHubJWTRefreshRequired.ps1 b/src/functions/private/Apps/GitHub Apps/Test-GitHubJWTRefreshRequired.ps1 new file mode 100644 index 000000000..4db614e1f --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/Test-GitHubJWTRefreshRequired.ps1 @@ -0,0 +1,43 @@ +function Test-GitHubJWTRefreshRequired { + <# + .SYNOPSIS + Test if the GitHub JWT should be refreshed. + + .DESCRIPTION + Test if the GitHub JWT should be refreshed. JWTs are refreshed when they have 150 seconds or less remaining before expiration. + + .EXAMPLE + Test-GitHubJWTRefreshRequired -Context $Context + + This will test if the GitHub JWT should be refreshed for the specified context. + + .NOTES + JWTs are short-lived tokens (typically 10 minutes) and need to be refreshed more frequently than user access tokens. + The refresh threshold is set to 150 seconds (2.5 minutes) to ensure the JWT doesn't expire during API operations. + #> + [OutputType([bool])] + [CmdletBinding()] + 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(Mandatory)] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + try { + ($Context.TokenExpiresAt - [datetime]::Now).TotalSeconds -le 60 + } catch { + return $true + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Apps/GitHub Apps/Uninstall-GitHubAppOnEnterpriseOrganization.ps1 b/src/functions/private/Apps/GitHub Apps/Uninstall-GitHubAppOnEnterpriseOrganization.ps1 index 2cbf0b22a..cfea6ff04 100644 --- a/src/functions/private/Apps/GitHub Apps/Uninstall-GitHubAppOnEnterpriseOrganization.ps1 +++ b/src/functions/private/Apps/GitHub Apps/Uninstall-GitHubAppOnEnterpriseOrganization.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Apps/GitHub Apps/Update-GitHubAppJWT.ps1 b/src/functions/private/Apps/GitHub Apps/Update-GitHubAppJWT.ps1 new file mode 100644 index 000000000..e9f6892e4 --- /dev/null +++ b/src/functions/private/Apps/GitHub Apps/Update-GitHubAppJWT.ps1 @@ -0,0 +1,140 @@ +function Update-GitHubAppJWT { + <# + .SYNOPSIS + Updates a JSON Web Token (JWT) for a GitHub App context. + + .DESCRIPTION + Updates a JSON Web Token (JWT) for a GitHub App context. If the JWT has half or less of its remaining duration before expiration, + it will be refreshed. This function implements mutex-based locking to prevent concurrent refreshes. + + .EXAMPLE + Update-GitHubAppJWT -Context $Context + + Updates the JSON Web Token (JWT) for a GitHub App using the specified context. + + .EXAMPLE + Update-GitHubAppJWT -Context $Context -PassThru + + This will update the GitHub App JWT for the specified context and return the updated context. + + .EXAMPLE + Update-GitHubAppJWT -Context $Context -Silent + + This will update the GitHub App JWT for the specified context without displaying progress messages. + + .OUTPUTS + object + + .NOTES + [Generating a JSON Web Token (JWT) for a GitHub App | GitHub Docs](https://docs.github.com/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-powershell-to-generate-a-jwt) + + .LINK + https://psmodule.io/GitHub/Functions/Apps/GitHub%20App/Update-GitHubAppJWT + #> + [CmdletBinding(SupportsShouldProcess)] + [OutputType([object])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidLongLines', '', + Justification = 'Contains a long link.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'Generated JWT is a plaintext string.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseShouldProcessForStateChangingFunctions', '', + Justification = 'Function creates a JWT without modifying system state' + )] + 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(Mandatory)] + [object] $Context, + + # Return the updated context. + [Parameter()] + [switch] $PassThru, + + # Timeout in milliseconds for waiting on mutex. Default is 30 seconds. + [Parameter()] + [int] $TimeoutMs = 30000 + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + if (Test-GitHubJWTRefreshRequired -Context $Context) { + $lockName = "PSModule.GitHub-$($Context.ID)".Replace('/', '-') + $lock = $null + try { + $lock = [System.Threading.Mutex]::new($false, $lockName) + $acquiredLock = $lock.WaitOne(0) + + if ($acquiredLock) { + try { + Write-Debug '⚠ JWT token nearing expiration. Refreshing JWT...' + $unsignedJWT = New-GitHubUnsignedJWT -ClientId $Context.ClientID + + if ($Context.KeyVaultKeyReference) { + Write-Debug "Using KeyVault Key Reference: $($Context.KeyVaultKeyReference)" + $Context.Token = Add-GitHubKeyVaultJWTSignature -UnsignedJWT $unsignedJWT.Base -KeyVaultKeyReference $Context.KeyVaultKeyReference + } elseif ($Context.PrivateKey) { + Write-Debug 'Using Private Key from context.' + $Context.Token = Add-GitHubLocalJWTSignature -UnsignedJWT $unsignedJWT.Base -PrivateKey $Context.PrivateKey + } else { + throw 'No Private Key or KeyVault Key Reference provided in the context.' + } + + $expiresAt = $unsignedJWT.ExpiresAt + if ($expiresAt.Kind -eq [DateTimeKind]::Utc) { + $expiresAt = $expiresAt.ToLocalTime() + } + $Context.TokenExpiresAt = $expiresAt + + if ($Context.ID) { + if ($PSCmdlet.ShouldProcess('JWT token', 'Update/refresh')) { + Set-Context -Context $Context -Vault $script:GitHub.ContextVault + } + } + } finally { + $lock.ReleaseMutex() + } + } else { + Write-Verbose "JWT token is being updated by another process. Waiting for mutex to be released (timeout: $($TimeoutMs)ms)..." + try { + if ($lock.WaitOne($TimeoutMs)) { + $Context = Resolve-GitHubContext -Context $Context.ID + $lock.ReleaseMutex() + } else { + Write-Warning 'Timeout waiting for JWT token update. Proceeding with current token state.' + } + } catch [System.Threading.AbandonedMutexException] { + Write-Debug 'Mutex was abandoned by another process. Re-checking JWT token state...' + $Context = Resolve-GitHubContext -Context $Context.ID + } + } + } finally { + if ($lock) { + $lock.Dispose() + } + } + } else { + Write-Debug 'JWT is still valid, no refresh needed' + } + + if ($PassThru) { + return $Context + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Artifacts/Get-GitHubArtifactById.ps1 b/src/functions/private/Artifacts/Get-GitHubArtifactById.ps1 index 1d634fbbb..0c0c911b0 100644 --- a/src/functions/private/Artifacts/Get-GitHubArtifactById.ps1 +++ b/src/functions/private/Artifacts/Get-GitHubArtifactById.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubArtifactById { +function Get-GitHubArtifactById { <# .SYNOPSIS Retrieves a specific artifact from a GitHub Actions workflow run. @@ -61,14 +61,14 @@ function Get-GitHubArtifactById { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/artifacts/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubArtifact]::new($_.Response, $Owner, $Repository, $Context.HostName) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubArtifact]::new($_.Response, $Owner, $Repository, $Context) } } diff --git a/src/functions/private/Artifacts/Get-GitHubArtifactFromRepository.ps1 b/src/functions/private/Artifacts/Get-GitHubArtifactFromRepository.ps1 index d7f86c044..8b12374ce 100644 --- a/src/functions/private/Artifacts/Get-GitHubArtifactFromRepository.ps1 +++ b/src/functions/private/Artifacts/Get-GitHubArtifactFromRepository.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubArtifactFromRepository { +function Get-GitHubArtifactFromRepository { <# .SYNOPSIS Lists artifacts for a GitHub repository. @@ -69,14 +69,14 @@ function Get-GitHubArtifactFromRepository { } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/artifacts" Body = $body Context = $Context } - $response = Invoke-GitHubAPI @inputObject + $response = Invoke-GitHubAPI @apiParams $artifacts = $response.Response.artifacts | Sort-Object -Property created_at -Descending @@ -88,7 +88,7 @@ function Get-GitHubArtifactFromRepository { } $artifacts | ForEach-Object { - [GitHubArtifact]::new($_, $Owner, $Repository, $Context.HostName) + [GitHubArtifact]::new($_, $Owner, $Repository, $Context) } } diff --git a/src/functions/private/Artifacts/Get-GitHubArtifactFromWorkflowRun.ps1 b/src/functions/private/Artifacts/Get-GitHubArtifactFromWorkflowRun.ps1 index a7b9b863b..07b2c9522 100644 --- a/src/functions/private/Artifacts/Get-GitHubArtifactFromWorkflowRun.ps1 +++ b/src/functions/private/Artifacts/Get-GitHubArtifactFromWorkflowRun.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubArtifactFromWorkflowRun { +function Get-GitHubArtifactFromWorkflowRun { <# .SYNOPSIS Retrieves artifacts from a specific GitHub Actions workflow run. @@ -80,14 +80,14 @@ function Get-GitHubArtifactFromWorkflowRun { } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/runs/$ID/artifacts" Body = $body Context = $Context } - $response = Invoke-GitHubAPI @inputObject + $response = Invoke-GitHubAPI @apiParams $artifacts = $response.Response.artifacts | Sort-Object -Property created_at -Descending @@ -99,7 +99,7 @@ function Get-GitHubArtifactFromWorkflowRun { } $artifacts | ForEach-Object { - [GitHubArtifact]::new($_, $Owner, $Repository, $Context.HostName) + [GitHubArtifact]::new($_, $Owner, $Repository, $Context) } } diff --git a/src/functions/private/Auth/Context/Assert-GitHubContext.ps1 b/src/functions/private/Auth/Context/Assert-GitHubContext.ps1 index 525037a89..60538b622 100644 --- a/src/functions/private/Auth/Context/Assert-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Assert-GitHubContext.ps1 @@ -53,9 +53,12 @@ } if ($Context -eq 'Anonymous' -and $AuthType -contains 'Anonymous') { return } if ($Context.AuthType -in $AuthType) { return } + + $errorText = "The context '$($Context.Name)' is of type [$($Context.AuthType)] which does not match the required" + + "types [$($AuthType -join ', ')] for [$command]." $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( - [System.Exception]::new("The context '$($Context.Name)' does not match the required AuthTypes [$AuthType] for [$command]."), + [System.Exception]::new($errorText), 'InvalidContextAuthType', [System.Management.Automation.ErrorCategory]::InvalidArgument, $Context diff --git a/src/functions/private/Auth/Context/Remove-GitHubContext.ps1 b/src/functions/private/Auth/Context/Remove-GitHubContext.ps1 index 4c93f5ddc..6186b4807 100644 --- a/src/functions/private/Auth/Context/Remove-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Remove-GitHubContext.ps1 @@ -23,7 +23,6 @@ param( # The name of the context. [Parameter(Mandatory)] - [Alias('Name')] [string] $Context ) @@ -43,4 +42,4 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } diff --git a/src/functions/private/Auth/Context/Resolve-GitHubContext.ps1 b/src/functions/private/Auth/Context/Resolve-GitHubContext.ps1 index e6fa3048c..a28e871a6 100644 --- a/src/functions/private/Auth/Context/Resolve-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Resolve-GitHubContext.ps1 @@ -41,25 +41,39 @@ } process { - Write-Verbose "Context: [$Context]" + Write-Verbose "Context:" + $Context | Out-String -Stream | ForEach-Object { Write-Verbose $_ } Write-Verbose "Anonymous: [$Anonymous]" if ($Anonymous -or $Context -eq 'Anonymous') { Write-Verbose 'Returning Anonymous context.' - return [GitHubContext]@{ - Name = 'Anonymous' - AuthType = 'Anonymous' - } + return [GitHubContext]::new( + [pscustomobject]@{ + Name = 'Anonymous' + AuthType = 'Anonymous' + } + ) } if ($Context -is [string]) { $contextName = $Context Write-Verbose "Getting context: [$contextName]" - return Get-GitHubContext -Context $contextName + $Context = Get-GitHubContext -Context $contextName } if ($null -eq $Context) { Write-Verbose 'Context is null, returning default context.' - return Get-GitHubContext + $Context = Get-GitHubContext + } + + switch ($Context.TokenType) { + 'ghu' { + Write-Verbose 'Update GitHub User Access Token.' + $Context = Update-GitHubUserAccessToken -Context $Context -PassThru + } + 'JWT' { + Write-Verbose 'Update GitHub App JWT Token.' + $Context = Update-GitHubAppJWT -Context $Context -PassThru + } } # TODO: Implement App installation context resolution diff --git a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 index 068bafa99..c20ea4281 100644 --- a/src/functions/private/Auth/Context/Set-GitHubContext.ps1 +++ b/src/functions/private/Auth/Context/Set-GitHubContext.ps1 @@ -168,4 +168,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/private/Auth/DeviceFlow/Test-GitHubAccessTokenRefreshRequired.ps1 b/src/functions/private/Auth/DeviceFlow/Test-GitHubAccessTokenRefreshRequired.ps1 index 1c2740a09..292ded46a 100644 --- a/src/functions/private/Auth/DeviceFlow/Test-GitHubAccessTokenRefreshRequired.ps1 +++ b/src/functions/private/Auth/DeviceFlow/Test-GitHubAccessTokenRefreshRequired.ps1 @@ -26,10 +26,11 @@ } process { - $tokenExpirationDate = $Context.TokenExpirationDate - $currentDateTime = Get-Date - $remainingDuration = [datetime]$tokenExpirationDate - $currentDateTime - $remainingDuration.TotalHours -lt $script:GitHub.Config.AccessTokenGracePeriodInHours + try { + ($Context.TokenExpiresAt - [datetime]::Now).TotalHours -lt $script:GitHub.Config.AccessTokenGracePeriodInHours + } catch { + return $true + } } end { diff --git a/src/functions/private/Auth/DeviceFlow/Update-GitHubUserAccessToken.ps1 b/src/functions/private/Auth/DeviceFlow/Update-GitHubUserAccessToken.ps1 index a6ec2dd4f..ab5183e7e 100644 --- a/src/functions/private/Auth/DeviceFlow/Update-GitHubUserAccessToken.ps1 +++ b/src/functions/private/Auth/DeviceFlow/Update-GitHubUserAccessToken.ps1 @@ -20,82 +20,96 @@ [Refreshing user access tokens](https://docs.github.com/apps/creating-github-apps/authenticating-with-a-github-app/refreshing-user-access-tokens) #> [CmdletBinding(SupportsShouldProcess)] - [OutputType([securestring])] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Is the CLI part of the module.')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The tokens are recieved as clear text. Mitigating exposure by removing variables and performing garbage collection.')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Reason for suppressing')] + [OutputType([GitHubContext])] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'The tokens are recieved as clear text. Mitigating exposure by removing variables and performing garbage collection.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidLongLines', '', + Justification = 'Reason for suppressing' + )] 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(Mandatory)] [object] $Context, - # Return the new access token. + # Return the updated context. [Parameter()] - [switch] $PassThru + [switch] $PassThru, + + # Timeout in milliseconds for waiting on mutex. Default is 30 seconds. + [Parameter()] + [int] $TimeoutMs = 30000 ) begin { $stackPath = Get-PSCallStackPath Write-Debug "[$stackPath] - Start" - Assert-GitHubContext -Context $Context -AuthType UAT } process { - Write-Verbose "Reusing previously stored ClientID: [$($Context.AuthClientID)]" - $authClientID = $Context.AuthClientID - $accessTokenValidity = [datetime]($Context.TokenExpirationDate) - (Get-Date) - $accessTokenIsValid = $accessTokenValidity.Seconds -gt 0 - $hours = $accessTokenValidity.Hours.ToString().PadLeft(2, '0') - $minutes = $accessTokenValidity.Minutes.ToString().PadLeft(2, '0') - $seconds = $accessTokenValidity.Seconds.ToString().PadLeft(2, '0') - $accessTokenValidityText = "$hours`:$minutes`:$seconds" - if ($accessTokenIsValid) { - if ($accessTokenValidity.TotalHours -gt $script:GitHub.Config.AccessTokenGracePeriodInHours) { - if (-not $Silent) { - Write-Host '✓ ' -ForegroundColor Green -NoNewline - Write-Host "Access token is still valid for $accessTokenValidityText ..." - } - return - } else { - if (-not $Silent) { - Write-Host '⚠ ' -ForegroundColor Yellow -NoNewline - Write-Host "Access token remaining validity $accessTokenValidityText. Refreshing access token..." + if (Test-GitHubAccessTokenRefreshRequired -Context $Context) { + $lockName = "PSModule.GitHub-$($Context.ID)".Replace('/', '-') + $lock = $null + try { + $lock = [System.Threading.Mutex]::new($false, $lockName) + $updateToken = $lock.WaitOne(0) + + if ($updateToken) { + try { + $refreshTokenValidity = [datetime]($Context.RefreshTokenExpiresAt) - [datetime]::Now + $refreshTokenIsValid = $refreshTokenValidity.TotalSeconds -gt 0 + if ($refreshTokenIsValid) { + Write-Debug '⚠ Access token expired. Refreshing access token...' + $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $Context.AuthClientID -RefreshToken $Context.RefreshToken -HostName $Context.HostName + } else { + Write-Verbose "Using $($Context.DeviceFlowType) authentication..." + $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $Context.AuthClientID -HostName $Context.HostName + } + $Context.Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token + $Context.TokenExpiresAt = ([DateTime]::Now).AddSeconds($tokenResponse.expires_in) + $Context.TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern + $Context.RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token + $Context.RefreshTokenExpiresAt = ([DateTime]::Now).AddSeconds($tokenResponse.refresh_token_expires_in) + + if ($PSCmdlet.ShouldProcess('Access token', 'Update/refresh')) { + Set-Context -Context $Context -Vault $script:GitHub.ContextVault + } + } finally { + $lock.ReleaseMutex() + } + } else { + Write-Verbose "Access token is not valid. Waiting for mutex to be released (timeout: $($TimeoutMs)ms)..." + try { + if ($lock.WaitOne($TimeoutMs)) { + $Context = Resolve-GitHubContext -Context $Context.ID + $lock.ReleaseMutex() + } else { + Write-Warning 'Timeout waiting for token update. Proceeding with current token state.' + } + } catch [System.Threading.AbandonedMutexException] { + Write-Debug 'Mutex was abandoned by another process. Re-checking token state...' + $Context = Resolve-GitHubContext -Context $Context.ID + } } - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -RefreshToken ($Context.RefreshToken) -HostName $Context.HostName - } - } else { - $refreshTokenValidity = [datetime]($Context.RefreshTokenExpirationDate) - (Get-Date) - $refreshTokenIsValid = $refreshTokenValidity.Seconds -gt 0 - if ($refreshTokenIsValid) { - if (-not $Silent) { - Write-Host '⚠ ' -ForegroundColor Yellow -NoNewline - Write-Host 'Access token expired. Refreshing access token...' + } finally { + if ($lock) { + $lock.Dispose() } - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -RefreshToken ($Context.RefreshToken) -HostName $Context.HostName - } else { - Write-Verbose "Using $Mode authentication..." - $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $authClientID -Scope $Scope -HostName $Context.HostName } } - $Context.Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token - $Context.TokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.expires_in) - $Context.TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern - $Context.RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token - $Context.RefreshTokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.refresh_token_expires_in) - - if ($PSCmdlet.ShouldProcess('Access token', 'Update/refresh')) { - Set-Context -Context $Context -Vault $script:GitHub.ContextVault - } - if ($PassThru) { - $Context.Token + return $Context } - } end { Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } diff --git a/src/functions/private/Branches/Get-GitHubBranchList.ps1 b/src/functions/private/Branches/Get-GitHubBranchList.ps1 index a036ccae7..f68175b86 100644 --- a/src/functions/private/Branches/Get-GitHubBranchList.ps1 +++ b/src/functions/private/Branches/Get-GitHubBranchList.ps1 @@ -37,13 +37,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/branches" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Config/Initialize-GitHubConfig.ps1 b/src/functions/private/Config/Initialize-GitHubConfig.ps1 index 8c513a8f5..651445856 100644 --- a/src/functions/private/Config/Initialize-GitHubConfig.ps1 +++ b/src/functions/private/Config/Initialize-GitHubConfig.ps1 @@ -75,4 +75,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/private/Enterprise/Get-GitHubEnterpriseByName.ps1 b/src/functions/private/Enterprise/Get-GitHubEnterpriseByName.ps1 new file mode 100644 index 000000000..81d33aa5d --- /dev/null +++ b/src/functions/private/Enterprise/Get-GitHubEnterpriseByName.ps1 @@ -0,0 +1,74 @@ +function Get-GitHubEnterpriseByName { + <# + .SYNOPSIS + Retrieves details about a GitHub Enterprise instance by name (slug). + + .DESCRIPTION + This function retrieves detailed information about a GitHub Enterprise instance, including its avatar, billing details, storage usage, + creation date, and other metadata based on the provided name (slug). It returns an object of type GitHubEnterprise populated with this + information. + + .EXAMPLE + Get-GitHubEnterpriseByName -Name 'my-enterprise' + + Output: + ```powershell + Name : My Enterprise + Slug : my-enterprise + URL : https://github.com/enterprises/my-enterprise + CreatedAt : 2022-01-01T00:00:00Z + ViewerIsAdmin : True + ``` + + Retrieves details about the GitHub Enterprise instance named 'my-enterprise'. + + .OUTPUTS + GitHubEnterprise + + .NOTES + An object containing detailed information about the GitHub Enterprise instance, including billing info, URLs, and metadata. + #> + [OutputType([GitHubEnterprise])] + [CmdletBinding()] + param( + # The name (slug) of the GitHub Enterprise instance to retrieve. + [Parameter(Mandatory)] + [Alias('Slug')] + [string] $Name, + + # 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(Mandatory)] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + $graphQLFields = ([GitHubEnterprise]::PropertyToGraphQLMap).Values + + $enterpriseQuery = @{ + query = @" +query(`$Slug: String!) { + enterprise(slug: `$Slug) { + $graphQLFields + } +} +"@ + Variables = @{ + Slug = $Name + } + Context = $Context + } + $enterpriseResult = Invoke-GitHubGraphQLQuery @enterpriseQuery + [GitHubEnterprise]::new($enterpriseResult.enterprise) + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Enterprise/Get-GitHubEnterpriseList.ps1 b/src/functions/private/Enterprise/Get-GitHubEnterpriseList.ps1 new file mode 100644 index 000000000..cd2f890e4 --- /dev/null +++ b/src/functions/private/Enterprise/Get-GitHubEnterpriseList.ps1 @@ -0,0 +1,100 @@ +function Get-GitHubEnterpriseList { + <# + .SYNOPSIS + Retrieves a list of all GitHub Enterprise instances. + + .DESCRIPTION + This function retrieves detailed information about all GitHub Enterprise instances, including their avatars, billing details, storage usage, + creation dates, and other metadata. It returns an array of objects of type GitHubEnterprise populated with this information. + + .EXAMPLE + Get-GitHubEnterpriseList + + Output: + ```powershell + Name : My Enterprise + Slug : my-enterprise + URL : https://github.com/enterprises/my-enterprise + CreatedAt : 2022-01-01T00:00:00Z + + Name : Another Enterprise + Slug : another-enterprise + URL : https://github.com/enterprises/another-enterprise + CreatedAt : 2021-12-01T00:00:00Z + ``` + + Retrieves details about the GitHub Enterprise instance. + + .OUTPUTS + GitHubEnterprise[] + + .NOTES + An array of objects containing detailed information about the GitHub Enterprise instances, including billing info, URLs, and metadata. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'hasNextPage', Scope = 'Function', + Justification = 'Unknown issue with var scoping in blocks.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'after', Scope = 'Function', + Justification = 'Unknown issue with var scoping in blocks.' + )] + [OutputType([GitHubEnterprise[]])] + [CmdletBinding()] + 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(Mandatory)] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + $hasNextPage = $false + $after = $null + $perPageSetting = Resolve-GitHubContextSetting -Name 'PerPage' -Value $PerPage -Context $Context + + $graphQLFields = ([GitHubEnterprise]::PropertyToGraphQLMap).Values + + do { + $enterpriseQuery = @{ + query = @" +query(`$perPage: Int!, `$after: String) { + viewer { + enterprises(first: `$perPage, after: `$after) { + nodes { + $graphQLFields + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +"@ + Variables = @{ + perPage = $perPageSetting + after = $after + } + Context = $Context + } + Invoke-GitHubGraphQLQuery @enterpriseQuery | ForEach-Object { + foreach ($enterprise in $_.viewer.enterprises.nodes) { + [GitHubEnterprise]::new($enterprise) + } + $hasNextPage = $_.viewer.enterprises.pageInfo.hasNextPage + $after = $_.viewer.enterprises.pageInfo.endCursor + } + } while ($hasNextPage) + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Environments/Get-GitHubEnvironmentByName.ps1 b/src/functions/private/Environments/Get-GitHubEnvironmentByName.ps1 index d4286cb88..bd0c7392d 100644 --- a/src/functions/private/Environments/Get-GitHubEnvironmentByName.ps1 +++ b/src/functions/private/Environments/Get-GitHubEnvironmentByName.ps1 @@ -1,4 +1,4 @@ -filter Get-GitHubEnvironmentByName { +filter Get-GitHubEnvironmentByName { <# .SYNOPSIS Retrieves details of a specified GitHub environment. @@ -50,7 +50,6 @@ filter Get-GitHubEnvironmentByName { Mandatory, ValueFromPipelineByPropertyName )] - [Alias('Organization', 'User')] [string] $Owner, # The name of the repository without the .git extension. The name is not case sensitive. @@ -81,16 +80,14 @@ filter Get-GitHubEnvironmentByName { process { $encodedName = [System.Uri]::EscapeDataString($Name) - $inputObject = @{ + $apiParams = @{ Method = 'GET' Uri = $Context.ApiBaseUri + "/repos/$Owner/$Repository/environments/$encodedName" Context = $Context } try { - Invoke-GitHubAPI @inputObject | ForEach-Object { - $environment = [GitHubEnvironment]::new($_.Response, $Owner, $Repository) - $environment.Url = "https://$($Context.HostName)/$Owner/$Repository/settings/environments/$($environment.ID)/edit" - $environment + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubEnvironment]::new($_.Response, $Owner, $Repository, $Context) } } catch { return diff --git a/src/functions/private/Environments/Get-GitHubEnvironmentList.ps1 b/src/functions/private/Environments/Get-GitHubEnvironmentList.ps1 index 85d191c16..3748f44a8 100644 --- a/src/functions/private/Environments/Get-GitHubEnvironmentList.ps1 +++ b/src/functions/private/Environments/Get-GitHubEnvironmentList.ps1 @@ -1,4 +1,4 @@ -filter Get-GitHubEnvironmentList { +filter Get-GitHubEnvironmentList { <# .SYNOPSIS Lists the environments for a repository. @@ -74,18 +74,16 @@ filter Get-GitHubEnvironmentList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response.environments | ForEach-Object { - $environment = [GitHubEnvironment]::new($_, $Owner, $Repository) - $environment.Url = "https://$($Context.HostName)/$Owner/$Repository/settings/environments/$($environment.ID)/edit" - $environment + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($environment in $_.Response.environments) { + [GitHubEnvironment]::new($environment, $Owner, $Repository, $Context) } } } diff --git a/src/functions/private/Gitignore/Get-GitHubGitignoreByName.ps1 b/src/functions/private/Gitignore/Get-GitHubGitignoreByName.ps1 index 89688cc4d..c79cc95f5 100644 --- a/src/functions/private/Gitignore/Get-GitHubGitignoreByName.ps1 +++ b/src/functions/private/Gitignore/Get-GitHubGitignoreByName.ps1 @@ -34,14 +34,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/gitignore/templates/$Name" Accept = 'application/vnd.github.raw+json' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Gitignore/Get-GitHubGitignoreList.ps1 b/src/functions/private/Gitignore/Get-GitHubGitignoreList.ps1 index 1e12c0e8d..fc6f70114 100644 --- a/src/functions/private/Gitignore/Get-GitHubGitignoreList.ps1 +++ b/src/functions/private/Gitignore/Get-GitHubGitignoreList.ps1 @@ -32,13 +32,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/gitignore/templates' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/License/Get-GitHubLicenseByName.ps1 b/src/functions/private/License/Get-GitHubLicenseByName.ps1 index 42a0da42b..e1924aaff 100644 --- a/src/functions/private/License/Get-GitHubLicenseByName.ps1 +++ b/src/functions/private/License/Get-GitHubLicenseByName.ps1 @@ -36,14 +36,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/licenses/$Name" Accept = 'application/vnd.github+json' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubLicense]::New($_.Response) } } diff --git a/src/functions/private/License/Get-GitHubLicenseList.ps1 b/src/functions/private/License/Get-GitHubLicenseList.ps1 index 03bcf4add..436596194 100644 --- a/src/functions/private/License/Get-GitHubLicenseList.ps1 +++ b/src/functions/private/License/Get-GitHubLicenseList.ps1 @@ -35,13 +35,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/licenses' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response | ForEach-Object { [GitHubLicense]::New($_) } } } diff --git a/src/functions/private/License/Get-GitHubRepositoryLicense.ps1 b/src/functions/private/License/Get-GitHubRepositoryLicense.ps1 index c6fdefe6a..31e4dfd1e 100644 --- a/src/functions/private/License/Get-GitHubRepositoryLicense.ps1 +++ b/src/functions/private/License/Get-GitHubRepositoryLicense.ps1 @@ -54,14 +54,14 @@ 'html' { 'application/vnd.github.html+json' } } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/license" ContentType = $contentType Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $Response = $_.Response $rawContent = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Response.content)) $Response | Add-Member -NotePropertyName 'raw_content' -NotePropertyValue $rawContent -Force diff --git a/src/functions/private/Organization/Blocking/Block-GitHubUserByOrganization.ps1 b/src/functions/private/Organization/Blocking/Block-GitHubUserByOrganization.ps1 index 90b9e5602..d25d9f1e2 100644 --- a/src/functions/private/Organization/Blocking/Block-GitHubUserByOrganization.ps1 +++ b/src/functions/private/Organization/Blocking/Block-GitHubUserByOrganization.ps1 @@ -49,13 +49,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Organization/blocks/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Organization/Blocking/Get-GitHubBlockedUserByOrganization.ps1 b/src/functions/private/Organization/Blocking/Get-GitHubBlockedUserByOrganization.ps1 index 38f98d8f9..5830f3a51 100644 --- a/src/functions/private/Organization/Blocking/Get-GitHubBlockedUserByOrganization.ps1 +++ b/src/functions/private/Organization/Blocking/Get-GitHubBlockedUserByOrganization.ps1 @@ -39,14 +39,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/blocks" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Organization/Blocking/Test-GitHubBlockedUserByOrganization.ps1 b/src/functions/private/Organization/Blocking/Test-GitHubBlockedUserByOrganization.ps1 index 5a6a1729e..b2dbc1fac 100644 --- a/src/functions/private/Organization/Blocking/Test-GitHubBlockedUserByOrganization.ps1 +++ b/src/functions/private/Organization/Blocking/Test-GitHubBlockedUserByOrganization.ps1 @@ -48,13 +48,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/blocks/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Organization/Blocking/Unblock-GitHubUserByOrganization.ps1 b/src/functions/private/Organization/Blocking/Unblock-GitHubUserByOrganization.ps1 index feac1635c..384cf5a73 100644 --- a/src/functions/private/Organization/Blocking/Unblock-GitHubUserByOrganization.ps1 +++ b/src/functions/private/Organization/Blocking/Unblock-GitHubUserByOrganization.ps1 @@ -49,12 +49,12 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Organization/blocks/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Organization/Get-GitHubAllOrganization.ps1 b/src/functions/private/Organization/Get-GitHubAllOrganization.ps1 index 3bf5f6898..159d39b7f 100644 --- a/src/functions/private/Organization/Get-GitHubAllOrganization.ps1 +++ b/src/functions/private/Organization/Get-GitHubAllOrganization.ps1 @@ -49,7 +49,7 @@ since = $Since } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/organizations' Body = $body @@ -57,8 +57,10 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { [GitHubOrganization]::new($_) } + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($organization in $_.Response) { + [GitHubOrganization]::new($organization, $Context) + } } } end { diff --git a/src/functions/private/Organization/Get-GitHubMyOrganization.ps1 b/src/functions/private/Organization/Get-GitHubMyOrganization.ps1 deleted file mode 100644 index 718dc43b9..000000000 --- a/src/functions/private/Organization/Get-GitHubMyOrganization.ps1 +++ /dev/null @@ -1,63 +0,0 @@ -filter Get-GitHubMyOrganization { - <# - .SYNOPSIS - List organizations for the authenticated user - - .DESCRIPTION - List organizations for the authenticated user. - - **OAuth scope requirements** - - This only lists organizations that your authorization allows you to operate on - in some way (e.g., you can list teams with `read:org` scope, you can publicize your - organization membership with `user` scope, etc.). Therefore, this API requires at - least `user` or `read:org` scope. OAuth requests with insufficient scope receive a - `403 Forbidden` response. - - .EXAMPLE - Get-GitHubMyOrganization - - List organizations for the authenticated user. - - .OUTPUTS - GitHubOrganization - - .NOTES - [List organizations for the authenticated user](https://docs.github.com/rest/orgs/orgs#list-organizations-for-the-authenticated-user) - #> - [OutputType([GitHubOrganization])] - [CmdletBinding()] - param( - # The number of results per page (max 100). - [Parameter()] - [System.Nullable[int]] $PerPage, - - # 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(Mandatory)] - [object] $Context - ) - - begin { - $stackPath = Get-PSCallStackPath - Write-Debug "[$stackPath] - Start" - Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT - } - - process { - $inputObject = @{ - Method = 'GET' - APIEndpoint = '/user/orgs' - PerPage = $PerPage - Context = $Context - } - - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { [GitHubOrganization]::new($_) } - } - } - - end { - Write-Debug "[$stackPath] - End" - } -} diff --git a/src/functions/private/Organization/Get-GitHubOrganizationByName.ps1 b/src/functions/private/Organization/Get-GitHubOrganizationByName.ps1 index 629db0ae9..87b512ae2 100644 --- a/src/functions/private/Organization/Get-GitHubOrganizationByName.ps1 +++ b/src/functions/private/Organization/Get-GitHubOrganizationByName.ps1 @@ -49,14 +49,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubOrganization]::new($_.Response) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubOrganization]::new($_.Response, $Context) } } end { diff --git a/src/functions/private/Organization/Get-GitHubOrganizationListForAuthUser.ps1 b/src/functions/private/Organization/Get-GitHubOrganizationListForAuthUser.ps1 new file mode 100644 index 000000000..a8024ee6b --- /dev/null +++ b/src/functions/private/Organization/Get-GitHubOrganizationListForAuthUser.ps1 @@ -0,0 +1,104 @@ +function Get-GitHubOrganizationListForAuthUser { + <# + .SYNOPSIS + Retrieves a list of all GitHub organizations for the authenticated user. + + .DESCRIPTION + This function retrieves detailed information about all GitHub organizations that the authenticated user belongs to, including their avatars, + creation dates, member counts, and other metadata. It returns an array of objects of type GitHubOrganization populated with this information. + + .EXAMPLE + Get-GitHubOrganizationListForAuthUser + + Output: + ```powershell + Name : MyOrganization + Login : my-org + URL : https://github.com/my-org + CreatedAt : 2022-01-01T00:00:00Z + + Name : Another Organization + Login : another-org + URL : https://github.com/another-org + CreatedAt : 2021-12-01T00:00:00Z + ``` + + Retrieves details about the GitHub organizations the authenticated user belongs to. + + .OUTPUTS + GitHubOrganization[] + + .NOTES + An array of objects containing detailed information about the GitHub organizations, including member info, URLs, and metadata. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'hasNextPage', Scope = 'Function', + Justification = 'Unknown issue with var scoping in blocks.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSUseDeclaredVarsMoreThanAssignments', 'after', Scope = 'Function', + Justification = 'Unknown issue with var scoping in blocks.' + )] + [OutputType([GitHubOrganization[]])] + [CmdletBinding()] + param( + # The number of results per page (max 100). + [Parameter()] + [System.Nullable[int]] $PerPage, + + # 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(Mandatory)] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + $hasNextPage = $false + $after = $null + $perPageSetting = Resolve-GitHubContextSetting -Name 'PerPage' -Value $PerPage -Context $Context + + $graphQLFields = ([GitHubOrganization]::PropertyToGraphQLMap).Values + + do { + $organizationQuery = @{ + query = @" +query(`$perPage: Int!, `$after: String) { + viewer { + organizations(first: `$perPage, after: `$after) { + nodes { + $graphQLFields + } + pageInfo { + endCursor + hasNextPage + } + } + } +} +"@ + Variables = @{ + perPage = $perPageSetting + after = $after + } + Context = $Context + } + Invoke-GitHubGraphQLQuery @organizationQuery | ForEach-Object { + foreach ($organization in $_.viewer.organizations.nodes) { + [GitHubOrganization]::new($organization, $Context) + } + $hasNextPage = $_.viewer.organizations.pageInfo.hasNextPage + $after = $_.viewer.organizations.pageInfo.endCursor + } + } while ($hasNextPage) + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Organization/Get-GitHubUserOrganization.ps1 b/src/functions/private/Organization/Get-GitHubUserOrganization.ps1 index 421a33c3e..f76da4920 100644 --- a/src/functions/private/Organization/Get-GitHubUserOrganization.ps1 +++ b/src/functions/private/Organization/Get-GitHubUserOrganization.ps1 @@ -47,15 +47,17 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/orgs" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { [GitHubOrganization]::new($_) } + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($organization in $_.Response) { + [GitHubOrganization]::new($organization, $Context) + } } } end { diff --git a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByID.ps1 b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByID.ps1 index d2a53b506..71a9a4053 100644 --- a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByID.ps1 +++ b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByID.ps1 @@ -48,14 +48,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/releases/assets/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubReleaseAsset]($_.Response) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubReleaseAsset]::new($_.Response) } } diff --git a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByReleaseID.ps1 b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByReleaseID.ps1 index 54db2d1a6..6d81aeace 100644 --- a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByReleaseID.ps1 +++ b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByReleaseID.ps1 @@ -55,22 +55,22 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/releases/$ID/assets" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($asset in $_.Response) { if ($PSBoundParameters.ContainsKey('Name')) { if ($asset.name -eq $Name) { - [GitHubReleaseAsset]($asset) + [GitHubReleaseAsset]::new($asset) break } } else { - [GitHubReleaseAsset]($asset) + [GitHubReleaseAsset]::new($asset) } } } diff --git a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByTag.ps1 b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByTag.ps1 index 2dfabdc9f..ad523dcc0 100644 --- a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByTag.ps1 +++ b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetByTag.ps1 @@ -70,7 +70,7 @@ $perPageSetting = Resolve-GitHubContextSetting -Name 'PerPage' -Value $PerPage -Context $Context do { - $inputObject = @{ + $apiParams = @{ Query = @" query(`$owner: String!, `$repository: String!, `$tag: String!, `$perPage: Int, `$after: String) { repository(owner: `$owner, name: `$repository) { @@ -109,7 +109,7 @@ query(`$owner: String!, `$repository: String!, `$tag: String!, `$perPage: Int, ` Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { $release = $_.repository.release $assets = $release.releaseAssets foreach ($asset in $assets.nodes) { diff --git a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetFromLatest.ps1 b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetFromLatest.ps1 index 2d7c7efd3..c0bbde666 100644 --- a/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetFromLatest.ps1 +++ b/src/functions/private/Releases/Assets/Get-GitHubReleaseAssetFromLatest.ps1 @@ -70,7 +70,7 @@ $perPageSetting = Resolve-GitHubContextSetting -Name 'PerPage' -Value $PerPage -Context $Context do { - $inputObject = @{ + $apiParams = @{ Query = @" query(`$owner: String!, `$repository: String!, `$perPage: Int, `$after: String) { repository(owner: `$owner, name: `$repository) { @@ -108,7 +108,7 @@ query(`$owner: String!, `$repository: String!, `$perPage: Int, `$after: String) Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { $release = $_.repository.latestRelease $assets = $release.releaseAssets foreach ($asset in $assets.nodes) { diff --git a/src/functions/private/Releases/Get-GitHubReleaseAll.ps1 b/src/functions/private/Releases/Get-GitHubReleaseAll.ps1 index 5603893cc..4e04235f7 100644 --- a/src/functions/private/Releases/Get-GitHubReleaseAll.ps1 +++ b/src/functions/private/Releases/Get-GitHubReleaseAll.ps1 @@ -63,7 +63,7 @@ $perPageSetting = Resolve-GitHubContextSetting -Name 'PerPage' -Value $PerPage -Context $Context do { - $inputObject = @{ + $apiParams = @{ Query = @' query($owner: String!, $repository: String!, $perPage: Int, $after: String) { repository(owner: $owner, name: $repository) { @@ -102,7 +102,7 @@ query($owner: String!, $repository: String!, $perPage: Int, $after: String) { Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { foreach ($release in $_.repository.releases.nodes) { [GitHubRelease]::new($release, $Owner, $Repository, $null) } diff --git a/src/functions/private/Releases/Get-GitHubReleaseByID.ps1 b/src/functions/private/Releases/Get-GitHubReleaseByID.ps1 index 126d87e8e..0fa9cf1f7 100644 --- a/src/functions/private/Releases/Get-GitHubReleaseByID.ps1 +++ b/src/functions/private/Releases/Get-GitHubReleaseByID.ps1 @@ -51,14 +51,14 @@ process { $latest = Get-GitHubReleaseLatest -Owner $Owner -Repository $Repository -Context $Context - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/releases/$ID" Context = $Context } try { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $isLatest = $_.Response.id -eq $latest.id [GitHubRelease]::new($_.Response, $Owner, $Repository, $isLatest) } diff --git a/src/functions/private/Releases/Get-GitHubReleaseByTagName.ps1 b/src/functions/private/Releases/Get-GitHubReleaseByTagName.ps1 index d3e29e765..7577e5c7e 100644 --- a/src/functions/private/Releases/Get-GitHubReleaseByTagName.ps1 +++ b/src/functions/private/Releases/Get-GitHubReleaseByTagName.ps1 @@ -48,7 +48,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Query = @' query($owner: String!, $repository: String!, $tag: String!) { repository(owner: $owner, name: $repository) { @@ -80,7 +80,7 @@ query($owner: String!, $repository: String!, $tag: String!) { Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { $release = $_.repository.release if ($release) { [GitHubRelease]::new($release, $Owner, $Repository, $null) diff --git a/src/functions/private/Releases/Get-GitHubReleaseLatest.ps1 b/src/functions/private/Releases/Get-GitHubReleaseLatest.ps1 index a0bddf908..221afdbb1 100644 --- a/src/functions/private/Releases/Get-GitHubReleaseLatest.ps1 +++ b/src/functions/private/Releases/Get-GitHubReleaseLatest.ps1 @@ -46,7 +46,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Query = @' query($owner: String!, $repository: String!) { repository(owner: $owner, name: $repository) { @@ -77,7 +77,7 @@ query($owner: String!, $repository: String!) { Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { $release = $_.repository.latestRelease if ($release) { [GitHubRelease]::new($release, $Owner, $Repository, $null) diff --git a/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkById.ps1 b/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkById.ps1 index 911d64a31..64dad648a 100644 --- a/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkById.ps1 +++ b/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkById.ps1 @@ -48,13 +48,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/autolinks/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkList.ps1 b/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkList.ps1 index 909c59277..cda80c94d 100644 --- a/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkList.ps1 +++ b/src/functions/private/Repositories/Autolinks/Get-GitHubRepositoryAutolinkList.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/autolinks" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Repositories/Get-GitHubMyRepositories.ps1 b/src/functions/private/Repositories/Get-GitHubMyRepositories.ps1 index 1129e56b5..f79bdc32e 100644 --- a/src/functions/private/Repositories/Get-GitHubMyRepositories.ps1 +++ b/src/functions/private/Repositories/Get-GitHubMyRepositories.ps1 @@ -85,7 +85,7 @@ $graphQLFields = ConvertTo-GitHubGraphQLField @graphParams do { - $inputObject = @{ + $apiParams = @{ Query = @" query( `$PerPage: Int!, @@ -126,7 +126,7 @@ $graphQLFields Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { $_.viewer.repositories.nodes | ForEach-Object { [GitHubRepository]::new($_) } diff --git a/src/functions/private/Repositories/Get-GitHubMyRepositoryByName.ps1 b/src/functions/private/Repositories/Get-GitHubMyRepositoryByName.ps1 index c43967ee5..14d125396 100644 --- a/src/functions/private/Repositories/Get-GitHubMyRepositoryByName.ps1 +++ b/src/functions/private/Repositories/Get-GitHubMyRepositoryByName.ps1 @@ -105,7 +105,7 @@ } $graphQLFields = ConvertTo-GitHubGraphQLField @graphParams - $inputObject = @{ + $apiParams = @{ Query = @" query( `$Name: String! @@ -125,7 +125,7 @@ $graphQLFields Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { [GitHubRepository]::new($_.viewer.repository) } } diff --git a/src/functions/private/Repositories/Get-GitHubRepositoryByName.ps1 b/src/functions/private/Repositories/Get-GitHubRepositoryByName.ps1 index 3238fbe35..06d6cd896 100644 --- a/src/functions/private/Repositories/Get-GitHubRepositoryByName.ps1 +++ b/src/functions/private/Repositories/Get-GitHubRepositoryByName.ps1 @@ -108,7 +108,7 @@ } $graphQLFields = ConvertTo-GitHubGraphQLField @graphParams - $inputObject = @{ + $apiParams = @{ Query = @" query( `$Owner: String!, @@ -132,7 +132,7 @@ $graphQLFields Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { [GitHubRepository]::new($_.repositoryOwner.repository) } } diff --git a/src/functions/private/Repositories/Get-GitHubRepositoryByNameAndTeam.ps1 b/src/functions/private/Repositories/Get-GitHubRepositoryByNameAndTeam.ps1 index f430e3e9b..594ed1ed8 100644 --- a/src/functions/private/Repositories/Get-GitHubRepositoryByNameAndTeam.ps1 +++ b/src/functions/private/Repositories/Get-GitHubRepositoryByNameAndTeam.ps1 @@ -60,14 +60,14 @@ process { $TeamOwner = [string]::IsNullOrEmpty($TeamOwner) ? $Owner : $TeamOwner - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$TeamOwner/teams/$Team/repos/$Owner/$Name" Accept = 'application/vnd.github.v3.repository+json' Context = $Context } try { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::new($_.Response) } } catch { diff --git a/src/functions/private/Repositories/Get-GitHubRepositoryListByOwner.ps1 b/src/functions/private/Repositories/Get-GitHubRepositoryListByOwner.ps1 index d2c20bb19..6476885ec 100644 --- a/src/functions/private/Repositories/Get-GitHubRepositoryListByOwner.ps1 +++ b/src/functions/private/Repositories/Get-GitHubRepositoryListByOwner.ps1 @@ -93,7 +93,7 @@ $graphQLFields = ConvertTo-GitHubGraphQLField @graphParams do { - $inputObject = @{ + $apiParams = @{ Query = @" query( `$Owner: String!, @@ -141,7 +141,7 @@ query( Context = $Context } - Invoke-GitHubGraphQLQuery @inputObject | ForEach-Object { + Invoke-GitHubGraphQLQuery @apiParams | ForEach-Object { foreach ($repository in $_.repositoryOwner.repositories.nodes) { [GitHubRepository]::new($repository) } diff --git a/src/functions/private/Repositories/Get-GitHubRepositoryListByTeam.ps1 b/src/functions/private/Repositories/Get-GitHubRepositoryListByTeam.ps1 index 3c2628de8..95e2c9238 100644 --- a/src/functions/private/Repositories/Get-GitHubRepositoryListByTeam.ps1 +++ b/src/functions/private/Repositories/Get-GitHubRepositoryListByTeam.ps1 @@ -50,13 +50,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/teams/$Team/repos" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($repo in $_.Response) { [GitHubRepository]::new($repo) } diff --git a/src/functions/private/Repositories/New-GitHubRepositoryAsFork.ps1 b/src/functions/private/Repositories/New-GitHubRepositoryAsFork.ps1 index 6ec895950..b29c11f2a 100644 --- a/src/functions/private/Repositories/New-GitHubRepositoryAsFork.ps1 +++ b/src/functions/private/Repositories/New-GitHubRepositoryAsFork.ps1 @@ -83,7 +83,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$ForkOwner/$ForkRepository/forks" Body = $body @@ -91,7 +91,7 @@ } if ($PSCmdlet.ShouldProcess("Repository [$Owner/$Name] as fork of [$ForkOwner/$ForkRepository]", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::New($_.Response) } } diff --git a/src/functions/private/Repositories/New-GitHubRepositoryFromTemplate.ps1 b/src/functions/private/Repositories/New-GitHubRepositoryFromTemplate.ps1 index 6a1054b4a..388483be0 100644 --- a/src/functions/private/Repositories/New-GitHubRepositoryFromTemplate.ps1 +++ b/src/functions/private/Repositories/New-GitHubRepositoryFromTemplate.ps1 @@ -92,7 +92,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$TemplateOwner/$TemplateRepository/generate" Body = $body @@ -100,7 +100,7 @@ } if ($PSCmdlet.ShouldProcess("Repository [$Owner/$Name] from template [$TemplateOwner/$TemplateRepository]", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::New($_.Response) } } diff --git a/src/functions/private/Repositories/New-GitHubRepositoryOrg.ps1 b/src/functions/private/Repositories/New-GitHubRepositoryOrg.ps1 index 6f827aa41..8a4b91906 100644 --- a/src/functions/private/Repositories/New-GitHubRepositoryOrg.ps1 +++ b/src/functions/private/Repositories/New-GitHubRepositoryOrg.ps1 @@ -182,7 +182,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/orgs/$Organization/repos" Body = $body @@ -190,7 +190,7 @@ } if ($PSCmdlet.ShouldProcess("Repository [$Name] in organization [$Organization]", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::New($_.Response) } } diff --git a/src/functions/private/Repositories/New-GitHubRepositoryUser.ps1 b/src/functions/private/Repositories/New-GitHubRepositoryUser.ps1 index 873b72abe..fbfaf3406 100644 --- a/src/functions/private/Repositories/New-GitHubRepositoryUser.ps1 +++ b/src/functions/private/Repositories/New-GitHubRepositoryUser.ps1 @@ -180,7 +180,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/repos' Body = $body @@ -188,7 +188,7 @@ } if ($PSCmdlet.ShouldProcess('Repository for user', 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::New($_.Response) } } diff --git a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 b/src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 similarity index 90% rename from src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 rename to src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 index 0bce42cff..9e80908b9 100644 --- a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 +++ b/src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteById.ps1 @@ -24,8 +24,6 @@ param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory)] - [Alias('Organization')] - [Alias('User')] [string] $Owner, # The name of the repository without the .git extension. The name is not case sensitive. @@ -34,7 +32,6 @@ # The unique identifier of the rule suite result. To get this ID, you can use GET /repos/ { owner }/ { repo }/rulesets/rule-suites for repositories and GET /orgs/ { org }/rulesets/rule-suites for organizations. [Parameter(Mandatory)] - [Alias('RuleSuiteId')] [int] $ID, # The context to run the command in. Used to get the details for the API call. @@ -51,13 +48,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/rulesets/rule-suites/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } @@ -66,5 +63,3 @@ Write-Debug "[$stackPath] - End" } } - -#SkipTest:FunctionTest:Will add a test for this function in a future PR diff --git a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 b/src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 similarity index 94% rename from src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 rename to src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 index d124a6b77..629992264 100644 --- a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 +++ b/src/functions/private/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuiteList.ps1 @@ -32,8 +32,6 @@ param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory)] - [Alias('Organization')] - [Alias('User')] [string] $Owner, # The name of the repository without the .git extension. The name is not case sensitive. @@ -87,7 +85,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/rulesets/rule-suites" Body = $body @@ -95,7 +93,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } @@ -104,5 +102,3 @@ Write-Debug "[$stackPath] - End" } } - -#SkipTest:FunctionTest:Will add a test for this function in a future PR diff --git a/src/functions/private/Secrets/Get-GitHubSecretEnvironmentByName.ps1 b/src/functions/private/Secrets/Get-GitHubSecretEnvironmentByName.ps1 index 221c84b02..669765d48 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretEnvironmentByName.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretEnvironmentByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretEnvironmentByName { +function Get-GitHubSecretEnvironmentByName { <# .SYNOPSIS Get an environment secret. @@ -62,13 +62,13 @@ function Get-GitHubSecretEnvironmentByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/secrets/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubSecret]::new($_.Response, $Owner, $Repository, $Environment, $null) } } diff --git a/src/functions/private/Secrets/Get-GitHubSecretEnvironmentList.ps1 b/src/functions/private/Secrets/Get-GitHubSecretEnvironmentList.ps1 index 355a2ae73..6f63d6c15 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretEnvironmentList.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretEnvironmentList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretEnvironmentList { +function Get-GitHubSecretEnvironmentList { <# .SYNOPSIS List environment secrets. @@ -64,13 +64,13 @@ function Get-GitHubSecretEnvironmentList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/secrets" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.secrets | ForEach-Object { [GitHubSecret]::new($_, $Owner, $Repository, $Environment, $null) } diff --git a/src/functions/private/Secrets/Get-GitHubSecretFromOrganization.ps1 b/src/functions/private/Secrets/Get-GitHubSecretFromOrganization.ps1 index 1fa1784c9..67df30aa0 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretFromOrganization.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretFromOrganization.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretFromOrganization { +function Get-GitHubSecretFromOrganization { <# .SYNOPSIS List repository organization secrets. @@ -81,7 +81,7 @@ function Get-GitHubSecretFromOrganization { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/organization-secrets" PerPage = $PerPage @@ -89,7 +89,7 @@ function Get-GitHubSecretFromOrganization { } try { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.secrets | ForEach-Object { [GitHubSecret]::new($_, $Owner, $null, $null, $null) } diff --git a/src/functions/private/Secrets/Get-GitHubSecretOwnerByName.ps1 b/src/functions/private/Secrets/Get-GitHubSecretOwnerByName.ps1 index d5b44d7e7..97179d7fe 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretOwnerByName.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretOwnerByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretOwnerByName { +function Get-GitHubSecretOwnerByName { <# .SYNOPSIS Create or update an organization secret. @@ -55,13 +55,13 @@ function Get-GitHubSecretOwnerByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $selectedRepositories = @() if ($_.Response.visibility -eq 'selected') { $selectedRepositories = Get-GitHubSecretSelectedRepository -Owner $Owner -Name $_.Response.name -Context $Context diff --git a/src/functions/private/Secrets/Get-GitHubSecretOwnerList.ps1 b/src/functions/private/Secrets/Get-GitHubSecretOwnerList.ps1 index 81faca4af..0289fb8ea 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretOwnerList.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretOwnerList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretOwnerList { +function Get-GitHubSecretOwnerList { <# .SYNOPSIS List organization secrets. @@ -46,13 +46,13 @@ function Get-GitHubSecretOwnerList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/secrets" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.secrets | ForEach-Object { $selectedRepositories = @() if ($_.visibility -eq 'selected') { diff --git a/src/functions/private/Secrets/Get-GitHubSecretRepositoryByName.ps1 b/src/functions/private/Secrets/Get-GitHubSecretRepositoryByName.ps1 index 756365337..19ddfba4e 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretRepositoryByName.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretRepositoryByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretRepositoryByName { +function Get-GitHubSecretRepositoryByName { <# .SYNOPSIS Get a repository secret. @@ -51,13 +51,13 @@ function Get-GitHubSecretRepositoryByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/secrets/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubSecret]::new($_.Response, $Owner, $Repository, $null, $null) } } diff --git a/src/functions/private/Secrets/Get-GitHubSecretRepositoryList.ps1 b/src/functions/private/Secrets/Get-GitHubSecretRepositoryList.ps1 index 4914a29d8..26501bd19 100644 --- a/src/functions/private/Secrets/Get-GitHubSecretRepositoryList.ps1 +++ b/src/functions/private/Secrets/Get-GitHubSecretRepositoryList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubSecretRepositoryList { +function Get-GitHubSecretRepositoryList { <# .SYNOPSIS List repository secrets. @@ -60,13 +60,13 @@ function Get-GitHubSecretRepositoryList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/secrets" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.secrets | ForEach-Object { [GitHubSecret]::new($_, $Owner, $Repository, $null, $null) } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnEnvironment.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnEnvironment.ps1 index 0e359ae6a..0c0977b76 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnEnvironment.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnEnvironment.ps1 @@ -57,13 +57,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/secrets/public-key" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'actions', $Owner, $Repository, $Environment) } } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnOrganization.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnOrganization.ps1 index 9396d6797..6f6d799a8 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnOrganization.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnOrganization.ps1 @@ -50,13 +50,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/secrets/public-key" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'actions', $Owner, $null, $null) } } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnRepository.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnRepository.ps1 index 573fd6606..7812b8754 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnRepository.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForActionOnRepository.ps1 @@ -53,13 +53,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/secrets/public-key" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'actions', $Owner, $Repository, $null) } } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnOrganization.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnOrganization.ps1 index d8d246700..f93081abe 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnOrganization.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnOrganization.ps1 @@ -48,13 +48,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/codespaces/secrets/public-key" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'codespaces', $Owner, $null, $null) } } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnRepository.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnRepository.ps1 index 8a7f73276..a3ee90088 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnRepository.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnRepository.ps1 @@ -52,13 +52,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/codespaces/secrets/public-key" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'codespaces', $Owner, $Repository, $null) } } diff --git a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnUser.ps1 b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnUser.ps1 index b23a0ba96..27eb34411 100644 --- a/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnUser.ps1 +++ b/src/functions/private/Secrets/PublicKey/Get-GitHubPublicKeyForCodespacesOnUser.ps1 @@ -45,13 +45,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/codespaces/secrets/public-key' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubPublicKey]::new($_.Response, 'codespaces', $Context.UserName, $null, $null) } } diff --git a/src/functions/private/Secrets/Remove-GitHubSecretFromEnvironment.ps1 b/src/functions/private/Secrets/Remove-GitHubSecretFromEnvironment.ps1 index ef32f5e7f..a7838c887 100644 --- a/src/functions/private/Secrets/Remove-GitHubSecretFromEnvironment.ps1 +++ b/src/functions/private/Secrets/Remove-GitHubSecretFromEnvironment.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubSecretFromEnvironment { +function Remove-GitHubSecretFromEnvironment { <# .SYNOPSIS Delete an environment secret. @@ -47,14 +47,14 @@ function Remove-GitHubSecretFromEnvironment { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/secrets/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner/$Repository/$Environment]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Secrets/Remove-GitHubSecretFromOwner.ps1 b/src/functions/private/Secrets/Remove-GitHubSecretFromOwner.ps1 index c788c6c9b..7cdb07809 100644 --- a/src/functions/private/Secrets/Remove-GitHubSecretFromOwner.ps1 +++ b/src/functions/private/Secrets/Remove-GitHubSecretFromOwner.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubSecretFromOwner { +function Remove-GitHubSecretFromOwner { <# .SYNOPSIS Delete an organization secret. @@ -40,14 +40,14 @@ function Remove-GitHubSecretFromOwner { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Secrets/Remove-GitHubSecretFromRepository.ps1 b/src/functions/private/Secrets/Remove-GitHubSecretFromRepository.ps1 index d029c8705..a6cce0757 100644 --- a/src/functions/private/Secrets/Remove-GitHubSecretFromRepository.ps1 +++ b/src/functions/private/Secrets/Remove-GitHubSecretFromRepository.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubSecretFromRepository { +function Remove-GitHubSecretFromRepository { <# .SYNOPSIS Delete a repository secret. @@ -43,14 +43,14 @@ function Remove-GitHubSecretFromRepository { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/actions/secrets/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner/$Repository]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Secrets/Set-GitHubSecretOnEnvironment.ps1 b/src/functions/private/Secrets/Set-GitHubSecretOnEnvironment.ps1 index c4d2549a5..1631f4c88 100644 --- a/src/functions/private/Secrets/Set-GitHubSecretOnEnvironment.ps1 +++ b/src/functions/private/Secrets/Set-GitHubSecretOnEnvironment.ps1 @@ -1,4 +1,4 @@ -function Set-GitHubSecretOnEnvironment { +function Set-GitHubSecretOnEnvironment { <# .SYNOPSIS Create or update an environment secret. @@ -71,7 +71,7 @@ function Set-GitHubSecretOnEnvironment { key_id = $KeyID } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/secrets/$Name" Body = $body @@ -79,7 +79,7 @@ function Set-GitHubSecretOnEnvironment { } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner/$Repository/$Environment]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Secrets/Set-GitHubSecretOnOwner.ps1 b/src/functions/private/Secrets/Set-GitHubSecretOnOwner.ps1 index 8d4c5f16e..705c87672 100644 --- a/src/functions/private/Secrets/Set-GitHubSecretOnOwner.ps1 +++ b/src/functions/private/Secrets/Set-GitHubSecretOnOwner.ps1 @@ -1,4 +1,4 @@ -function Set-GitHubSecretOnOwner { +function Set-GitHubSecretOnOwner { <# .SYNOPSIS Create or update an organization secret. @@ -78,7 +78,7 @@ function Set-GitHubSecretOnOwner { $body['selected_repository_ids'] = $SelectedRepositories } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name" Body = $body @@ -86,7 +86,7 @@ function Set-GitHubSecretOnOwner { } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Secrets/Set-GitHubSecretOnRepository.ps1 b/src/functions/private/Secrets/Set-GitHubSecretOnRepository.ps1 index 075ae07da..4ae7efdb1 100644 --- a/src/functions/private/Secrets/Set-GitHubSecretOnRepository.ps1 +++ b/src/functions/private/Secrets/Set-GitHubSecretOnRepository.ps1 @@ -1,4 +1,4 @@ -function Set-GitHubSecretOnRepository { +function Set-GitHubSecretOnRepository { <# .SYNOPSIS Create or update a repository secret. @@ -59,7 +59,7 @@ function Set-GitHubSecretOnRepository { key_id = $KeyID } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Repository/actions/secrets/$Name" Body = $body @@ -67,7 +67,7 @@ function Set-GitHubSecretOnRepository { } if ($PSCmdlet.ShouldProcess("secret [$Name] on [$Owner/$Repository]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 b/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 index abab4ded6..0b4b10245 100644 --- a/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 +++ b/src/functions/private/Teams/Get-GitHubTeamBySlug.ps1 @@ -38,13 +38,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/teams/$Slug" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubTeam]::new($_.Response, $Organization) } } diff --git a/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 b/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 index cc9874234..7060a63c8 100644 --- a/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 +++ b/src/functions/private/Teams/Get-GitHubTeamListByOrg.ps1 @@ -36,13 +36,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/teams" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($team in $_.Response) { [GitHubTeam]::new($team, $Organization) } diff --git a/src/functions/private/Teams/Get-GitHubTeamListByRepo.ps1 b/src/functions/private/Teams/Get-GitHubTeamListByRepo.ps1 index c08ae0015..f01f277f1 100644 --- a/src/functions/private/Teams/Get-GitHubTeamListByRepo.ps1 +++ b/src/functions/private/Teams/Get-GitHubTeamListByRepo.ps1 @@ -50,13 +50,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/teams" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($team in $_.Response) { [GitHubTeam]::new($team, $Organization) } diff --git a/src/functions/private/Users/Blocking/Block-GitHubUserByUser.ps1 b/src/functions/private/Users/Blocking/Block-GitHubUserByUser.ps1 index 5ded734db..5333cc35a 100644 --- a/src/functions/private/Users/Blocking/Block-GitHubUserByUser.ps1 +++ b/src/functions/private/Users/Blocking/Block-GitHubUserByUser.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/user/blocks/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Users/Blocking/Get-GitHubBlockedUserByUser.ps1 b/src/functions/private/Users/Blocking/Get-GitHubBlockedUserByUser.ps1 index 14ff2fabd..617a42038 100644 --- a/src/functions/private/Users/Blocking/Get-GitHubBlockedUserByUser.ps1 +++ b/src/functions/private/Users/Blocking/Get-GitHubBlockedUserByUser.ps1 @@ -34,14 +34,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/blocks' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Blocking/Test-GitHubBlockedUserByUser.ps1 b/src/functions/private/Users/Blocking/Test-GitHubBlockedUserByUser.ps1 index 13947ca01..ee8eaf8ff 100644 --- a/src/functions/private/Users/Blocking/Test-GitHubBlockedUserByUser.ps1 +++ b/src/functions/private/Users/Blocking/Test-GitHubBlockedUserByUser.ps1 @@ -46,14 +46,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/user/blocks/$Username" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Users/Blocking/Unblock-GitHubUserByUser.ps1 b/src/functions/private/Users/Blocking/Unblock-GitHubUserByUser.ps1 index 1a476643d..f4270adc8 100644 --- a/src/functions/private/Users/Blocking/Unblock-GitHubUserByUser.ps1 +++ b/src/functions/private/Users/Blocking/Unblock-GitHubUserByUser.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/user/blocks/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Users/Emails/Get-GitHubUserAllEmail.ps1 b/src/functions/private/Users/Emails/Get-GitHubUserAllEmail.ps1 index 74b7b21af..7a1167828 100644 --- a/src/functions/private/Users/Emails/Get-GitHubUserAllEmail.ps1 +++ b/src/functions/private/Users/Emails/Get-GitHubUserAllEmail.ps1 @@ -36,14 +36,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/emails' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Emails/Get-GitHubUserPublicEmail.ps1 b/src/functions/private/Users/Emails/Get-GitHubUserPublicEmail.ps1 index 568c3d93d..f5e67b6f0 100644 --- a/src/functions/private/Users/Emails/Get-GitHubUserPublicEmail.ps1 +++ b/src/functions/private/Users/Emails/Get-GitHubUserPublicEmail.ps1 @@ -38,14 +38,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/public_emails' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Followers/Get-GitHubUserFollowersOfUser.ps1 b/src/functions/private/Users/Followers/Get-GitHubUserFollowersOfUser.ps1 index 651c3cffe..abd3a7431 100644 --- a/src/functions/private/Users/Followers/Get-GitHubUserFollowersOfUser.ps1 +++ b/src/functions/private/Users/Followers/Get-GitHubUserFollowersOfUser.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/followers" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Followers/Get-GitHubUserFollowingMe.ps1 b/src/functions/private/Users/Followers/Get-GitHubUserFollowingMe.ps1 index 0664785b2..15509b6cc 100644 --- a/src/functions/private/Users/Followers/Get-GitHubUserFollowingMe.ps1 +++ b/src/functions/private/Users/Followers/Get-GitHubUserFollowingMe.ps1 @@ -35,14 +35,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/following' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Followers/Get-GitHubUserFollowingUser.ps1 b/src/functions/private/Users/Followers/Get-GitHubUserFollowingUser.ps1 index 0618427b9..c75ff3e0b 100644 --- a/src/functions/private/Users/Followers/Get-GitHubUserFollowingUser.ps1 +++ b/src/functions/private/Users/Followers/Get-GitHubUserFollowingUser.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/following" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Followers/Get-GitHubUserMyFollower.ps1 b/src/functions/private/Users/Followers/Get-GitHubUserMyFollower.ps1 index 9b4de061e..e2244e1d7 100644 --- a/src/functions/private/Users/Followers/Get-GitHubUserMyFollower.ps1 +++ b/src/functions/private/Users/Followers/Get-GitHubUserMyFollower.ps1 @@ -39,14 +39,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/followers' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Followers/Test-GitHubUserFollowedByMe.ps1 b/src/functions/private/Users/Followers/Test-GitHubUserFollowedByMe.ps1 index 5b9c2eb3a..c196d68c8 100644 --- a/src/functions/private/Users/Followers/Test-GitHubUserFollowedByMe.ps1 +++ b/src/functions/private/Users/Followers/Test-GitHubUserFollowedByMe.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/user/following/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Users/Followers/Test-GitHubUserFollowedByUser.ps1 b/src/functions/private/Users/Followers/Test-GitHubUserFollowedByUser.ps1 index 711d6bd38..56ab102f8 100644 --- a/src/functions/private/Users/Followers/Test-GitHubUserFollowedByUser.ps1 +++ b/src/functions/private/Users/Followers/Test-GitHubUserFollowedByUser.ps1 @@ -45,13 +45,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/following/$Follows" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/private/Users/GPG-Keys/Get-GitHubUserGpgKeyForUser.ps1 b/src/functions/private/Users/GPG-Keys/Get-GitHubUserGpgKeyForUser.ps1 index 49a2d0671..826bc9d9b 100644 --- a/src/functions/private/Users/GPG-Keys/Get-GitHubUserGpgKeyForUser.ps1 +++ b/src/functions/private/Users/GPG-Keys/Get-GitHubUserGpgKeyForUser.ps1 @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/gpg_keys" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKey.ps1 b/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKey.ps1 index 6e43c8912..6b9ecbde3 100644 --- a/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKey.ps1 +++ b/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKey.ps1 @@ -37,14 +37,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/gpg_keys' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKeyById.ps1 b/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKeyById.ps1 index b08e22e64..62594cd6e 100644 --- a/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKeyById.ps1 +++ b/src/functions/private/Users/GPG-Keys/Get-GitHubUserMyGpgKeyById.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/user/gpg_keys/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Get-GitHubAllUser.ps1 b/src/functions/private/Users/Get-GitHubAllUser.ps1 index 9a1d1283d..4d5f3c849 100644 --- a/src/functions/private/Users/Get-GitHubAllUser.ps1 +++ b/src/functions/private/Users/Get-GitHubAllUser.ps1 @@ -49,7 +49,7 @@ since = $Since } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/users' Body = $body @@ -57,14 +57,14 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { - if ($_.type -eq 'Organization') { - [GitHubOrganization]::New($_) - } elseif ($_.type -eq 'User') { - [GitHubUser]::New($_) + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($account in $_.Response) { + if ($account.type -eq 'Organization') { + [GitHubOrganization]::New($account, $Context) + } elseif ($account.type -eq 'User') { + [GitHubUser]::New($account) } else { - [GitHubOwner]::New($_) + [GitHubOwner]::New($account) } } } diff --git a/src/functions/private/Users/Get-GitHubMyUser.ps1 b/src/functions/private/Users/Get-GitHubMyUser.ps1 index c3da62c86..af5308aed 100644 --- a/src/functions/private/Users/Get-GitHubMyUser.ps1 +++ b/src/functions/private/Users/Get-GitHubMyUser.ps1 @@ -36,15 +36,15 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { if ($_.Response.type -eq 'Organization') { - [GitHubOrganization]::New($_.Response) + [GitHubOrganization]::New($_.Response, $Context) } elseif ($_.Response.type -eq 'User') { [GitHubUser]::New($_.Response) } else { diff --git a/src/functions/private/Users/Get-GitHubUserByName.ps1 b/src/functions/private/Users/Get-GitHubUserByName.ps1 index 51b5d52cb..ae3e644af 100644 --- a/src/functions/private/Users/Get-GitHubUserByName.ps1 +++ b/src/functions/private/Users/Get-GitHubUserByName.ps1 @@ -53,20 +53,24 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - if ($_.Response.type -eq 'Organization') { - [GitHubOrganization]::New($_.Response) - } elseif ($_.Response.type -eq 'User') { - [GitHubUser]::New($_.Response) - } else { - [GitHubOwner]::New($_.Response) + try { + Invoke-GitHubAPI @apiParams | ForEach-Object { + if ($_.Response.type -eq 'Organization') { + [GitHubOrganization]::New($_.Response, $Context) + } elseif ($_.Response.type -eq 'User') { + [GitHubUser]::New($_.Response) + } else { + [GitHubOwner]::New($_.Response) + } } + } catch { + return } } diff --git a/src/functions/private/Users/Keys/Get-GitHubUserKeyForUser.ps1 b/src/functions/private/Users/Keys/Get-GitHubUserKeyForUser.ps1 index fe8e45150..2bab33bb4 100644 --- a/src/functions/private/Users/Keys/Get-GitHubUserKeyForUser.ps1 +++ b/src/functions/private/Users/Keys/Get-GitHubUserKeyForUser.ps1 @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/keys" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Keys/Get-GitHubUserMyKey.ps1 b/src/functions/private/Users/Keys/Get-GitHubUserMyKey.ps1 index bbb59e5c7..305e86323 100644 --- a/src/functions/private/Users/Keys/Get-GitHubUserMyKey.ps1 +++ b/src/functions/private/Users/Keys/Get-GitHubUserMyKey.ps1 @@ -37,14 +37,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/keys' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Keys/Get-GitHubUserMyKeyById.ps1 b/src/functions/private/Users/Keys/Get-GitHubUserMyKeyById.ps1 index b46ce6ad7..41e32fb4a 100644 --- a/src/functions/private/Users/Keys/Get-GitHubUserMyKeyById.ps1 +++ b/src/functions/private/Users/Keys/Get-GitHubUserMyKeyById.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/user/keys/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKey.ps1 b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKey.ps1 index e1a87c449..a74b4ea19 100644 --- a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKey.ps1 +++ b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKey.ps1 @@ -37,14 +37,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/ssh_signing_keys' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKeyById.ps1 b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKeyById.ps1 index 34b13c546..c2571f0e5 100644 --- a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKeyById.ps1 +++ b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserMySigningKeyById.ps1 @@ -41,13 +41,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/user/ssh_signing_keys/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserSigningKeyForUser.ps1 b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserSigningKeyForUser.ps1 index ae39b0a41..b17735e9e 100644 --- a/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserSigningKeyForUser.ps1 +++ b/src/functions/private/Users/SSH-Signing-Keys/Get-GitHubUserSigningKeyForUser.ps1 @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/ssh_signing_keys" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Social-Accounts/Get-GitHubMyUserSocials.ps1 b/src/functions/private/Users/Social-Accounts/Get-GitHubMyUserSocials.ps1 index ddb1e7c30..1f4eee04f 100644 --- a/src/functions/private/Users/Social-Accounts/Get-GitHubMyUserSocials.ps1 +++ b/src/functions/private/Users/Social-Accounts/Get-GitHubMyUserSocials.ps1 @@ -35,14 +35,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/user/social_accounts' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Users/Social-Accounts/Get-GitHubUserSocialsByName.ps1 b/src/functions/private/Users/Social-Accounts/Get-GitHubUserSocialsByName.ps1 index 876314349..acaea233b 100644 --- a/src/functions/private/Users/Social-Accounts/Get-GitHubUserSocialsByName.ps1 +++ b/src/functions/private/Users/Social-Accounts/Get-GitHubUserSocialsByName.ps1 @@ -39,13 +39,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/users/$Username/social_accounts" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/private/Utilities/PowerShell/Test-GitHubAzPowerShell.ps1 b/src/functions/private/Utilities/PowerShell/Test-GitHubAzPowerShell.ps1 new file mode 100644 index 000000000..54aa9cf2c --- /dev/null +++ b/src/functions/private/Utilities/PowerShell/Test-GitHubAzPowerShell.ps1 @@ -0,0 +1,66 @@ +function Test-GitHubAzPowerShell { + <# + .SYNOPSIS + Tests if Azure PowerShell module is installed and authenticated. + + .DESCRIPTION + This function checks if the Azure PowerShell module (Az) is installed and the user is authenticated. + It verifies both the availability of the module and the authentication status. + + .EXAMPLE + Test-GitHubAzPowerShell + + Returns $true if Azure PowerShell module is installed and authenticated, $false otherwise. + + .OUTPUTS + [bool] + Returns $true if Azure PowerShell module is installed and authenticated, $false otherwise. + + .NOTES + This function is used internally by other GitHub module functions that require Azure PowerShell authentication, + such as Azure Key Vault operations for GitHub App JWT signing. + #> + [OutputType([bool])] + [CmdletBinding()] + param() + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + try { + # Check if Azure PowerShell module is installed + $azModule = Get-Module -Name 'Az.Accounts' -ListAvailable -ErrorAction SilentlyContinue + if (-not $azModule) { + Write-Debug "[$stackPath] - Azure PowerShell module (Az.Accounts) not found" + return $false + } + + # Check if the module is imported + $importedModule = Get-Module -Name 'Az.Accounts' -ErrorAction SilentlyContinue + if (-not $importedModule) { + Write-Debug "[$stackPath] - Attempting to import Az.Accounts module" + Import-Module -Name 'Az.Accounts' -ErrorAction SilentlyContinue + } + + # Check if user is authenticated by trying to get current context + $context = Get-AzContext -ErrorAction SilentlyContinue + if (-not $context -or [string]::IsNullOrEmpty($context.Account)) { + Write-Debug "[$stackPath] - Azure PowerShell authentication failed or no account logged in" + return $false + } + + Write-Debug "[$stackPath] - Azure PowerShell is installed and authenticated (Account: $($context.Account.Id))" + return $true + } catch { + Write-Debug "[$stackPath] - Error checking Azure PowerShell: $($_.Exception.Message)" + return $false + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Utilities/PowerShell/Test-GitHubAzureCLI.ps1 b/src/functions/private/Utilities/PowerShell/Test-GitHubAzureCLI.ps1 new file mode 100644 index 000000000..19a6db1f1 --- /dev/null +++ b/src/functions/private/Utilities/PowerShell/Test-GitHubAzureCLI.ps1 @@ -0,0 +1,65 @@ +function Test-GitHubAzureCLI { + <# + .SYNOPSIS + Tests if Azure CLI is installed and authenticated. + + .DESCRIPTION + This function checks if Azure CLI (az) is installed and the user is authenticated. + It verifies both the availability of the CLI tool and the authentication status. + + .EXAMPLE + Test-GitHubAzureCLI + + Returns $true if Azure CLI is installed and authenticated, $false otherwise. + + .OUTPUTS + bool + + .NOTES + This function is used internally by other GitHub module functions that require Azure CLI authentication, + such as Azure Key Vault operations for GitHub App JWT signing. + #> + [OutputType([bool])] + [CmdletBinding()] + param() + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } + + process { + try { + # Check if Azure CLI is installed + $azCommand = Get-Command -Name 'az' -ErrorAction SilentlyContinue + if (-not $azCommand) { + Write-Debug "[$stackPath] - Azure CLI (az) command not found" + return $false + } + + # Check if user is authenticated by trying to get account info + $accountInfo = az account show --output json 2>$null + if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrEmpty($accountInfo)) { + Write-Debug "[$stackPath] - Azure CLI authentication failed or no account logged in" + return $false + } + + # Parse the account info to ensure it's valid + $account = $accountInfo | ConvertFrom-Json -ErrorAction SilentlyContinue + if (-not $account -or [string]::IsNullOrEmpty($account.id)) { + Write-Debug "[$stackPath] - Azure CLI account information is invalid" + return $false + } + + Write-Debug "[$stackPath] - Azure CLI is installed and authenticated (Account: $($account.id))" + return $true + } catch { + Write-Debug "[$stackPath] - Error checking Azure CLI: $($_.Exception.Message)" + return $false + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/private/Variables/Get-GitHubVariableEnvironmentByName.ps1 b/src/functions/private/Variables/Get-GitHubVariableEnvironmentByName.ps1 index 13525ad59..69812c051 100644 --- a/src/functions/private/Variables/Get-GitHubVariableEnvironmentByName.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableEnvironmentByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableEnvironmentByName { +function Get-GitHubVariableEnvironmentByName { <# .SYNOPSIS Retrieves a specific variable from a GitHub repository. @@ -64,13 +64,13 @@ function Get-GitHubVariableEnvironmentByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/variables/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubVariable]::new($_.Response, $Owner, $Repository, $Environment, $null) } } diff --git a/src/functions/private/Variables/Get-GitHubVariableEnvironmentList.ps1 b/src/functions/private/Variables/Get-GitHubVariableEnvironmentList.ps1 index c66ee2a83..a3241f63c 100644 --- a/src/functions/private/Variables/Get-GitHubVariableEnvironmentList.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableEnvironmentList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableEnvironmentList { +function Get-GitHubVariableEnvironmentList { <# .SYNOPSIS Retrieves all variables for a specified environment in a GitHub repository. @@ -66,13 +66,13 @@ function Get-GitHubVariableEnvironmentList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/variables" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.variables | ForEach-Object { [GitHubVariable]::new($_, $Owner, $Repository, $Environment, $null) } diff --git a/src/functions/private/Variables/Get-GitHubVariableFromOrganization.ps1 b/src/functions/private/Variables/Get-GitHubVariableFromOrganization.ps1 index b8336dbc5..74b0cd0b3 100644 --- a/src/functions/private/Variables/Get-GitHubVariableFromOrganization.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableFromOrganization.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableFromOrganization { +function Get-GitHubVariableFromOrganization { <# .SYNOPSIS List repository organization variables. @@ -84,7 +84,7 @@ function Get-GitHubVariableFromOrganization { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/organization-variables" PerPage = $PerPage @@ -92,7 +92,7 @@ function Get-GitHubVariableFromOrganization { } try { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.variables | ForEach-Object { [GitHubVariable]::new($_, $Owner, $null, $null, $null) } diff --git a/src/functions/private/Variables/Get-GitHubVariableOwnerByName.ps1 b/src/functions/private/Variables/Get-GitHubVariableOwnerByName.ps1 index 8b60db7db..a222c70d7 100644 --- a/src/functions/private/Variables/Get-GitHubVariableOwnerByName.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableOwnerByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableOwnerByName { +function Get-GitHubVariableOwnerByName { <# .SYNOPSIS Get an organization variable. @@ -54,13 +54,13 @@ function Get-GitHubVariableOwnerByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/variables/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $selectedRepositories = @() if ($_.Response.visibility -eq 'selected') { $selectedRepositories = Get-GitHubVariableSelectedRepository -Owner $Owner -Name $_.Response.name -Context $Context diff --git a/src/functions/private/Variables/Get-GitHubVariableOwnerList.ps1 b/src/functions/private/Variables/Get-GitHubVariableOwnerList.ps1 index 08767d625..892ad9eff 100644 --- a/src/functions/private/Variables/Get-GitHubVariableOwnerList.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableOwnerList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableOwnerList { +function Get-GitHubVariableOwnerList { <# .SYNOPSIS List organization variables @@ -77,13 +77,13 @@ function Get-GitHubVariableOwnerList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/variables" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.variables | ForEach-Object { $selectedRepositories = @() if ($_.visibility -eq 'selected') { diff --git a/src/functions/private/Variables/Get-GitHubVariableRepositoryByName.ps1 b/src/functions/private/Variables/Get-GitHubVariableRepositoryByName.ps1 index a1a23457a..20865a359 100644 --- a/src/functions/private/Variables/Get-GitHubVariableRepositoryByName.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableRepositoryByName.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableRepositoryByName { +function Get-GitHubVariableRepositoryByName { <# .SYNOPSIS Get a repository variable @@ -53,13 +53,13 @@ function Get-GitHubVariableRepositoryByName { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/variables/$Name" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubVariable]::new($_.Response, $Owner, $Repository, $null, $null) } } diff --git a/src/functions/private/Variables/Get-GitHubVariableRepositoryList.ps1 b/src/functions/private/Variables/Get-GitHubVariableRepositoryList.ps1 index ff2e39cb6..d38e51eec 100644 --- a/src/functions/private/Variables/Get-GitHubVariableRepositoryList.ps1 +++ b/src/functions/private/Variables/Get-GitHubVariableRepositoryList.ps1 @@ -1,4 +1,4 @@ -function Get-GitHubVariableRepositoryList { +function Get-GitHubVariableRepositoryList { <# .SYNOPSIS List repository variables. @@ -62,13 +62,13 @@ function Get-GitHubVariableRepositoryList { } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/variables" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.variables | ForEach-Object { [GitHubVariable]::new($_, $Owner, $Repository, $null, $null) } diff --git a/src/functions/private/Variables/New-GitHubVariableOnEnvironment.ps1 b/src/functions/private/Variables/New-GitHubVariableOnEnvironment.ps1 index 607282a45..8b3f764ec 100644 --- a/src/functions/private/Variables/New-GitHubVariableOnEnvironment.ps1 +++ b/src/functions/private/Variables/New-GitHubVariableOnEnvironment.ps1 @@ -1,4 +1,4 @@ -function New-GitHubVariableOnEnvironment { +function New-GitHubVariableOnEnvironment { <# .SYNOPSIS Create an environment variable. @@ -65,7 +65,7 @@ function New-GitHubVariableOnEnvironment { value = $Value } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/variables" Body = $body @@ -73,7 +73,7 @@ function New-GitHubVariableOnEnvironment { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository/$Environment]", 'Create')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/New-GitHubVariableOnOwner.ps1 b/src/functions/private/Variables/New-GitHubVariableOnOwner.ps1 index e705f543d..5ba36f62e 100644 --- a/src/functions/private/Variables/New-GitHubVariableOnOwner.ps1 +++ b/src/functions/private/Variables/New-GitHubVariableOnOwner.ps1 @@ -1,4 +1,4 @@ -function New-GitHubVariableOnOwner { +function New-GitHubVariableOnOwner { <# .SYNOPSIS Create an organization variable. @@ -72,7 +72,7 @@ function New-GitHubVariableOnOwner { $body['selected_repository_ids'] = $SelectedRepositories } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/orgs/$Owner/actions/variables" Body = $body @@ -80,7 +80,7 @@ function New-GitHubVariableOnOwner { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner]", 'Create')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/New-GitHubVariableOnRepository.ps1 b/src/functions/private/Variables/New-GitHubVariableOnRepository.ps1 index 774d65826..32899700f 100644 --- a/src/functions/private/Variables/New-GitHubVariableOnRepository.ps1 +++ b/src/functions/private/Variables/New-GitHubVariableOnRepository.ps1 @@ -1,4 +1,4 @@ -function New-GitHubVariableOnRepository { +function New-GitHubVariableOnRepository { <# .SYNOPSIS Create a repository variable. @@ -53,7 +53,7 @@ function New-GitHubVariableOnRepository { value = $Value } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/actions/variables" Body = $body @@ -61,7 +61,7 @@ function New-GitHubVariableOnRepository { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository]", 'Create')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Remove-GitHubVariableFromEnvironment.ps1 b/src/functions/private/Variables/Remove-GitHubVariableFromEnvironment.ps1 index bb8fc08f2..f09b9fc8a 100644 --- a/src/functions/private/Variables/Remove-GitHubVariableFromEnvironment.ps1 +++ b/src/functions/private/Variables/Remove-GitHubVariableFromEnvironment.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubVariableFromEnvironment { +function Remove-GitHubVariableFromEnvironment { <# .SYNOPSIS Delete an environment variable. @@ -48,14 +48,14 @@ function Remove-GitHubVariableFromEnvironment { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/variables/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository/$Environment]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Remove-GitHubVariableFromOwner.ps1 b/src/functions/private/Variables/Remove-GitHubVariableFromOwner.ps1 index bfa1df9ac..885416444 100644 --- a/src/functions/private/Variables/Remove-GitHubVariableFromOwner.ps1 +++ b/src/functions/private/Variables/Remove-GitHubVariableFromOwner.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubVariableFromOwner { +function Remove-GitHubVariableFromOwner { <# .SYNOPSIS Delete an organization variable. @@ -41,14 +41,14 @@ function Remove-GitHubVariableFromOwner { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Owner/actions/variables/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Remove-GitHubVariableFromRepository.ps1 b/src/functions/private/Variables/Remove-GitHubVariableFromRepository.ps1 index 0cf9c7ec2..3822bb9ba 100644 --- a/src/functions/private/Variables/Remove-GitHubVariableFromRepository.ps1 +++ b/src/functions/private/Variables/Remove-GitHubVariableFromRepository.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubVariableFromRepository { +function Remove-GitHubVariableFromRepository { <# .SYNOPSIS Delete a repository variable. @@ -44,14 +44,14 @@ function Remove-GitHubVariableFromRepository { } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/actions/variables/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Update-GitHubVariableOnEnvironment.ps1 b/src/functions/private/Variables/Update-GitHubVariableOnEnvironment.ps1 index 568f43d9d..53a21c906 100644 --- a/src/functions/private/Variables/Update-GitHubVariableOnEnvironment.ps1 +++ b/src/functions/private/Variables/Update-GitHubVariableOnEnvironment.ps1 @@ -1,4 +1,4 @@ -function Update-GitHubVariableOnEnvironment { +function Update-GitHubVariableOnEnvironment { <# .SYNOPSIS Update an environment variable. @@ -73,7 +73,7 @@ function Update-GitHubVariableOnEnvironment { $body.value = $Value } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/repos/$Owner/$Repository/environments/$Environment/variables/$Name" Body = $body @@ -81,7 +81,7 @@ function Update-GitHubVariableOnEnvironment { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository/$Environment]", 'Update')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Update-GitHubVariableOnOwner.ps1 b/src/functions/private/Variables/Update-GitHubVariableOnOwner.ps1 index fc8d5d60f..3463b8b87 100644 --- a/src/functions/private/Variables/Update-GitHubVariableOnOwner.ps1 +++ b/src/functions/private/Variables/Update-GitHubVariableOnOwner.ps1 @@ -1,4 +1,4 @@ -function Update-GitHubVariableOnOwner { +function Update-GitHubVariableOnOwner { <# .SYNOPSIS Update an organization variable. @@ -84,7 +84,7 @@ function Update-GitHubVariableOnOwner { $body['selected_repository_ids'] = $SelectedRepositories } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/orgs/$Owner/actions/variables/$Name" Body = $body @@ -92,7 +92,7 @@ function Update-GitHubVariableOnOwner { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner]", 'Update')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Variables/Update-GitHubVariableOnRepository.ps1 b/src/functions/private/Variables/Update-GitHubVariableOnRepository.ps1 index 3e1e59b42..d35bd664e 100644 --- a/src/functions/private/Variables/Update-GitHubVariableOnRepository.ps1 +++ b/src/functions/private/Variables/Update-GitHubVariableOnRepository.ps1 @@ -1,4 +1,4 @@ -function Update-GitHubVariableOnRepository { +function Update-GitHubVariableOnRepository { <# .SYNOPSIS Update a repository variable. @@ -60,7 +60,7 @@ function Update-GitHubVariableOnRepository { $body.value = $Value } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/repos/$Owner/$Repository/actions/variables/$Name" Body = $body @@ -68,7 +68,7 @@ function Update-GitHubVariableOnRepository { } if ($PSCmdlet.ShouldProcess("variable [$Name] on [$Owner/$Repository]", 'Update')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByID.ps1 b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByID.ps1 index 042cfc640..a9aab7b78 100644 --- a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByID.ps1 +++ b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByID.ps1 @@ -40,13 +40,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/app/hook/deliveries/$ID" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubWebhookDelivery]::new($_.Response) } } diff --git a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByList.ps1 b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByList.ps1 index e418768af..8f0dac867 100644 --- a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByList.ps1 +++ b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryByList.ps1 @@ -39,14 +39,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/app/hook/deliveries' PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response | ForEach-Object { [GitHubWebhookDelivery]@{ ID = $_.id diff --git a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryToRedeliver.ps1 b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryToRedeliver.ps1 index 923c6f9d3..957dad479 100644 --- a/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryToRedeliver.ps1 +++ b/src/functions/private/Webhooks/Get-GitHubAppWebhookDeliveryToRedeliver.ps1 @@ -34,7 +34,7 @@ } process { - $checkPoint = (Get-Date).AddHours($TimeSpan) + $checkPoint = ([DateTime]::Now).AddHours($TimeSpan) Get-GitHubAppWebhookDeliveryByList -Context $Context -PerPage $PerPage | Where-Object { $_.DeliveredAt -gt $checkPoint } | Group-Object -Property GUID | Where-Object { $_.Group.Status -notcontains 'OK' } | ForEach-Object { $refObject = $_.Group | Sort-Object -Property DeliveredAt diff --git a/src/functions/public/API/Invoke-GitHubAPI.ps1 b/src/functions/public/API/Invoke-GitHubAPI.ps1 index 638c72f81..2c4d9279b 100644 --- a/src/functions/public/API/Invoke-GitHubAPI.ps1 +++ b/src/functions/public/API/Invoke-GitHubAPI.ps1 @@ -120,7 +120,6 @@ function Invoke-GitHubAPI { process { $Token = $Context.Token - $HttpVersion = Resolve-GitHubContextSetting -Name 'HttpVersion' -Value $HttpVersion -Context $Context $ApiBaseUri = Resolve-GitHubContextSetting -Name 'ApiBaseUri' -Value $ApiBaseUri -Context $Context $ApiVersion = Resolve-GitHubContextSetting -Name 'ApiVersion' -Value $ApiVersion -Context $Context @@ -136,23 +135,11 @@ function Invoke-GitHubAPI { TokenType = $TokenType } | Format-List | Out-String -Stream | ForEach-Object { Write-Debug $_ } } - $jwt = $null - switch ($TokenType) { - 'ghu' { - if (Test-GitHubAccessTokenRefreshRequired -Context $Context) { - $Token = Update-GitHubUserAccessToken -Context $Context -PassThru - } - } - 'PEM' { - $jwt = Get-GitHubAppJSONWebToken -ClientId $Context.ClientID -PrivateKey $Context.Token - $Token = $jwt.Token - } - } $headers = @{ Accept = $Accept 'X-GitHub-Api-Version' = $ApiVersion - 'User-Agent' = "PSModule.GitHub $($script:PSModuleInfo.ModuleVersion)" + 'User-Agent' = $script:UserAgent } $headers | Remove-HashtableEntry -NullOrEmptyValues @@ -209,20 +196,6 @@ function Invoke-GitHubAPI { Write-Debug '----------------------------------' } do { - switch ($TokenType) { - 'ghu' { - if (Test-GitHubAccessTokenRefreshRequired -Context $Context) { - $Token = Update-GitHubUserAccessToken -Context $Context -PassThru - } - } - 'PEM' { - if ($jwt.ExpiresAt -lt (Get-Date)) { - $jwt = Get-GitHubAppJSONWebToken -ClientId $Context.ClientID -PrivateKey $Context.Token - $Token = $jwt.Token - $APICall['Token'] = $Token - } - } - } $response = Invoke-WebRequest @APICall -ProgressAction 'SilentlyContinue' -Debug:$false -Verbose:$false $headers = @{} @@ -342,6 +315,10 @@ function Invoke-GitHubAPI { } $exception = @" + +---------------------------------- +Context: +$($Context | Format-List | Out-String) ---------------------------------- Request: $([pscustomobject]$APICall | Select-Object -ExcludeProperty Body, Headers | Format-List | Out-String) diff --git a/src/functions/public/API/Invoke-GitHubGraphQLQuery.ps1 b/src/functions/public/API/Invoke-GitHubGraphQLQuery.ps1 index 1c62e85a8..6da9cd426 100644 --- a/src/functions/public/API/Invoke-GitHubGraphQLQuery.ps1 +++ b/src/functions/public/API/Invoke-GitHubGraphQLQuery.ps1 @@ -44,7 +44,7 @@ variables = $Variables } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/graphql' Body = $body @@ -52,7 +52,7 @@ } try { - $apiResponse = Invoke-GitHubAPI @inputObject + $apiResponse = Invoke-GitHubAPI @apiParams $graphQLResponse = $apiResponse.Response # Handle GraphQL-specific errors (200 OK with errors in response) if ($graphQLResponse.errors) { @@ -60,21 +60,23 @@ $queryLines = $Query -split "`n" | ForEach-Object { $_.Trim() } foreach ($errorItem in $graphQLResponse.errors) { $errorMessages += @" + +GraphQL errors occurred: +Full Error: +$($errorItem | ConvertTo-Json -Depth 10 | Out-String) + GraphQL Error [$($errorItem.type)]: Message: $($errorItem.message) Path: $($errorItem.path -join '/') Locations: $($errorItem.locations | ForEach-Object { " - [$($_.line):$($_.column)] - $($queryLines[$_.line - 1])" }) -Full Error: -$($errorItem | ConvertTo-Json -Depth 10 | Out-String -Stream) - "@ } $PSCmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( - [System.Exception]::new("GraphQL errors occurred:`n$($errorMessages -join "`n`n")"), + [System.Exception]::new($errorMessages), 'GraphQLError', [System.Management.Automation.ErrorCategory]::InvalidOperation, $graphQLResponse diff --git a/src/functions/public/Apps/GitHub App Installations/Add-GitHubAppInstallationRepositoryAccess.ps1 b/src/functions/public/Apps/GitHub App Installations/Add-GitHubAppInstallationRepositoryAccess.ps1 index 50086f504..b447032cb 100644 --- a/src/functions/public/Apps/GitHub App Installations/Add-GitHubAppInstallationRepositoryAccess.ps1 +++ b/src/functions/public/Apps/GitHub App Installations/Add-GitHubAppInstallationRepositoryAccess.ps1 @@ -70,7 +70,7 @@ repositories = $Repositories } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations/$ID/repositories/add" Body = $body @@ -78,7 +78,7 @@ } if ($PSCmdlet.ShouldProcess("$Enterprise/$Organization", 'Add repo access to installation')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppAccessibleRepository.ps1 b/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppAccessibleRepository.ps1 index c010bc146..a884c9083 100644 --- a/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppAccessibleRepository.ps1 +++ b/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppAccessibleRepository.ps1 @@ -61,14 +61,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/enterprises/$Enterprise/apps/installable_organizations/$Organization/accessible_repositories" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($repo in $_.Response) { [GitHubRepository]@{ ID = $repo.id diff --git a/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppInstallationRepositoryAccess.ps1 b/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppInstallationRepositoryAccess.ps1 index e54f6d663..0c6423e19 100644 --- a/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppInstallationRepositoryAccess.ps1 +++ b/src/functions/public/Apps/GitHub App Installations/Get-GitHubAppInstallationRepositoryAccess.ps1 @@ -67,14 +67,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations/$ID/repositories" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Apps/GitHub App Installations/Remove-GitHubAppInstallationRepositoryAccess.ps1 b/src/functions/public/Apps/GitHub App Installations/Remove-GitHubAppInstallationRepositoryAccess.ps1 index 3d0097fa8..9c604ab1b 100644 --- a/src/functions/public/Apps/GitHub App Installations/Remove-GitHubAppInstallationRepositoryAccess.ps1 +++ b/src/functions/public/Apps/GitHub App Installations/Remove-GitHubAppInstallationRepositoryAccess.ps1 @@ -70,7 +70,7 @@ repositories = $Repositories } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations/$ID/repositories/remove" Body = $body @@ -78,7 +78,7 @@ } if ($PSCmdlet.ShouldProcess("$Enterprise/$Organization - $Repositories", 'Remove repository access')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Apps/GitHub App Installations/Update-GitHubAppInstallationRepositoryAccess.ps1 b/src/functions/public/Apps/GitHub App Installations/Update-GitHubAppInstallationRepositoryAccess.ps1 index cd6b9905b..58111e114 100644 --- a/src/functions/public/Apps/GitHub App Installations/Update-GitHubAppInstallationRepositoryAccess.ps1 +++ b/src/functions/public/Apps/GitHub App Installations/Update-GitHubAppInstallationRepositoryAccess.ps1 @@ -85,7 +85,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/enterprises/$Enterprise/apps/organizations/$Organization/installations/$ID/repositories" Body = $body @@ -93,7 +93,7 @@ } if ($PSCmdlet.ShouldProcess("$Enterprise/$Organization", 'Update repository access')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Apps/GitHub App/Get-GitHubAppInstallationRequest.ps1 b/src/functions/public/Apps/GitHub App/Get-GitHubAppInstallationRequest.ps1 index 006451ab6..7f9da82ce 100644 --- a/src/functions/public/Apps/GitHub App/Get-GitHubAppInstallationRequest.ps1 +++ b/src/functions/public/Apps/GitHub App/Get-GitHubAppInstallationRequest.ps1 @@ -37,13 +37,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/app/installation-requests' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response | ForEach-Object { [GitHubAppInstallationRequest]::new($_) } diff --git a/src/functions/public/Apps/GitHub App/Get-GitHubAppJSONWebToken.ps1 b/src/functions/public/Apps/GitHub App/Get-GitHubAppJSONWebToken.ps1 deleted file mode 100644 index 133816e4f..000000000 --- a/src/functions/public/Apps/GitHub App/Get-GitHubAppJSONWebToken.ps1 +++ /dev/null @@ -1,135 +0,0 @@ -function Get-GitHubAppJSONWebToken { - <# - .SYNOPSIS - Generates a JSON Web Token (JWT) for a GitHub App. - - .DESCRIPTION - Generates a JSON Web Token (JWT) for a GitHub App. - - .EXAMPLE - Get-GitHubAppJWT -ClientId 'Iv987654321' -PrivateKeyFilePath '/path/to/private-key.pem' - - Generates a JSON Web Token (JWT) for a GitHub App using the specified client ID and private key file path. - - .EXAMPLE - Get-GitHubAppJWT -ClientId 'Iv987654321' -PrivateKey '--- BEGIN RSA PRIVATE KEY --- ... --- END RSA PRIVATE KEY ---' - - Generates a JSON Web Token (JWT) for a GitHub App using the specified client ID and private key. - - .OUTPUTS - GitHubJsonWebToken - - .NOTES - [Generating a JSON Web Token (JWT) for a GitHub App | GitHub Docs](https://docs.github.com/apps/creating-github-apps/authenticating-with-a-github-app/generating-a-json-web-token-jwt-for-a-github-app#example-using-powershell-to-generate-a-jwt) - - .LINK - https://psmodule.io/GitHub/Functions/Apps/GitHub%20App/Get-GitHubAppJSONWebToken - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSAvoidLongLines', - '', - Justification = 'Contains a long link.' - )] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute( - 'PSAvoidUsingConvertToSecureStringWithPlainText', - '', - Justification = 'Generated JWT is a plaintext string.' - )] - - [CmdletBinding(DefaultParameterSetName = 'PrivateKey')] - [Alias('Get-GitHubAppJWT')] - [OutputType([GitHubJsonWebToken])] - param( - # The client ID of the GitHub App. - # Can use the GitHub App ID or the client ID. - [Parameter(Mandatory)] - [string] $ClientId, - - # The path to the private key file of the GitHub App. - [Parameter( - Mandatory, - ParameterSetName = 'FilePath' - )] - [string] $PrivateKeyFilePath, - - # The private key of the GitHub App. - [Parameter( - Mandatory, - ParameterSetName = 'PrivateKey' - )] - [object] $PrivateKey - ) - - begin { - $stackPath = Get-PSCallStackPath - Write-Debug "[$stackPath] - Start" - } - - process { - if ($PrivateKeyFilePath) { - if (-not (Test-Path -Path $PrivateKeyFilePath)) { - throw "The private key path [$PrivateKeyFilePath] does not exist." - } - - $PrivateKey = Get-Content -Path $PrivateKeyFilePath -Raw - } - - if ($PrivateKey -is [securestring]) { - $PrivateKey = $PrivateKey | ConvertFrom-SecureString -AsPlainText - } - - $header = [Convert]::ToBase64String( - [System.Text.Encoding]::UTF8.GetBytes( - ( - ConvertTo-Json -InputObject @{ - alg = 'RS256' - typ = 'JWT' - } - ) - ) - ).TrimEnd('=').Replace('+', '-').Replace('/', '_') - - $iat = [System.DateTimeOffset]::UtcNow.AddSeconds(-$script:GitHub.Config.JwtTimeTolerance).ToUnixTimeSeconds() - $exp = [System.DateTimeOffset]::UtcNow.AddSeconds($script:GitHub.Config.JwtTimeTolerance).ToUnixTimeSeconds() - $payload = [Convert]::ToBase64String( - [System.Text.Encoding]::UTF8.GetBytes( - ( - ConvertTo-Json -InputObject @{ - iat = $iat - exp = $exp - iss = $ClientId - } - ) - ) - ).TrimEnd('=').Replace('+', '-').Replace('/', '_') - - $rsa = [System.Security.Cryptography.RSA]::Create() - $rsa.ImportFromPem($PrivateKey) - - $signature = [Convert]::ToBase64String( - $rsa.SignData( - [System.Text.Encoding]::UTF8.GetBytes("$header.$payload"), - [System.Security.Cryptography.HashAlgorithmName]::SHA256, - [System.Security.Cryptography.RSASignaturePadding]::Pkcs1 - ) - ).TrimEnd('=').Replace('+', '-').Replace('/', '_') - $jwt = "$header.$payload.$signature" - [GitHubJsonWebToken]@{ - Token = ConvertTo-SecureString -String $jwt -AsPlainText - IssuedAt = [DateTime]::UnixEpoch.AddSeconds($iat) - ExpiresAt = [DateTime]::UnixEpoch.AddSeconds($exp) - Issuer = $ClientId - } - } - - end { - Write-Debug "[$stackPath] - End" - } - - clean { - Remove-Variable -Name jwt -ErrorAction SilentlyContinue - Remove-Variable -Name rsa -ErrorAction SilentlyContinue - Remove-Variable -Name signature -ErrorAction SilentlyContinue - [System.GC]::Collect() - } -} diff --git a/src/functions/public/Artifacts/Remove-GitHubArtifact.ps1 b/src/functions/public/Artifacts/Remove-GitHubArtifact.ps1 index ca705b122..747d32150 100644 --- a/src/functions/public/Artifacts/Remove-GitHubArtifact.ps1 +++ b/src/functions/public/Artifacts/Remove-GitHubArtifact.ps1 @@ -1,4 +1,4 @@ -function Remove-GitHubArtifact { +function Remove-GitHubArtifact { <# .SYNOPSIS Deletes an artifact from a GitHub repository by its unique ID. @@ -31,7 +31,7 @@ [Delete an artifact](https://docs.github.com/rest/actions/artifacts#delete-an-artifact) #> [OutputType([GitHubArtifact])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] @@ -59,14 +59,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/actions/artifacts/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("artifact [$Owner/$Repository/$ID]", 'Remove')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Artifacts/Save-GitHubArtifact.ps1 b/src/functions/public/Artifacts/Save-GitHubArtifact.ps1 index dfadd17df..6c34f001d 100644 --- a/src/functions/public/Artifacts/Save-GitHubArtifact.ps1 +++ b/src/functions/public/Artifacts/Save-GitHubArtifact.ps1 @@ -99,13 +99,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/artifacts/$ID/zip" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $headers = $_.Headers $itemType = $Path.EndsWith('.zip') ? 'File' : 'Directory' $isAbsolute = [System.IO.Path]::IsPathRooted($Path) diff --git a/src/functions/public/Auth/Connect-GitHubAccount.ps1 b/src/functions/public/Auth/Connect-GitHubAccount.ps1 index c0fe3d3fd..20816ce7d 100644 --- a/src/functions/public/Auth/Connect-GitHubAccount.ps1 +++ b/src/functions/public/Auth/Connect-GitHubAccount.ps1 @@ -48,9 +48,18 @@ #> [Alias('Connect-GitHub')] [OutputType([void])] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Long links for documentation.')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'Is the CLI part of the module.')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingConvertToSecureStringWithPlainText', '', Justification = 'The tokens are received as clear text. Mitigating exposure by removing variables and performing garbage collection.')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidLongLines', '', + Justification = 'Long links for documentation.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingWriteHost', '', + Justification = 'Is the CLI part of the module.' + )] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute( + 'PSAvoidUsingConvertToSecureStringWithPlainText', '', + Justification = 'The tokens are received as clear text. Mitigating exposure by removing variables and performing garbage collection.' + )] [CmdletBinding(DefaultParameterSetName = 'UAT')] param( # Choose between authentication methods, either OAuthApp or GitHubApp. @@ -69,41 +78,39 @@ # The user will be prompted to enter the token. - [Parameter( - Mandatory, - ParameterSetName = 'PAT' - )] + [Parameter(Mandatory, ParameterSetName = 'PAT')] [switch] $UseAccessToken, # An access token to use for authentication. Can be both a string or a SecureString. # Supports both personal access tokens (PAT) and GitHub App installation access tokens (IAT). # Example: 'ghp_1234567890abcdef' # Example: 'ghs_1234567890abcdef' - [Parameter( - Mandatory, - ParameterSetName = 'Token' - )] + [Parameter(Mandatory, ParameterSetName = 'Token')] [object] $Token, # The client ID for the GitHub App to use for authentication. [Parameter(ParameterSetName = 'UAT')] - [Parameter( - Mandatory, - ParameterSetName = 'App' - )] + [Parameter(Mandatory, ParameterSetName = 'GitHub App using a PrivateKey')] + [Parameter(Mandatory, ParameterSetName = 'GitHub App using a KeyVault Key Reference')] [string] $ClientID, - # The private key for the GitHub App when authenticating as a GitHub App. - [Parameter( - Mandatory, - ParameterSetName = 'App' - )] - [string] $PrivateKey, + # The private key that is used to sign JWTs for the GitHub App. + [Parameter(Mandatory, ParameterSetName = 'GitHub App using a PrivateKey')] + [object] $PrivateKey, + + # The KeyVault Key Reference that can sign JWTs for the GitHub App. + [Parameter(Mandatory, ParameterSetName = 'GitHub App using a KeyVault Key Reference')] + [ValidateScript({ + if ($_ -notlike 'https://*.vault.azure.net/keys/*') { + throw "Invalid Key Vault key reference format: $_" + } + return $true + })] + [string] $KeyVaultKeyReference, # Automatically load installations for the GitHub App. - [Parameter( - ParameterSetName = 'App' - )] + [Parameter(ParameterSetName = 'GitHub App using a PrivateKey')] + [Parameter(ParameterSetName = 'GitHub App using a KeyVault Key Reference')] [switch] $AutoloadInstallations, # The default enterprise to use in commands. @@ -160,10 +167,9 @@ $ApiVersion = $script:GitHub.Config.ApiVersion $HostName = $HostName -replace '^https?://' $ApiBaseUri = "https://api.$HostName" - $authType = $PSCmdlet.ParameterSetName # If running on GitHub Actions and no access token is provided, use the GitHub token. - if (($env:GITHUB_ACTIONS -eq 'true') -and $PSCmdlet.ParameterSetName -ne 'App') { + if ($script:IsGitHubActions -and $PSCmdlet.ParameterSetName -notin @('GitHub App using a PrivateKey', 'GitHub App using a KeyVault Key Reference')) { $customTokenProvided = -not [string]::IsNullOrEmpty($Token) $gitHubTokenPresent = Test-GitHubToken Write-Verbose "A token was provided: [$customTokenProvided]" @@ -181,7 +187,6 @@ HostName = [string]$HostName HttpVersion = [string]$httpVersion PerPage = [int]$perPage - AuthType = [string]$authType Enterprise = [string]$Enterprise Owner = [string]$Owner Repository = [string]$Repository @@ -189,7 +194,7 @@ $context | Format-Table | Out-String -Stream | ForEach-Object { Write-Verbose $_ } - switch ($authType) { + switch ($PSCmdlet.ParameterSetName) { 'UAT' { Write-Verbose 'Logging in using device flow...' if (-not [string]::IsNullOrEmpty($ClientID)) { @@ -218,18 +223,20 @@ switch ($Mode) { 'GitHubApp' { $context += @{ - Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token - TokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.expires_in) - TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern - AuthClientID = $authClientID - DeviceFlowType = $Mode - RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token - RefreshTokenExpirationDate = (Get-Date).AddSeconds($tokenResponse.refresh_token_expires_in) - Scope = $tokenResponse.scope + AuthType = 'UAT' + Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token + TokenExpiresAt = ([DateTime]::Now).AddSeconds($tokenResponse.expires_in) + TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern + AuthClientID = $authClientID + DeviceFlowType = $Mode + RefreshToken = ConvertTo-SecureString -AsPlainText $tokenResponse.refresh_token + RefreshTokenExpiresAt = ([DateTime]::Now).AddSeconds($tokenResponse.refresh_token_expires_in) + Scope = $tokenResponse.scope } } 'OAuthApp' { $context += @{ + AuthType = 'UAT' Token = ConvertTo-SecureString -AsPlainText $tokenResponse.access_token TokenType = $tokenResponse.access_token -replace $script:GitHub.TokenPrefixPattern AuthClientID = $authClientID @@ -244,12 +251,25 @@ } } } - 'App' { - Write-Verbose 'Logging in as a GitHub App...' + 'GitHub App using a PrivateKey' { + Write-Verbose 'Logging in as a GitHub App using PrivateKey...' + if (-not($PrivateKey -is [System.Security.SecureString])) { + $PrivateKey = $PrivateKey | ConvertTo-SecureString -AsPlainText + } + $context += @{ + AuthType = 'APP' + PrivateKey = $PrivateKey + TokenType = 'JWT' + ClientID = $ClientID + } + } + 'GitHub App using a KeyVault Key Reference' { + Write-Verbose 'Logging in as a GitHub App using KeyVault Key Reference...' $context += @{ - Token = ConvertTo-SecureString -AsPlainText $PrivateKey - TokenType = 'PEM' - ClientID = $ClientID + AuthType = 'APP' + KeyVaultKeyReference = $KeyVaultKeyReference + TokenType = 'JWT' + ClientID = $ClientID } } 'PAT' { @@ -261,6 +281,7 @@ $Token = ConvertFrom-SecureString $accessTokenValue -AsPlainText $tokenType = $Token -replace $script:GitHub.TokenPrefixPattern $context += @{ + AuthType = 'PAT' Token = ConvertTo-SecureString -AsPlainText $Token TokenType = $tokenType } @@ -291,6 +312,7 @@ } } } + default {} } $contextObj = Set-GitHubContext -Context $context -Default:(!$NotDefault) -PassThru $contextObj | Format-List | Out-String -Stream | ForEach-Object { Write-Verbose $_ } diff --git a/src/functions/public/Auth/Connect-GitHubApp.ps1 b/src/functions/public/Auth/Connect-GitHubApp.ps1 index 27661ca33..e69343465 100644 --- a/src/functions/public/Auth/Connect-GitHubApp.ps1 +++ b/src/functions/public/Auth/Connect-GitHubApp.ps1 @@ -85,7 +85,6 @@ } process { - $installations = Get-GitHubAppInstallation -Context $Context $selectedInstallations = @() Write-Verbose "Found [$($installations.Count)] installations." @@ -123,24 +122,24 @@ $selectedInstallations | ForEach-Object { $installation = $_ Write-Verbose "Processing installation [$($installation.Target.Name)] [$($installation.id)]" - $token = New-GitHubAppInstallationAccessToken -Context $Context -InstallationID $installation.id + $token = New-GitHubAppInstallationAccessToken -Context $Context -ID $installation.id $contextParams = @{ - AuthType = [string]'IAT' - TokenType = [string]'ghs' - DisplayName = [string]$Context.DisplayName - ApiBaseUri = [string]$Context.ApiBaseUri - ApiVersion = [string]$Context.ApiVersion - HostName = [string]$Context.HostName - HttpVersion = [string]$Context.HttpVersion - PerPage = [int]$Context.PerPage - ClientID = [string]$Context.ClientID - InstallationID = [string]$installation.id - Permissions = [pscustomobject]$installation.permissions - Events = [string[]]$installation.events - InstallationType = [string]$installation.Type - Token = [securestring]$token.Token - TokenExpirationDate = [datetime]$token.ExpiresAt + AuthType = [string]'IAT' + TokenType = [string]'ghs' + DisplayName = [string]$Context.DisplayName + ApiBaseUri = [string]$Context.ApiBaseUri + ApiVersion = [string]$Context.ApiVersion + HostName = [string]$Context.HostName + HttpVersion = [string]$Context.HttpVersion + PerPage = [int]$Context.PerPage + ClientID = [string]$Context.ClientID + InstallationID = [string]$installation.id + Permissions = [pscustomobject]$installation.permissions + Events = [string[]]$installation.events + InstallationType = [string]$installation.Type + Token = [securestring]$token.Token + TokenExpiresAt = [datetime]$token.ExpiresAt } switch ($installation.Type) { @@ -159,7 +158,7 @@ } Write-Verbose 'Logging in using a managed installation access token...' $contextParams | Format-Table | Out-String -Stream | ForEach-Object { Write-Verbose $_ } - $contextObj = [InstallationGitHubContext]::new((Set-GitHubContext -Context $contextParams.Clone() -PassThru -Default:$Default)) + $contextObj = [GitHubAppInstallationContext]::new((Set-GitHubContext -Context $contextParams.Clone() -PassThru -Default:$Default)) $contextObj | Format-List | Out-String -Stream | ForEach-Object { Write-Verbose $_ } if (-not $Silent) { $name = $contextObj.Name diff --git a/src/functions/public/Auth/Context/Get-GitHubContext.ps1 b/src/functions/public/Auth/Context/Get-GitHubContext.ps1 index 2e7deec7c..9f0015551 100644 --- a/src/functions/public/Auth/Context/Get-GitHubContext.ps1 +++ b/src/functions/public/Auth/Context/Get-GitHubContext.ps1 @@ -70,16 +70,16 @@ Write-Verbose 'Context:' $contextObj | Select-Object * | Out-String -Stream | ForEach-Object { Write-Verbose $_ } - Write-Verbose "Converting to: [$($contextObj.Type)GitHubContext]" + Write-Verbose "Converting to: [GitHub$($contextObj.Type)Context]" switch ($contextObj.Type) { 'User' { - [UserGitHubContext]::new($contextObj) + [GitHubUserContext]::new([pscustomobject]$contextObj) } 'App' { - [AppGitHubContext]::new($contextObj) + [GitHubAppContext]::new([pscustomobject]$contextObj) } 'Installation' { - [InstallationGitHubContext]::new($contextObj) + [GitHubAppInstallationContext]::new([pscustomobject]$contextObj) } default { throw "Unknown context type: [$($contextObj.Type)]" @@ -92,4 +92,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/public/Auth/Disconnect-GitHubAccount.ps1 b/src/functions/public/Auth/Disconnect-GitHubAccount.ps1 index c7825b96f..0007e1958 100644 --- a/src/functions/public/Auth/Disconnect-GitHubAccount.ps1 +++ b/src/functions/public/Auth/Disconnect-GitHubAccount.ps1 @@ -4,7 +4,8 @@ Disconnects from GitHub and removes the GitHub context. .DESCRIPTION - Disconnects from GitHub and removes the GitHub context. + Disconnects from GitHub and removes the GitHub context. Optionally revokes the access token + to ensure it cannot be used after disconnection. .EXAMPLE Disconnect-GitHubAccount @@ -16,6 +17,16 @@ Disconnects from GitHub and removes the context 'github.com/Octocat'. + .EXAMPLE + Disconnect-GitHubAccount -RevokeToken + + Disconnects from GitHub, revokes the access token, and removes the default GitHub context. + + .EXAMPLE + Disconnect-GithubAccount -Context 'github.com/Octocat' -RevokeToken + + Disconnects from GitHub, revokes the access token, and removes the context 'github.com/Octocat'. + .LINK https://psmodule.io/GitHub/Functions/Auth/Disconnect-GitHubAccount #> @@ -46,7 +57,19 @@ } foreach ($contextItem in $Context) { $contextItem = Resolve-GitHubContext -Context $contextItem - Remove-GitHubContext -Context $contextItem + + $contextToken = Get-GitHubAccessToken -Context $contextItem -AsPlainText + $isNotGitHubToken = -not ($contextToken -eq (Get-GitHubToken | ConvertFrom-SecureString -AsPlainText)) + $isIATAuthType = $contextItem.AuthType -eq 'IAT' + $isNotExpired = $contextItem.TokenExpiresIn -gt 0 + Write-Debug "isNotGitHubToken: $isNotGitHubToken" + Write-Debug "isIATAuthType: $isIATAuthType" + Write-Debug "isNotExpired: $isNotExpired" + if ($isNotGitHubToken -and $isIATAuthType -and $isNotExpired) { + Revoke-GitHubAppInstallationAccessToken -Context $contextItem + } + + Remove-GitHubContext -Context $contextItem.ID $isDefaultContext = $contextItem.Name -eq $script:GitHub.Config.DefaultContext if ($isDefaultContext) { Remove-GitHubConfig -Name 'DefaultContext' diff --git a/src/functions/public/Auth/Revoke-GitHubAccessToken.ps1 b/src/functions/public/Auth/Revoke-GitHubAccessToken.ps1 new file mode 100644 index 000000000..67791fe8b --- /dev/null +++ b/src/functions/public/Auth/Revoke-GitHubAccessToken.ps1 @@ -0,0 +1,53 @@ +function Revoke-GitHubAccessToken { + <# + .SYNOPSIS + Revoke a list of tokens. + + .DESCRIPTION + Submit a list of credentials to be revoked. This endpoint is intended to revoke credentials the caller does not own and may have found + exposed on GitHub.com or elsewhere. It can also be used for credentials associated with an old user account that you no longer have access to. + Credential owners will be notified of the revocation. + + .LINK + https://psmodule.io/GitHub/Functions/Auth/Revoke-GitHubAccessToken/ + + .NOTES + [Revoke a list of credentials](https://docs.github.com/rest/credentials/revoke#revoke-a-list-of-credentials) + #> + [CmdletBinding(SupportsShouldProcess)] + param( + # An array of tokens to revoke. + [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [string[]] $Token + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $tokenList = [System.Collections.ArrayList]::new() + } + + process { + $Token | ForEach-Object { + $tokenList.Add($_) + } + } + + end { + for ($i = 0; $i -lt $tokenList.Count; $i += 1000) { + $batch = $tokenList[$i..([Math]::Min($i + 999, $tokenList.Count - 1))] + $body = @{ credentials = $batch } + $InputObject = @{ + Method = 'POST' + APIEndpoint = '/credentials/revoke' + Body = $body + Anonymous = $true + } + if ($PSCmdlet.ShouldProcess('Tokens', 'Revoke')) { + $null = Invoke-GitHubAPI @InputObject + } + } + Write-Debug "[$stackPath] - End" + } +} +#SkipTest:FunctionTest:Will add a test for this function in a future PR diff --git a/src/functions/public/Config/Get-GitHubConfig.ps1 b/src/functions/public/Config/Get-GitHubConfig.ps1 index 9a2a30922..9471de437 100644 --- a/src/functions/public/Config/Get-GitHubConfig.ps1 +++ b/src/functions/public/Config/Get-GitHubConfig.ps1 @@ -47,4 +47,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/public/Config/Remove-GitHubConfig.ps1 b/src/functions/public/Config/Remove-GitHubConfig.ps1 index 148e3f264..8ae72d979 100644 --- a/src/functions/public/Config/Remove-GitHubConfig.ps1 +++ b/src/functions/public/Config/Remove-GitHubConfig.ps1 @@ -44,4 +44,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/public/Config/Set-GitHubConfig.ps1 b/src/functions/public/Config/Set-GitHubConfig.ps1 index 029b5640b..648ab97a7 100644 --- a/src/functions/public/Config/Set-GitHubConfig.ps1 +++ b/src/functions/public/Config/Set-GitHubConfig.ps1 @@ -54,4 +54,5 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.0' } +#Requires -Modules @{ ModuleName = 'Context'; RequiredVersion = '8.1.3' } + diff --git a/src/functions/public/Emojis/Get-GitHubEmoji.ps1 b/src/functions/public/Emojis/Get-GitHubEmoji.ps1 index 78602ffe6..ec00fba7e 100644 --- a/src/functions/public/Emojis/Get-GitHubEmoji.ps1 +++ b/src/functions/public/Emojis/Get-GitHubEmoji.ps1 @@ -50,13 +50,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/emojis' Context = $Context } - $response = Invoke-GitHubAPI @inputObject | Select-Object -ExpandProperty Response + $response = Invoke-GitHubAPI @apiParams | Select-Object -ExpandProperty Response switch ($PSCmdlet.ParameterSetName) { 'Download' { diff --git a/src/functions/public/Enterprise/Get-GitHubEnterprise.ps1 b/src/functions/public/Enterprise/Get-GitHubEnterprise.ps1 new file mode 100644 index 000000000..20493743e --- /dev/null +++ b/src/functions/public/Enterprise/Get-GitHubEnterprise.ps1 @@ -0,0 +1,88 @@ +function Get-GitHubEnterprise { + <# + .SYNOPSIS + Retrieves GitHub Enterprise instance details for the authenticated user. + + .DESCRIPTION + Retrieves detailed information about GitHub Enterprise instances available to the authenticated user. + By default, the command lists all accessible instances, including metadata such as the enterprise name, slug, URL, and creation date. If a + specific enterprise name is provided, details about that single instance are returned. + + .EXAMPLE + Get-GitHubEnterprise + + Output: + ```powershell + Name : My Enterprise + Slug : my-enterprise + URL : https://github.com/enterprises/my-enterprise + CreatedAt : 2022-01-01T00:00:00Z + + Name : Another Enterprise + Slug : another-enterprise + URL : https://github.com/enterprises/another-enterprise + CreatedAt : 2022-01-01T00:00:00Z + ``` + + Retrieves details about all GitHub Enterprise instances for the user. + + .EXAMPLE + Get-GitHubEnterprise -Name 'my-enterprise' + + Output: + ```powershell + Name : My Enterprise + Slug : my-enterprise + URL : https://github.com/enterprises/my-enterprise + CreatedAt : 2022-01-01T00:00:00Z + ``` + + Retrieves details about the GitHub Enterprise instance named 'my-enterprise'. + + .OUTPUTS + GitHubEnterprise + + .NOTES + An object containing detailed information about the GitHub Enterprise instance, including billing info, URLs, and metadata. + + .LINK + https://psmodule.io/GitHub/Functions/Enterprise/Get-GitHubEnterprise/ + #> + [OutputType([GitHubEnterprise])] + [CmdletBinding(DefaultParameterSetName = 'List enterprises for the authenticated user')] + param( + # The name (slug) of the GitHub Enterprise instance to retrieve. + [Parameter(Mandatory, ParameterSetName = 'Get enterprise by name')] + [Alias('Slug')] + [string] $Name, + + # 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()] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + Write-Debug "ParameterSet: $($PSCmdlet.ParameterSetName)" + switch ($PSCmdlet.ParameterSetName) { + 'Get enterprise by name' { + Get-GitHubEnterpriseByName -Name $Name -Context $Context + break + } + default { + Get-GitHubEnterpriseList -Context $Context + } + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} diff --git a/src/functions/public/Environments/Remove-GitHubEnvironment.ps1 b/src/functions/public/Environments/Remove-GitHubEnvironment.ps1 index 47b379e6d..393d988f1 100644 --- a/src/functions/public/Environments/Remove-GitHubEnvironment.ps1 +++ b/src/functions/public/Environments/Remove-GitHubEnvironment.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubEnvironment { +filter Remove-GitHubEnvironment { <# .SYNOPSIS Deletes an environment from a repository. @@ -19,7 +19,7 @@ [Delete environments](https://docs.github.com/rest/deployments/environments?#delete-an-environment) #> [OutputType([void])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The name of the organization. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] @@ -49,14 +49,14 @@ process { $encodedName = [System.Uri]::EscapeDataString($Name) - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' Uri = $Context.ApiBaseUri + "/repos/$Owner/$Repository/environments/$encodedName" Context = $Context } if ($PSCmdlet.ShouldProcess("Environment [$Owner/$Repository/$Name]", 'Delete')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Environments/Set-GitHubEnvironment.ps1 b/src/functions/public/Environments/Set-GitHubEnvironment.ps1 index 65c0b326c..fe8992859 100644 --- a/src/functions/public/Environments/Set-GitHubEnvironment.ps1 +++ b/src/functions/public/Environments/Set-GitHubEnvironment.ps1 @@ -173,7 +173,7 @@ } $encodedName = [System.Uri]::EscapeDataString($Name) - $inputObject = @{ + $apiParams = @{ Method = 'PUT' Uri = $Context.ApiBaseUri + "/repos/$Owner/$Repository/environments/$encodedName" Body = $body @@ -181,10 +181,8 @@ } if ($PSCmdlet.ShouldProcess("Environment [$Owner/$Repository/$Name]", 'Set')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { - $environment = [GitHubEnvironment]::new($_.Response, $Owner, $Repository) - $environment.Url = "https://$($Context.HostName)/$Owner/$Repository/settings/environments/$($environment.ID)/edit" - $environment + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubEnvironment]::new($_.Response, $Owner, $Repository, $Context) } } } diff --git a/src/functions/public/Markdown/Get-GitHubMarkdown.ps1 b/src/functions/public/Markdown/Get-GitHubMarkdown.ps1 index 3ba19ea1d..e04d5f538 100644 --- a/src/functions/public/Markdown/Get-GitHubMarkdown.ps1 +++ b/src/functions/public/Markdown/Get-GitHubMarkdown.ps1 @@ -63,14 +63,14 @@ text = $Text } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/markdown' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Markdown/Get-GitHubMarkdownRaw.ps1 b/src/functions/public/Markdown/Get-GitHubMarkdownRaw.ps1 index 54358242d..212815387 100644 --- a/src/functions/public/Markdown/Get-GitHubMarkdownRaw.ps1 +++ b/src/functions/public/Markdown/Get-GitHubMarkdownRaw.ps1 @@ -48,7 +48,7 @@ text = $Text } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/markdown/raw' ContentType = 'text/plain' @@ -56,7 +56,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Meta/Get-GitHubApiVersion.ps1 b/src/functions/public/Meta/Get-GitHubApiVersion.ps1 index e95f0b309..7a618a104 100644 --- a/src/functions/public/Meta/Get-GitHubApiVersion.ps1 +++ b/src/functions/public/Meta/Get-GitHubApiVersion.ps1 @@ -38,13 +38,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' ApiEndpoint = '/versions' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Meta/Get-GitHubMeta.ps1 b/src/functions/public/Meta/Get-GitHubMeta.ps1 index ad6a36efc..156523d2f 100644 --- a/src/functions/public/Meta/Get-GitHubMeta.ps1 +++ b/src/functions/public/Meta/Get-GitHubMeta.ps1 @@ -46,13 +46,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' ApiEndpoint = '/meta' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Meta/Get-GitHubOctocat.ps1 b/src/functions/public/Meta/Get-GitHubOctocat.ps1 index 600ff3268..ae2c8d438 100644 --- a/src/functions/public/Meta/Get-GitHubOctocat.ps1 +++ b/src/functions/public/Meta/Get-GitHubOctocat.ps1 @@ -51,14 +51,14 @@ s = $Saying } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/octocat' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Meta/Get-GitHubRoot.ps1 b/src/functions/public/Meta/Get-GitHubRoot.ps1 index b38e36842..196782dcf 100644 --- a/src/functions/public/Meta/Get-GitHubRoot.ps1 +++ b/src/functions/public/Meta/Get-GitHubRoot.ps1 @@ -37,13 +37,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Meta/Get-GitHubZen.ps1 b/src/functions/public/Meta/Get-GitHubZen.ps1 index 274989e2e..7f2fbb7cc 100644 --- a/src/functions/public/Meta/Get-GitHubZen.ps1 +++ b/src/functions/public/Meta/Get-GitHubZen.ps1 @@ -37,13 +37,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/zen' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Organization/Get-GitHubOrganization.ps1 b/src/functions/public/Organization/Get-GitHubOrganization.ps1 index cd490cd5d..526e66e74 100644 --- a/src/functions/public/Organization/Get-GitHubOrganization.ps1 +++ b/src/functions/public/Organization/Get-GitHubOrganization.ps1 @@ -114,7 +114,7 @@ Get-GitHubAllOrganization -Since $Since -PerPage $PerPage -Context $Context } 'List all organizations for the authenticated user' { - Get-GitHubMyOrganization -PerPage $PerPage -Context $Context + Get-GitHubOrganizationListForAuthUser -PerPage $PerPage -Context $Context } default { Write-Error "Invalid parameter set name: $($PSCmdlet.ParameterSetName)" diff --git a/src/functions/public/Organization/Members/Get-GitHubOrganizationMember.ps1 b/src/functions/public/Organization/Members/Get-GitHubOrganizationMember.ps1 index 75e9bc920..9c51dda37 100644 --- a/src/functions/public/Organization/Members/Get-GitHubOrganizationMember.ps1 +++ b/src/functions/public/Organization/Members/Get-GitHubOrganizationMember.ps1 @@ -58,7 +58,7 @@ role = $Role } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/members" Body = $body @@ -66,8 +66,10 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - $_.Response | ForEach-Object { [GitHubUser]::new($_) } + Invoke-GitHubAPI @apiParams | ForEach-Object { + foreach ($user in $_.Response) { + [GitHubUser]::new($user) + } } } diff --git a/src/functions/public/Organization/Members/Get-GitHubOrganizationPendingInvitation.ps1 b/src/functions/public/Organization/Members/Get-GitHubOrganizationPendingInvitation.ps1 index a3a423aae..3476a62c3 100644 --- a/src/functions/public/Organization/Members/Get-GitHubOrganizationPendingInvitation.ps1 +++ b/src/functions/public/Organization/Members/Get-GitHubOrganizationPendingInvitation.ps1 @@ -64,7 +64,7 @@ invitation_source = $InvitationSource } - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Organization/invitations" Body = $body @@ -72,7 +72,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Organization/Members/New-GitHubOrganizationInvitation.ps1 b/src/functions/public/Organization/Members/New-GitHubOrganizationInvitation.ps1 index 7f6b5015c..b1fb3633a 100644 --- a/src/functions/public/Organization/Members/New-GitHubOrganizationInvitation.ps1 +++ b/src/functions/public/Organization/Members/New-GitHubOrganizationInvitation.ps1 @@ -86,7 +86,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/orgs/$Organization/invitations" Body = $body @@ -94,7 +94,7 @@ } if ($PSCmdlet.ShouldProcess("$InviteeID$Email to organization $Organization", 'Invite')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Organization/Members/Remove-GitHubOrganizationInvitation.ps1 b/src/functions/public/Organization/Members/Remove-GitHubOrganizationInvitation.ps1 index de656a1aa..2b3192f3c 100644 --- a/src/functions/public/Organization/Members/Remove-GitHubOrganizationInvitation.ps1 +++ b/src/functions/public/Organization/Members/Remove-GitHubOrganizationInvitation.ps1 @@ -51,14 +51,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Organization/invitations/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess('GitHub Organization invitation', 'Remove')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Organization/New-GitHubOrganization.ps1 b/src/functions/public/Organization/New-GitHubOrganization.ps1 new file mode 100644 index 000000000..7279e9358 --- /dev/null +++ b/src/functions/public/Organization/New-GitHubOrganization.ps1 @@ -0,0 +1,88 @@ +function New-GitHubOrganization { + <# + .SYNOPSIS + Creates a new GitHub organization within a specified enterprise. + + .DESCRIPTION + This function creates a new GitHub organization within the specified enterprise. + + .EXAMPLE + New-GitHubOrganization -Enterprise 'my-enterprise' -Name 'my-org' -Owner 'user1' -BillingEmail 'billing@example.com' + + .OUTPUTS + GitHubOrganization + + .LINK + https://psmodule.io/GitHub/Functions/Organization/New-GitHubOrganization/ + #> + [OutputType([GitHubOrganization])] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] + param( + # The name of the enterprise to create the organization in. + [Parameter()] + [string]$Enterprise, + + # The name of the organization to create. + [Parameter(Mandatory)] + [string]$Name, + + # The owners of the organization. This should be a list of GitHub usernames. + [Parameter(Mandatory)] + [string[]]$Owner, + + # The billing email for the organization. + [Parameter()] + [string]$BillingEmail, + + # 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()] + [object] $Context + ) + + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + $Context = Resolve-GitHubContext -Context $Context + Assert-GitHubContext -Context $Context -AuthType IAT, PAT, UAT + } + + process { + $enterpriseObject = Get-GitHubEnterprise -Name $Enterprise -Context $Context + Write-Verbose "Creating organization in enterprise: $($enterpriseObject.Name)" + $graphQLFields = ([GitHubOrganization]::PropertyToGraphQLMap).Values + + $inputParams = @{ + adminLogins = $Owner + billingEmail = $BillingEmail + enterpriseId = $enterpriseObject.NodeID + login = $Name + profileName = $Name + } + + $updateGraphQLInputs = @{ + Query = @" +mutation(`$input:CreateEnterpriseOrganizationInput!) { + createEnterpriseOrganization(input:`$input) { + organization { + $graphQLFields + } + } +} +"@ + Variables = @{ + input = $inputParams + } + Context = $Context + } + if ($PSCmdlet.ShouldProcess("Creating organization '$Name' in enterprise '$Enterprise'")) { + $orgresult = Invoke-GitHubGraphQLQuery @updateGraphQLInputs + [GitHubOrganization]::new($orgresult.createEnterpriseOrganization.organization, $Context) + } + } + + end { + Write-Debug "[$stackPath] - End" + } +} + diff --git a/src/functions/public/Organization/Remove-GitHubOrganization.ps1 b/src/functions/public/Organization/Remove-GitHubOrganization.ps1 index 0d9f3c310..8f31b57c3 100644 --- a/src/functions/public/Organization/Remove-GitHubOrganization.ps1 +++ b/src/functions/public/Organization/Remove-GitHubOrganization.ps1 @@ -24,16 +24,16 @@ https://psmodule.io/GitHub/Functions/Organization/Remove-GitHubOrganization #> [OutputType([void])] - [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] + [CmdletBinding(DefaultParameterSetName = 'Remove an organization', SupportsShouldProcess, ConfirmImpact = 'High')] param( # The organization name. The name is not case sensitive. - [Parameter( - Mandatory, - ValueFromPipeline, - ValueFromPipelineByPropertyName - )] + [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [string] $Name, + # The input object to process. Can be a single or an array of GitHubOrganization objects. + [Parameter(Mandatory, ParameterSetName = 'ArrayInput', ValueFromPipeline)] + [GitHubOrganization[]] $InputObject, + # 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()] @@ -48,14 +48,28 @@ } process { - $inputObject = @{ - Method = 'DELETE' - APIEndpoint = "/orgs/$Name" - Context = $Context - } + switch ($PSCmdlet.ParameterSetName) { + 'ArrayInput' { + foreach ($item in $InputObject) { + $params = @{ + Name = $item.Name + Context = $Context + } + Remove-GitHubOrganization @params + } + break + } + default { + $apiParams = @{ + Method = 'DELETE' + APIEndpoint = "/orgs/$Name" + Context = $Context + } - if ($PSCmdlet.ShouldProcess("organization [$Name]", 'DELETE')) { - $null = Invoke-GitHubAPI @inputObject + if ($PSCmdlet.ShouldProcess("organization [$Name]", 'Delete')) { + $null = Invoke-GitHubAPI @apiParams + } + } } } @@ -63,5 +77,3 @@ Write-Debug "[$stackPath] - End" } } - -#SkipTest:FunctionTest:Will add a test for this function in a future PR diff --git a/src/functions/public/Organization/Update-GitHubOrganization.ps1 b/src/functions/public/Organization/Update-GitHubOrganization.ps1 index 5023ec0d8..2315d6c90 100644 --- a/src/functions/public/Organization/Update-GitHubOrganization.ps1 +++ b/src/functions/public/Organization/Update-GitHubOrganization.ps1 @@ -8,9 +8,9 @@ profile and member privileges. .EXAMPLE - Update-GitHubOrganization -Organization 'GitHub' -Blog 'https://github.blog' + Update-GitHubOrganization -Organization 'GitHub' -Description 'The official GitHub organization.' - Sets the blog URL for the organization 'GitHub' to ''. + Sets the description for the organization 'GitHub' to 'The official GitHub organization.'. .EXAMPLE $param = @{ @@ -134,9 +134,9 @@ [Parameter(ValueFromPipelineByPropertyName)] [bool] $WebCommitSignoffRequired, - # Path to the organization's blog. + # Path to the organization's site. [Parameter(ValueFromPipelineByPropertyName)] - [string] $Blog, + [string] $Website, # Whether secret scanning push protection is automatically enabled for new repositories. # To use this parameter, you must have admin permissions for the repository or be an owner or security manager for @@ -171,7 +171,7 @@ $body = @{ name = $NewName billing_email = $BillingEmail - blog = $Blog + blog = $Website company = $Company description = $Description email = $Email @@ -195,7 +195,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/orgs/$Name" Body = $body @@ -203,8 +203,8 @@ } if ($PSCmdlet.ShouldProcess("organization [$Name]", 'Set')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubOrganization]::new($_.Response) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubOrganization]::new($_.Response, $Context) } } } diff --git a/src/functions/public/Rate-Limit/Get-GitHubRateLimit.ps1 b/src/functions/public/Rate-Limit/Get-GitHubRateLimit.ps1 index 3d3a193e1..c7a6830c0 100644 --- a/src/functions/public/Rate-Limit/Get-GitHubRateLimit.ps1 +++ b/src/functions/public/Rate-Limit/Get-GitHubRateLimit.ps1 @@ -8,15 +8,15 @@ Some categories of endpoints have custom rate limits that are separate from the rate limit governing the other REST API endpoints. For this reason, the API response categorizes your rate limit. Under `resources`, you'll see objects relating to different categories: - * The `core` object provides your rate limit status for all non-search-related resources in the REST API. - * The `search` object provides your rate limit status for the REST API for searching (excluding code searches). For more information, see "[Search](https://docs.github.com/rest/search)." - * The `code_search` object provides your rate limit status for the REST API for searching code. For more information, see "[Search code](https://docs.github.com/rest/search/search#search-code)." - * The `graphql` object provides your rate limit status for the GraphQL API. For more information, see "[Resource limitations](https://docs.github.com/graphql/overview/resource-limitations#rate-limit)." - * The `integration_manifest` object provides your rate limit status for the `POST /app-manifests/{code}/conversions` operation. For more information, see "[Creating a GitHub App from a manifest](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app-from-a-manifest#3-you-exchange-the-temporary-code-to-retrieve-the-app-configuration)." - * The `dependency_snapshots` object provides your rate limit status for submitting snapshots to the dependency graph. For more information, see "[Dependency graph](https://docs.github.com/rest/dependency-graph)." - * The `code_scanning_upload` object provides your rate limit status for uploading SARIF results to code scanning. For more information, see "[Uploading a SARIF file to GitHub](https://docs.github.com/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github)." - * The `actions_runner_registration` object provides your rate limit status for registering self-hosted runners in GitHub Actions. For more information, see "[Self-hosted runners](https://docs.github.com/rest/actions/self-hosted-runners)." - * The `source_import` object is no longer in use for any API endpoints, and it will be removed in the next API version. For more information about API versions, see "[API Versions](https://docs.github.com/rest/overview/api-versions)." + - The `core` object provides your rate limit status for all non-search-related resources in the REST API. + - The `search` object provides your rate limit status for the REST API for searching (excluding code searches). For more information, see "[Search](https://docs.github.com/rest/search)." + - The `code_search` object provides your rate limit status for the REST API for searching code. For more information, see "[Search code](https://docs.github.com/rest/search/search#search-code)." + - The `graphql` object provides your rate limit status for the GraphQL API. For more information, see "[Resource limitations](https://docs.github.com/graphql/overview/resource-limitations#rate-limit)." + - The `integration_manifest` object provides your rate limit status for the `POST /app-manifests/{code}/conversions` operation. For more information, see "[Creating a GitHub App from a manifest](https://docs.github.com/apps/creating-github-apps/setting-up-a-github-app/creating-a-github-app-from-a-manifest#3-you-exchange-the-temporary-code-to-retrieve-the-app-configuration)." + - The `dependency_snapshots` object provides your rate limit status for submitting snapshots to the dependency graph. For more information, see "[Dependency graph](https://docs.github.com/rest/dependency-graph)." + - The `code_scanning_upload` object provides your rate limit status for uploading SARIF results to code scanning. For more information, see "[Uploading a SARIF file to GitHub](https://docs.github.com/code-security/code-scanning/integrating-with-code-scanning/uploading-a-sarif-file-to-github)." + - The `actions_runner_registration` object provides your rate limit status for registering self-hosted runners in GitHub Actions. For more information, see "[Self-hosted runners](https://docs.github.com/rest/actions/self-hosted-runners)." + - The `source_import` object is no longer in use for any API endpoints, and it will be removed in the next API version. For more information about API versions, see "[API Versions](https://docs.github.com/rest/overview/api-versions)." **Note:** The `rate` object is deprecated. If you're writing new API client code or updating existing code, you should use the `core` object instead of the `rate` object. The `core` object contains the same information that is present in the `rate` object. @@ -26,6 +26,9 @@ Gets the rate limit status for the authenticated user. + .OUTPUTS + GitHubRateLimitResource + .NOTES [Get rate limit status for the authenticated user](https://docs.github.com/rest/rate-limit/rate-limit#get-rate-limit-status-for-the-authenticated-user) @@ -54,30 +57,20 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/rate_limit' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.Resources.PSObject.Properties | ForEach-Object { - [GitHubRateLimitResource]@{ - Name = $_.Name - Limit = $_.Value.limit - Used = $_.Value.used - Remaining = $_.Value.remaining - Reset = [DateTime]::UnixEpoch.AddSeconds($_.Value.reset).ToLocalTime() - } + $resource = $_.Value | Add-Member -MemberType NoteProperty -Name 'name' -Value $_.Name -PassThru + [GitHubRateLimitResource]::new($resource) } if ($_.Response.Rate) { - [GitHubRateLimitResource]@{ - Name = 'rate' - Limit = $_.Response.Rate.limit - Used = $_.Response.Rate.used - Remaining = $_.Response.Rate.remaining - Reset = [DateTime]::UnixEpoch.AddSeconds($_.Response.Rate.reset).ToLocalTime() - } + $resource = $_.Response.Rate | Add-Member -MemberType NoteProperty -Name 'name' -Value 'rate' -PassThru + [GitHubRateLimitResource]::new($resource) } } } diff --git a/src/functions/public/Releases/Assets/Add-GitHubReleaseAsset.ps1 b/src/functions/public/Releases/Assets/Add-GitHubReleaseAsset.ps1 index a713bad96..5e9832840 100644 --- a/src/functions/public/Releases/Assets/Add-GitHubReleaseAsset.ps1 +++ b/src/functions/public/Releases/Assets/Add-GitHubReleaseAsset.ps1 @@ -172,7 +172,7 @@ } $uploadUrl = New-Uri @urlParams - $inputObject = @{ + $apiParams = @{ Method = 'POST' Uri = $uploadUrl ContentType = $ContentType @@ -180,8 +180,8 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubReleaseAsset]($_.Response) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubReleaseAsset]::new($_.Response) } } diff --git a/src/functions/public/Releases/Assets/Remove-GitHubReleaseAsset.ps1 b/src/functions/public/Releases/Assets/Remove-GitHubReleaseAsset.ps1 index 22e0c5f89..76efada9c 100644 --- a/src/functions/public/Releases/Assets/Remove-GitHubReleaseAsset.ps1 +++ b/src/functions/public/Releases/Assets/Remove-GitHubReleaseAsset.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubReleaseAsset { +filter Remove-GitHubReleaseAsset { <# .SYNOPSIS Delete a release asset @@ -20,7 +20,7 @@ .NOTES [Delete a release asset](https://docs.github.com/rest/releases/assets#delete-a-release-asset) #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory)] @@ -49,14 +49,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/releases/assets/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("Asset with ID [$ID] in [$Owner/$Repository]", 'DELETE')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Releases/Assets/Save-GitHubReleaseAsset.ps1 b/src/functions/public/Releases/Assets/Save-GitHubReleaseAsset.ps1 index 038224750..575c8b7c3 100644 --- a/src/functions/public/Releases/Assets/Save-GitHubReleaseAsset.ps1 +++ b/src/functions/public/Releases/Assets/Save-GitHubReleaseAsset.ps1 @@ -152,13 +152,13 @@ } # Now download the asset - $inputObject = @{ + $apiParams = @{ Method = 'GET' Uri = $asset.Url Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $itemType = $Path.EndsWith('.zip') ? 'File' : 'Directory' $isAbsolute = [System.IO.Path]::IsPathRooted($Path) $filename = $asset.Name diff --git a/src/functions/public/Releases/Assets/Update-GitHubReleaseAsset.ps1 b/src/functions/public/Releases/Assets/Update-GitHubReleaseAsset.ps1 index a632eaaef..ac95ac0d5 100644 --- a/src/functions/public/Releases/Assets/Update-GitHubReleaseAsset.ps1 +++ b/src/functions/public/Releases/Assets/Update-GitHubReleaseAsset.ps1 @@ -74,7 +74,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/repos/$Owner/$Repository/releases/assets/$ID" Body = $body @@ -82,8 +82,8 @@ } if ($PSCmdlet.ShouldProcess("assets for release with ID [$ID] in [$Owner/$Repository]", 'Set')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { - [GitHubReleaseAsset]($_.Response) + Invoke-GitHubAPI @apiParams | ForEach-Object { + [GitHubReleaseAsset]::new($_.Response) } } } diff --git a/src/functions/public/Releases/New-GitHubRelease.ps1 b/src/functions/public/Releases/New-GitHubRelease.ps1 index 7d4c239fa..67aa10cf7 100644 --- a/src/functions/public/Releases/New-GitHubRelease.ps1 +++ b/src/functions/public/Releases/New-GitHubRelease.ps1 @@ -132,7 +132,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/releases" Body = $body @@ -140,7 +140,7 @@ } if ($PSCmdlet.ShouldProcess("$Owner/$Repository", 'Create a release')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRelease]::new($_.Response , $Owner, $Repository, $Latest) } } diff --git a/src/functions/public/Releases/New-GitHubReleaseNote.ps1 b/src/functions/public/Releases/New-GitHubReleaseNote.ps1 index 210789b9b..a81e18130 100644 --- a/src/functions/public/Releases/New-GitHubReleaseNote.ps1 +++ b/src/functions/public/Releases/New-GitHubReleaseNote.ps1 @@ -118,7 +118,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/releases/generate-notes" Body = $body @@ -126,7 +126,7 @@ } if ($PSCmdlet.ShouldProcess("release notes for release on $Owner/$Repository", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [PSCustomObject]@{ Name = $_.Response.name Notes = $_.Response.body diff --git a/src/functions/public/Releases/Remove-GitHubRelease.ps1 b/src/functions/public/Releases/Remove-GitHubRelease.ps1 index 409e47711..6bddf1cb0 100644 --- a/src/functions/public/Releases/Remove-GitHubRelease.ps1 +++ b/src/functions/public/Releases/Remove-GitHubRelease.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubRelease { +filter Remove-GitHubRelease { <# .SYNOPSIS Delete a release @@ -24,7 +24,7 @@ [Delete a release](https://docs.github.com/rest/releases/releases#delete-a-release) #> [OutputType([void])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] @@ -53,14 +53,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/releases/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("Release with ID [$ID] in [$Owner/$Repository]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Releases/Update-GitHubRelease.ps1 b/src/functions/public/Releases/Update-GitHubRelease.ps1 index 657fbbe0d..92d27100c 100644 --- a/src/functions/public/Releases/Update-GitHubRelease.ps1 +++ b/src/functions/public/Releases/Update-GitHubRelease.ps1 @@ -175,7 +175,7 @@ $body['draft'] = [bool]$Draft } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/repos/$Owner/$Repository/releases/$ID" Body = $body @@ -184,7 +184,7 @@ if ($PSCmdlet.ShouldProcess("release with ID [$ID] in [$Owner/$Repository]", 'Update')) { $resultLatest = $PSBoundParameters.ContainsKey('Latest') ? $Latest : $release.IsLatest - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRelease]::new($_.Response , $Owner, $Repository, $resultLatest) } } diff --git a/src/functions/public/Repositories/Autolinks/New-GitHubRepositoryAutolink.ps1 b/src/functions/public/Repositories/Autolinks/New-GitHubRepositoryAutolink.ps1 index 0e14196ef..bdfc0ce06 100644 --- a/src/functions/public/Repositories/Autolinks/New-GitHubRepositoryAutolink.ps1 +++ b/src/functions/public/Repositories/Autolinks/New-GitHubRepositoryAutolink.ps1 @@ -67,7 +67,7 @@ is_alphanumeric = $IsAlphanumeric } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/autolinks" Body = $body @@ -75,7 +75,7 @@ } if ($PSCmdlet.ShouldProcess("Autolink for repository [$Owner/$Repository]", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Autolinks/Remove-GitHubRepositoryAutolink.ps1 b/src/functions/public/Repositories/Autolinks/Remove-GitHubRepositoryAutolink.ps1 index 27fcd6fcc..1e1b9b2cb 100644 --- a/src/functions/public/Repositories/Autolinks/Remove-GitHubRepositoryAutolink.ps1 +++ b/src/functions/public/Repositories/Autolinks/Remove-GitHubRepositoryAutolink.ps1 @@ -51,7 +51,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Repository/autolinks/$ID" Body = $body @@ -59,7 +59,7 @@ } if ($PSCmdlet.ShouldProcess("Autolink with ID [$ID] for repository [$Owner/$Repository]", 'DELETE')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1 b/src/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1 index 58b814718..f82e932f7 100644 --- a/src/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1 +++ b/src/functions/public/Repositories/CustomProperties/Get-GitHubRepositoryCustomProperty.ps1 @@ -46,13 +46,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/properties/values" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Move-GitHubRepository.ps1 b/src/functions/public/Repositories/Move-GitHubRepository.ps1 index 599061245..204d24318 100644 --- a/src/functions/public/Repositories/Move-GitHubRepository.ps1 +++ b/src/functions/public/Repositories/Move-GitHubRepository.ps1 @@ -72,14 +72,14 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Name/transfer" Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubRepository]::New($_.Response) } } diff --git a/src/functions/public/Repositories/Permissions/Remove-GitHubRepositoryPermission.ps1 b/src/functions/public/Repositories/Permissions/Remove-GitHubRepositoryPermission.ps1 index 742e20b35..ba25d66e8 100644 --- a/src/functions/public/Repositories/Permissions/Remove-GitHubRepositoryPermission.ps1 +++ b/src/functions/public/Repositories/Permissions/Remove-GitHubRepositoryPermission.ps1 @@ -59,14 +59,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$TeamOwner/teams/$Team/repos/$Owner/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("Team [$TeamOwner/$Team] repository permission on [$Owner/$Name]", 'Remove')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Repositories/Permissions/Set-GitHubRepositoryPermission.ps1 b/src/functions/public/Repositories/Permissions/Set-GitHubRepositoryPermission.ps1 index f7ff1abee..a2ab0cbe6 100644 --- a/src/functions/public/Repositories/Permissions/Set-GitHubRepositoryPermission.ps1 +++ b/src/functions/public/Repositories/Permissions/Set-GitHubRepositoryPermission.ps1 @@ -102,7 +102,7 @@ permission = $Permission.ToLower() } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$TeamOwner/teams/$Team/repos/$Owner/$Name" Body = $body @@ -110,7 +110,7 @@ } if ($PSCmdlet.ShouldProcess("Team [$TeamOwner/$Team] repository permission [$Permission] on [$Owner/$Name]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Repositories/Remove-GitHubRepository.ps1 b/src/functions/public/Repositories/Remove-GitHubRepository.ps1 index 952211811..c96546fb3 100644 --- a/src/functions/public/Repositories/Remove-GitHubRepository.ps1 +++ b/src/functions/public/Repositories/Remove-GitHubRepository.ps1 @@ -47,14 +47,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Name" Context = $Context } if ($PSCmdlet.ShouldProcess("repo [$Owner/$Name]", 'Delete')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams Write-Verbose "Repository [$Owner/$Name] deleted." } } diff --git a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 index d68a8969c..fcaccf936 100644 --- a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 +++ b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Name/private-vulnerability-reporting" Context = $Context } if ($PSCmdlet.ShouldProcess("Private Vulnerability Reporting for [$Owner/$Name]", 'Disable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositorySecurityFix.ps1 b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositorySecurityFix.ps1 index 66bf3a7c1..452365d86 100644 --- a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositorySecurityFix.ps1 +++ b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositorySecurityFix.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Name/automated-security-fixes" Context = $Context } if ($PSCmdlet.ShouldProcess("Security Fixes for [$Owner/$Name]", 'Disable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryVulnerabilityAlert.ps1 b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryVulnerabilityAlert.ps1 index e56f7d50b..3b43161d5 100644 --- a/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryVulnerabilityAlert.ps1 +++ b/src/functions/public/Repositories/Repositories/Disable-GitHubRepositoryVulnerabilityAlert.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/repos/$Owner/$Name/vulnerability-alerts" Context = $Context } if ($PSCmdlet.ShouldProcess("Vulnerability Alerts for [$Owner/$Name]", 'Disable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 index 674d94cb0..d6bebba62 100644 --- a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 +++ b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryPrivateVulnerabilityReporting.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Name/private-vulnerability-reporting" Context = $Context } if ($PSCmdlet.ShouldProcess("Private Vulnerability Reporting for [$Owner/$Name]", 'Enable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositorySecurityFix.ps1 b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositorySecurityFix.ps1 index 4e3a04c5f..67fa2f94f 100644 --- a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositorySecurityFix.ps1 +++ b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositorySecurityFix.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Name/automated-security-fixes" Context = $Context } if ($PSCmdlet.ShouldProcess("Security Fixes for [$Owner/$Name]", 'Enable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryVulnerabilityAlert.ps1 b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryVulnerabilityAlert.ps1 index 09bb6f7f1..584ed3668 100644 --- a/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryVulnerabilityAlert.ps1 +++ b/src/functions/public/Repositories/Repositories/Enable-GitHubRepositoryVulnerabilityAlert.ps1 @@ -45,14 +45,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Name/vulnerability-alerts" Context = $Context } if ($PSCmdlet.ShouldProcess("Vulnerability Alerts for [$Owner/$Name]", 'Enable')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryActivity.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryActivity.ps1 index 283394912..e73fcf83d 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryActivity.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryActivity.ps1 @@ -129,7 +129,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/activity" Body = $body @@ -137,7 +137,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryCodeownersError.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryCodeownersError.ps1 index 6ce679aa2..20d8ae32f 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryCodeownersError.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryCodeownersError.ps1 @@ -57,14 +57,14 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/codeowners/errors" Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryContributor.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryContributor.ps1 index be517a48e..fe85303fd 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryContributor.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryContributor.ps1 @@ -61,7 +61,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/contributors" Body = $body @@ -69,7 +69,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryFork.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryFork.ps1 index 87bfe3c2d..d9d6ab9f9 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryFork.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryFork.ps1 @@ -57,7 +57,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/forks" Body = $body @@ -65,7 +65,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryLanguage.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryLanguage.ps1 index 9ac7934fc..19c67c1e0 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryLanguage.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryLanguage.ps1 @@ -45,13 +45,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/languages" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositorySecurityFix.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositorySecurityFix.ps1 index e5356b44d..c1c190893 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositorySecurityFix.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositorySecurityFix.ps1 @@ -19,7 +19,6 @@ .LINK https://psmodule.io/GitHub/Functions/Repositories/Repositories/Get-GitHubRepositorySecurityFix #> - [Alias('Get-GitHubRepoSecurityFixes')] [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains a long link.')] param( @@ -47,13 +46,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/automated-security-fixes" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTag.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTag.ps1 index 3bce11daf..c0b990b43 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTag.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTag.ps1 @@ -48,14 +48,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/tags" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTeam.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTeam.ps1 index a9bacc676..f448e6163 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTeam.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTeam.ps1 @@ -56,14 +56,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/teams" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTopic.ps1 b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTopic.ps1 index 8ef7f644b..b2a9aeabb 100644 --- a/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTopic.ps1 +++ b/src/functions/public/Repositories/Repositories/Get-GitHubRepositoryTopic.ps1 @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/topics" PerPage = $PerPage Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response.names } } diff --git a/src/functions/public/Repositories/Repositories/Set-GitHubRepositoryTopic.ps1 b/src/functions/public/Repositories/Repositories/Set-GitHubRepositoryTopic.ps1 index 21230444e..93ca0c5b1 100644 --- a/src/functions/public/Repositories/Repositories/Set-GitHubRepositoryTopic.ps1 +++ b/src/functions/public/Repositories/Repositories/Set-GitHubRepositoryTopic.ps1 @@ -50,7 +50,7 @@ names = $Topic | ForEach-Object { $_.ToLower() } } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Name/topics" Body = $body @@ -58,7 +58,7 @@ } if ($PSCmdlet.ShouldProcess("topics for repo [$Owner/$Name]", 'Set')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response.names } } diff --git a/src/functions/public/Repositories/Repositories/Start-GitHubRepositoryEvent.ps1 b/src/functions/public/Repositories/Repositories/Start-GitHubRepositoryEvent.ps1 index ca5b19c3f..eb4fef843 100644 --- a/src/functions/public/Repositories/Repositories/Start-GitHubRepositoryEvent.ps1 +++ b/src/functions/public/Repositories/Repositories/Start-GitHubRepositoryEvent.ps1 @@ -83,14 +83,14 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Name/dispatches" Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Repositories/Repositories/Test-GitHubRepositoryVulnerabilityAlert.ps1 b/src/functions/public/Repositories/Repositories/Test-GitHubRepositoryVulnerabilityAlert.ps1 index 0fdaa1c1d..c4bf7ebaf 100644 --- a/src/functions/public/Repositories/Repositories/Test-GitHubRepositoryVulnerabilityAlert.ps1 +++ b/src/functions/public/Repositories/Repositories/Test-GitHubRepositoryVulnerabilityAlert.ps1 @@ -48,13 +48,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Name/vulnerability-alerts" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuite.ps1 b/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuite.ps1 index d24ceb1e3..741f9e9b3 100644 --- a/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuite.ps1 +++ b/src/functions/public/Repositories/RuleSuite/Get-GitHubRepositoryRuleSuite.ps1 @@ -39,7 +39,6 @@ param( # The account owner of the repository. The name is not case sensitive. [Parameter()] - [Alias('org')] [string] $Owner, # The name of the repository without the .git extension. The name is not case sensitive. diff --git a/src/functions/public/Repositories/Update-GitHubRepository.ps1 b/src/functions/public/Repositories/Update-GitHubRepository.ps1 index d326e0321..615c1bb29 100644 --- a/src/functions/public/Repositories/Update-GitHubRepository.ps1 +++ b/src/functions/public/Repositories/Update-GitHubRepository.ps1 @@ -281,7 +281,7 @@ [pscustomobject]$body | Out-String -Stream | ForEach-Object { Write-Debug $_ } } if ($body.Keys.Count -gt 0) { - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/repos/$Owner/$Name" Body = $body @@ -289,7 +289,7 @@ } if ($PSCmdlet.ShouldProcess("Repository [$Owner/$Name]", 'Update')) { - $updatedRepo = Invoke-GitHubAPI @inputObject | Select-Object -ExpandProperty Response + $updatedRepo = Invoke-GitHubAPI @apiParams | Select-Object -ExpandProperty Response } if ($DebugPreference -eq 'Continue') { Write-Debug 'Repo has been updated' diff --git a/src/functions/public/Secrets/Remove-GitHubSecret.ps1 b/src/functions/public/Secrets/Remove-GitHubSecret.ps1 index 4c8295d57..2d4f1a4b4 100644 --- a/src/functions/public/Secrets/Remove-GitHubSecret.ps1 +++ b/src/functions/public/Secrets/Remove-GitHubSecret.ps1 @@ -32,7 +32,7 @@ 'PSShouldProcess', '', Scope = 'Function', Justification = 'This check is performed in the private functions.' )] - [CmdletBinding(DefaultParameterSetName = 'AuthenticatedUser', SupportsShouldProcess)] + [CmdletBinding(DefaultParameterSetName = 'AuthenticatedUser', SupportsShouldProcess, ConfirmImpact = 'High')] param ( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory, ParameterSetName = 'Organization', ValueFromPipelineByPropertyName)] diff --git a/src/functions/public/Secrets/SelectedRepository/Add-GitHubSecretSelectedRepository.ps1 b/src/functions/public/Secrets/SelectedRepository/Add-GitHubSecretSelectedRepository.ps1 index a6f8f4f74..56ed38a60 100644 --- a/src/functions/public/Secrets/SelectedRepository/Add-GitHubSecretSelectedRepository.ps1 +++ b/src/functions/public/Secrets/SelectedRepository/Add-GitHubSecretSelectedRepository.ps1 @@ -67,13 +67,13 @@ Write-Debug 'Repo is already selected, returning' return } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name/repositories/$RepositoryID" Context = $Context } - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/public/Secrets/SelectedRepository/Get-GitHubSecretSelectedRepository.ps1 b/src/functions/public/Secrets/SelectedRepository/Get-GitHubSecretSelectedRepository.ps1 index fe814b412..21c00582d 100644 --- a/src/functions/public/Secrets/SelectedRepository/Get-GitHubSecretSelectedRepository.ps1 +++ b/src/functions/public/Secrets/SelectedRepository/Get-GitHubSecretSelectedRepository.ps1 @@ -72,13 +72,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name/repositories" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.repositories | ForEach-Object { [GitHubRepository]::New($_) } diff --git a/src/functions/public/Secrets/SelectedRepository/Remove-GitHubSecretSelectedRepository.ps1 b/src/functions/public/Secrets/SelectedRepository/Remove-GitHubSecretSelectedRepository.ps1 index 446259266..e4c97e672 100644 --- a/src/functions/public/Secrets/SelectedRepository/Remove-GitHubSecretSelectedRepository.ps1 +++ b/src/functions/public/Secrets/SelectedRepository/Remove-GitHubSecretSelectedRepository.ps1 @@ -28,7 +28,7 @@ Justification = 'Long links' )] [OutputType([void])] - [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')] + [CmdletBinding(SupportsShouldProcess)] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory)] @@ -66,14 +66,14 @@ Write-Debug 'Repo is not selected, returning' return } - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name/repositories/$RepositoryID" Context = $Context } if ($PSCmdlet.ShouldProcess("access to secret [$Owner/$Name] for repository [$RepositoryID]", 'Remove')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Secrets/SelectedRepository/Set-GitHubSecretSelectedRepository.ps1 b/src/functions/public/Secrets/SelectedRepository/Set-GitHubSecretSelectedRepository.ps1 index b671b8e3a..c82c5c0ce 100644 --- a/src/functions/public/Secrets/SelectedRepository/Set-GitHubSecretSelectedRepository.ps1 +++ b/src/functions/public/Secrets/SelectedRepository/Set-GitHubSecretSelectedRepository.ps1 @@ -56,7 +56,7 @@ $body = @{ selected_repository_ids = @($RepositoryID) } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Owner/actions/secrets/$Name/repositories" Body = $body @@ -64,7 +64,7 @@ } if ($PSCmdlet.ShouldProcess("access to secret [$Owner/$Name] for repository [$RepositoryID]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Secrets/Set-GitHubSecret.ps1 b/src/functions/public/Secrets/Set-GitHubSecret.ps1 index a92474e0e..c1da2a007 100644 --- a/src/functions/public/Secrets/Set-GitHubSecret.ps1 +++ b/src/functions/public/Secrets/Set-GitHubSecret.ps1 @@ -143,4 +143,4 @@ Write-Debug "[$stackPath] - End" } } -#Requires -Modules @{ ModuleName = 'Sodium'; RequiredVersion = '2.2.0'} +#Requires -Modules @{ ModuleName = 'Sodium'; RequiredVersion = '2.2.2'} diff --git a/src/functions/public/Status/Get-GitHubScheduledMaintenance.ps1 b/src/functions/public/Status/Get-GitHubScheduledMaintenance.ps1 index a86dae724..df799f233 100644 --- a/src/functions/public/Status/Get-GitHubScheduledMaintenance.ps1 +++ b/src/functions/public/Status/Get-GitHubScheduledMaintenance.ps1 @@ -48,7 +48,7 @@ # The stamp to check status for. [Parameter()] - [ValidateSet('Public', 'Europe', 'Australia')] + [ValidateSet('Public', 'Europe', 'Australia', 'US')] [string] $Stamp = 'Public' ) diff --git a/src/functions/public/Status/Get-GitHubStatus.ps1 b/src/functions/public/Status/Get-GitHubStatus.ps1 index e507ad48a..8a0072f1b 100644 --- a/src/functions/public/Status/Get-GitHubStatus.ps1 +++ b/src/functions/public/Status/Get-GitHubStatus.ps1 @@ -38,7 +38,7 @@ # The stamp to check status for. [Parameter()] - [ValidateSet('Public', 'Europe', 'Australia')] + [ValidateSet('Public', 'Europe', 'Australia', 'US')] [string] $Stamp = 'Public' ) begin { diff --git a/src/functions/public/Status/Get-GitHubStatusComponent.ps1 b/src/functions/public/Status/Get-GitHubStatusComponent.ps1 index 812d910c5..130c5af14 100644 --- a/src/functions/public/Status/Get-GitHubStatusComponent.ps1 +++ b/src/functions/public/Status/Get-GitHubStatusComponent.ps1 @@ -24,7 +24,7 @@ param( # The stamp to check status for. [Parameter()] - [ValidateSet('Public', 'Europe', 'Australia')] + [ValidateSet('Public', 'Europe', 'Australia', 'US')] [string] $Stamp = 'Public' ) diff --git a/src/functions/public/Status/Get-GitHubStatusIncident.ps1 b/src/functions/public/Status/Get-GitHubStatusIncident.ps1 index f591af37b..146c88f94 100644 --- a/src/functions/public/Status/Get-GitHubStatusIncident.ps1 +++ b/src/functions/public/Status/Get-GitHubStatusIncident.ps1 @@ -37,7 +37,7 @@ # The stamp to check status for. [Parameter()] - [ValidateSet('Public', 'Europe', 'Australia')] + [ValidateSet('Public', 'Europe', 'Australia', 'US')] [string] $Stamp = 'Public' ) diff --git a/src/functions/public/Teams/New-GitHubTeam.ps1 b/src/functions/public/Teams/New-GitHubTeam.ps1 index bcaeb2a60..05f4178a1 100644 --- a/src/functions/public/Teams/New-GitHubTeam.ps1 +++ b/src/functions/public/Teams/New-GitHubTeam.ps1 @@ -98,7 +98,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/orgs/$Organization/teams" Body = $body @@ -106,7 +106,7 @@ } if ($PSCmdlet.ShouldProcess("team '$Name' in '$Organization'", 'Create')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($team in $_.Response) { [GitHubTeam]::new($team, $Organization) } diff --git a/src/functions/public/Teams/Remove-GitHubTeam.ps1 b/src/functions/public/Teams/Remove-GitHubTeam.ps1 index 3328a3afc..b795eca9a 100644 --- a/src/functions/public/Teams/Remove-GitHubTeam.ps1 +++ b/src/functions/public/Teams/Remove-GitHubTeam.ps1 @@ -17,7 +17,7 @@ https://psmodule.io/GitHub/Functions/Teams/Remove-GitHubTeam #> [OutputType([void])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The organization name. The name is not case sensitive. # If not provided, the organization from the context is used. @@ -42,14 +42,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Organization/teams/$Slug" Context = $Context } if ($PSCmdlet.ShouldProcess("$Organization/$Slug", 'DELETE')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Teams/Update-GitHubTeam.ps1 b/src/functions/public/Teams/Update-GitHubTeam.ps1 index 1fae8b8a2..642122513 100644 --- a/src/functions/public/Teams/Update-GitHubTeam.ps1 +++ b/src/functions/public/Teams/Update-GitHubTeam.ps1 @@ -95,7 +95,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = "/orgs/$Organization/teams/$Slug" Body = $body @@ -103,7 +103,7 @@ } if ($PSCmdlet.ShouldProcess("$Organization/$Slug", 'Update')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { foreach ($team in $_.Response) { [GitHubTeam]::new($team, $Organization) } diff --git a/src/functions/public/Users/Emails/Add-GitHubUserEmail.ps1 b/src/functions/public/Users/Emails/Add-GitHubUserEmail.ps1 index a2bf8ef70..1ee277e8d 100644 --- a/src/functions/public/Users/Emails/Add-GitHubUserEmail.ps1 +++ b/src/functions/public/Users/Emails/Add-GitHubUserEmail.ps1 @@ -49,14 +49,14 @@ emails = $Email } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/emails' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/Emails/Remove-GitHubUserEmail.ps1 b/src/functions/public/Users/Emails/Remove-GitHubUserEmail.ps1 index cbd9f8a3f..2a7ed95ec 100644 --- a/src/functions/public/Users/Emails/Remove-GitHubUserEmail.ps1 +++ b/src/functions/public/Users/Emails/Remove-GitHubUserEmail.ps1 @@ -47,7 +47,7 @@ emails = $Email } - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = '/user/emails' Body = $body @@ -55,7 +55,7 @@ } if ($PSCmdlet.ShouldProcess("Email addresses [$($Email -join ', ')]", 'DELETE')) { - $null = Invoke-GitHubAPI @inputObject | ForEach-Object { + $null = Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/Emails/Update-GitHubUserEmailVisibility.ps1 b/src/functions/public/Users/Emails/Update-GitHubUserEmailVisibility.ps1 index 69581f1ac..67ee2bb42 100644 --- a/src/functions/public/Users/Emails/Update-GitHubUserEmailVisibility.ps1 +++ b/src/functions/public/Users/Emails/Update-GitHubUserEmailVisibility.ps1 @@ -53,7 +53,7 @@ visibility = $Visibility.ToLower() } - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = '/user/email/visibility' Body = $body @@ -61,7 +61,7 @@ } if ($PSCmdlet.ShouldProcess("Email visibility [$Visibility]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject | ForEach-Object { + $null = Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/Followers/Add-GitHubUserFollowing.ps1 b/src/functions/public/Users/Followers/Add-GitHubUserFollowing.ps1 index cbc73efc1..b0f73ecb3 100644 --- a/src/functions/public/Users/Followers/Add-GitHubUserFollowing.ps1 +++ b/src/functions/public/Users/Followers/Add-GitHubUserFollowing.ps1 @@ -45,13 +45,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/user/following/$Username" Context = $Context } - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/public/Users/Followers/Remove-GitHubUserFollowing.ps1 b/src/functions/public/Users/Followers/Remove-GitHubUserFollowing.ps1 index 70b0172a5..2b55d921c 100644 --- a/src/functions/public/Users/Followers/Remove-GitHubUserFollowing.ps1 +++ b/src/functions/public/Users/Followers/Remove-GitHubUserFollowing.ps1 @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/user/following/$Username" Context = $Context } if ($PSCmdlet.ShouldProcess("User [$Username]", 'Unfollow')) { - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Users/GPG-Keys/Add-GitHubUserGpgKey.ps1 b/src/functions/public/Users/GPG-Keys/Add-GitHubUserGpgKey.ps1 index 0fe941753..3fcfc38df 100644 --- a/src/functions/public/Users/GPG-Keys/Add-GitHubUserGpgKey.ps1 +++ b/src/functions/public/Users/GPG-Keys/Add-GitHubUserGpgKey.ps1 @@ -63,14 +63,14 @@ armored_public_key = $ArmoredPublicKey } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/gpg_keys' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/GPG-Keys/Remove-GitHubUserGpgKey.ps1 b/src/functions/public/Users/GPG-Keys/Remove-GitHubUserGpgKey.ps1 index 9ca2538f2..b577a1fff 100644 --- a/src/functions/public/Users/GPG-Keys/Remove-GitHubUserGpgKey.ps1 +++ b/src/functions/public/Users/GPG-Keys/Remove-GitHubUserGpgKey.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubUserGpgKey { +filter Remove-GitHubUserGpgKey { <# .SYNOPSIS Delete a GPG key for the authenticated user @@ -20,7 +20,7 @@ https://psmodule.io/GitHub/Functions/Users/GPG-Keys/Remove-GitHubUserGpgKey #> [OutputType([pscustomobject])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The ID of the GPG key. [Parameter( @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/user/gpg_keys/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("GPG key with ID [$ID]", 'DELETE')) { - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Users/Keys/Add-GitHubUserKey.ps1 b/src/functions/public/Users/Keys/Add-GitHubUserKey.ps1 index ffa72bdec..e16740d1d 100644 --- a/src/functions/public/Users/Keys/Add-GitHubUserKey.ps1 +++ b/src/functions/public/Users/Keys/Add-GitHubUserKey.ps1 @@ -57,14 +57,14 @@ key = $Key } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/keys' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/Keys/Remove-GitHubUserKey.ps1 b/src/functions/public/Users/Keys/Remove-GitHubUserKey.ps1 index 91627fb5e..661cc342d 100644 --- a/src/functions/public/Users/Keys/Remove-GitHubUserKey.ps1 +++ b/src/functions/public/Users/Keys/Remove-GitHubUserKey.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubUserKey { +filter Remove-GitHubUserKey { <# .SYNOPSIS Delete a public SSH key for the authenticated user @@ -21,7 +21,7 @@ #> [OutputType([pscustomobject])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains a long link.')] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The unique identifier of the key. [Parameter( @@ -44,14 +44,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/user/keys/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("Key with ID [$ID]", 'DELETE')) { - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Users/SSH-Signing-Keys/Add-GitHubUserSigningKey.ps1 b/src/functions/public/Users/SSH-Signing-Keys/Add-GitHubUserSigningKey.ps1 index d682a4c8d..648782a91 100644 --- a/src/functions/public/Users/SSH-Signing-Keys/Add-GitHubUserSigningKey.ps1 +++ b/src/functions/public/Users/SSH-Signing-Keys/Add-GitHubUserSigningKey.ps1 @@ -59,14 +59,14 @@ key = $Key } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/ssh_signing_keys' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/SSH-Signing-Keys/Remove-GitHubUserSigningKey.ps1 b/src/functions/public/Users/SSH-Signing-Keys/Remove-GitHubUserSigningKey.ps1 index f85eceee0..f9fdd5154 100644 --- a/src/functions/public/Users/SSH-Signing-Keys/Remove-GitHubUserSigningKey.ps1 +++ b/src/functions/public/Users/SSH-Signing-Keys/Remove-GitHubUserSigningKey.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubUserSigningKey { +filter Remove-GitHubUserSigningKey { <# .SYNOPSIS Delete an SSH signing key for the authenticated user @@ -22,7 +22,7 @@ #> [OutputType([pscustomobject])] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains a long link.')] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The unique identifier of the SSH signing key. [Parameter( @@ -45,14 +45,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/user/ssh_signing_keys/$ID" Context = $Context } if ($PSCmdlet.ShouldProcess("SSH signing key with ID [$ID]", 'DELETE')) { - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Users/Social-Accounts/Add-GitHubUserSocial.ps1 b/src/functions/public/Users/Social-Accounts/Add-GitHubUserSocial.ps1 index f31a51f96..3dda88403 100644 --- a/src/functions/public/Users/Social-Accounts/Add-GitHubUserSocial.ps1 +++ b/src/functions/public/Users/Social-Accounts/Add-GitHubUserSocial.ps1 @@ -45,14 +45,14 @@ account_urls = $URL } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = '/user/social_accounts' Body = $body Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Users/Social-Accounts/Remove-GitHubUserSocial.ps1 b/src/functions/public/Users/Social-Accounts/Remove-GitHubUserSocial.ps1 index 27cbb1dba..eaaa15fa6 100644 --- a/src/functions/public/Users/Social-Accounts/Remove-GitHubUserSocial.ps1 +++ b/src/functions/public/Users/Social-Accounts/Remove-GitHubUserSocial.ps1 @@ -46,7 +46,7 @@ account_urls = $URL } - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = '/user/social_accounts' Body = $body @@ -54,7 +54,7 @@ } if ($PSCmdlet.ShouldProcess("Social accounts [$($URL -join ', ')]", 'DELETE')) { - Invoke-GitHubAPI @inputObject + Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Users/Update-GitHubUser.ps1 b/src/functions/public/Users/Update-GitHubUser.ps1 index 69af9e3d2..4f97098ec 100644 --- a/src/functions/public/Users/Update-GitHubUser.ps1 +++ b/src/functions/public/Users/Update-GitHubUser.ps1 @@ -19,9 +19,9 @@ Update the authenticated user's location to 'San Francisco' .EXAMPLE - Update-GitHubUser -Hireable $true -Bio 'I love programming' + Update-GitHubUser -Hireable $true -Description 'I love programming' - Update the authenticated user's hiring availability to 'true' and their biography to 'I love programming' + Update the authenticated user's hiring availability to 'true' and their description to 'I love programming' .NOTES [Update the authenticated user](https://docs.github.com/rest/users/users#update-the-authenticated-user) @@ -40,9 +40,10 @@ [Parameter()] [string] $Email, - # The new blog URL of the user. + # The new site for the user. [Parameter()] - [string] $Blog, + [Alias('Blog')] + [string] $Website, # The new Twitter username of the user. [Parameter()] @@ -62,7 +63,8 @@ # The new short biography of the user. [Parameter()] - [string] $Bio, + [Alias('Bio')] + [string] $Description, # 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. @@ -81,16 +83,16 @@ $body = @{ name = $DisplayName email = $Email - blog = $Blog + blog = $Website twitter_username = $TwitterUsername company = $Company location = $Location hireable = $Hireable - bio = $Bio + bio = $Description } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = '/user' Body = $body @@ -98,7 +100,7 @@ } if ($PSCmdlet.ShouldProcess('authenticated user', 'Set')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubUser]::New($_.Response) } } diff --git a/src/functions/public/Variables/Remove-GitHubVariable.ps1 b/src/functions/public/Variables/Remove-GitHubVariable.ps1 index caee1d0e7..ffe376cc3 100644 --- a/src/functions/public/Variables/Remove-GitHubVariable.ps1 +++ b/src/functions/public/Variables/Remove-GitHubVariable.ps1 @@ -42,7 +42,7 @@ Justification = 'This check is performed in the private functions.' )] [OutputType([void])] - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory, ParameterSetName = 'Organization', ValueFromPipelineByPropertyName)] diff --git a/src/functions/public/Variables/SelectedRepository/Add-GitHubVariableSelectedRepository.ps1 b/src/functions/public/Variables/SelectedRepository/Add-GitHubVariableSelectedRepository.ps1 index 571a10723..37ce7cfcf 100644 --- a/src/functions/public/Variables/SelectedRepository/Add-GitHubVariableSelectedRepository.ps1 +++ b/src/functions/public/Variables/SelectedRepository/Add-GitHubVariableSelectedRepository.ps1 @@ -67,13 +67,13 @@ Write-Debug 'Repo is already selected, returning' return } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Owner/actions/variables/$Name/repositories/$RepositoryID" Context = $Context } - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } end { diff --git a/src/functions/public/Variables/SelectedRepository/Get-GitHubVariableSelectedRepository.ps1 b/src/functions/public/Variables/SelectedRepository/Get-GitHubVariableSelectedRepository.ps1 index 9effd40da..6947ef247 100644 --- a/src/functions/public/Variables/SelectedRepository/Get-GitHubVariableSelectedRepository.ps1 +++ b/src/functions/public/Variables/SelectedRepository/Get-GitHubVariableSelectedRepository.ps1 @@ -56,13 +56,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/orgs/$Owner/actions/variables/$Name/repositories" Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { $_.Response.repositories | ForEach-Object { [GitHubRepository]::New($_) } diff --git a/src/functions/public/Variables/SelectedRepository/Remove-GitHubVariableSelectedRepository.ps1 b/src/functions/public/Variables/SelectedRepository/Remove-GitHubVariableSelectedRepository.ps1 index fa5279597..a81a40bac 100644 --- a/src/functions/public/Variables/SelectedRepository/Remove-GitHubVariableSelectedRepository.ps1 +++ b/src/functions/public/Variables/SelectedRepository/Remove-GitHubVariableSelectedRepository.ps1 @@ -37,7 +37,7 @@ Justification = 'Long links' )] [OutputType([void])] - [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Low')] + [CmdletBinding(SupportsShouldProcess)] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory)] @@ -75,14 +75,14 @@ Write-Debug 'Repo is not selected, returning' return } - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "/orgs/$Owner/actions/variables/$Name/repositories/$RepositoryID" Context = $Context } if ($PSCmdlet.ShouldProcess("access to variable [$Owner/$Name] for repository [$RepositoryID]", 'Remove')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Variables/SelectedRepository/Set-GitHubVariableSelectedRepository.ps1 b/src/functions/public/Variables/SelectedRepository/Set-GitHubVariableSelectedRepository.ps1 index 45a5b11ca..24e99861b 100644 --- a/src/functions/public/Variables/SelectedRepository/Set-GitHubVariableSelectedRepository.ps1 +++ b/src/functions/public/Variables/SelectedRepository/Set-GitHubVariableSelectedRepository.ps1 @@ -56,7 +56,7 @@ $body = @{ selected_repository_ids = @($RepositoryID) } - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/orgs/$Owner/actions/variables/$Name/repositories" Body = $body @@ -64,7 +64,7 @@ } if ($PSCmdlet.ShouldProcess("access to variable [$Owner/$Name] for repository [$RepositoryID]", 'Set')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Webhooks/Get-GitHubAppWebhookConfiguration.ps1 b/src/functions/public/Webhooks/Get-GitHubAppWebhookConfiguration.ps1 index 15edc15c7..cfaa54435 100644 --- a/src/functions/public/Webhooks/Get-GitHubAppWebhookConfiguration.ps1 +++ b/src/functions/public/Webhooks/Get-GitHubAppWebhookConfiguration.ps1 @@ -37,13 +37,13 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = '/app/hook/config' Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { [GitHubWebhookConfiguration]::new($_.Response) } } diff --git a/src/functions/public/Webhooks/Invoke-GitHubAppWebhookReDelivery.ps1 b/src/functions/public/Webhooks/Invoke-GitHubAppWebhookReDelivery.ps1 index c0ae367e7..c1a649b3a 100644 --- a/src/functions/public/Webhooks/Invoke-GitHubAppWebhookReDelivery.ps1 +++ b/src/functions/public/Webhooks/Invoke-GitHubAppWebhookReDelivery.ps1 @@ -49,14 +49,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/app/hook/deliveries/$ID/attempts" Context = $Context } if ($PSCmdlet.ShouldProcess("[$ID]", 'Redeliver event')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Webhooks/Test-GitHubWebhookSignature.ps1 b/src/functions/public/Webhooks/Test-GitHubWebhookSignature.ps1 index 71b040663..a7e53b49a 100644 --- a/src/functions/public/Webhooks/Test-GitHubWebhookSignature.ps1 +++ b/src/functions/public/Webhooks/Test-GitHubWebhookSignature.ps1 @@ -5,9 +5,9 @@ .DESCRIPTION This function validates the integrity and authenticity of a GitHub webhook request by comparing - the received HMAC SHA-256 signature against a computed hash of the payload using a shared secret. - It uses a constant-time comparison to mitigate timing attacks and returns a boolean indicating - whether the signature is valid. + the received HMAC signature against a computed hash of the payload using a shared secret. + It uses the SHA-256 algorithm and employs a constant-time comparison to mitigate + timing attacks. The function returns a boolean indicating whether the signature is valid. .EXAMPLE Test-GitHubWebhookSignature -Secret $env:WEBHOOK_SECRET -Body $Request.RawBody -Signature $Request.Headers['X-Hub-Signature-256'] @@ -19,6 +19,16 @@ Validates the provided webhook payload against the HMAC SHA-256 signature using the given secret. + .EXAMPLE + Test-GitHubWebhookSignature -Secret $env:WEBHOOK_SECRET -Request $Request + + Output: + ```powershell + True + ``` + + Validates the webhook request using the entire request object, automatically extracting the body and signature. + .OUTPUTS bool @@ -29,11 +39,12 @@ .LINK https://psmodule.io/GitHub/Functions/Webhooks/Test-GitHubWebhookSignature - .LINK - https://docs.github.com/webhooks/using-webhooks/validating-webhook-deliveries + .NOTES + [Validating Webhook Deliveries | GitHub Docs](https://docs.github.com/webhooks/using-webhooks/validating-webhook-deliveries) + [Webhook events and payloads | GitHub Docs](https://docs.github.com/en/webhooks/webhook-events-and-payloads) #> [OutputType([bool])] - [CmdletBinding()] + [CmdletBinding(DefaultParameterSetName = 'ByBody')] param ( # The secret key used to compute the HMAC hash. # Example: 'mysecret' @@ -43,25 +54,59 @@ # The JSON body of the GitHub webhook request. # This must be the compressed JSON payload received from GitHub. # Example: '{"action":"opened"}' - [Parameter(Mandatory)] + [Parameter(Mandatory, ParameterSetName = 'ByBody')] [string] $Body, # The signature received from GitHub to compare against. # Example: 'sha256=abc123...' - [Parameter(Mandatory)] - [string] $Signature + [Parameter(Mandatory, ParameterSetName = 'ByBody')] + [string] $Signature, + + # The entire request object containing RawBody and Headers. + # Used in Azure Function Apps or similar environments. + [Parameter(Mandatory, ParameterSetName = 'ByRequest')] + [PSObject] $Request ) - $keyBytes = [Text.Encoding]::UTF8.GetBytes($Secret) - $payloadBytes = [Text.Encoding]::UTF8.GetBytes($Body) + begin { + $stackPath = Get-PSCallStackPath + Write-Debug "[$stackPath] - Start" + } - $hmac = [System.Security.Cryptography.HMACSHA256]::new() - $hmac.Key = $keyBytes - $hashBytes = $hmac.ComputeHash($payloadBytes) - $computedSignature = 'sha256=' + (($hashBytes | ForEach-Object { $_.ToString('x2') }) -join '') + process { + # Handle parameter sets + if ($PSCmdlet.ParameterSetName -eq 'ByRequest') { + $Body = $Request.RawBody + $Signature = $Request.Headers['X-Hub-Signature-256'] - [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( - [Text.Encoding]::UTF8.GetBytes($computedSignature), - [Text.Encoding]::UTF8.GetBytes($Signature) - ) + # If signature not found, throw an error + if (-not $Signature) { + throw "No webhook signature found in request headers. Expected 'X-Hub-Signature-256' for SHA256 algorithm." + } + } + + $keyBytes = [Text.Encoding]::UTF8.GetBytes($Secret) + $payloadBytes = [Text.Encoding]::UTF8.GetBytes($Body) + + # Create HMAC SHA256 object + $hmac = [System.Security.Cryptography.HMACSHA256]::new() + $algorithmPrefix = 'sha256=' + + $hmac.Key = $keyBytes + $hashBytes = $hmac.ComputeHash($payloadBytes) + $computedSignature = $algorithmPrefix + (($hashBytes | ForEach-Object { $_.ToString('x2') }) -join '') + + # Dispose of the HMAC object + $hmac.Dispose() + + [System.Security.Cryptography.CryptographicOperations]::FixedTimeEquals( + [Text.Encoding]::UTF8.GetBytes($computedSignature), + [Text.Encoding]::UTF8.GetBytes($Signature) + ) + } + + end { + Write-Debug "[$stackPath] - End" + } } + diff --git a/src/functions/public/Webhooks/Update-GitHubAppWebhookConfiguration.ps1 b/src/functions/public/Webhooks/Update-GitHubAppWebhookConfiguration.ps1 index 27677c631..1560ada7c 100644 --- a/src/functions/public/Webhooks/Update-GitHubAppWebhookConfiguration.ps1 +++ b/src/functions/public/Webhooks/Update-GitHubAppWebhookConfiguration.ps1 @@ -79,7 +79,7 @@ } $body | Remove-HashtableEntry -NullOrEmptyValues - $inputObject = @{ + $apiParams = @{ Method = 'PATCH' APIEndpoint = '/app/hook/config' Body = $body @@ -87,7 +87,7 @@ } if ($PSCmdlet.ShouldProcess('webhook configuration', 'Update')) { - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response } } diff --git a/src/functions/public/Workflows/Disable-GitHubWorkflow.ps1 b/src/functions/public/Workflows/Disable-GitHubWorkflow.ps1 index d17d2d9dc..8e7cf2661 100644 --- a/src/functions/public/Workflows/Disable-GitHubWorkflow.ps1 +++ b/src/functions/public/Workflows/Disable-GitHubWorkflow.ps1 @@ -46,14 +46,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Repository/actions/workflows/$ID/disable" Context = $Context } if ($PSCmdlet.ShouldProcess("$Owner/$Repository/$ID", 'Disable workflow')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Workflows/Enable-GitHubWorkflow.ps1 b/src/functions/public/Workflows/Enable-GitHubWorkflow.ps1 index b93f601da..49d9b4c41 100644 --- a/src/functions/public/Workflows/Enable-GitHubWorkflow.ps1 +++ b/src/functions/public/Workflows/Enable-GitHubWorkflow.ps1 @@ -43,14 +43,14 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'PUT' APIEndpoint = "/repos/$Owner/$Repository/actions/workflows/$ID/enable" Context = $Context } if ($PSCmdlet.ShouldProcess("$Owner/$Repository/$ID", 'Enable workflow')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Workflows/Get-GitHubWorkflow.ps1 b/src/functions/public/Workflows/Get-GitHubWorkflow.ps1 index 866a6c958..d9a4ba822 100644 --- a/src/functions/public/Workflows/Get-GitHubWorkflow.ps1 +++ b/src/functions/public/Workflows/Get-GitHubWorkflow.ps1 @@ -61,7 +61,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'GET' APIEndpoint = "/repos/$Owner/$Repository/actions/workflows" Body = $body @@ -69,7 +69,7 @@ Context = $Context } - Invoke-GitHubAPI @inputObject | ForEach-Object { + Invoke-GitHubAPI @apiParams | ForEach-Object { Write-Output $_.Response.workflows | Where-Object { $_.name -like $Name } | ForEach-Object { [GitHubWorkflow]::new($_, $Owner, $Repository) } diff --git a/src/functions/public/Workflows/Runs/Remove-GitHubWorkflowRun.ps1 b/src/functions/public/Workflows/Runs/Remove-GitHubWorkflowRun.ps1 index 083e325fc..cd92f4a61 100644 --- a/src/functions/public/Workflows/Runs/Remove-GitHubWorkflowRun.ps1 +++ b/src/functions/public/Workflows/Runs/Remove-GitHubWorkflowRun.ps1 @@ -1,4 +1,4 @@ -filter Remove-GitHubWorkflowRun { +filter Remove-GitHubWorkflowRun { <# .SYNOPSIS Delete a workflow run @@ -22,7 +22,7 @@ .NOTES [Delete a workflow run](https://docs.github.com/rest/actions/workflow-runs#delete-a-workflow-run) #> - [CmdletBinding(SupportsShouldProcess)] + [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param( # The account owner of the repository. The name is not case sensitive. [Parameter(Mandatory, ValueFromPipelineByPropertyName)] @@ -50,7 +50,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'DELETE' APIEndpoint = "repos/$Owner/$Repository/actions/runs/$ID" Context = $Context @@ -58,7 +58,7 @@ if ($PSCmdlet.ShouldProcess("$Owner/$Repository/$ID", 'Delete workflow run')) { Write-Verbose "Deleted workflow run [$ID] in [$Owner/$Repository]" - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Workflows/Runs/Restart-GitHubWorkflowRun.ps1 b/src/functions/public/Workflows/Runs/Restart-GitHubWorkflowRun.ps1 index c1f0df4f9..4011fca79 100644 --- a/src/functions/public/Workflows/Runs/Restart-GitHubWorkflowRun.ps1 +++ b/src/functions/public/Workflows/Runs/Restart-GitHubWorkflowRun.ps1 @@ -46,7 +46,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/actions/runs/$ID/rerun" Context = $Context @@ -54,7 +54,7 @@ if ($PSCmdlet.ShouldProcess("workflow with ID [$ID] in [$Owner/$Repository]", 'Re-run')) { Write-Verbose "Re-run workflow [$ID] in [$Owner/$Repository]" - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Workflows/Runs/Stop-GitHubWorkflowRun.ps1 b/src/functions/public/Workflows/Runs/Stop-GitHubWorkflowRun.ps1 index ca67a06af..c166b7573 100644 --- a/src/functions/public/Workflows/Runs/Stop-GitHubWorkflowRun.ps1 +++ b/src/functions/public/Workflows/Runs/Stop-GitHubWorkflowRun.ps1 @@ -49,7 +49,7 @@ } process { - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/actions/runs/$ID/cancel" Context = $Context @@ -57,7 +57,7 @@ if ($PSCmdlet.ShouldProcess("$Owner/$Repository/$ID", 'Cancel/Stop workflow run')) { Write-Verbose "Cancelled workflow run [$ID] in [$Owner/$Repository]" - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/functions/public/Workflows/Start-GitHubWorkflow.ps1 b/src/functions/public/Workflows/Start-GitHubWorkflow.ps1 index 8d6481ccc..3a651b57e 100644 --- a/src/functions/public/Workflows/Start-GitHubWorkflow.ps1 +++ b/src/functions/public/Workflows/Start-GitHubWorkflow.ps1 @@ -62,7 +62,7 @@ inputs = $Inputs } - $inputObject = @{ + $apiParams = @{ Method = 'POST' APIEndpoint = "/repos/$Owner/$Repository/actions/workflows/$ID/dispatches" Body = $body @@ -70,7 +70,7 @@ } if ($PSCmdlet.ShouldProcess("$Owner/$Repository/$ID", 'Start workflow')) { - $null = Invoke-GitHubAPI @inputObject + $null = Invoke-GitHubAPI @apiParams } } diff --git a/src/header.ps1 b/src/header.ps1 index cc1fde9ac..9b563d153 100644 --- a/src/header.ps1 +++ b/src/header.ps1 @@ -1,3 +1,5 @@ -[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] +#Requires -Modules @{ ModuleName = 'TimeSpan'; RequiredVersion = '3.0.1' } + +[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidLongLines', '', Justification = 'Contains long links.')] [CmdletBinding()] param() diff --git a/src/types/GitHubArtifact.Types.ps1xml b/src/types/GitHubArtifact.Types.ps1xml new file mode 100644 index 000000000..39c87f736 --- /dev/null +++ b/src/types/GitHubArtifact.Types.ps1xml @@ -0,0 +1,19 @@ + + + + GitHubArtifact + + + ExpiresIn + + if ($null -eq $this.ExpiresAt) { return } + $timeRemaining = $this.ExpiresAt - [DateTime]::Now + if ($timeRemaining.TotalSeconds -lt 0) { + return [TimeSpan]::Zero + } + return $timeRemaining + + + + + diff --git a/src/types/GitHubContext.Types.ps1xml b/src/types/GitHubContext.Types.ps1xml new file mode 100644 index 000000000..6ea11368c --- /dev/null +++ b/src/types/GitHubContext.Types.ps1xml @@ -0,0 +1,46 @@ + + + + GitHubUserContext + + + TokenExpiresIn + + if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero } + $this.TokenExpiresAt - [DateTime]::Now + + + + RefreshTokenExpiresIn + + if ($null -eq $this.RefreshTokenExpiresAt) { return [TimeSpan]::Zero } + $this.RefreshTokenExpiresAt - [DateTime]::Now + + + + + + GitHubAppInstallationContext + + + TokenExpiresIn + + if ($null -eq $this.TokenExpiresAt) { return [TimeSpan]::Zero } + $this.TokenExpiresAt - [DateTime]::Now + + + + + + GitHubAppContext + + + TokenExpiresIn + + if ($null -eq $this.TokenExpiresAt) { return } + $this.TokenExpiresAt - [DateTime]::Now + + + + + diff --git a/src/types/GitHubEnvironment.Types.ps1xml b/src/types/GitHubEnvironment.Types.ps1xml index e058ce6f6..704e05b6d 100644 --- a/src/types/GitHubEnvironment.Types.ps1xml +++ b/src/types/GitHubEnvironment.Types.ps1xml @@ -3,10 +3,10 @@ GitHubEnvironment - + Environment - $this.Name - + Name + diff --git a/src/types/GitHubNode.Types.ps1xml b/src/types/GitHubNode.Types.ps1xml index 93f44daeb..11b5f73f6 100644 --- a/src/types/GitHubNode.Types.ps1xml +++ b/src/types/GitHubNode.Types.ps1xml @@ -3,10 +3,10 @@ GitHubNode - + DatabaseID - $this.ID - + ID + diff --git a/src/types/GitHubOrganization.Types.ps1xml b/src/types/GitHubOrganization.Types.ps1xml index 076d7b359..a793f8b4c 100644 --- a/src/types/GitHubOrganization.Types.ps1xml +++ b/src/types/GitHubOrganization.Types.ps1xml @@ -3,10 +3,10 @@ GitHubOrganization - + Organization - $this.Name - + Name + diff --git a/src/types/GitHubOwner.Types.ps1xml b/src/types/GitHubOwner.Types.ps1xml index 6fce86651..a6d41dd47 100644 --- a/src/types/GitHubOwner.Types.ps1xml +++ b/src/types/GitHubOwner.Types.ps1xml @@ -3,10 +3,10 @@ GitHubOwner - + Owner - $this.Name - + Name + diff --git a/src/types/GitHubRateLimitResource.Types.ps1xml b/src/types/GitHubRateLimitResource.Types.ps1xml new file mode 100644 index 000000000..1b3b538a3 --- /dev/null +++ b/src/types/GitHubRateLimitResource.Types.ps1xml @@ -0,0 +1,19 @@ + + + + GitHubRateLimitResource + + + ResetsIn + + if ($null -eq $this.ResetsAt) { return } + $timeRemaining = $this.ResetsAt - [DateTime]::Now + if ($timeRemaining.TotalSeconds -lt 0) { + return [TimeSpan]::Zero + } + return $timeRemaining + + + + + diff --git a/src/types/GitHubRelease.Types.ps1xml b/src/types/GitHubRelease.Types.ps1xml index 1c1acc885..f1225bf33 100644 --- a/src/types/GitHubRelease.Types.ps1xml +++ b/src/types/GitHubRelease.Types.ps1xml @@ -3,10 +3,10 @@ GitHubRelease - + Release - $this.ID - + ID + diff --git a/src/types/GitHubRepository.Types.ps1xml b/src/types/GitHubRepository.Types.ps1xml index db9388f09..20ee8b423 100644 --- a/src/types/GitHubRepository.Types.ps1xml +++ b/src/types/GitHubRepository.Types.ps1xml @@ -3,10 +3,10 @@ GitHubRepository - + Repository - $this.Name - + Name + diff --git a/src/types/GitHubTeam.Types.ps1xml b/src/types/GitHubTeam.Types.ps1xml index 38abc2cc2..c5dc2c484 100644 --- a/src/types/GitHubTeam.Types.ps1xml +++ b/src/types/GitHubTeam.Types.ps1xml @@ -3,10 +3,10 @@ GitHubTeam - + Team - $this.Slug - + Slug + diff --git a/src/types/GitHubUser.Types.ps1xml b/src/types/GitHubUser.Types.ps1xml index f81e6433f..ab12006c7 100644 --- a/src/types/GitHubUser.Types.ps1xml +++ b/src/types/GitHubUser.Types.ps1xml @@ -3,10 +3,18 @@ GitHubUser - + User - $this.Name - + Name + + + Bio + Description + + + Blog + Website + diff --git a/src/types/GitHubWorkflow.Types.ps1xml b/src/types/GitHubWorkflow.Types.ps1xml index cd6ddb51a..17f85b830 100644 --- a/src/types/GitHubWorkflow.Types.ps1xml +++ b/src/types/GitHubWorkflow.Types.ps1xml @@ -3,10 +3,10 @@ GitHubWorkflow - + Workflow - $this.ID - + ID + diff --git a/src/types/GitHubWorkflowRun.Types.ps1xml b/src/types/GitHubWorkflowRun.Types.ps1xml index 0a65f47a4..7d793df99 100644 --- a/src/types/GitHubWorkflowRun.Types.ps1xml +++ b/src/types/GitHubWorkflowRun.Types.ps1xml @@ -3,10 +3,10 @@ GitHubWorkflowRun - + WorkflowRun - $this.ID - + ID + diff --git a/src/variables/private/GitHub.ps1 b/src/variables/private/GitHub.ps1 index 7f372fd8e..3891118b3 100644 --- a/src/variables/private/GitHub.ps1 +++ b/src/variables/private/GitHub.ps1 @@ -1,5 +1,5 @@ $script:IsGitHubActions = $env:GITHUB_ACTIONS -eq 'true' -$script:IsFunctionApp = -not [string]::IsNullOrEmpty($env:WEBSITE_PLATFORM_VERSION) +$script:IsFunctionApp = $env:FUNCTIONS_WORKER_RUNTIME -eq 'powershell' $script:IsLocal = -not ($script:IsGitHubActions -or $script:IsFunctionApp) $script:GitHub = [pscustomobject]@{ ContextVault = 'PSModule.GitHub' @@ -9,7 +9,7 @@ $script:GitHub = [pscustomobject]@{ ID = 'Module' HostName = ($env:GITHUB_SERVER_URL ?? 'github.com') -replace '^https?://' ApiBaseUri = "https://api.$(($env:GITHUB_SERVER_URL ?? 'github.com') -replace '^https?://')" - AccessTokenGracePeriodInHours = 4 + AccessTokenGracePeriodInHours = 4.0 GitHubAppClientID = 'Iv1.f26b61bc99e69405' OAuthAppClientID = '7204ae9b0580f2cb8288' DefaultContext = '' @@ -18,7 +18,6 @@ $script:GitHub = [pscustomobject]@{ PerPage = 100 RetryCount = 0 RetryInterval = 1 - JwtTimeTolerance = 300 EnvironmentType = Get-GitHubEnvironmentType } Config = $null diff --git a/src/variables/private/StatusBaseURL.ps1 b/src/variables/private/StatusBaseURL.ps1 index be2f94de8..a5ddbabdd 100644 --- a/src/variables/private/StatusBaseURL.ps1 +++ b/src/variables/private/StatusBaseURL.ps1 @@ -2,4 +2,5 @@ Public = 'https://www.githubstatus.com' Europe = 'https://eu.githubstatus.com' Australia = 'https://au.githubstatus.com' + US = 'https://us.githubstatus.com' } diff --git a/src/variables/private/UserAgent.ps1 b/src/variables/private/UserAgent.ps1 new file mode 100644 index 000000000..384084618 --- /dev/null +++ b/src/variables/private/UserAgent.ps1 @@ -0,0 +1,5 @@ +$script:Prerelease = $script:PSModuleInfo.PrivateData.PSData.Prerelease +$script:UserAgent = "PSModule.GitHub $($script:PSModuleInfo.ModuleVersion)" +if ($script:Prerelease) { + $script:UserAgent += "-$script:Prerelease" +} diff --git a/tests/Apps.Tests.ps1 b/tests/Apps.Tests.ps1 index e6613bb13..44803729b 100644 --- a/tests/Apps.Tests.ps1 +++ b/tests/Apps.Tests.ps1 @@ -24,8 +24,8 @@ Describe 'Apps' { Context 'As using on ' -ForEach $authCases { BeforeAll { - $context = Connect-GitHubAccount @connectParams -PassThru -Silent LogGroup 'Context' { + $context = Connect-GitHubAccount @connectParams -PassThru -Silent Write-Host ($context | Format-List | Out-String) } } @@ -61,14 +61,6 @@ Describe 'Apps' { $app.Installations | Should -Not -BeNullOrEmpty } - It 'Get-GitHubAppJSONWebToken - Can get a JWT for the app' { - $jwt = Get-GitHubAppJSONWebToken @connectParams - LogGroup 'JWT' { - Write-Host ($jwt | Format-Table | Out-String) - } - $jwt | Should -Not -BeNullOrEmpty - } - It 'Get-GitHubAppInstallationRequest - Can get installation requests' { $installationRequests = Get-GitHubAppInstallationRequest LogGroup 'Installation requests' { @@ -104,17 +96,6 @@ Describe 'Apps' { } } - It 'New-GitHubAppInstallationAccessToken - Can get app installation access tokens' { - $installations = Get-GitHubAppInstallation - LogGroup 'Tokens' { - $installations | ForEach-Object { - $token = New-GitHubAppInstallationAccessToken -InstallationID $_.id - Write-Host ($token | Format-List | Out-String) - } - $token | Should -Not -BeNullOrEmpty - } - } - It 'Get-GitHubAppInstallation - ' { $githubApp = Get-GitHubApp $installation = Get-GitHubAppInstallation | Where-Object { ($_.Target.Name -eq $owner) -and ($_.Type -eq $ownerType) } @@ -183,37 +164,59 @@ Describe 'Apps' { } } } - It 'Connect-GitHubApp - Connects as a GitHub App to ' { - $githubApp = Get-GitHubApp - $config = Get-GitHubConfig - $context = Connect-GitHubApp @connectAppParams -PassThru -Silent - LogGroup 'Context' { - Write-Host ($context | Format-List | 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 '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 + } + + 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 } - $context | Should -BeOfType 'InstallationGitHubContext' - $context.ClientID | Should -Be $githubApp.ClientID - $context.TokenExpirationDate | 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 } } diff --git a/tests/Data/AuthCases.ps1 b/tests/Data/AuthCases.ps1 index ad252ae73..f01ec07da 100644 --- a/tests/Data/AuthCases.ps1 +++ b/tests/Data/AuthCases.ps1 @@ -51,7 +51,7 @@ @{ AuthType = 'App' Type = 'a GitHub App from an Organization' - Case = 'PEM + IAT' + Case = 'JWT + IAT' TokenType = 'APP_ORG' Target = 'organization account' Owner = 'psmodule-test-org' @@ -64,36 +64,36 @@ Organization = 'psmodule-test-org' } } -) -@{ - AuthType = 'App' - Type = 'a GitHub App from an Enterprise' - Case = 'PEM + IAT' - TokenType = 'APP_ENT' - Target = 'organization account' - Owner = 'psmodule-test-org3' - OwnerType = 'organization' - ConnectParams = @{ - ClientID = $env:TEST_APP_ENT_CLIENT_ID - PrivateKey = $env:TEST_APP_ENT_PRIVATE_KEY - } - ConnectAppParams = @{ - Organization = 'psmodule-test-org3' - } -} -@{ - AuthType = 'App' - Type = 'a GitHub App from an Enterprise' - Case = 'PEM + IAT' - TokenType = 'APP_ENT' - Target = 'enterprise account' - Owner = 'msx' - OwnerType = 'enterprise' - ConnectParams = @{ - ClientID = $env:TEST_APP_ENT_CLIENT_ID - PrivateKey = $env:TEST_APP_ENT_PRIVATE_KEY + @{ + AuthType = 'App' + Type = 'a GitHub App from an Enterprise' + Case = 'JWT + IAT' + TokenType = 'APP_ENT' + Target = 'organization account' + Owner = 'psmodule-test-org3' + OwnerType = 'organization' + ConnectParams = @{ + ClientID = $env:TEST_APP_ENT_CLIENT_ID + PrivateKey = $env:TEST_APP_ENT_PRIVATE_KEY + } + ConnectAppParams = @{ + Organization = 'psmodule-test-org3' + } } - ConnectAppParams = @{ - Enterprise = 'msx' + @{ + AuthType = 'App' + Type = 'a GitHub App from an Enterprise' + Case = 'JWT + IAT' + TokenType = 'APP_ENT' + Target = 'enterprise account' + Owner = 'msx' + OwnerType = 'enterprise' + ConnectParams = @{ + ClientID = $env:TEST_APP_ENT_CLIENT_ID + PrivateKey = $env:TEST_APP_ENT_PRIVATE_KEY + } + ConnectAppParams = @{ + Enterprise = 'msx' + } } -} +) diff --git a/tests/Emojis.Tests.ps1 b/tests/Emojis.Tests.ps1 new file mode 100644 index 000000000..ff3100a2e --- /dev/null +++ b/tests/Emojis.Tests.ps1 @@ -0,0 +1,67 @@ +#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 'Emojis' { + $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) + } + + # Tests for APP goes here + if ($AuthType -eq 'APP') { + It 'Connect-GitHubApp - Connects as a GitHub App to ' { + $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + LogGroup 'Context' { + Write-Host ($context | Format-List | Out-String) + } + } + } + + # Tests for runners goes here + if ($Type -eq 'GitHub Actions') {} + + # Tests for IAT UAT and PAT goes here + It 'Get-GitHubEmoji - Gets a list of all emojis' { + $emojis = Get-GitHubEmoji + LogGroup 'emojis' { + Write-Host ($emojis | Format-Table | Out-String) + } + $emojis | Should -Not -BeNullOrEmpty + } + It 'Get-GitHubEmoji - Downloads all emojis' { + Get-GitHubEmoji -Path $Home + LogGroup 'emojis' { + $emojis = Get-ChildItem -Path $Home -File + Write-Host ($emojis | Format-Table | Out-String) + } + $emojis | Should -Not -BeNullOrEmpty + } + } +} diff --git a/tests/Enterprise.Tests.ps1 b/tests/Enterprise.Tests.ps1 new file mode 100644 index 000000000..b6b7a73dd --- /dev/null +++ b/tests/Enterprise.Tests.ps1 @@ -0,0 +1,70 @@ +#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() + +BeforeAll { + # DEFAULTS ACCROSS ALL TESTS +} + +Describe 'Template' { + $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) + } + if ($AuthType -eq 'APP') { + LogGroup 'Context - Installation' { + $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + Write-Host ($context | Format-List | Out-String) + } + } + } + AfterAll { + Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent + Write-Host ('-' * 60) + } + + It 'Get-GitHubEnterprise - Can get info about an enterprise' -Skip:($OwnerType -ne 'enterprise') { + $enterprise = Get-GitHubEnterprise -Name $Owner + LogGroup 'Enterprise' { + Write-Host ($enterprise | Select-Object * | Out-String) + } + $enterprise | Should -Not -BeNullOrEmpty + $enterprise | Should -BeOfType 'GitHubEnterprise' + $enterprise.Name | Should -Be 'msx' + $enterprise.DisplayName | Should -Be 'MSX' + $enterprise.ID | Should -Be 15567 + $enterprise.NodeID | Should -Be 'E_kgDNPM8' + $enterprise.AvatarUrl | Should -Be 'https://avatars.githubusercontent.com/b/15567?v=4' + $enterprise.Url | Should -Be 'https://github.com/enterprises/msx' + $enterprise.Type | Should -Be 'Enterprise' + $enterprise.Readme | Should -Be 'This is a test' + $enterprise.ReadmeHTML | Should -Be '

This is a test

' + $enterprise.CreatedAt | Should -BeOfType 'DateTime' + $enterprise.UpdatedAt | Should -BeOfType 'DateTime' + $enterprise.Description | Should -Be 'This is the description' + $enterprise.Location | Should -Be 'Oslo, Norway' + $enterprise.Website | Should -Be 'https://msx.no' + } + } +} diff --git a/tests/GitHub.Tests.ps1 b/tests/GitHub.Tests.ps1 index dc23e96c1..53b9e347e 100644 --- a/tests/GitHub.Tests.ps1 +++ b/tests/GitHub.Tests.ps1 @@ -79,6 +79,9 @@ Describe 'Auth' { Write-Host ($context | Format-List | Out-String) } $context | Should -Not -BeNullOrEmpty + $context | Should -BeOfType [GitHubContext] + $context.TokenExpiresAt | Should -BeOfType [DateTime] + $context.TokenExpiresIn | Should -BeOfType [TimeSpan] } It 'Connect-GitHubApp - Connects as a GitHub App to ' -Skip:($AuthType -ne 'APP') { @@ -106,6 +109,8 @@ Describe 'Auth' { LogGroup 'Connect-GithubApp' { $context } + $context.TokenExpiresAt | Should -BeOfType [DateTime] + $context.TokenExpiresIn | Should -BeOfType [TimeSpan] LogGroup 'Context' { Write-Host ($context | Format-List | Out-String) } @@ -179,6 +184,37 @@ Describe 'Auth' { } } +Describe 'Anonymous - Functions that can run anonymously' { + It 'Get-GithubRateLimit - Using -Anonymous' { + $rateLimit = Get-GitHubRateLimit -Anonymous + LogGroup 'Rate Limit' { + Write-Host ($rateLimit | Format-Table | Out-String) + } + $rateLimit | Should -Not -BeNullOrEmpty + } + It 'Invoke-GitHubAPI - Using -Anonymous' { + $rateLimit = Invoke-GitHubAPI -ApiEndpoint '/rate_limit' -Anonymous | Select-Object -ExpandProperty Response + LogGroup 'Rate Limit' { + Write-Host ($rateLimit | Format-Table | Out-String) + } + $rateLimit | Should -Not -BeNullOrEmpty + } + It 'Get-GithubRateLimit - Using -Context Anonymous' { + $rateLimit = Get-GitHubRateLimit -Context Anonymous + LogGroup 'Rate Limit' { + Write-Host ($rateLimit | Format-List | Out-String) + } + $rateLimit | Should -Not -BeNullOrEmpty + } + It 'Invoke-GitHubAPI - Using -Context Anonymous' { + $rateLimit = Invoke-GitHubAPI -ApiEndpoint '/rate_limit' -Context Anonymous | Select-Object -ExpandProperty Response + LogGroup 'Rate Limit' { + Write-Host ($rateLimit | Format-Table | Out-String) + } + $rateLimit | Should -Not -BeNullOrEmpty + } +} + Describe 'GitHub' { Context 'Config' { It 'Get-GitHubConfig - Gets the module configuration' { @@ -226,7 +262,7 @@ Describe 'GitHub' { $runnerData | Should -Not -BeNullOrEmpty } } - Context 'Status' -ForEach @('Public', 'Europe', 'Australia') { + Context 'Status' -ForEach @('Public', 'Europe', 'Australia', 'US') { It 'Get-GitHubScheduledMaintenance - Gets scheduled maintenance for <_>' { { Get-GitHubScheduledMaintenance -Stamp $_ } | Should -Not -Throw } @@ -580,13 +616,53 @@ Describe 'API' { } } - Context 'Rate-Limit' { + Context 'RateLimit' { + BeforeAll { + $rateLimits = Get-GitHubRateLimit + } It 'Get-GitHubRateLimit - Gets the rate limit status for the authenticated user' { - $rateLimit = Get-GitHubRateLimit LogGroup 'RateLimit' { - Write-Host ($rateLimit | Format-Table | Out-String) + Write-Host ($rateLimits | Format-Table | Out-String) + } + $rateLimits | Should -Not -BeNullOrEmpty + } + + It 'Get-GitHubRateLimit - ResetsAt property should be a datetime' { + $rateLimit = $rateLimits | Select-Object -First 1 + $rateLimit.ResetsAt | Should -BeOfType [DateTime] + $rateLimit.ResetsAt | Should -BeGreaterThan ([DateTime]::Now) + } + + It 'Get-GitHubRateLimit - ResetsIn property should be calculated correctly' { + $rateLimit = $rateLimits | Select-Object -First 1 + $rateLimit.ResetsIn | Should -BeOfType [TimeSpan] + $rateLimit.ResetsIn.TotalSeconds | Should -BeGreaterThan 0 + $rateLimit.ResetsIn.TotalHours | Should -BeLessOrEqual 1 + } + + It 'Get-GitHubRateLimit - Should return objects with names core and rate' { + LogGroup 'RateLimit Names' { + Write-Host ($rateLimits.Name | Out-String) } - $rateLimit | Should -Not -BeNullOrEmpty + $rateLimits.Name | Should -Contain 'core' + $rateLimits.Name | Should -Contain 'rate' + } + + It 'Get-GitHubRateLimit - Objects should be of type GitHubRateLimitResource' { + $rateLimits | Should -BeOfType 'GitHubRateLimitResource' + } + + It 'Get-GitHubRateLimit - Should have correct property types for all objects' { + $rateLimits.Name | Should -BeOfType [String] + $rateLimits.Limit | Should -BeOfType [UInt64] + $rateLimits.Used | Should -BeOfType [UInt64] + $rateLimits.Remaining | Should -BeOfType [UInt64] + $rateLimits.ResetsAt | Should -BeOfType [DateTime] + $rateLimits.ResetsIn | Should -BeOfType [TimeSpan] + $rateLimits.Name | Should -Not -BeNullOrEmpty + $rateLimits.Limit | Should -BeGreaterOrEqual 0 + $rateLimits.Used | Should -BeGreaterOrEqual 0 + $rateLimits.Remaining | Should -BeGreaterOrEqual 0 } } @@ -692,90 +768,41 @@ Describe 'API' { } } -Describe 'Emojis' { - $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) - } - - # Tests for APP goes here - if ($AuthType -eq 'APP') { - It 'Connect-GitHubApp - Connects as a GitHub App to ' { - $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent - LogGroup 'Context' { - Write-Host ($context | Format-List | Out-String) - } - } - } - - # Tests for runners goes here - if ($Type -eq 'GitHub Actions') {} - - # Tests for IAT UAT and PAT goes here - It 'Get-GitHubEmoji - Gets a list of all emojis' { - $emojis = Get-GitHubEmoji - LogGroup 'emojis' { - Write-Host ($emojis | Format-Table | Out-String) - } - $emojis | Should -Not -BeNullOrEmpty - } - It 'Get-GitHubEmoji - Downloads all emojis' { - Get-GitHubEmoji -Path $Home - LogGroup 'emojis' { - $emojis = Get-ChildItem -Path $Home -File - Write-Host ($emojis | Format-Table | Out-String) - } - $emojis | Should -Not -BeNullOrEmpty - } - } -} - Describe 'Webhooks' { - It 'Test-GitHubWebhookSignature - Validates the webhook payload using known correct signature' { + BeforeAll { $secret = "It's a Secret to Everybody" $payload = 'Hello, World!' $signature = 'sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17' + } + + It 'Test-GitHubWebhookSignature - Validates the webhook payload using known correct signature (SHA256)' { $result = Test-GitHubWebhookSignature -Secret $secret -Body $payload -Signature $signature $result | Should -Be $true } -} -Describe 'Anonymous - Functions that can run anonymously' { - It 'Get-GithubRateLimit - Using -Anonymous' { - $rateLimit = Get-GitHubRateLimit -Anonymous - LogGroup 'Rate Limit' { - Write-Host ($rateLimit | Format-Table | Out-String) - } - $rateLimit | Should -Not -BeNullOrEmpty - } - It 'Invoke-GitHubAPI - Using -Anonymous' { - $rateLimit = Invoke-GitHubAPI -ApiEndpoint '/rate_limit' -Anonymous | Select-Object -ExpandProperty Response - LogGroup 'Rate Limit' { - Write-Host ($rateLimit | Format-Table | Out-String) + It 'Test-GitHubWebhookSignature - Validates the webhook using Request object' { + $mockRequest = [PSCustomObject]@{ + RawBody = $payload + Headers = @{ + 'X-Hub-Signature-256' = $signature + } } - $rateLimit | Should -Not -BeNullOrEmpty + $result = Test-GitHubWebhookSignature -Secret $secret -Request $mockRequest + $result | Should -Be $true } - It 'Get-GithubRateLimit - Using -Context Anonymous' { - $rateLimit = Get-GitHubRateLimit -Context Anonymous - LogGroup 'Rate Limit' { - Write-Host ($rateLimit | Format-List | Out-String) - } - $rateLimit | Should -Not -BeNullOrEmpty + + It 'Test-GitHubWebhookSignature - Should fail with invalid signature' { + $invalidSignature = 'sha256=invalid' + $result = Test-GitHubWebhookSignature -Secret $secret -Body $payload -Signature $invalidSignature + $result | Should -Be $false } - It 'Invoke-GitHubAPI - Using -Context Anonymous' { - $rateLimit = Invoke-GitHubAPI -ApiEndpoint '/rate_limit' -Context Anonymous | Select-Object -ExpandProperty Response - LogGroup 'Rate Limit' { - Write-Host ($rateLimit | Format-Table | Out-String) + + It 'Test-GitHubWebhookSignature - Should throw when signature header is missing from request' { + $mockRequest = [PSCustomObject]@{ + RawBody = $payload + Headers = @{} } - $rateLimit | Should -Not -BeNullOrEmpty + + { Test-GitHubWebhookSignature -Secret $secret -Request $mockRequest } | Should -Throw } } diff --git a/tests/Organizations.Tests.ps1 b/tests/Organizations.Tests.ps1 index 32ddb2b28..7d1740e5c 100644 --- a/tests/Organizations.Tests.ps1 +++ b/tests/Organizations.Tests.ps1 @@ -20,7 +20,9 @@ param() BeforeAll { - # DEFAULTS ACCROSS ALL TESTS + $testName = 'MsxOrgTests' + $os = $env:RUNNER_OS + $number = Get-Random } Describe 'Organizations' { @@ -32,25 +34,21 @@ Describe 'Organizations' { LogGroup 'Context' { Write-Host ($context | Select-Object * | Out-String) } + $orgPrefix = "$testName-$os-" + $orgName = "$orgPrefix$number" + + if ($AuthType -eq 'APP') { + $installationContext = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent + LogGroup 'Context - Installation' { + Write-Host ($installationContext | Select-Object * | Out-String) + } + } } AfterAll { Get-GitHubContext -ListAvailable | Disconnect-GitHubAccount -Silent Write-Host ('-' * 60) } - # Tests for APP goes here - if ($AuthType -eq 'APP') { - It 'Connect-GitHubApp - Connects as a GitHub App to ' { - $context = Connect-GitHubApp @connectAppParams -PassThru -Default -Silent - LogGroup 'Context' { - Write-Host ($context | Select-Object * | Out-String) - } - } - } - - # Tests for runners goes here - if ($Type -eq 'GitHub Actions') {} - It "Get-GitHubOrganization - Gets a specific organization 'PSModule'" { $organization = Get-GitHubOrganization -Name 'PSModule' LogGroup 'Organization' { @@ -58,6 +56,7 @@ Describe 'Organizations' { } $organization | Should -Not -BeNullOrEmpty } + It "Get-GitHubOrganization - List public organizations for the user 'psmodule-user'" { $organizations = Get-GitHubOrganization -Username 'psmodule-user' LogGroup 'Organization' { @@ -65,6 +64,7 @@ Describe 'Organizations' { } $organizations | Should -Not -BeNullOrEmpty } + It 'Get-GitHubOrganizationMember - Gets the members of a specific organization' -Skip:($OwnerType -in ('user', 'enterprise')) { $members = Get-GitHubOrganizationMember -Organization $owner LogGroup 'Members' { @@ -73,29 +73,78 @@ Describe 'Organizations' { $members | Should -Not -BeNullOrEmpty } - # Tests for IAT UAT and PAT goes here - It 'Get-GitHubOrganization - Gets the organizations for the authenticated user' -Skip:($OwnerType -notin ('user')) { + It 'Get-GitHubOrganization - Gets the organizations for the authenticated user' -Skip:($Type -eq 'GitHub Actions') { + $orgs = Get-GitHubOrganization | Where-Object { $_.Name -like "$orgPrefix*" } | Out-String + LogGroup 'Organizations' { + $orgs | Format-Table -AutoSize | Out-String + } { Get-GitHubOrganization } | Should -Not -Throw } - if ($OwnerType -eq 'organization' -and $Type -ne 'GitHub Actions') { - It 'Update-GitHubOrganization - Sets the organization configuration' { - { Update-GitHubOrganization -Name $owner -Company 'ABC' } | Should -Not -Throw - { - $email = (New-Guid).Guid + '@psmodule.io' - Update-GitHubOrganization -Name $owner -BillingEmail $email - } | Should -Not -Throw - { - $email = (New-Guid).Guid + '@psmodule.io' - Update-GitHubOrganization -Name $owner -Email $email - } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -TwitterUsername 'PSModule' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Location 'USA' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Description 'Test Organization' } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -DefaultRepositoryPermission read } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -MembersCanCreateRepositories $true } | Should -Not -Throw - { Update-GitHubOrganization -Name $owner -Blog 'https://psmodule.io' } | Should -Not -Throw + It 'Update-GitHubOrganization - Sets the organization configuration' -Skip:($OwnerType -ne 'organization' -or $Type -eq 'GitHub Actions') { + { Update-GitHubOrganization -Name $owner -Company 'ABC' } | Should -Not -Throw + { + $email = (New-Guid).Guid + '@psmodule.io' + Update-GitHubOrganization -Name $owner -BillingEmail $email + } | Should -Not -Throw + { + $email = (New-Guid).Guid + '@psmodule.io' + Update-GitHubOrganization -Name $owner -Email $email + } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -TwitterUsername 'PSModule' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Location 'USA' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Description 'Test Organization' } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -DefaultRepositoryPermission read } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -MembersCanCreateRepositories $true } | Should -Not -Throw + { Update-GitHubOrganization -Name $owner -Website 'https://psmodule.io' } | Should -Not -Throw + } + + It 'New-GitHubOrganization - Creates a new organization' -Skip:($OwnerType -ne 'enterprise') { + $orgParam = @{ + Enterprise = 'msx' + Name = $orgName + Owner = 'MariusStorhaug' + BillingEmail = 'post@msx.no' + } + LogGroup 'Organization' { + $org = New-GitHubOrganization @orgParam + Write-Host ($org | Select-Object * | Out-String) + } + } + + It 'Update-GitHubOrganization - Updates the organization location using enterprise installation' -Skip:($OwnerType -ne 'enterprise') { + { Update-GitHubOrganization -Name $orgName -Location 'New Location' } | Should -Throw + } + + It 'Remove-GitHubOrganization - Removes an organization using enterprise installation' -Skip:($OwnerType -ne 'enterprise') { + { Remove-GitHubOrganization -Name $orgName -Confirm:$false } | Should -Throw + } + + It 'Install-GitHubApp - Installs a GitHub App to an organization' -Skip:($OwnerType -ne 'enterprise') { + $installation = Install-GitHubApp -Enterprise $owner -Organization $orgName -ClientID $installationContext.ClientID -RepositorySelection 'all' + LogGroup 'Installed App' { + Write-Host ($installation | Select-Object * | Out-String) + } + $installation | Should -Not -BeNullOrEmpty + $installation | Should -BeOfType 'GitHubAppInstallation' + } + + It 'Connect-GitHubApp - Connects as a GitHub App to the organization' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + LogGroup 'Context' { + Write-Host ($orgContext | Select-Object * | Out-String) } + $orgContext | Should -Not -BeNullOrEmpty + } + + It 'Update-GitHubOrganization - Updates the organization location using organization installation' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + Update-GitHubOrganization -Name $orgName -Location 'New Location' -Context $orgContext + } + + It 'Remove-GitHubOrganization - Removes an organization using organization installation' -Skip:($OwnerType -ne 'enterprise') { + $orgContext = Connect-GitHubApp -Organization $orgName -Context $context -PassThru -Silent + Remove-GitHubOrganization -Name $orgName -Confirm:$false -Context $orgContext } Context 'Invitations' -Skip:($Owner -notin 'psmodule-test-org', 'psmodule-test-org2') { diff --git a/tests/Releases.Tests.ps1 b/tests/Releases.Tests.ps1 index 2837cabb6..3dd9f843f 100644 --- a/tests/Releases.Tests.ps1 +++ b/tests/Releases.Tests.ps1 @@ -644,6 +644,33 @@ ID,Name,Value Test-Path -Path $downloadedFile.FullName | Should -BeTrue $downloadedFile.Name | Should -Be $asset.Name } + + It 'Get-GitHubReleaseAsset - Gets assets from release using pipeline' { + $assets = Get-GitHubRelease -Owner ryanoasis -Repository nerd-fonts | Get-GitHubReleaseAsset + LogGroup 'Release assets from pipeline' { + Write-Host ($assets | Format-List -Property * | Out-String) + } + $assets | Should -Not -BeNullOrEmpty + $assets.Count | Should -BeGreaterThan 0 + foreach ($asset in $assets) { + $asset | Should -BeOfType [GitHubReleaseAsset] + $asset.ID | Should -Not -BeNullOrEmpty + $asset.NodeID | Should -Not -BeNullOrEmpty + $asset.Url | Should -Not -BeNullOrEmpty + $asset.Name | Should -Not -BeNullOrEmpty + # $asset.Label | Should -Not -BeNullOrEmpty - Label is optional and may not be set + $asset.State | Should -Be 'uploaded' + $asset.ContentType | Should -Not -BeNullOrEmpty + $asset.Size | Should -BeGreaterOrEqual 0 + $asset.Downloads | Should -BeGreaterOrEqual 0 + $asset.CreatedAt | Should -Not -BeNullOrEmpty + $asset.CreatedAt | Should -BeOfType 'DateTime' + $asset.UpdatedAt | Should -Not -BeNullOrEmpty + $asset.UpdatedAt | Should -BeOfType 'DateTime' + $asset.UploadedBy | Should -Not -BeNullOrEmpty + $asset.UploadedBy | Should -BeOfType 'GitHubUser' + } + } } } } diff --git a/tests/Secrets.Tests.ps1 b/tests/Secrets.Tests.ps1 index 2384187ab..48bff8af7 100644 --- a/tests/Secrets.Tests.ps1 +++ b/tests/Secrets.Tests.ps1 @@ -129,6 +129,7 @@ Describe 'Secrets' { Write-Host ($org | Format-List | Out-String) } } + Context 'PublicKey' { It 'Get-GitHubPublicKey - Action' { $result = Get-GitHubPublicKey @scope @@ -139,18 +140,13 @@ Describe 'Secrets' { } It 'Get-GitHubPublicKey - Codespaces' { - switch ($org.plan.name) { - 'free' { - { Get-GitHubPublicKey @scope -Type codespaces } | Should -Throw - } - default { - $result = Get-GitHubPublicKey @scope -Type codespaces - LogGroup 'PublicKey' { - Write-Host "$($result | Select-Object * | Format-Table -AutoSize | Out-String)" - } - $result | Should -Not -BeNullOrEmpty - } + $plan = $org.plan.name + Write-Host "Running with plan [$plan]" + $result = Get-GitHubPublicKey @scope -Type codespaces + LogGroup 'PublicKey' { + Write-Host "$($result | Select-Object * | Format-Table -AutoSize | Out-String)" } + $result | Should -Not -BeNullOrEmpty } } diff --git a/tests/Users.Tests.ps1 b/tests/Users.Tests.ps1 index 5dcd0ec18..f4cb7cc32 100644 --- a/tests/Users.Tests.ps1 +++ b/tests/Users.Tests.ps1 @@ -54,17 +54,21 @@ Describe 'Users' { $user = Get-GitHubUser { Update-GitHubUser -DisplayName 'Octocat' } | Should -Not -Throw { Update-GitHubUser -Blog 'https://psmodule.io' } | Should -Not -Throw + { Update-GitHubUser -Website 'https://psmodule.io' } | Should -Not -Throw { Update-GitHubUser -TwitterUsername 'PSModule' } | Should -Not -Throw { Update-GitHubUser -Company 'PSModule' } | Should -Not -Throw { Update-GitHubUser -Location 'USA' } | Should -Not -Throw { Update-GitHubUser -Bio 'I love programming' } | Should -Not -Throw + { Update-GitHubUser -Description 'I love programming' } | Should -Not -Throw $tmpUser = Get-GitHubUser $tmpUser.DisplayName | Should -Be 'Octocat' $tmpUser.Blog | Should -Be 'https://psmodule.io' + $tmpUser.Website | Should -Be 'https://psmodule.io' $tmpUser.TwitterUsername | Should -Be 'PSModule' $tmpUser.Company | Should -Be 'PSModule' $tmpUser.Location | Should -Be 'USA' $tmpUser.Bio | Should -Be 'I love programming' + $tmpUser.Description | Should -Be 'I love programming' # Flaky tests # { Update-GitHubUser -Hireable $true } | Should -Not -Throw