diff --git a/GitHubSecrets.ps1 b/GitHubSecrets.ps1 new file mode 100644 index 00000000..1c836008 --- /dev/null +++ b/GitHubSecrets.ps1 @@ -0,0 +1,298 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +function Get-GitHubRepositoryPublicKey { +<# + .SYNOPSIS + Gets the public key for a given repository, which is needed to encrypt secrets. + + .DESCRIPTION + Gets the public key for a given repository, which is needed to encrypt secrets before creating or updating. + Anyone with read access to the repository can use this cmdlet. + If the repository is private you must use an access token with the repo scope. + GitHub Apps must have the secrets repository permission to use this cmdlet. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Get-GitHubRepositoryPublicKey -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + .EXAMPLE + Get-GitHubRepositoryPublicKey -Uri 'https://github.com/Microsoft/PowerShellForGitHub' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $params = @{ + 'UriFragment' = "/repos/$OwnerName/$RepositoryName/actions/secrets/public-key" + 'Description' = "Getting public key for $RepositoryName." + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + return Invoke-GHRestMethodMultipleResult @params +} + +function Get-GitHubSecretInfo { +<# + .SYNOPSIS + Lists all secrets or gets a particular secret available in a repository without revealing their encrypted values. + + .DESCRIPTION + Lists all secrets or gets a particular secret available in a repository without revealing their encrypted values. + You must authenticate using an access token with the repo scope to use this cmdlet. + GitHub Apps must have the secrets repository permission to use this cmdlet. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Get-GitHubSecretInfo -OwnerName Microsoft -RepositoryName PowerShellForGitHub + + .EXAMPLE + Get-GitHubSecretInfo -Uri 'https://github.com/Microsoft/PowerShellForGitHub' + + .EXAMPLE + Get-GitHubSecretInfo -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [string] $SecretName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + if ([WildcardPattern]::ContainsWildcardCharacters($SecretName)) + { + throw "The Name parameter cannot contain wild card characters." + } + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + if ($PSBoundParameters.ContainsKey('SecretName')) + { + $description = "Getting secret info of $SecretName for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" + } + else + { + $description = "Getting secret infos for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets" + } + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + try { + $result = Invoke-GHRestMethodMultipleResult @params + } catch { + $message = $_.ErrorDetails.Message + if ($message -notmatch 'Not Found') { + throw $_ + } + } + + if($PSBoundParameters.ContainsKey('SecretName')) { + $result + } else { + $result.secrets + } +} + +function Remove-GitHubSecret { +<# + .SYNOPSIS + Removes a repository secret with a value. + + .DESCRIPTION + Removes a repository secret with a value. The value is encrypted using PSSodium which + is simple wrapper around Sodium.Core. + + .PARAMETER OwnerName + Owner of the repository. + If not supplied here, the DefaultOwnerName configuration property value will be used. + + .PARAMETER RepositoryName + Name of the repository. + If not supplied here, the DefaultRepositoryName configuration property value will be used. + + .PARAMETER Uri + Uri for the repository. + The OwnerName and RepositoryName will be extracted from here instead of needing to provide + them individually. + + .PARAMETER Name + Name of the secret. + If not provided, it will retrieve all secrets in a repository. + + .PARAMETER AccessToken + If provided, this will be used as the AccessToken for authentication with the + REST Api. Otherwise, will attempt to use the configured value or will run unauthenticated. + + .PARAMETER NoStatus + If this switch is specified, long-running commands will run on the main thread + with no commandline status update. When not specified, those commands run in + the background, enabling the command prompt to provide status information. + If not supplied here, the DefaultNoStatus configuration property value will be used. + + .EXAMPLE + Set-GitHubSecret -OwnerName Microsoft -RepositoryName PowerShellForGitHub -SecretName MySecret -SecretValue 'my text' + + .EXAMPLE + Set-GitHubSecret -Uri 'https://github.com/Microsoft/PowerShellForGitHub' -SecretName MySecret -SecretValue 'my text' +#> + [CmdletBinding( + SupportsShouldProcess, + DefaultParameterSetName='Elements')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSShouldProcess", "", Justification="Methods called within here make use of PSShouldProcess, and the switch is passed on to them inherently.")] + param( + [Parameter(ParameterSetName='Elements')] + [string] $OwnerName, + + [Parameter(ParameterSetName='Elements')] + [string] $RepositoryName, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [string] $Uri, + + [Parameter(Mandatory, ParameterSetName='Uri')] + [Parameter(Mandatory, ParameterSetName='Elements')] + [string] $SecretName, + + [string] $AccessToken, + + [switch] $NoStatus + ) + + Write-InvocationLog + + $elements = Resolve-RepositoryElements + $OwnerName = $elements.ownerName + $RepositoryName = $elements.repositoryName + + $telemetryProperties = @{ + 'OwnerName' = (Get-PiiSafeString -PlainText $OwnerName) + 'RepositoryName' = (Get-PiiSafeString -PlainText $RepositoryName) + } + + $description = "Setting secret of $SecretName for $RepositoryName" + $uriFragment = "/repos/$OwnerName/$RepositoryName/actions/secrets/$SecretName" + + $params = @{ + 'UriFragment' = $uriFragment + 'Description' = $description + 'Method' = 'Delete' + 'AcceptHeader' = 'application/vnd.github.symmetra-preview+json' + 'AccessToken' = $AccessToken + 'TelemetryEventName' = $MyInvocation.MyCommand.Name + 'TelemetryProperties' = $telemetryProperties + 'NoStatus' = (Resolve-ParameterWithDefaultConfigurationValue -Name NoStatus -ConfigValueName DefaultNoStatus) + } + + Invoke-GHRestMethod @params +} diff --git a/PowerShellForGitHub.psd1 b/PowerShellForGitHub.psd1 index 5cbc5e48..23f3eece 100644 --- a/PowerShellForGitHub.psd1 +++ b/PowerShellForGitHub.psd1 @@ -51,6 +51,7 @@ 'GitHubRepositories.ps1', 'GitHubRepositoryForks.ps1', 'GitHubRepositoryTraffic.ps1', + 'GitHubSecrets.ps1', 'GitHubTeams.ps1', 'GitHubUsers.ps1', 'Telemetry.ps1', @@ -105,9 +106,11 @@ 'Get-GitHubRepositoryContributor', 'Get-GitHubRepositoryFork', 'Get-GitHubRepositoryLanguage', + 'Get-GitHubRepositoryPublicKey', 'Get-GitHubRepositoryTag', 'Get-GitHubRepositoryTopic', 'Get-GitHubRepositoryUniqueContributor', + 'Get-GitHubSecretInfo', 'Get-GitHubTeam', 'Get-GitHubTeamMember', 'Get-GitHubUser', @@ -159,6 +162,7 @@ 'Remove-GitHubRepositoryBranch' 'Rename-GitHubGistFile', 'Rename-GitHubRepository', + 'Remove-GitHubSecret', 'Reset-GitHubConfiguration', 'Restore-GitHubConfiguration', 'Set-GitHubAuthentication', diff --git a/Tests/GitHubSecrets.Tests.ps1 b/Tests/GitHubSecrets.Tests.ps1 new file mode 100644 index 00000000..f2fea4ea --- /dev/null +++ b/Tests/GitHubSecrets.Tests.ps1 @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Tests for GitHubSecrets.ps1 module +#> + +# This is common test code setup logic for all Pester test files +$moduleRootPath = Split-Path -Path $PSScriptRoot -Parent +. (Join-Path -Path $moduleRootPath -ChildPath 'Tests\Common.ps1') + +try +{ + # How do we test secrets? + # Ideally _with_ GitHub Actions +} +finally +{ + if (Test-Path -Path $script:originalConfigFile -PathType Leaf) + { + # Restore the user's configuration to its pre-test state + Restore-GitHubConfiguration -Path $script:originalConfigFile + $script:originalConfigFile = $null + } +}