Compare commits

..

14 Commits

Author SHA1 Message Date
Tingluo Huang
40302373ba Create releaseVersion (#237) 2019-12-18 15:39:26 -05:00
Tingluo Huang
9a08f7418f delete unused files. (#235) 2019-12-18 15:28:36 -05:00
Tingluo Huang
80b6038cdc consume dotnet core 3.1 in runner. (#213) 2019-12-18 15:09:03 -05:00
David Kale
70a09bc5ac shell from prependpath (#231)
* Prepend path before locating shell tool

* Join optional prepended path to path before searching it

* Use prepended path when whiching shell tool

* Addition prependPath location

* Also use prepended paths when writing out run details

* Small tweak to undo unnecessary change
2019-12-18 15:00:12 -05:00
Tingluo Huang
c6cf1eb3f1 Release runner using actions (#223)
* update runner release workflow

* trim script.

* feedback.
2019-12-18 14:56:37 -05:00
Julio Barba
50d979f1b2 Bring back tools folder fallback code (#232) 2019-12-17 18:21:13 -05:00
Tingluo Huang
91b7e7a07a delete more unused code. (#230)
* delete more unused code.

* pr feedback.
2019-12-17 16:47:14 -05:00
Tingluo Huang
d0a4a41a63 delete un-used code. (#218) 2019-12-16 17:05:26 -05:00
Julio Barba
c3c66bb14a Replace remaining Agent -> Runner references (#229) 2019-12-16 15:45:00 -05:00
Tingluo Huang
86df779fe9 expose github.run_id and github.run_number to action runtime env. (#224) 2019-12-16 15:23:55 -05:00
David Kale
1918906505 First pass (#221) 2019-12-16 14:53:19 -05:00
Julio Barba
9448135fcd Replace a few more instances Agent -> Runner (#228) 2019-12-16 11:51:08 -05:00
Tingluo Huang
f3aedd86fd Update AGENT_ALLOW_RUNASROOT to RUNNER_ALLOW_RUNASROOT (#227)
* Update config.sh

* Update run.sh
2019-12-16 11:27:46 -05:00
Mike Coutermarsh
d778f13dee Remove runner flow: Change from PAT to "deletion token" in prompt (#225)
* Updating prompt deletion token

Currently if you leave the token off the command, we're showing "Enter your personal access token:"

Which won't work. This updates prompt to "deletion token"

* Call correct function in test

* Fix command text in test
2019-12-15 21:38:42 -05:00
624 changed files with 400 additions and 68336 deletions

View File

@@ -3,10 +3,47 @@ name: Runner CD
on:
push:
paths:
- src/runnerversion_block # Change this to src/runnerversion when we are ready.
- releaseVersion
jobs:
check:
if: startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Make sure ./releaseVersion match ./src/runnerversion
# Query GitHub release ensure version is not used
- name: Check version
uses: actions/github-script@0.3.0
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
const releaseVersion = fs.readFileSync('${{ github.workspace }}/releaseVersion', 'utf8').replace(/\n$/g, '')
if (runnerVersion != releaseVersion) {
console.log('Request Release Version: ' + releaseVersion + '\nCurrent Runner Version: ' + runnerVersion)
core.setFailed('Version mismatch! Make sure ./releaseVersion match ./src/runnerVersion')
return
}
try {
const release = await github.repos.getReleaseByTag({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}',
tag: 'v' + runnerVersion
})
core.setFailed('Release with same tag already created: ' + release.data.html_url)
} catch (e) {
// We are good to create the release if release with same tag doesn't exists
if (e.status != 404) {
throw e
}
}
build:
needs: check
strategy:
matrix:
runtime: [ linux-x64, linux-arm64, linux-arm, win-x64, osx-x64 ]
@@ -52,7 +89,7 @@ jobs:
- name: Package Release
if: github.event_name != 'pull_request'
run: |
${{ matrix.devScript }} package Release
${{ matrix.devScript }} package Release ${{ matrix.runtime }}
working-directory: src
# Upload runner package tar.gz/zip as artifact.
@@ -66,14 +103,17 @@ jobs:
release:
needs: build
runs-on: linux-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Download runner package tar.gz/zip produced by 'build' job
- name: Download Artifact
uses: actions/download-artifact@v1
with:
name: runner-packages
path: ./
# Create ReleaseNote file
- name: Create ReleaseNote
@@ -82,103 +122,74 @@ jobs:
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
const core = require('@actions/core')
const fs = require('fs');
// Get runner version from ./src/runnerVersion file
const versionContent = await github.repos.getContents({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}',
path: 'src/runnerversion'
ref: ${{ github.sha }}
})
const runnerVersion = Buffer.from(versionContent.data.content, 'base64').toString()
console.log("Runner Version ' + runnerVersion)
const runnerVersion = fs.readFileSync('${{ github.workspace }}/src/runnerversion', 'utf8').replace(/\n$/g, '')
const releaseNote = fs.readFileSync('${{ github.workspace }}/releaseNote.md', 'utf8').replace(/<RUNNER_VERSION>/g, runnerVersion)
console.log(releaseNote)
core.setOutput('version', runnerVersion);
// Query GitHub release ensure version is bumped
const latestRelease = await github.repos.getLatestRelease({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}'
})
console.log(latestRelease.name)
const latestReleaseVersion = latestRelease.name.substring(1)
const vLatest = latestReleaseVersion.split('.')
const vNew = runnerVersion.split('.')
let versionBumped = true
for (let i = 0; i < 3; ++i) {
var v1 = parseInt(vLatest[i], 10);
var v2 = parseInt(vNew[i], 10);
if (v2 > v1) {
console.log(runnerVersion + " > " + latestReleaseVersion + "(Latest)")
break
}
if (v1 > v2) {
versionBumped = false
core.setFailed(runnerVersion + " < " + latestReleaseVersion + "(Latest)")
break
}
}
// Generate release note
if (versionBumped) {
const releaseNoteContent = await github.repos.getContents({
owner: '${{ github.event.repository.owner.name }}',
repo: '${{ github.event.repository.name }}',
path: 'releaseNote.md'
ref: ${{ github.sha }}
})
const releaseNote = Buffer.from(releaseNoteContent.data.content, 'base64').toString().replace("<RUNNER_VERSION>", runnerVersion)
console.log(releaseNote)
core.setOutput('note', releaseNote);
}
core.setOutput('note', releaseNote);
# Create GitHub release
- uses: actions/create-release@v1
- uses: actions/create-release@master
id: createRelease
name: Create ${{ steps.releaseNote.outputs.version }} Runner Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: "v${{ steps.releaseNote.outputs.version }}"
release_name: "v${{ steps.releaseNote.outputs.version }}"
body: ${{ steps.releaseNote.outputs.note }}
body: |
${{ steps.releaseNote.outputs.note }}
prerelease: true
# Upload release assets
- name: Upload Release Asset (win-x64)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-win-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-x64)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (mac-x64)
- name: Upload Release Asset (osx-x64)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./actions-runner-mac-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-mac-x64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-osx-x64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-arm-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream
- name: Upload Release Asset (linux-arm64)
uses: actions/upload-release-asset@v1.0.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.createRelease.outputs.upload_url }}
asset_path: ./actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.zip
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.zip
asset_path: ${{ github.workspace }}/actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_name: actions-runner-linux-arm64-${{ steps.releaseNote.outputs.version }}.tar.gz
asset_content_type: application/octet-stream

View File

@@ -1,32 +0,0 @@
[
{
"name": "actions-runner-win-x64-<RUNNER_VERSION>.zip",
"platform": "win-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip"
},
{
"name": "actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz",
"platform": "osx-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz",
"platform": "linux-x64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz",
"platform": "linux-arm64",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz"
},
{
"name": "actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz",
"platform": "linux-arm",
"version": "<RUNNER_VERSION>",
"downloadUrl": "https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz"
}
]

View File

@@ -1,237 +0,0 @@
stages:
- stage: Build
jobs:
################################################################################
- job: build_windows_agent_x64
################################################################################
displayName: Windows Agent (x64)
pool:
vmImage: vs2017-win2016
steps:
# Steps template for windows platform
- template: windows.template.yml
parameters:
targetRuntime: win-x64
# Package dotnet core windows dependency (VC++ Redistributable)
- powershell: |
Write-Host "Downloading 'VC++ Redistributable' package."
$outDir = Join-Path -Path $env:TMP -ChildPath ([Guid]::NewGuid())
New-Item -Path $outDir -ItemType directory
$outFile = Join-Path -Path $outDir -ChildPath "ucrt.zip"
Invoke-WebRequest -Uri https://vstsagenttools.blob.core.windows.net/tools/ucrt/ucrt_x64.zip -OutFile $outFile
Write-Host "Unzipping 'VC++ Redistributable' package to agent layout."
$unzipDir = Join-Path -Path $outDir -ChildPath "unzip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($outFile, $unzipDir)
$agentLayoutBin = Join-Path -Path $(Build.SourcesDirectory) -ChildPath "_layout\bin"
Copy-Item -Path $unzipDir -Destination $agentLayoutBin -Force
displayName: Package UCRT
# Create agent package zip
- script: dev.cmd package Release win-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Windows x64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_linux_agent_x64
################################################################################
displayName: Linux Agent (x64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-x64
# Create agent package zip
- script: ./dev.sh package Release linux-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux x64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_linux_agent_arm64
################################################################################
displayName: Linux Agent (arm64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-arm64
# Create agent package zip
- script: ./dev.sh package Release linux-arm64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux ARM64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_linux_agent_arm
################################################################################
displayName: Linux Agent (arm)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: linux-arm
# Create agent package zip
- script: ./dev.sh package Release linux-arm
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux ARM)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
################################################################################
- job: build_osx_agent_x64
################################################################################
displayName: macOS Agent (x64)
pool:
vmImage: macOS-10.13
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
parameters:
targetRuntime: osx-x64
# Create agent package zip
- script: ./dev.sh package Release osx-x64
workingDirectory: src
displayName: Package Release
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (OSX x64)
inputs:
pathToPublish: _package
artifactName: runners
artifactType: container
- stage: Release
dependsOn: Build
jobs:
################################################################################
- job: publish_agent_packages
################################################################################
displayName: Publish Agents (Windows/Linux/OSX)
pool:
name: ProductionRMAgents
steps:
# Download all agent packages from all previous phases
- task: DownloadBuildArtifacts@0
displayName: Download Agent Packages
inputs:
artifactName: runners
# Upload agent packages to Azure blob storage and refresh Azure CDN
- powershell: |
Write-Host "Preloading Azure modules." # This is for better performance, to avoid module-autoloading.
Import-Module AzureRM, AzureRM.profile, AzureRM.Storage, Azure.Storage, AzureRM.Cdn -ErrorAction Ignore -PassThru
Enable-AzureRmAlias -Scope CurrentUser
$uploadFiles = New-Object System.Collections.ArrayList
$certificateThumbprint = (Get-ItemProperty -Path "$(ServicePrincipalReg)").ServicePrincipalCertThumbprint
$clientId = (Get-ItemProperty -Path "$(ServicePrincipalReg)").ServicePrincipalClientId
Write-Host "##vso[task.setsecret]$certificateThumbprint"
Write-Host "##vso[task.setsecret]$clientId"
Login-AzureRmAccount -ServicePrincipal -CertificateThumbprint $certificateThumbprint -ApplicationId $clientId -TenantId $(GitHubTenantId)
Select-AzureRmSubscription -SubscriptionId $(GitHubSubscriptionId)
$storage = Get-AzureRmStorageAccount -ResourceGroupName githubassets -AccountName githubassets
Get-ChildItem -LiteralPath "$(System.ArtifactsDirectory)/runners" | ForEach-Object {
$versionDir = $_.Name.Trim('.zip').Trim('.tar.gz')
$versionDir = $versionDir.SubString($versionDir.LastIndexOf('-') + 1)
Write-Host "##vso[task.setvariable variable=ReleaseAgentVersion;]$versionDir"
Write-Host "Uploading $_ to BlobStorage githubassets/runners/$versionDir"
Set-AzureStorageBlobContent -Context $storage.Context -Container runners -File "$(System.ArtifactsDirectory)/runners/$_" -Blob "$versionDir/$_" -Force
$uploadFiles.Add("/runners/$versionDir/$_")
}
Write-Host "Get CDN info"
Get-AzureRmCdnEndpoint -ProfileName githubassets -ResourceGroupName githubassets
Write-Host "Purge Azure CDN Cache"
Unpublish-AzureRmCdnEndpointContent -EndpointName githubassets -ProfileName githubassets -ResourceGroupName githubassets -PurgeContent $uploadFiles
Write-Host "Pull assets through Azure CDN"
$uploadFiles | ForEach-Object {
$downloadUrl = "https://githubassets.azureedge.net" + $_
Write-Host $downloadUrl
Invoke-WebRequest -Uri $downloadUrl -OutFile $_.SubString($_.LastIndexOf('/') + 1)
}
displayName: Upload to Azure Blob
# Create agent release on Github
- powershell: |
Write-Host "Creating github release."
$releaseNotes = [System.IO.File]::ReadAllText("$(Build.SourcesDirectory)\releaseNote.md").Replace("<RUNNER_VERSION>","$(ReleaseAgentVersion)")
$releaseData = @{
tag_name = "v$(ReleaseAgentVersion)";
target_commitish = "$(Build.SourceVersion)";
name = "v$(ReleaseAgentVersion)";
body = $releaseNotes;
draft = $false;
prerelease = $true;
}
$releaseParams = @{
Uri = "https://api.github.com/repos/actions/runner/releases";
Method = 'POST';
Headers = @{
Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("github:$(GithubToken)"));
}
ContentType = 'application/json';
Body = (ConvertTo-Json $releaseData -Compress)
}
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$releaseCreated = Invoke-RestMethod @releaseParams
Write-Host $releaseCreated
$releaseId = $releaseCreated.id
Get-ChildItem -LiteralPath "$(System.ArtifactsDirectory)/runners" | ForEach-Object {
Write-Host "Uploading $_ as GitHub release assets"
$assetsParams = @{
Uri = "https://uploads.github.com/repos/actions/runner/releases/$releaseId/assets?name=$($_.Name)"
Method = 'POST';
Headers = @{
Authorization = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("github:$(GithubToken)"));
}
ContentType = 'application/octet-stream';
Body = [System.IO.File]::ReadAllBytes($_.FullName)
}
Invoke-RestMethod @assetsParams
}
displayName: Create agent release on Github

View File

@@ -1,95 +0,0 @@
jobs:
################################################################################
- job: build_windows_x64_agent
################################################################################
displayName: Windows Agent (x64)
pool:
vmImage: vs2017-win2016
steps:
# Steps template for windows platform
- template: windows.template.yml
# Package dotnet core windows dependency (VC++ Redistributable)
- powershell: |
Write-Host "Downloading 'VC++ Redistributable' package."
$outDir = Join-Path -Path $env:TMP -ChildPath ([Guid]::NewGuid())
New-Item -Path $outDir -ItemType directory
$outFile = Join-Path -Path $outDir -ChildPath "ucrt.zip"
Invoke-WebRequest -Uri https://vstsagenttools.blob.core.windows.net/tools/ucrt/ucrt_x64.zip -OutFile $outFile
Write-Host "Unzipping 'VC++ Redistributable' package to agent layout."
$unzipDir = Join-Path -Path $outDir -ChildPath "unzip"
Add-Type -AssemblyName System.IO.Compression.FileSystem
[System.IO.Compression.ZipFile]::ExtractToDirectory($outFile, $unzipDir)
$agentLayoutBin = Join-Path -Path $(Build.SourcesDirectory) -ChildPath "_layout\bin"
Copy-Item -Path $unzipDir -Destination $agentLayoutBin -Force
displayName: Package UCRT
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Create agent package zip
- script: dev.cmd package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Windows x64)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container
################################################################################
- job: build_linux_x64_agent
################################################################################
displayName: Linux Agent (x64)
pool:
vmImage: ubuntu-16.04
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (Linux x64)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container
################################################################################
- job: build_osx_agent
################################################################################
displayName: macOS Agent (x64)
pool:
vmImage: macOS-10.14
steps:
# Steps template for non-windows platform
- template: nonwindows.template.yml
# Create agent package zip
- script: ./dev.sh package Release
workingDirectory: src
displayName: Package Release
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
# Upload agent package zip as build artifact
- task: PublishBuildArtifacts@1
displayName: Publish Artifact (OSX)
condition: and(succeeded(), ne(variables['build.reason'], 'PullRequest'))
inputs:
pathToPublish: _package
artifactName: agent
artifactType: container

View File

@@ -14,16 +14,16 @@ Navigate to the `src` directory and run the following command:
**Commands:**
* `layout` (`l`): Run first time to create a full agent layout in `{root}/_layout`
* `build` (`b`): Build everything and update agent layout folder
* `test` (`t`): Build agent binaries and run unit tests
* `layout` (`l`): Run first time to create a full runner layout in `{root}/_layout`
* `build` (`b`): Build everything and update runner layout folder
* `test` (`t`): Build runner binaries and run unit tests
Sample developer flow:
```bash
git clone https://github.com/actions/runner
cd ./src
./dev.(sh/cmd) layout # the agent that build from source is in {root}/_layout
./dev.(sh/cmd) layout # the runner that build from source is in {root}/_layout
<make code changes>
./dev.(sh/cmd) build # {root}/_layout will get updated
./dev.(sh/cmd) test # run all unit tests before git commit/push

View File

@@ -1,33 +0,0 @@
parameters:
targetRuntime: ''
steps:
# Build agent layout
- script: ./dev.sh layout Release ${{ parameters.targetRuntime }}
workingDirectory: src
displayName: Build & Layout Release ${{ parameters.targetRuntime }}
# Run test
- script: ./dev.sh test
workingDirectory: src
displayName: Test
condition: and(ne('${{ parameters.targetRuntime }}', 'linux-arm64'), ne('${{ parameters.targetRuntime }}', 'linux-arm'))
# # Publish test results
# - task: PublishTestResults@2
# displayName: Publish Test Results **/*.trx
# condition: always()
# inputs:
# testRunner: VSTest
# testResultsFiles: '**/*.trx'
# testRunTitle: 'Agent Tests'
# # Upload test log
# - task: PublishBuildArtifacts@1
# displayName: Publish Test logs
# condition: always()
# inputs:
# pathToPublish: src/Test/TestLogs
# artifactName: $(System.JobId)
# artifactType: container

View File

@@ -1,14 +1,14 @@
## Features
- Added Proxy Support for self-hosted runner. (#206)
- Introduce `--name` configure argument for runner name. (#217)
- Better repo matching for issue file path (checkout v2 related) (#208)
- Remove runner flow: Change from PAT to "deletion token" in prompt (#225)
- Expose github.run_id and github.run_number to action runtime env. (#224)
## Bugs
- N/A
- Clean up error messages for container scenarios (#221)
- Pick shell from prependpath (#231)
## Misc
- Runner code cleanup (#197, #209, #214, #219)
- Update node external to 12.13.1 (#215)
- Runner code cleanup (#218 #227, #228, #229, #230)
- Consume dotnet core 3.1 in runner. (#213)
## Windows x64
We recommend configuring the runner under "<DRIVE>:\actions-runner". This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows
@@ -16,7 +16,7 @@ We recommend configuring the runner under "<DRIVE>:\actions-runner". This will h
// Create a folder under the drive root
mkdir \actions-runner ; cd \actions-runner
// Download the latest runner package
Invoke-WebRequest -Uri https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
Invoke-WebRequest -Uri https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-win-x64-<RUNNER_VERSION>.zip -OutFile actions-runner-win-x64-<RUNNER_VERSION>.zip
// Extract the installer
Add-Type -AssemblyName System.IO.Compression.FileSystem ;
[System.IO.Compression.ZipFile]::ExtractToDirectory("$HOME\Downloads\actions-runner-win-x64-<RUNNER_VERSION>.zip", "$PWD")
@@ -28,7 +28,7 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem ;
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
```
@@ -39,7 +39,7 @@ tar xzf ./actions-runner-osx-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
```
@@ -50,7 +50,7 @@ tar xzf ./actions-runner-linux-x64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
```
@@ -61,7 +61,7 @@ tar xzf ./actions-runner-linux-arm64-<RUNNER_VERSION>.tar.gz
// Create a folder
mkdir actions-runner && cd actions-runner
// Download the latest runner package
curl -O https://githubassets.azureedge.net/runners/<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
curl -O https://github.com/actions/runner/releases/download/v<RUNNER_VERSION>/actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
// Extract the installer
tar xzf ./actions-runner-linux-arm-<RUNNER_VERSION>.tar.gz
```

1
releaseVersion Normal file
View File

@@ -0,0 +1 @@
2.164.0

View File

@@ -3,7 +3,7 @@
user_id=`id -u`
# we want to snapshot the environment of the config user
if [ $user_id -eq 0 -a -z "$AGENT_ALLOW_RUNASROOT" ]; then
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
echo "Must not run with sudo"
exit 1
fi

View File

@@ -2,7 +2,7 @@
# Validate not sudo
user_id=`id -u`
if [ $user_id -eq 0 -a -z "$AGENT_ALLOW_RUNASROOT" ]; then
if [ $user_id -eq 0 -a -z "$RUNNER_ALLOW_RUNASROOT" ]; then
echo "Must not run interactively with sudo"
exit 1
fi
@@ -26,8 +26,8 @@ if [[ "$1" == "localRun" ]]; then
else
"$DIR"/bin/Runner.Listener run $*
# Return code 4 means the run once agent received an update message.
# Sleep 5 seconds to wait for the update process finish and run the agent again.
# Return code 4 means the run once runner received an update message.
# Sleep 5 seconds to wait for the update process finish and run the runner again.
returnCode=$?
if [[ $returnCode == 4 ]]; then
if [ ! -x "$(command -v sleep)" ]; then

View File

@@ -1,33 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace GitHub.Runner.Common
{
//Stephen Toub: http://blogs.msdn.com/b/pfxteam/archive/2012/02/11/10266920.aspx
public class AsyncManualResetEvent
{
private volatile TaskCompletionSource<bool> m_tcs = new TaskCompletionSource<bool>();
public Task WaitAsync() { return m_tcs.Task; }
public void Set()
{
var tcs = m_tcs;
Task.Factory.StartNew(s => ((TaskCompletionSource<bool>)s).TrySetResult(true),
tcs, CancellationToken.None, TaskCreationOptions.PreferFairness, TaskScheduler.Default);
tcs.Task.Wait();
}
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<bool>(), tcs) == tcs)
return;
}
}
}
}

View File

@@ -71,15 +71,6 @@ namespace GitHub.Runner.Common
}
}
[DataContract]
public sealed class RunnerRuntimeOptions
{
#if OS_WINDOWS
[DataMember(EmitDefaultValue = false)]
public bool GitUseSecureChannel { get; set; }
#endif
}
[ServiceLocator(Default = typeof(ConfigurationStore))]
public interface IConfigurationStore : IRunnerService
{
@@ -92,9 +83,6 @@ namespace GitHub.Runner.Common
void SaveSettings(RunnerSettings settings);
void DeleteCredential();
void DeleteSettings();
RunnerRuntimeOptions GetRunnerRuntimeOptions();
void SaveRunnerRuntimeOptions(RunnerRuntimeOptions options);
void DeleteRunnerRuntimeOptions();
}
public sealed class ConfigurationStore : RunnerService, IConfigurationStore
@@ -103,11 +91,9 @@ namespace GitHub.Runner.Common
private string _configFilePath;
private string _credFilePath;
private string _serviceConfigFilePath;
private string _runtimeOptionsFilePath;
private CredentialData _creds;
private RunnerSettings _settings;
private RunnerRuntimeOptions _runtimeOptions;
public override void Initialize(IHostContext hostContext)
{
@@ -130,16 +116,12 @@ namespace GitHub.Runner.Common
_serviceConfigFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Service);
Trace.Info("ServiceConfigFilePath: {0}", _serviceConfigFilePath);
_runtimeOptionsFilePath = hostContext.GetConfigFile(WellKnownConfigFile.Options);
Trace.Info("RuntimeOptionsFilePath: {0}", _runtimeOptionsFilePath);
}
public string RootFolder { get; private set; }
public bool HasCredentials()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("HasCredentials()");
bool credsStored = (new FileInfo(_credFilePath)).Exists;
Trace.Info("stored {0}", credsStored);
@@ -149,14 +131,13 @@ namespace GitHub.Runner.Common
public bool IsConfigured()
{
Trace.Info("IsConfigured()");
bool configured = HostContext.RunMode == RunMode.Local || (new FileInfo(_configFilePath)).Exists;
bool configured = new FileInfo(_configFilePath).Exists;
Trace.Info("IsConfigured: {0}", configured);
return configured;
}
public bool IsServiceConfigured()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("IsServiceConfigured()");
bool serviceConfigured = (new FileInfo(_serviceConfigFilePath)).Exists;
Trace.Info($"IsServiceConfigured: {serviceConfigured}");
@@ -165,7 +146,6 @@ namespace GitHub.Runner.Common
public CredentialData GetCredentials()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
if (_creds == null)
{
_creds = IOUtil.LoadObject<CredentialData>(_credFilePath);
@@ -195,7 +175,6 @@ namespace GitHub.Runner.Common
public void SaveCredential(CredentialData credential)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("Saving {0} credential @ {1}", credential.Scheme, _credFilePath);
if (File.Exists(_credFilePath))
{
@@ -211,7 +190,6 @@ namespace GitHub.Runner.Common
public void SaveSettings(RunnerSettings settings)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info("Saving runner settings.");
if (File.Exists(_configFilePath))
{
@@ -227,44 +205,12 @@ namespace GitHub.Runner.Common
public void DeleteCredential()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
IOUtil.Delete(_credFilePath, default(CancellationToken));
}
public void DeleteSettings()
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
IOUtil.Delete(_configFilePath, default(CancellationToken));
}
public RunnerRuntimeOptions GetRunnerRuntimeOptions()
{
if (_runtimeOptions == null && File.Exists(_runtimeOptionsFilePath))
{
_runtimeOptions = IOUtil.LoadObject<RunnerRuntimeOptions>(_runtimeOptionsFilePath);
}
return _runtimeOptions;
}
public void SaveRunnerRuntimeOptions(RunnerRuntimeOptions options)
{
Trace.Info("Saving runtime options.");
if (File.Exists(_runtimeOptionsFilePath))
{
// Delete existing runtime options file first, since the file is hidden and not able to overwrite.
Trace.Info("Delete exist runtime options file.");
IOUtil.DeleteFile(_runtimeOptionsFilePath);
}
IOUtil.SaveObject(options, _runtimeOptionsFilePath);
Trace.Info("Options Saved.");
File.SetAttributes(_runtimeOptionsFilePath, File.GetAttributes(_runtimeOptionsFilePath) | FileAttributes.Hidden);
}
public void DeleteRunnerRuntimeOptions()
{
IOUtil.Delete(_runtimeOptionsFilePath, default(CancellationToken));
}
}
}

View File

@@ -2,12 +2,6 @@
namespace GitHub.Runner.Common
{
public enum RunMode
{
Normal, // Keep "Normal" first (default value).
Local,
}
public enum WellKnownDirectory
{
Bin,
@@ -94,10 +88,6 @@ namespace GitHub.Runner.Common
public static readonly string MonitorSocketAddress = "monitorsocketaddress";
public static readonly string Name = "name";
public static readonly string Pool = "pool";
public static readonly string SslCACert = "sslcacert";
public static readonly string SslClientCert = "sslclientcert";
public static readonly string SslClientCertKey = "sslclientcertkey";
public static readonly string SslClientCertArchive = "sslclientcertarchive";
public static readonly string StartupType = "startuptype";
public static readonly string Url = "url";
public static readonly string UserName = "username";
@@ -105,14 +95,10 @@ namespace GitHub.Runner.Common
public static readonly string Work = "work";
// Secret args. Must be added to the "Secrets" getter as well.
public static readonly string Password = "password";
public static readonly string SslClientCertPassword = "sslclientcertpassword";
public static readonly string Token = "token";
public static readonly string WindowsLogonPassword = "windowslogonpassword";
public static string[] Secrets => new[]
{
Password,
SslClientCertPassword,
Token,
WindowsLogonPassword,
};
@@ -131,13 +117,10 @@ namespace GitHub.Runner.Common
public static class Flags
{
public static readonly string Commit = "commit";
public static readonly string GitUseSChannel = "gituseschannel";
public static readonly string Help = "help";
public static readonly string Replace = "replace";
public static readonly string LaunchBrowser = "launchbrowser";
public static readonly string Once = "once";
public static readonly string RunAsService = "runasservice";
public static readonly string SslSkipCertValidation = "sslskipcertvalidation";
public static readonly string Unattended = "unattended";
public static readonly string Version = "version";
}
@@ -164,9 +147,7 @@ namespace GitHub.Runner.Common
public static class Configuration
{
public static readonly string AAD = "AAD";
public static readonly string OAuthAccessToken = "OAuthAccessToken";
public static readonly string PAT = "PAT";
public static readonly string OAuth = "OAuth";
}
@@ -208,6 +189,11 @@ namespace GitHub.Runner.Common
public static readonly string StepDebug = "ACTIONS_STEP_DEBUG";
}
public static class Agent
{
public static readonly string ToolsDirectory = "agent.ToolsDirectory";
}
public static class System
{
//

View File

@@ -20,7 +20,6 @@ namespace GitHub.Runner.Common
{
public interface IHostContext : IDisposable
{
RunMode RunMode { get; set; }
StartupType StartupType { get; set; }
CancellationToken RunnerShutdownToken { get; }
ShutdownReason RunnerShutdownReason { get; }
@@ -58,10 +57,9 @@ namespace GitHub.Runner.Common
private readonly ProductInfoHeaderValue _userAgent = new ProductInfoHeaderValue($"GitHubActionsRunner-{BuildConstants.RunnerPackage.PackageName}", BuildConstants.RunnerPackage.Version);
private CancellationTokenSource _runnerShutdownTokenSource = new CancellationTokenSource();
private object _perfLock = new object();
private RunMode _runMode = RunMode.Normal;
private Tracing _trace;
private Tracing _vssTrace;
private Tracing _httpTrace;
private Tracing _actionsHttpTrace;
private Tracing _netcoreHttpTrace;
private ITraceManager _traceManager;
private AssemblyLoadContext _loadContext;
private IDisposable _httpTraceSubscription;
@@ -119,8 +117,7 @@ namespace GitHub.Runner.Common
}
_trace = GetTrace(nameof(HostContext));
_vssTrace = GetTrace("GitHubActionsRunner"); // VisualStudioService
_actionsHttpTrace = GetTrace("GitHubActionsService");
// Enable Http trace
bool enableHttpTrace;
if (bool.TryParse(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_RUNNER_HTTPTRACE"), out enableHttpTrace) && enableHttpTrace)
@@ -132,7 +129,7 @@ namespace GitHub.Runner.Common
_trace.Warning("** **");
_trace.Warning("*****************************************************************************************");
_httpTrace = GetTrace("HttpTrace");
_netcoreHttpTrace = GetTrace("HttpTrace");
_diagListenerSubscription = DiagnosticListener.AllListeners.Subscribe(this);
}
@@ -194,20 +191,6 @@ namespace GitHub.Runner.Common
}
}
public RunMode RunMode
{
get
{
return _runMode;
}
set
{
_trace.Info($"Set run mode: {value}");
_runMode = value;
}
}
public string GetDirectory(WellKnownDirectory directory)
{
string path;
@@ -246,8 +229,9 @@ namespace GitHub.Runner.Common
break;
case WellKnownDirectory.Tools:
path = Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE");
// TODO: Coallesce to just check RUNNER_TOOL_CACHE when images stabilize
path = Environment.GetEnvironmentVariable("RUNNER_TOOL_CACHE") ?? Environment.GetEnvironmentVariable("RUNNER_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable("AGENT_TOOLSDIRECTORY") ?? Environment.GetEnvironmentVariable(Constants.Variables.Agent.ToolsDirectory);
if (string.IsNullOrEmpty(path))
{
path = Path.Combine(
@@ -494,12 +478,12 @@ namespace GitHub.Runner.Common
void IObserver<DiagnosticListener>.OnCompleted()
{
_httpTrace.Info("DiagListeners finished transmitting data.");
_netcoreHttpTrace.Info("DiagListeners finished transmitting data.");
}
void IObserver<DiagnosticListener>.OnError(Exception error)
{
_httpTrace.Error(error);
_netcoreHttpTrace.Error(error);
}
void IObserver<DiagnosticListener>.OnNext(DiagnosticListener listener)
@@ -512,22 +496,22 @@ namespace GitHub.Runner.Common
void IObserver<KeyValuePair<string, object>>.OnCompleted()
{
_httpTrace.Info("HttpHandlerDiagnosticListener finished transmitting data.");
_netcoreHttpTrace.Info("HttpHandlerDiagnosticListener finished transmitting data.");
}
void IObserver<KeyValuePair<string, object>>.OnError(Exception error)
{
_httpTrace.Error(error);
_netcoreHttpTrace.Error(error);
}
void IObserver<KeyValuePair<string, object>>.OnNext(KeyValuePair<string, object> value)
{
_httpTrace.Info($"Trace {value.Key} event:{Environment.NewLine}{value.Value.ToString()}");
_netcoreHttpTrace.Info($"Trace {value.Key} event:{Environment.NewLine}{value.Value.ToString()}");
}
protected override void OnEventSourceCreated(EventSource source)
{
if (source.Name.Equals("Microsoft-VSS-Http"))
if (source.Name.Equals("GitHub-Actions-Http"))
{
EnableEvents(source, EventLevel.Verbose);
}
@@ -567,24 +551,24 @@ namespace GitHub.Runner.Common
{
case EventLevel.Critical:
case EventLevel.Error:
_vssTrace.Error(message);
_actionsHttpTrace.Error(message);
break;
case EventLevel.Warning:
_vssTrace.Warning(message);
_actionsHttpTrace.Warning(message);
break;
case EventLevel.Informational:
_vssTrace.Info(message);
_actionsHttpTrace.Info(message);
break;
default:
_vssTrace.Verbose(message);
_actionsHttpTrace.Verbose(message);
break;
}
}
catch (Exception ex)
{
_vssTrace.Error(ex);
_vssTrace.Info(eventData.Message);
_vssTrace.Info(string.Join(", ", eventData.Payload?.ToArray() ?? new string[0]));
_actionsHttpTrace.Error(ex);
_actionsHttpTrace.Info(eventData.Message);
_actionsHttpTrace.Info(string.Join(", ", eventData.Payload?.ToArray() ?? new string[0]));
}
}

View File

@@ -32,11 +32,6 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(VssConnection jobConnection)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
_connection = jobConnection;
int attemptCount = 5;
while (!_connection.HasAuthenticated && attemptCount-- > 0)
@@ -73,88 +68,48 @@ namespace GitHub.Runner.Common
public Task<TaskLog> AppendLogContentAsync(Guid scopeIdentifier, string hubName, Guid planId, int logId, Stream uploadStream, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskLog>(null);
}
CheckConnection();
return _taskClient.AppendLogContentAsync(scopeIdentifier, hubName, planId, logId, uploadStream, cancellationToken: cancellationToken);
}
public Task AppendTimelineRecordFeedAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, Guid stepId, IList<string> lines, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.CompletedTask;
}
CheckConnection();
return _taskClient.AppendTimelineRecordFeedAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, stepId, lines, cancellationToken: cancellationToken);
}
public Task<TaskAttachment> CreateAttachmentAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, Guid timelineRecordId, string type, string name, Stream uploadStream, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskAttachment>(null);
}
CheckConnection();
return _taskClient.CreateAttachmentAsync(scopeIdentifier, hubName, planId, timelineId, timelineRecordId, type, name, uploadStream, cancellationToken: cancellationToken);
}
public Task<TaskLog> CreateLogAsync(Guid scopeIdentifier, string hubName, Guid planId, TaskLog log, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskLog>(null);
}
CheckConnection();
return _taskClient.CreateLogAsync(scopeIdentifier, hubName, planId, log, cancellationToken: cancellationToken);
}
public Task<Timeline> CreateTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<Timeline>(null);
}
CheckConnection();
return _taskClient.CreateTimelineAsync(scopeIdentifier, hubName, planId, new Timeline(timelineId), cancellationToken: cancellationToken);
}
public Task<List<TimelineRecord>> UpdateTimelineRecordsAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, IEnumerable<TimelineRecord> records, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<List<TimelineRecord>>(null);
}
CheckConnection();
return _taskClient.UpdateTimelineRecordsAsync(scopeIdentifier, hubName, planId, timelineId, records, cancellationToken: cancellationToken);
}
public Task RaisePlanEventAsync<T>(Guid scopeIdentifier, string hubName, Guid planId, T eventData, CancellationToken cancellationToken) where T : JobEvent
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.CompletedTask;
}
CheckConnection();
return _taskClient.RaisePlanEventAsync(scopeIdentifier, hubName, planId, eventData, cancellationToken: cancellationToken);
}
public Task<Timeline> GetTimelineAsync(Guid scopeIdentifier, string hubName, Guid planId, Guid timelineId, CancellationToken cancellationToken)
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<Timeline>(null);
}
CheckConnection();
return _taskClient.GetTimelineAsync(scopeIdentifier, hubName, planId, timelineId, includeRecords: true, cancellationToken: cancellationToken);
}

View File

@@ -63,7 +63,6 @@ namespace GitHub.Runner.Common
private Task[] _allDequeueTasks;
private readonly TaskCompletionSource<int> _jobCompletionSource = new TaskCompletionSource<int>();
private bool _queueInProcess = false;
private ITerminal _term;
public event EventHandler<ThrottlingEventArgs> JobServerQueueThrottling;
@@ -85,11 +84,6 @@ namespace GitHub.Runner.Common
public void Start(Pipelines.AgentJobRequestMessage jobRequest)
{
Trace.Entering();
if (HostContext.RunMode == RunMode.Local)
{
_term = HostContext.GetService<ITerminal>();
return;
}
if (_queueInProcess)
{
@@ -129,11 +123,6 @@ namespace GitHub.Runner.Common
// TimelineUpdate queue error will become critical when timeline records contain output variabls.
public async Task ShutdownAsync()
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
if (!_queueInProcess)
{
Trace.Info("No-op, all queue process tasks have been stopped.");
@@ -169,32 +158,11 @@ namespace GitHub.Runner.Common
public void QueueWebConsoleLine(Guid stepRecordId, string line)
{
Trace.Verbose("Enqueue web console line queue: {0}", line);
if (HostContext.RunMode == RunMode.Local)
{
if ((line ?? string.Empty).StartsWith("##[section]"))
{
Console.WriteLine("******************************************************************************");
Console.WriteLine(line.Substring("##[section]".Length));
Console.WriteLine("******************************************************************************");
}
else
{
Console.WriteLine(line);
}
return;
}
_webConsoleLineQueue.Enqueue(new ConsoleLineInfo(stepRecordId, line));
}
public void QueueFileUpload(Guid timelineId, Guid timelineRecordId, string type, string name, string path, bool deleteSource)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
ArgUtil.NotEmpty(timelineRecordId, nameof(timelineRecordId));
@@ -215,11 +183,6 @@ namespace GitHub.Runner.Common
public void QueueTimelineRecordUpdate(Guid timelineId, TimelineRecord timelineRecord)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
ArgUtil.NotEmpty(timelineId, nameof(timelineId));
ArgUtil.NotNull(timelineRecord, nameof(timelineRecord));
ArgUtil.NotEmpty(timelineRecord.Id, nameof(timelineRecord.Id));

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>

View File

@@ -1,231 +0,0 @@
using System;
using GitHub.Runner.Common.Util;
using System.IO;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Security;
using System.Net.Http;
using GitHub.Services.WebApi;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
[ServiceLocator(Default = typeof(RunnerCertificateManager))]
public interface IRunnerCertificateManager : IRunnerService
{
bool SkipServerCertificateValidation { get; }
string CACertificateFile { get; }
string ClientCertificateFile { get; }
string ClientCertificatePrivateKeyFile { get; }
string ClientCertificateArchiveFile { get; }
string ClientCertificatePassword { get; }
IVssClientCertificateManager VssClientCertificateManager { get; }
}
public class RunnerCertificateManager : RunnerService, IRunnerCertificateManager
{
private RunnerClientCertificateManager _runnerClientCertificateManager = new RunnerClientCertificateManager();
public bool SkipServerCertificateValidation { private set; get; }
public string CACertificateFile { private set; get; }
public string ClientCertificateFile { private set; get; }
public string ClientCertificatePrivateKeyFile { private set; get; }
public string ClientCertificateArchiveFile { private set; get; }
public string ClientCertificatePassword { private set; get; }
public IVssClientCertificateManager VssClientCertificateManager => _runnerClientCertificateManager;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
LoadCertificateSettings();
}
// This should only be called from config
public void SetupCertificate(bool skipCertValidation, string caCert, string clientCert, string clientCertPrivateKey, string clientCertArchive, string clientCertPassword)
{
Trace.Info("Setup runner certificate setting base on configuration inputs.");
if (skipCertValidation)
{
Trace.Info("Ignore SSL server certificate validation error");
SkipServerCertificateValidation = true;
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
if (!string.IsNullOrEmpty(caCert))
{
ArgUtil.File(caCert, nameof(caCert));
Trace.Info($"Self-Signed CA '{caCert}'");
}
if (!string.IsNullOrEmpty(clientCert))
{
ArgUtil.File(clientCert, nameof(clientCert));
ArgUtil.File(clientCertPrivateKey, nameof(clientCertPrivateKey));
ArgUtil.File(clientCertArchive, nameof(clientCertArchive));
Trace.Info($"Client cert '{clientCert}'");
Trace.Info($"Client cert private key '{clientCertPrivateKey}'");
Trace.Info($"Client cert archive '{clientCertArchive}'");
}
CACertificateFile = caCert;
ClientCertificateFile = clientCert;
ClientCertificatePrivateKeyFile = clientCertPrivateKey;
ClientCertificateArchiveFile = clientCertArchive;
ClientCertificatePassword = clientCertPassword;
_runnerClientCertificateManager.AddClientCertificate(ClientCertificateArchiveFile, ClientCertificatePassword);
}
// This should only be called from config
public void SaveCertificateSetting()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
IOUtil.DeleteFile(certSettingFile);
var setting = new RunnerCertificateSetting();
if (SkipServerCertificateValidation)
{
Trace.Info($"Store Skip ServerCertificateValidation setting to '{certSettingFile}'");
setting.SkipServerCertValidation = true;
}
if (!string.IsNullOrEmpty(CACertificateFile))
{
Trace.Info($"Store CA cert setting to '{certSettingFile}'");
setting.CACert = CACertificateFile;
}
if (!string.IsNullOrEmpty(ClientCertificateFile) &&
!string.IsNullOrEmpty(ClientCertificatePrivateKeyFile) &&
!string.IsNullOrEmpty(ClientCertificateArchiveFile))
{
Trace.Info($"Store client cert settings to '{certSettingFile}'");
setting.ClientCert = ClientCertificateFile;
setting.ClientCertPrivatekey = ClientCertificatePrivateKeyFile;
setting.ClientCertArchive = ClientCertificateArchiveFile;
if (!string.IsNullOrEmpty(ClientCertificatePassword))
{
string lookupKey = Guid.NewGuid().ToString("D").ToUpperInvariant();
Trace.Info($"Store client cert private key password with lookup key {lookupKey}");
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Write($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{lookupKey}", "GitHub", ClientCertificatePassword);
setting.ClientCertPasswordLookupKey = lookupKey;
}
}
if (SkipServerCertificateValidation ||
!string.IsNullOrEmpty(CACertificateFile) ||
!string.IsNullOrEmpty(ClientCertificateFile))
{
IOUtil.SaveObject(setting, certSettingFile);
File.SetAttributes(certSettingFile, File.GetAttributes(certSettingFile) | FileAttributes.Hidden);
}
}
// This should only be called from unconfig
public void DeleteCertificateSetting()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
if (File.Exists(certSettingFile))
{
Trace.Info($"Load runner certificate setting from '{certSettingFile}'");
var certSetting = IOUtil.LoadObject<RunnerCertificateSetting>(certSettingFile);
if (certSetting != null && !string.IsNullOrEmpty(certSetting.ClientCertPasswordLookupKey))
{
Trace.Info("Delete client cert private key password from credential store.");
var credStore = HostContext.GetService<IRunnerCredentialStore>();
credStore.Delete($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}");
}
Trace.Info($"Delete cert setting file: {certSettingFile}");
IOUtil.DeleteFile(certSettingFile);
}
}
public void LoadCertificateSettings()
{
string certSettingFile = HostContext.GetConfigFile(WellKnownConfigFile.Certificates);
if (File.Exists(certSettingFile))
{
Trace.Info($"Load runner certificate setting from '{certSettingFile}'");
var certSetting = IOUtil.LoadObject<RunnerCertificateSetting>(certSettingFile);
ArgUtil.NotNull(certSetting, nameof(RunnerCertificateSetting));
if (certSetting.SkipServerCertValidation)
{
Trace.Info("Ignore SSL server certificate validation error");
SkipServerCertificateValidation = true;
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
if (!string.IsNullOrEmpty(certSetting.CACert))
{
// make sure all settings file exist
ArgUtil.File(certSetting.CACert, nameof(certSetting.CACert));
Trace.Info($"CA '{certSetting.CACert}'");
CACertificateFile = certSetting.CACert;
}
if (!string.IsNullOrEmpty(certSetting.ClientCert))
{
// make sure all settings file exist
ArgUtil.File(certSetting.ClientCert, nameof(certSetting.ClientCert));
ArgUtil.File(certSetting.ClientCertPrivatekey, nameof(certSetting.ClientCertPrivatekey));
ArgUtil.File(certSetting.ClientCertArchive, nameof(certSetting.ClientCertArchive));
Trace.Info($"Client cert '{certSetting.ClientCert}'");
Trace.Info($"Client cert private key '{certSetting.ClientCertPrivatekey}'");
Trace.Info($"Client cert archive '{certSetting.ClientCertArchive}'");
ClientCertificateFile = certSetting.ClientCert;
ClientCertificatePrivateKeyFile = certSetting.ClientCertPrivatekey;
ClientCertificateArchiveFile = certSetting.ClientCertArchive;
if (!string.IsNullOrEmpty(certSetting.ClientCertPasswordLookupKey))
{
var cerdStore = HostContext.GetService<IRunnerCredentialStore>();
ClientCertificatePassword = cerdStore.Read($"GITHUB_ACTIONS_RUNNER_CLIENT_CERT_PASSWORD_{certSetting.ClientCertPasswordLookupKey}").Password;
HostContext.SecretMasker.AddValue(ClientCertificatePassword);
}
_runnerClientCertificateManager.AddClientCertificate(ClientCertificateArchiveFile, ClientCertificatePassword);
}
}
else
{
Trace.Info("No certificate setting found.");
}
}
}
[DataContract]
internal class RunnerCertificateSetting
{
[DataMember]
public bool SkipServerCertValidation { get; set; }
[DataMember]
public string CACert { get; set; }
[DataMember]
public string ClientCert { get; set; }
[DataMember]
public string ClientCertPrivatekey { get; set; }
[DataMember]
public string ClientCertArchive { get; set; }
[DataMember]
public string ClientCertPasswordLookupKey { get; set; }
}
}

View File

@@ -1,948 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using GitHub.Runner.Common.Util;
using Newtonsoft.Json;
using System.IO;
using System.Runtime.Serialization;
using System.Security.Cryptography;
using GitHub.Runner.Sdk;
namespace GitHub.Runner.Common
{
// The purpose of this class is to store user's credential during runner configuration and retrive the credential back at runtime.
#if OS_WINDOWS
[ServiceLocator(Default = typeof(WindowsRunnerCredentialStore))]
#elif OS_OSX
[ServiceLocator(Default = typeof(MacOSRunnerCredentialStore))]
#else
[ServiceLocator(Default = typeof(LinuxRunnerCredentialStore))]
#endif
public interface IRunnerCredentialStore : IRunnerService
{
NetworkCredential Write(string target, string username, string password);
// throw exception when target not found from cred store
NetworkCredential Read(string target);
// throw exception when target not found from cred store
void Delete(string target);
}
#if OS_WINDOWS
// Windows credential store is per user.
// This is a limitation for user configure the runner run as windows service, when user's current login account is different with the service run as account.
// Ex: I login the box as domain\admin, configure the runner as windows service and run as domian\buildserver
// domain\buildserver won't read the stored credential from domain\admin's windows credential store.
// To workaround this limitation.
// Anytime we try to save a credential:
// 1. store it into current user's windows credential store
// 2. use DP-API do a machine level encrypt and store the encrypted content on disk.
// At the first time we try to read the credential:
// 1. read from current user's windows credential store, delete the DP-API encrypted backup content on disk if the windows credential store read succeed.
// 2. if credential not found in current user's windows credential store, read from the DP-API encrypted backup content on disk,
// write the credential back the current user's windows credential store and delete the backup on disk.
public sealed class WindowsRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
private string _credStoreFile;
private Dictionary<string, string> _credStore;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_credStoreFile = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
if (File.Exists(_credStoreFile))
{
_credStore = IOUtil.LoadObject<Dictionary<string, string>>(_credStoreFile);
}
else
{
_credStore = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
// save to .credential_store file first, then Windows credential store
string usernameBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(username));
string passwordBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(password));
// Base64Username:Base64Password -> DP-API machine level encrypt -> Base64Encoding
string encryptedUsernamePassword = Convert.ToBase64String(ProtectedData.Protect(Encoding.UTF8.GetBytes($"{usernameBase64}:{passwordBase64}"), null, DataProtectionScope.LocalMachine));
Trace.Info($"Credentials for '{target}' written to credential store file.");
_credStore[target] = encryptedUsernamePassword;
// save to .credential_store file
SyncCredentialStoreFile();
// save to Windows Credential Store
return WriteInternal(target, username, password);
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
IntPtr credPtr = IntPtr.Zero;
try
{
if (CredRead(target, CredentialType.Generic, 0, out credPtr))
{
Credential credStruct = (Credential)Marshal.PtrToStructure(credPtr, typeof(Credential));
int passwordLength = (int)credStruct.CredentialBlobSize;
string password = passwordLength > 0 ? Marshal.PtrToStringUni(credStruct.CredentialBlob, passwordLength / sizeof(char)) : String.Empty;
string username = Marshal.PtrToStringUni(credStruct.UserName);
Trace.Info($"Credentials for '{target}' read from windows credential store.");
// delete from .credential_store file since we are able to read it from windows credential store
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
return new NetworkCredential(username, password);
}
else
{
// Can't read from Windows Credential Store, fail back to .credential_store file
if (_credStore.ContainsKey(target) && !string.IsNullOrEmpty(_credStore[target]))
{
Trace.Info($"Credentials for '{target}' read from credential store file.");
// Base64Decode -> DP-API machine level decrypt -> Base64Username:Base64Password -> Base64Decode
string decryptedUsernamePassword = Encoding.UTF8.GetString(ProtectedData.Unprotect(Convert.FromBase64String(_credStore[target]), null, DataProtectionScope.LocalMachine));
string[] credential = decryptedUsernamePassword.Split(':');
if (credential.Length == 2 && !string.IsNullOrEmpty(credential[0]) && !string.IsNullOrEmpty(credential[1]))
{
string username = Encoding.UTF8.GetString(Convert.FromBase64String(credential[0]));
string password = Encoding.UTF8.GetString(Convert.FromBase64String(credential[1]));
// store back to windows credential store for current user
NetworkCredential creds = WriteInternal(target, username, password);
// delete from .credential_store file since we are able to write the credential to windows credential store for current user.
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
return creds;
}
else
{
throw new ArgumentOutOfRangeException(nameof(decryptedUsernamePassword));
}
}
throw new Win32Exception(Marshal.GetLastWin32Error(), $"CredRead throw an error for '{target}'");
}
}
finally
{
if (credPtr != IntPtr.Zero)
{
CredFree(credPtr);
}
}
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
// remove from .credential_store file
if (_credStore.Remove(target))
{
Trace.Info($"Delete credentials for '{target}' from credential store file.");
SyncCredentialStoreFile();
}
// remove from windows credential store
if (!CredDelete(target, CredentialType.Generic, 0))
{
throw new Win32Exception(Marshal.GetLastWin32Error(), $"Failed to delete credentials for {target}");
}
else
{
Trace.Info($"Credentials for '{target}' deleted from windows credential store.");
}
}
private NetworkCredential WriteInternal(string target, string username, string password)
{
// save to Windows Credential Store
Credential credential = new Credential()
{
Type = CredentialType.Generic,
Persist = (UInt32)CredentialPersist.LocalMachine,
TargetName = Marshal.StringToCoTaskMemUni(target),
UserName = Marshal.StringToCoTaskMemUni(username),
CredentialBlob = Marshal.StringToCoTaskMemUni(password),
CredentialBlobSize = (UInt32)Encoding.Unicode.GetByteCount(password),
AttributeCount = 0,
Comment = IntPtr.Zero,
Attributes = IntPtr.Zero,
TargetAlias = IntPtr.Zero
};
try
{
if (CredWrite(ref credential, 0))
{
Trace.Info($"Credentials for '{target}' written to windows credential store.");
return new NetworkCredential(username, password);
}
else
{
int error = Marshal.GetLastWin32Error();
throw new Win32Exception(error, "Failed to write credentials");
}
}
finally
{
if (credential.CredentialBlob != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.CredentialBlob);
}
if (credential.TargetName != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.TargetName);
}
if (credential.UserName != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(credential.UserName);
}
}
}
private void SyncCredentialStoreFile()
{
Trace.Info("Sync in-memory credential store with credential store file.");
// delete the cred store file first anyway, since it's a readonly file.
IOUtil.DeleteFile(_credStoreFile);
// delete cred store file when all creds gone
if (_credStore.Count == 0)
{
return;
}
else
{
IOUtil.SaveObject(_credStore, _credStoreFile);
File.SetAttributes(_credStoreFile, File.GetAttributes(_credStoreFile) | FileAttributes.Hidden);
}
}
[DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredDelete(string target, CredentialType type, int reservedFlag);
[DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredRead(string target, CredentialType type, int reservedFlag, out IntPtr CredentialPtr);
[DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern bool CredWrite([In] ref Credential userCredential, [In] UInt32 flags);
[DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)]
internal static extern bool CredFree([In] IntPtr cred);
internal enum CredentialPersist : UInt32
{
Session = 0x01,
LocalMachine = 0x02
}
internal enum CredentialType : uint
{
Generic = 0x01,
DomainPassword = 0x02,
DomainCertificate = 0x03
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct Credential
{
public UInt32 Flags;
public CredentialType Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public UInt32 CredentialBlobSize;
public IntPtr CredentialBlob;
public UInt32 Persist;
public UInt32 AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}
}
#elif OS_OSX
public sealed class MacOSRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
private const string _osxRunnerCredStoreKeyChainName = "_GITHUB_ACTIONS_RUNNER_CREDSTORE_INTERNAL_";
// Keychain requires a password, but this is not intended to add security
private const string _osxRunnerCredStoreKeyChainPassword = "C46F23C36AF94B72B1EAEE32C68670A0";
private string _securityUtil;
private string _runnerCredStoreKeyChain;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_securityUtil = WhichUtil.Which("security", true, Trace);
_runnerCredStoreKeyChain = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
// Create osx key chain if it doesn't exists.
if (!File.Exists(_runnerCredStoreKeyChain))
{
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"create-keychain -p {_osxRunnerCredStoreKeyChainPassword} \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully create-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security create-keychain' failed with exit code {exitCode}.");
}
}
}
else
{
// Try unlock and lock the keychain, make sure it's still in good stage
UnlockKeyChain();
LockKeyChain();
}
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
try
{
UnlockKeyChain();
// base64encode username + ':' + base64encode password
// OSX keychain requires you provide -s target and -a username to retrieve password
// So, we will trade both username and password as 'secret' store into keychain
string usernameBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(username));
string passwordBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(password));
string secretForKeyChain = $"{usernameBase64}:{passwordBase64}";
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"add-generic-password -s {target} -a GITHUBACTIONSRUNNER -w {secretForKeyChain} -T \"{_securityUtil}\" \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully add-generic-password for {target} (GITHUBACTIONSRUNNER)");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security add-generic-password' failed with exit code {exitCode}.");
}
}
return new NetworkCredential(username, password);
}
finally
{
LockKeyChain();
}
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
try
{
UnlockKeyChain();
string username;
string password;
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"find-generic-password -s {target} -a GITHUBACTIONSRUNNER -w -g \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
string keyChainSecret = securityOut.First();
string[] secrets = keyChainSecret.Split(':');
if (secrets.Length == 2 && !string.IsNullOrEmpty(secrets[0]) && !string.IsNullOrEmpty(secrets[1]))
{
Trace.Info($"Successfully find-generic-password for {target} (GITHUBACTIONSRUNNER)");
username = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[0]));
password = Encoding.UTF8.GetString(Convert.FromBase64String(secrets[1]));
return new NetworkCredential(username, password);
}
else
{
throw new ArgumentOutOfRangeException(nameof(keyChainSecret));
}
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security find-generic-password' failed with exit code {exitCode}.");
}
}
}
finally
{
LockKeyChain();
}
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
try
{
UnlockKeyChain();
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"delete-generic-password -s {target} -a GITHUBACTIONSRUNNER \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully delete-generic-password for {target} (GITHUBACTIONSRUNNER)");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security delete-generic-password' failed with exit code {exitCode}.");
}
}
}
finally
{
LockKeyChain();
}
}
private void UnlockKeyChain()
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(_securityUtil, nameof(_securityUtil));
ArgUtil.NotNullOrEmpty(_runnerCredStoreKeyChain, nameof(_runnerCredStoreKeyChain));
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"unlock-keychain -p {_osxRunnerCredStoreKeyChainPassword} \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully unlock-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security unlock-keychain' failed with exit code {exitCode}.");
}
}
}
private void LockKeyChain()
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(_securityUtil, nameof(_securityUtil));
ArgUtil.NotNullOrEmpty(_runnerCredStoreKeyChain, nameof(_runnerCredStoreKeyChain));
List<string> securityOut = new List<string>();
List<string> securityError = new List<string>();
object outputLock = new object();
using (var p = HostContext.CreateService<IProcessInvoker>())
{
p.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
if (!string.IsNullOrEmpty(stdout.Data))
{
lock (outputLock)
{
securityOut.Add(stdout.Data);
}
}
};
p.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (outputLock)
{
securityError.Add(stderr.Data);
}
}
};
// make sure the 'security' has access to the key so we won't get prompt at runtime.
int exitCode = p.ExecuteAsync(workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Root),
fileName: _securityUtil,
arguments: $"lock-keychain \"{_runnerCredStoreKeyChain}\"",
environment: null,
cancellationToken: CancellationToken.None).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info($"Successfully lock-keychain for {_runnerCredStoreKeyChain}");
}
else
{
if (securityOut.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityOut));
}
if (securityError.Count > 0)
{
Trace.Error(string.Join(Environment.NewLine, securityError));
}
throw new InvalidOperationException($"'security lock-keychain' failed with exit code {exitCode}.");
}
}
}
}
#else
public sealed class LinuxRunnerCredentialStore : RunnerService, IRunnerCredentialStore
{
// 'ghrunner' 128 bits iv
private readonly byte[] iv = new byte[] { 0x67, 0x68, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72, 0x67, 0x68, 0x72, 0x75, 0x6e, 0x6e, 0x65, 0x72 };
// 256 bits key
private byte[] _symmetricKey;
private string _credStoreFile;
private Dictionary<string, Credential> _credStore;
public override void Initialize(IHostContext hostContext)
{
base.Initialize(hostContext);
_credStoreFile = hostContext.GetConfigFile(WellKnownConfigFile.CredentialStore);
if (File.Exists(_credStoreFile))
{
_credStore = IOUtil.LoadObject<Dictionary<string, Credential>>(_credStoreFile);
}
else
{
_credStore = new Dictionary<string, Credential>(StringComparer.OrdinalIgnoreCase);
}
string machineId;
if (File.Exists("/etc/machine-id"))
{
// try use machine-id as encryption key
// this helps avoid accidental information disclosure, but isn't intended for true security
machineId = File.ReadAllLines("/etc/machine-id").FirstOrDefault();
Trace.Info($"machine-id length {machineId?.Length ?? 0}.");
// machine-id doesn't exist or machine-id is not 256 bits
if (string.IsNullOrEmpty(machineId) || machineId.Length != 32)
{
Trace.Warning("Can not get valid machine id from '/etc/machine-id'.");
machineId = "43e7fe5da07740cf914b90f1dac51c2a";
}
}
else
{
// /etc/machine-id not exist
Trace.Warning("/etc/machine-id doesn't exist.");
machineId = "43e7fe5da07740cf914b90f1dac51c2a";
}
List<byte> keyBuilder = new List<byte>();
foreach (var c in machineId)
{
keyBuilder.Add(Convert.ToByte(c));
}
_symmetricKey = keyBuilder.ToArray();
}
public NetworkCredential Write(string target, string username, string password)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
ArgUtil.NotNullOrEmpty(username, nameof(username));
ArgUtil.NotNullOrEmpty(password, nameof(password));
Trace.Info($"Store credential for '{target}' to cred store.");
Credential cred = new Credential(username, Encrypt(password));
_credStore[target] = cred;
SyncCredentialStoreFile();
return new NetworkCredential(username, password);
}
public NetworkCredential Read(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
Trace.Info($"Read credential for '{target}' from cred store.");
if (_credStore.ContainsKey(target))
{
Credential cred = _credStore[target];
if (!string.IsNullOrEmpty(cred.UserName) && !string.IsNullOrEmpty(cred.Password))
{
Trace.Info($"Return credential for '{target}' from cred store.");
return new NetworkCredential(cred.UserName, Decrypt(cred.Password));
}
}
throw new KeyNotFoundException(target);
}
public void Delete(string target)
{
Trace.Entering();
ArgUtil.NotNullOrEmpty(target, nameof(target));
if (_credStore.ContainsKey(target))
{
Trace.Info($"Delete credential for '{target}' from cred store.");
_credStore.Remove(target);
SyncCredentialStoreFile();
}
else
{
throw new KeyNotFoundException(target);
}
}
private void SyncCredentialStoreFile()
{
Trace.Entering();
Trace.Info("Sync in-memory credential store with credential store file.");
// delete cred store file when all creds gone
if (_credStore.Count == 0)
{
IOUtil.DeleteFile(_credStoreFile);
return;
}
if (!File.Exists(_credStoreFile))
{
CreateCredentialStoreFile();
}
IOUtil.SaveObject(_credStore, _credStoreFile);
}
private string Encrypt(string secret)
{
using (Aes aes = Aes.Create())
{
aes.Key = _symmetricKey;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aes.CreateEncryptor();
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(secret);
}
return Convert.ToBase64String(msEncrypt.ToArray());
}
}
}
}
private string Decrypt(string encryptedText)
{
using (Aes aes = Aes.Create())
{
aes.Key = _symmetricKey;
aes.IV = iv;
// Create a decrytor to perform the stream transform.
ICryptoTransform decryptor = aes.CreateDecryptor();
// Create the streams used for decryption.
using (MemoryStream msDecrypt = new MemoryStream(Convert.FromBase64String(encryptedText)))
{
using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (StreamReader srDecrypt = new StreamReader(csDecrypt))
{
// Read the decrypted bytes from the decrypting stream and place them in a string.
return srDecrypt.ReadToEnd();
}
}
}
}
}
private void CreateCredentialStoreFile()
{
File.WriteAllText(_credStoreFile, "");
File.SetAttributes(_credStoreFile, File.GetAttributes(_credStoreFile) | FileAttributes.Hidden);
// Try to lock down the .credentials_store file to the owner/group
var chmodPath = WhichUtil.Which("chmod", trace: Trace);
if (!String.IsNullOrEmpty(chmodPath))
{
var arguments = $"600 {new FileInfo(_credStoreFile).FullName}";
using (var invoker = HostContext.CreateService<IProcessInvoker>())
{
var exitCode = invoker.ExecuteAsync(HostContext.GetDirectory(WellKnownDirectory.Root), chmodPath, arguments, null, default(CancellationToken)).GetAwaiter().GetResult();
if (exitCode == 0)
{
Trace.Info("Successfully set permissions for credentials store file {0}", _credStoreFile);
}
else
{
Trace.Warning("Unable to successfully set permissions for credentials store file {0}. Received exit code {1} from {2}", _credStoreFile, exitCode, chmodPath);
}
}
}
else
{
Trace.Warning("Unable to locate chmod to set permissions for credentials store file {0}.", _credStoreFile);
}
}
}
[DataContract]
internal class Credential
{
public Credential()
{ }
public Credential(string userName, string password)
{
UserName = userName;
Password = password;
}
[DataMember(IsRequired = true)]
public string UserName { get; set; }
[DataMember(IsRequired = true)]
public string Password { get; set; }
}
#endif
}

View File

@@ -66,11 +66,6 @@ namespace GitHub.Runner.Common
public async Task ConnectAsync(Uri serverUrl, VssCredentials credentials)
{
if (HostContext.RunMode == RunMode.Local)
{
return;
}
var createGenericConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(100));
var createMessageConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
var createRequestConnection = EstablishVssConnection(serverUrl, credentials, TimeSpan.FromSeconds(60));
@@ -303,29 +298,18 @@ namespace GitHub.Runner.Common
public Task<TaskAgentJobRequest> RenewAgentRequestAsync(int poolId, long requestId, Guid lockToken, CancellationToken cancellationToken = default(CancellationToken))
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult(JsonUtility.FromString<TaskAgentJobRequest>("{ lockedUntil: \"" + DateTime.Now.Add(TimeSpan.FromMinutes(5)).ToString("u") + "\" }"));
}
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.RenewAgentRequestAsync(poolId, requestId, lockToken, cancellationToken: cancellationToken);
}
public Task<TaskAgentJobRequest> FinishAgentRequestAsync(int poolId, long requestId, Guid lockToken, DateTime finishTime, TaskResult result, CancellationToken cancellationToken = default(CancellationToken))
{
if (HostContext.RunMode == RunMode.Local)
{
return Task.FromResult<TaskAgentJobRequest>(null);
}
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.FinishAgentRequestAsync(poolId, requestId, lockToken, finishTime, result, cancellationToken: cancellationToken);
}
public Task<TaskAgentJobRequest> GetAgentRequestAsync(int poolId, long requestId, CancellationToken cancellationToken = default(CancellationToken))
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
CheckConnection(RunnerConnectionType.JobRequest);
return _requestTaskAgentClient.GetAgentRequestAsync(poolId, requestId, cancellationToken: cancellationToken);
}
@@ -335,7 +319,6 @@ namespace GitHub.Runner.Common
//-----------------------------------------------------------------
public Task<List<PackageMetadata>> GetPackagesAsync(string packageType, string platform, int top, CancellationToken cancellationToken)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
CheckConnection(RunnerConnectionType.Generic);
return _genericTaskAgentClient.GetPackagesAsync(packageType, platform, top, cancellationToken: cancellationToken);
}

View File

@@ -28,14 +28,10 @@ namespace GitHub.Runner.Listener
private readonly string[] validFlags =
{
Constants.Runner.CommandLine.Flags.Commit,
#if OS_WINDOWS
Constants.Runner.CommandLine.Flags.GitUseSChannel,
#endif
Constants.Runner.CommandLine.Flags.Help,
Constants.Runner.CommandLine.Flags.Replace,
Constants.Runner.CommandLine.Flags.RunAsService,
Constants.Runner.CommandLine.Flags.Once,
Constants.Runner.CommandLine.Flags.SslSkipCertValidation,
Constants.Runner.CommandLine.Flags.Unattended,
Constants.Runner.CommandLine.Flags.Version
};
@@ -45,13 +41,7 @@ namespace GitHub.Runner.Listener
Constants.Runner.CommandLine.Args.Auth,
Constants.Runner.CommandLine.Args.MonitorSocketAddress,
Constants.Runner.CommandLine.Args.Name,
Constants.Runner.CommandLine.Args.Password,
Constants.Runner.CommandLine.Args.Pool,
Constants.Runner.CommandLine.Args.SslCACert,
Constants.Runner.CommandLine.Args.SslClientCert,
Constants.Runner.CommandLine.Args.SslClientCertKey,
Constants.Runner.CommandLine.Args.SslClientCertArchive,
Constants.Runner.CommandLine.Args.SslClientCertPassword,
Constants.Runner.CommandLine.Args.StartupType,
Constants.Runner.CommandLine.Args.Token,
Constants.Runner.CommandLine.Args.Url,
@@ -73,9 +63,6 @@ namespace GitHub.Runner.Listener
public bool Unattended => TestFlag(Constants.Runner.CommandLine.Flags.Unattended);
public bool Version => TestFlag(Constants.Runner.CommandLine.Flags.Version);
#if OS_WINDOWS
public bool GitUseSChannel => TestFlag(Constants.Runner.CommandLine.Flags.GitUseSChannel);
#endif
public bool RunOnce => TestFlag(Constants.Runner.CommandLine.Flags.Once);
// Constructor.
@@ -160,13 +147,6 @@ namespace GitHub.Runner.Listener
defaultValue: false);
}
public bool GetAutoLaunchBrowser()
{
return TestFlagOrPrompt(
name: Constants.Runner.CommandLine.Flags.LaunchBrowser,
description: "Would you like to launch your browser for AAD Device Code Flow? (Y/N)",
defaultValue: true);
}
//
// Args.
//
@@ -179,24 +159,6 @@ namespace GitHub.Runner.Listener
validator: Validators.AuthSchemeValidator);
}
public string GetPassword()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Password,
description: "What is your GitHub password?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetPool()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Pool,
description: "Enter the name of your runner pool:",
defaultValue: "default",
validator: Validators.NonEmptyValidator);
}
public string GetRunnerName()
{
return GetArgOrPrompt(
@@ -210,7 +172,7 @@ namespace GitHub.Runner.Listener
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter your personal access token:",
description: "What is your pool admin oauth access token?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
@@ -219,7 +181,16 @@ namespace GitHub.Runner.Listener
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter runner register token:",
description: "What is your runner register token?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetRunnerDeletionToken()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.Token,
description: "Enter runner deletion token:",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
@@ -240,15 +211,6 @@ namespace GitHub.Runner.Listener
validator: Validators.ServerUrlValidator);
}
public string GetUserName()
{
return GetArgOrPrompt(
name: Constants.Runner.CommandLine.Args.UserName,
description: "What is your GitHub username?",
defaultValue: string.Empty,
validator: Validators.NonEmptyValidator);
}
public string GetWindowsLogonAccount(string defaultValue, string descriptionMsg)
{
return GetArgOrPrompt(
@@ -287,36 +249,6 @@ namespace GitHub.Runner.Listener
return GetArg(Constants.Runner.CommandLine.Args.StartupType);
}
public bool GetSkipCertificateValidation()
{
return TestFlag(Constants.Runner.CommandLine.Flags.SslSkipCertValidation);
}
public string GetCACertificate()
{
return GetArg(Constants.Runner.CommandLine.Args.SslCACert);
}
public string GetClientCertificate()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCert);
}
public string GetClientCertificatePrivateKey()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertKey);
}
public string GetClientCertificateArchrive()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertArchive);
}
public string GetClientCertificatePassword()
{
return GetArg(Constants.Runner.CommandLine.Args.SslClientCertPassword);
}
//
// Private helpers.
//

View File

@@ -79,61 +79,12 @@ namespace GitHub.Runner.Listener.Configuration
_term.WriteLine("| |", ConsoleColor.White);
_term.WriteLine("--------------------------------------------------------------------------------", ConsoleColor.White);
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
Trace.Info(nameof(ConfigureAsync));
if (IsConfigured())
{
throw new InvalidOperationException("Cannot configure the runner because it is already configured. To reconfigure the runner, run 'config.cmd remove' or './config.sh remove' first.");
}
// Populate cert setting from commandline args
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
bool saveCertSetting = false;
bool skipCertValidation = command.GetSkipCertificateValidation();
string caCert = command.GetCACertificate();
string clientCert = command.GetClientCertificate();
string clientCertKey = command.GetClientCertificatePrivateKey();
string clientCertArchive = command.GetClientCertificateArchrive();
string clientCertPassword = command.GetClientCertificatePassword();
// We require all Certificate files are under agent root.
// So we can set ACL correctly when configure as service
if (!string.IsNullOrEmpty(caCert))
{
caCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), caCert);
ArgUtil.File(caCert, nameof(caCert));
}
if (!string.IsNullOrEmpty(clientCert) &&
!string.IsNullOrEmpty(clientCertKey) &&
!string.IsNullOrEmpty(clientCertArchive))
{
// Ensure all client cert pieces are there.
clientCert = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCert);
clientCertKey = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertKey);
clientCertArchive = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Root), clientCertArchive);
ArgUtil.File(clientCert, nameof(clientCert));
ArgUtil.File(clientCertKey, nameof(clientCertKey));
ArgUtil.File(clientCertArchive, nameof(clientCertArchive));
}
else if (!string.IsNullOrEmpty(clientCert) ||
!string.IsNullOrEmpty(clientCertKey) ||
!string.IsNullOrEmpty(clientCertArchive))
{
// Print out which args are missing.
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCert, Constants.Runner.CommandLine.Args.SslClientCert);
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCertKey, Constants.Runner.CommandLine.Args.SslClientCertKey);
ArgUtil.NotNullOrEmpty(Constants.Runner.CommandLine.Args.SslClientCertArchive, Constants.Runner.CommandLine.Args.SslClientCertArchive);
}
if (skipCertValidation || !string.IsNullOrEmpty(caCert) || !string.IsNullOrEmpty(clientCert))
{
Trace.Info("Reset runner cert setting base on commandline args.");
(runnerCertManager as RunnerCertificateManager).SetupCertificate(skipCertValidation, caCert, clientCert, clientCertKey, clientCertArchive, clientCertPassword);
saveCertSetting = true;
}
RunnerSettings runnerSettings = new RunnerSettings();
bool isHostedServer = false;
@@ -353,31 +304,10 @@ namespace GitHub.Runner.Listener.Configuration
_store.SaveSettings(runnerSettings);
if (saveCertSetting)
{
Trace.Info("Save agent cert setting to disk.");
(runnerCertManager as RunnerCertificateManager).SaveCertificateSetting();
}
_term.WriteLine();
_term.WriteSuccessMessage("Settings Saved.");
_term.WriteLine();
bool saveRuntimeOptions = false;
var runtimeOptions = new RunnerRuntimeOptions();
#if OS_WINDOWS
if (command.GitUseSChannel)
{
saveRuntimeOptions = true;
runtimeOptions.GitUseSecureChannel = true;
}
#endif
if (saveRuntimeOptions)
{
Trace.Info("Save agent runtime options to disk.");
_store.SaveRunnerRuntimeOptions(runtimeOptions);
}
#if OS_WINDOWS
// config windows service
bool runAsService = command.GetRunAsService();
@@ -397,7 +327,6 @@ namespace GitHub.Runner.Listener.Configuration
public async Task UnconfigureAsync(CommandSettings command)
{
ArgUtil.Equal(RunMode.Normal, HostContext.RunMode, nameof(HostContext.RunMode));
string currentAction = string.Empty;
_term.WriteSection("Runner removal");
@@ -443,7 +372,7 @@ namespace GitHub.Runner.Listener.Configuration
}
else
{
var githubToken = command.GetToken();
var githubToken = command.GetRunnerDeletionToken();
GitHubAuthResult authResult = await GetTenantCredential(settings.GitHubUrl, githubToken);
creds = authResult.ToVssCredentials();
Trace.Info("cred retrieved via GitHub auth");
@@ -491,13 +420,6 @@ namespace GitHub.Runner.Listener.Configuration
currentAction = "Removing .runner";
if (isConfigured)
{
// delete agent cert setting
(HostContext.GetService<IRunnerCertificateManager>() as RunnerCertificateManager).DeleteCertificateSetting();
// delete agent runtime option
_store.DeleteRunnerRuntimeOptions();
_store.DeleteSettings();
_term.WriteSuccessMessage("Removed .runner");
}
@@ -520,7 +442,7 @@ namespace GitHub.Runner.Listener.Configuration
Trace.Info(nameof(GetCredentialProvider));
var credentialManager = HostContext.GetService<ICredentialManager>();
string authType = command.GetAuth(defaultValue: Constants.Configuration.AAD);
string authType = command.GetAuth(defaultValue: Constants.Configuration.OAuthAccessToken);
// Create the credential.
Trace.Info("Creating credential for auth: {0}", authType);

View File

@@ -20,8 +20,6 @@ namespace GitHub.Runner.Listener.Configuration
{
public static readonly Dictionary<string, Type> CredentialTypes = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
{
{ Constants.Configuration.AAD, typeof(AadDeviceCodeAccessToken)},
{ Constants.Configuration.PAT, typeof(PersonalAccessToken)},
{ Constants.Configuration.OAuth, typeof(OAuthCredential)},
{ Constants.Configuration.OAuthAccessToken, typeof(OAuthAccessTokenCredential)},
};
@@ -80,7 +78,7 @@ namespace GitHub.Runner.Listener.Configuration
if (string.Equals(TokenSchema, "OAuthAccessToken", StringComparison.OrdinalIgnoreCase))
{
return new VssCredentials(null, new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
return new VssCredentials(new VssOAuthAccessTokenCredential(Token), CredentialPromptType.DoNotPrompt);
}
else
{

View File

@@ -1,13 +1,5 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Runner.Common.Util;
using GitHub.Services.Client;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
using GitHub.Services.OAuth;
@@ -37,125 +29,6 @@ namespace GitHub.Runner.Listener.Configuration
public abstract void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl);
}
public sealed class AadDeviceCodeAccessToken : CredentialProvider
{
private string _azureDevOpsClientId = "97877f11-0fc6-4aee-b1ff-febb0519dd00";
public override Boolean RequireInteractive => true;
public AadDeviceCodeAccessToken() : base(Constants.Configuration.AAD) { }
public override VssCredentials GetVssCredentials(IHostContext context)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
trace.Info(nameof(GetVssCredentials));
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Url, out string serverUrl);
ArgUtil.NotNullOrEmpty(serverUrl, nameof(serverUrl));
var tenantAuthorityUrl = GetTenantAuthorityUrl(context, serverUrl);
if (tenantAuthorityUrl == null)
{
throw new NotSupportedException($"'{serverUrl}' is not backed by Azure Active Directory.");
}
LoggerCallbackHandler.LogCallback = ((LogLevel level, string message, bool containsPii) =>
{
switch (level)
{
case LogLevel.Information:
trace.Info(message);
break;
case LogLevel.Error:
trace.Error(message);
break;
case LogLevel.Warning:
trace.Warning(message);
break;
default:
trace.Verbose(message);
break;
}
});
LoggerCallbackHandler.UseDefaultLogging = false;
AuthenticationContext ctx = new AuthenticationContext(tenantAuthorityUrl.AbsoluteUri);
var queryParameters = $"redirect_uri={Uri.EscapeDataString(new Uri(serverUrl).GetLeftPart(UriPartial.Authority))}";
DeviceCodeResult codeResult = ctx.AcquireDeviceCodeAsync("https://management.core.windows.net/", _azureDevOpsClientId, queryParameters).GetAwaiter().GetResult();
var term = context.GetService<ITerminal>();
term.WriteLine($"Please finish AAD device code flow in browser ({codeResult.VerificationUrl}), user code: {codeResult.UserCode}");
if (string.Equals(CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser], bool.TrueString, StringComparison.OrdinalIgnoreCase))
{
try
{
#if OS_WINDOWS
Process.Start(new ProcessStartInfo() { FileName = codeResult.VerificationUrl, UseShellExecute = true });
#elif OS_LINUX
Process.Start(new ProcessStartInfo() { FileName = "xdg-open", Arguments = codeResult.VerificationUrl });
#else
Process.Start(new ProcessStartInfo() { FileName = "open", Arguments = codeResult.VerificationUrl });
#endif
}
catch (Exception ex)
{
// not able to open browser, ex: xdg-open/open is not installed.
trace.Error(ex);
term.WriteLine($"Fail to open browser. {codeResult.Message}");
}
}
AuthenticationResult authResult = ctx.AcquireTokenByDeviceCodeAsync(codeResult).GetAwaiter().GetResult();
ArgUtil.NotNull(authResult, nameof(authResult));
trace.Info($"receive AAD auth result with {authResult.AccessTokenType} token");
var aadCred = new VssAadCredential(new VssAadToken(authResult));
VssCredentials creds = new VssCredentials(null, aadCred, CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
}
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(AadDeviceCodeAccessToken));
trace.Info(nameof(EnsureCredential));
ArgUtil.NotNull(command, nameof(command));
CredentialData.Data[Constants.Runner.CommandLine.Args.Url] = serverUrl;
CredentialData.Data[Constants.Runner.CommandLine.Flags.LaunchBrowser] = command.GetAutoLaunchBrowser().ToString();
}
private Uri GetTenantAuthorityUrl(IHostContext context, string serverUrl)
{
using (var client = new HttpClient(context.CreateHttpClientHandler()))
{
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Add("X-TFS-FedAuthRedirect", "Suppress");
client.DefaultRequestHeaders.UserAgent.Clear();
client.DefaultRequestHeaders.UserAgent.AddRange(VssClientHttpRequestSettings.Default.UserAgent);
var requestMessage = new HttpRequestMessage(HttpMethod.Head, $"{serverUrl.Trim('/')}/_apis/connectiondata");
var response = client.SendAsync(requestMessage).GetAwaiter().GetResult();
// Get the tenant from the Login URL, MSA backed accounts will not return `Bearer` www-authenticate header.
var bearerResult = response.Headers.WwwAuthenticate.Where(p => p.Scheme.Equals("Bearer", StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (bearerResult != null && bearerResult.Parameter.StartsWith("authorization_uri=", StringComparison.OrdinalIgnoreCase))
{
var authorizationUri = bearerResult.Parameter.Substring("authorization_uri=".Length);
if (Uri.TryCreate(authorizationUri, UriKind.Absolute, out Uri aadTenantUrl))
{
return aadTenantUrl;
}
}
return null;
}
}
}
public sealed class OAuthAccessTokenCredential : CredentialProvider
{
public OAuthAccessTokenCredential() : base(Constants.Configuration.OAuthAccessToken) { }
@@ -175,7 +48,7 @@ namespace GitHub.Runner.Listener.Configuration
ArgUtil.NotNullOrEmpty(token, nameof(token));
trace.Info("token retrieved: {0} chars", token.Length);
VssCredentials creds = new VssCredentials(null, new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
VssCredentials creds = new VssCredentials(new VssOAuthAccessTokenCredential(token), CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
@@ -190,42 +63,4 @@ namespace GitHub.Runner.Listener.Configuration
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
}
}
public sealed class PersonalAccessToken : CredentialProvider
{
public PersonalAccessToken() : base(Constants.Configuration.PAT) { }
public override VssCredentials GetVssCredentials(IHostContext context)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
trace.Info(nameof(GetVssCredentials));
ArgUtil.NotNull(CredentialData, nameof(CredentialData));
string token;
if (!CredentialData.Data.TryGetValue(Constants.Runner.CommandLine.Args.Token, out token))
{
token = null;
}
ArgUtil.NotNullOrEmpty(token, nameof(token));
trace.Info("token retrieved: {0} chars", token.Length);
// PAT uses a basic credential
VssBasicCredential basicCred = new VssBasicCredential("ActionsRunner", token);
VssCredentials creds = new VssCredentials(null, basicCred, CredentialPromptType.DoNotPrompt);
trace.Info("cred created");
return creds;
}
public override void EnsureCredential(IHostContext context, CommandSettings command, string serverUrl)
{
ArgUtil.NotNull(context, nameof(context));
Tracing trace = context.GetTrace(nameof(PersonalAccessToken));
trace.Info(nameof(EnsureCredential));
ArgUtil.NotNull(command, nameof(command));
CredentialData.Data[Constants.Runner.CommandLine.Args.Token] = command.GetToken();
}
}
}

View File

@@ -6,7 +6,7 @@ using GitHub.Runner.Common;
namespace GitHub.Runner.Listener.Configuration
{
/// <summary>
/// Manages an RSA key for the agent using the most appropriate store for the target platform.
/// Manages an RSA key for the runner using the most appropriate store for the target platform.
/// </summary>
#if OS_WINDOWS
[ServiceLocator(Default = typeof(RSAEncryptedFileKeyManager))]
@@ -16,10 +16,10 @@ namespace GitHub.Runner.Listener.Configuration
public interface IRSAKeyManager : IRunnerService
{
/// <summary>
/// Creates a new <c>RSACryptoServiceProvider</c> instance for the current agent. If a key file is found then the current
/// Creates a new <c>RSACryptoServiceProvider</c> instance for the current runner. If a key file is found then the current
/// key is returned to the caller.
/// </summary>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the agent</returns>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
RSACryptoServiceProvider CreateKey();
/// <summary>
@@ -30,7 +30,7 @@ namespace GitHub.Runner.Listener.Configuration
/// <summary>
/// Gets the <c>RSACryptoServiceProvider</c> instance currently stored by the key manager.
/// </summary>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the agent</returns>
/// <returns>An <c>RSACryptoServiceProvider</c> instance representing the key for the runner</returns>
/// <exception cref="CryptographicException">No key exists in the store</exception>
RSACryptoServiceProvider GetKey();
}

View File

@@ -447,7 +447,7 @@ namespace GitHub.Runner.Listener.Configuration
{
Trace.Entering();
string agentServiceExecutable = "\"" + Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), WindowsServiceControlManager.WindowsServiceControllerName) + "\"";
string runnerServiceExecutable = "\"" + Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Bin), WindowsServiceControlManager.WindowsServiceControllerName) + "\"";
IntPtr scmHndl = IntPtr.Zero;
IntPtr svcHndl = IntPtr.Zero;
IntPtr tmpBuf = IntPtr.Zero;
@@ -468,7 +468,7 @@ namespace GitHub.Runner.Listener.Configuration
};
processInvoker.ExecuteAsync(workingDirectory: string.Empty,
fileName: agentServiceExecutable,
fileName: runnerServiceExecutable,
arguments: "init",
environment: null,
requireExitCodeZero: true,
@@ -490,7 +490,7 @@ namespace GitHub.Runner.Listener.Configuration
SERVICE_WIN32_OWN_PROCESS,
ServiceBootFlag.AutoStart,
ServiceError.Normal,
agentServiceExecutable,
runnerServiceExecutable,
null,
IntPtr.Zero,
null,

View File

@@ -43,7 +43,7 @@ namespace GitHub.Runner.Listener.Configuration
// Construct a credentials cache with a single OAuth credential for communication. The windows credential
// is explicitly set to null to ensure we never do that negotiation.
return new VssCredentials(null, agentCredential, CredentialPromptType.DoNotPrompt);
return new VssCredentials(agentCredential, CredentialPromptType.DoNotPrompt);
}
}
}

View File

@@ -38,25 +38,6 @@ namespace GitHub.Runner.Listener.Configuration
return CredentialManager.CredentialTypes.ContainsKey(value);
}
public static bool FilePathValidator(string value)
{
var directoryInfo = new DirectoryInfo(value);
if (!directoryInfo.Exists)
{
try
{
Directory.CreateDirectory(value);
}
catch (Exception)
{
return false;
}
}
return true;
}
public static bool BoolValidator(string value)
{
return string.Equals(value, "true", StringComparison.OrdinalIgnoreCase) ||

View File

@@ -22,7 +22,6 @@ namespace GitHub.Runner.Listener
void Run(Pipelines.AgentJobRequestMessage message, bool runOnce = false);
bool Cancel(JobCancelMessage message);
Task WaitAsync(CancellationToken token);
TaskResult GetLocalRunJobResult(AgentJobRequestMessage message);
Task ShutdownAsync();
}
@@ -165,11 +164,6 @@ namespace GitHub.Runner.Listener
}
}
public TaskResult GetLocalRunJobResult(AgentJobRequestMessage message)
{
return _localRunJobResult.Value[message.RequestId];
}
public async Task ShutdownAsync()
{
Trace.Info($"Shutting down JobDispatcher. Make sure all WorkerDispatcher has finished.");
@@ -373,37 +367,29 @@ namespace GitHub.Runner.Listener
ArgUtil.NotNullOrEmpty(pipeHandleOut, nameof(pipeHandleOut));
ArgUtil.NotNullOrEmpty(pipeHandleIn, nameof(pipeHandleIn));
if (HostContext.RunMode == RunMode.Normal)
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
{
// Save STDOUT from worker, worker will use STDOUT report unhandle exception.
processInvoker.OutputDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stdout)
if (!string.IsNullOrEmpty(stdout.Data))
{
if (!string.IsNullOrEmpty(stdout.Data))
lock (_outputLock)
{
lock (_outputLock)
{
workerOutput.Add(stdout.Data);
}
workerOutput.Add(stdout.Data);
}
};
}
};
// Save STDERR from worker, worker will use STDERR on crash.
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (_outputLock)
{
workerOutput.Add(stderr.Data);
}
}
};
}
else if (HostContext.RunMode == RunMode.Local)
// Save STDERR from worker, worker will use STDERR on crash.
processInvoker.ErrorDataReceived += delegate (object sender, ProcessDataReceivedEventArgs stderr)
{
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs e) => Console.WriteLine(e.Data);
}
if (!string.IsNullOrEmpty(stderr.Data))
{
lock (_outputLock)
{
workerOutput.Add(stderr.Data);
}
}
};
// Start the child process.
HostContext.WritePerfCounter("StartingWorkerProcess");
@@ -730,11 +716,6 @@ namespace GitHub.Runner.Listener
private async Task CompleteJobRequestAsync(int poolId, Pipelines.AgentJobRequestMessage message, Guid lockToken, TaskResult result, string detailInfo = null)
{
Trace.Entering();
if (HostContext.RunMode == RunMode.Local)
{
_localRunJobResult.Value[message.RequestId] = result;
return;
}
if (PlanUtil.GetFeatures(message.Plan).HasFlag(PlanFeatures.JobCompletedPlanEvent))
{

View File

@@ -80,7 +80,7 @@ namespace GitHub.Runner.Listener
Trace.Info($"Attempt to create session.");
try
{
Trace.Info("Connecting to the Agent Server...");
Trace.Info("Connecting to the Runner Server...");
await _runnerServer.ConnectAsync(new Uri(serverUrl), creds);
Trace.Info("VssConnection created");
@@ -110,7 +110,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Session creation failed.");
Trace.Info("Runner OAuth token has been revoked. Session creation failed.");
throw;
}
catch (Exception ex)
@@ -190,7 +190,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Unable to pull message.");
Trace.Info("Runner OAuth token has been revoked. Unable to pull message.");
throw;
}
catch (Exception ex)
@@ -336,7 +336,7 @@ namespace GitHub.Runner.Listener
{
if (ex is TaskAgentNotFoundException)
{
Trace.Info("The agent no longer exists on the server. Stopping the runner.");
Trace.Info("The runner no longer exists on the server. Stopping the runner.");
_term.WriteError("The runner no longer exists on the server. Please reconfigure the runner.");
return false;
}
@@ -364,7 +364,7 @@ namespace GitHub.Runner.Listener
}
else if (ex is VssOAuthTokenRequestException && ex.Message.Contains("Current server time is"))
{
Trace.Info("Local clock might skewed.");
Trace.Info("Local clock might be skewed.");
_term.WriteError("The local machine's clock may be out of sync with the server time by more than five minutes. Please sync your clock with your domain or internet time and try again.");
if (_sessionCreationExceptionTracker.ContainsKey(nameof(VssOAuthTokenRequestException)))
{

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>
@@ -24,7 +24,6 @@
<PackageReference Include="System.IO.FileSystem.AccessControl" Version="4.4.0" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="4.4.0" />
<PackageReference Include="System.ServiceProcess.ServiceController" Version="4.4.0" />
<PackageReference Include="Microsoft.IdentityModel.Clients.ActiveDirectory" Version="3.19.4" />
</ItemGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">

View File

@@ -37,8 +37,7 @@ namespace GitHub.Runner.Listener
{
try
{
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy, runnerCertManager.VssClientCertificateManager);
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
_inConfigStage = true;
_completedCommand.Reset();
@@ -434,7 +433,7 @@ namespace GitHub.Runner.Listener
}
catch (TaskAgentAccessTokenExpiredException)
{
Trace.Info("Agent OAuth token has been revoked. Shutting down.");
Trace.Info("Runner OAuth token has been revoked. Shutting down.");
}
return Constants.Runner.ReturnCode.Success;

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>

View File

@@ -79,11 +79,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
{
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
bool acceptUntrustedCerts = false;
executionContext.Output($"Syncing repository: {repoFullName}");
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
if (!repositoryUrl.IsAbsoluteUri)
@@ -112,9 +107,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
}
}
var runnerCert = executionContext.GetCertConfiguration();
acceptUntrustedCerts = runnerCert?.SkipServerCertificateValidation ?? false;
executionContext.Debug($"repository url={repositoryUrl}");
executionContext.Debug($"targetPath={targetPath}");
executionContext.Debug($"sourceBranch={sourceBranch}");
@@ -124,12 +116,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
executionContext.Debug($"checkoutNestedSubmodules={checkoutNestedSubmodules}");
executionContext.Debug($"fetchDepth={fetchDepth}");
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
executionContext.Debug($"acceptUntrustedCerts={acceptUntrustedCerts}");
#if OS_WINDOWS
bool schannelSslBackend = StringUtil.ConvertToBoolean(executionContext.GetRunnerContext("gituseschannel"));
executionContext.Debug($"schannelSslBackend={schannelSslBackend}");
#endif
// Initialize git command manager with additional environment variables.
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -164,54 +150,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
{
if (!string.IsNullOrEmpty(runnerCert.CACertificateFile))
{
useSelfSignedCACert = true;
}
if (!string.IsNullOrEmpty(runnerCert.ClientCertificateFile) &&
!string.IsNullOrEmpty(runnerCert.ClientCertificatePrivateKeyFile))
{
useClientCert = true;
// prepare askpass for client cert password
if (!string.IsNullOrEmpty(runnerCert.ClientCertificatePassword))
{
clientCertPrivateKeyAskPassFile = Path.Combine(executionContext.GetRunnerContext("temp"), $"{Guid.NewGuid()}.sh");
List<string> askPass = new List<string>();
askPass.Add("#!/bin/sh");
askPass.Add($"echo \"{runnerCert.ClientCertificatePassword}\"");
File.WriteAllLines(clientCertPrivateKeyAskPassFile, askPass);
#if !OS_WINDOWS
string toolPath = WhichUtil.Which("chmod", true);
string argLine = $"775 {clientCertPrivateKeyAskPassFile}";
executionContext.Command($"chmod {argLine}");
var processInvoker = new ProcessInvoker(executionContext);
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
string workingDirectory = executionContext.GetRunnerContext("workspace");
await processInvoker.ExecuteAsync(workingDirectory, toolPath, argLine, null, true, CancellationToken.None);
#endif
}
}
}
// Check the current contents of the root folder to see if there is already a repo
// If there is a repo, see if it matches the one we are expecting to be there based on the remote fetch url
@@ -361,46 +299,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalFetchArgs.Add($"-c http.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalFetchArgs.Add($"-c http.sslVerify=false");
additionalLfsFetchArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for fetch from server.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed certificate '{runnerCert.CACertificateFile}' for git fetch.");
additionalFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for fetch from server.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git fetch.");
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git fetch.");
additionalFetchArgs.Add("-c http.sslbackend=\"schannel\"");
additionalLfsFetchArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
// Prepare gitlfs url for fetch and checkout
if (gitLfsSupport)
{
@@ -502,55 +400,12 @@ namespace GitHub.Runner.Plugins.Repository.v1_0
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.extraheader=\"AUTHORIZATION: {GenerateBasicAuthHeader(executionContext, accessToken)}\"");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalSubmoduleUpdateArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for submodule update.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed CA certificate '{runnerCert.CACertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for submodule update.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.{authorityUrl}.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git submodule update.");
additionalSubmoduleUpdateArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
if (exitCode_submoduleUpdate != 0)
{
throw new InvalidOperationException($"Git submodule update failed with exit code: {exitCode_submoduleUpdate}");
}
}
if (useClientCert && !string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
executionContext.Debug("Remove git.sslkey askpass file.");
IOUtil.DeleteFile(clientCertPrivateKeyAskPassFile);
}
}
private async Task<bool> IsRepositoryOriginUrlMatch(RunnerActionPluginExecutionContext context, GitCliManager gitCommandManager, string repositoryPath, Uri expectedRepositoryOriginUrl)

View File

@@ -65,11 +65,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// Validate args.
ArgUtil.NotNull(executionContext, nameof(executionContext));
Dictionary<string, string> configModifications = new Dictionary<string, string>();
bool useSelfSignedCACert = false;
bool useClientCert = false;
string clientCertPrivateKeyAskPassFile = null;
bool acceptUntrustedCerts = false;
executionContext.Output($"Syncing repository: {repoFullName}");
Uri repositoryUrl = new Uri($"https://github.com/{repoFullName}");
if (!repositoryUrl.IsAbsoluteUri)
@@ -98,9 +93,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
}
}
var runnerCert = executionContext.GetCertConfiguration();
acceptUntrustedCerts = runnerCert?.SkipServerCertificateValidation ?? false;
executionContext.Debug($"repository url={repositoryUrl}");
executionContext.Debug($"targetPath={targetPath}");
executionContext.Debug($"sourceBranch={sourceBranch}");
@@ -110,12 +102,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
executionContext.Debug($"checkoutNestedSubmodules={checkoutNestedSubmodules}");
executionContext.Debug($"fetchDepth={fetchDepth}");
executionContext.Debug($"gitLfsSupport={gitLfsSupport}");
executionContext.Debug($"acceptUntrustedCerts={acceptUntrustedCerts}");
#if OS_WINDOWS
bool schannelSslBackend = StringUtil.ConvertToBoolean(executionContext.GetRunnerContext("gituseschannel"));
executionContext.Debug($"schannelSslBackend={schannelSslBackend}");
#endif
// Initialize git command manager with additional environment variables.
Dictionary<string, string> gitEnv = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -153,54 +139,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
// prepare askpass for client cert private key, if the repository's endpoint url match the runner config url
var systemConnection = executionContext.Endpoints.Single(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
if (runnerCert != null && Uri.Compare(repositoryUrl, systemConnection.Url, UriComponents.SchemeAndServer, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0)
{
if (!string.IsNullOrEmpty(runnerCert.CACertificateFile))
{
useSelfSignedCACert = true;
}
if (!string.IsNullOrEmpty(runnerCert.ClientCertificateFile) &&
!string.IsNullOrEmpty(runnerCert.ClientCertificatePrivateKeyFile))
{
useClientCert = true;
// prepare askpass for client cert password
if (!string.IsNullOrEmpty(runnerCert.ClientCertificatePassword))
{
clientCertPrivateKeyAskPassFile = Path.Combine(executionContext.GetRunnerContext("temp"), $"{Guid.NewGuid()}.sh");
List<string> askPass = new List<string>();
askPass.Add("#!/bin/sh");
askPass.Add($"echo \"{runnerCert.ClientCertificatePassword}\"");
File.WriteAllLines(clientCertPrivateKeyAskPassFile, askPass);
#if !OS_WINDOWS
string toolPath = WhichUtil.Which("chmod", true);
string argLine = $"775 {clientCertPrivateKeyAskPassFile}";
executionContext.Command($"chmod {argLine}");
var processInvoker = new ProcessInvoker(executionContext);
processInvoker.OutputDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
processInvoker.ErrorDataReceived += (object sender, ProcessDataReceivedEventArgs args) =>
{
if (!string.IsNullOrEmpty(args.Data))
{
executionContext.Output(args.Data);
}
};
string workingDirectory = executionContext.GetRunnerContext("workspace");
await processInvoker.ExecuteAsync(workingDirectory, toolPath, argLine, null, true, CancellationToken.None);
#endif
}
}
}
// Check the current contents of the root folder to see if there is already a repo
// If there is a repo, see if it matches the one we are expecting to be there based on the remote fetch url
@@ -355,46 +293,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
throw new InvalidOperationException($"Git config failed with exit code: {exitCode_config}");
}
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalFetchArgs.Add($"-c http.sslVerify=false");
additionalLfsFetchArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for fetch from server.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed certificate '{runnerCert.CACertificateFile}' for git fetch.");
additionalFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for fetch from server.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git fetch.");
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
additionalLfsFetchArgs.Add($"-c http.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git fetch.");
additionalFetchArgs.Add("-c http.sslbackend=\"schannel\"");
additionalLfsFetchArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
// Prepare gitlfs url for fetch and checkout
if (gitLfsSupport)
{
@@ -484,43 +382,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
List<string> additionalSubmoduleUpdateArgs = new List<string>();
// Prepare ignore ssl cert error config for fetch.
if (acceptUntrustedCerts)
{
additionalSubmoduleUpdateArgs.Add($"-c http.sslVerify=false");
}
// Prepare self-signed CA cert config for submodule update.
if (useSelfSignedCACert)
{
executionContext.Debug($"Use self-signed CA certificate '{runnerCert.CACertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcainfo=\"{runnerCert.CACertificateFile}\"");
}
// Prepare client cert config for submodule update.
if (useClientCert)
{
executionContext.Debug($"Use client certificate '{runnerCert.ClientCertificateFile}' for git submodule update.");
string authorityUrl = repositoryUrl.AbsoluteUri.Replace(repositoryUrl.PathAndQuery, string.Empty);
if (!string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\" -c http.{authorityUrl}.sslCertPasswordProtected=true -c core.askpass=\"{clientCertPrivateKeyAskPassFile}\"");
}
else
{
additionalSubmoduleUpdateArgs.Add($"-c http.{authorityUrl}.sslcert=\"{runnerCert.ClientCertificateFile}\" -c http.{authorityUrl}.sslkey=\"{runnerCert.ClientCertificatePrivateKeyFile}\"");
}
}
#if OS_WINDOWS
if (schannelSslBackend)
{
executionContext.Debug("Use SChannel SslBackend for git submodule update.");
additionalSubmoduleUpdateArgs.Add("-c http.sslbackend=\"schannel\"");
}
#endif
int exitCode_submoduleUpdate = await gitCommandManager.GitSubmoduleUpdate(executionContext, targetPath, fetchDepth, string.Join(" ", additionalSubmoduleUpdateArgs), checkoutNestedSubmodules, cancellationToken);
if (exitCode_submoduleUpdate != 0)
{
@@ -528,12 +389,6 @@ namespace GitHub.Runner.Plugins.Repository.v1_1
}
}
if (useClientCert && !string.IsNullOrEmpty(clientCertPrivateKeyAskPassFile))
{
executionContext.Debug("Remove git.sslkey askpass file.");
IOUtil.DeleteFile(clientCertPrivateKeyAskPassFile);
}
// Set intra-task variable for post job cleanup
executionContext.SetIntraActionState("repositoryPath", targetPath);
executionContext.SetIntraActionState("modifiedgitconfig", JsonUtility.ToString(configModifications.Keys));

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>

View File

@@ -83,21 +83,6 @@ namespace GitHub.Runner.Sdk
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
var certSetting = GetCertConfiguration();
if (certSetting != null)
{
if (!string.IsNullOrEmpty(certSetting.ClientCertificateArchiveFile))
{
VssClientHttpRequestSettings.Default.ClientCertificateManager = new RunnerClientCertificateManager(certSetting.ClientCertificateArchiveFile, certSetting.ClientCertificatePassword);
}
if (certSetting.SkipServerCertificateValidation)
{
VssClientHttpRequestSettings.Default.ServerCertificateValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
}
}
VssHttpMessageHandler.DefaultWebProxy = this.WebProxy;
ServiceEndpoint systemConnection = this.Endpoints.FirstOrDefault(e => string.Equals(e.Name, WellKnownServiceEndpointNames.SystemVssConnection, StringComparison.OrdinalIgnoreCase));
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
@@ -227,40 +212,6 @@ namespace GitHub.Runner.Sdk
}
}
public RunnerCertificateSettings GetCertConfiguration()
{
bool skipCertValidation = StringUtil.ConvertToBoolean(GetRunnerContext("SkipCertValidation"));
string caFile = GetRunnerContext("CAInfo");
string clientCertFile = GetRunnerContext("ClientCert");
if (!string.IsNullOrEmpty(caFile) || !string.IsNullOrEmpty(clientCertFile) || skipCertValidation)
{
var certConfig = new RunnerCertificateSettings();
certConfig.SkipServerCertificateValidation = skipCertValidation;
certConfig.CACertificateFile = caFile;
if (!string.IsNullOrEmpty(clientCertFile))
{
certConfig.ClientCertificateFile = clientCertFile;
string clientCertKey = GetRunnerContext("ClientCertKey");
string clientCertArchive = GetRunnerContext("ClientCertArchive");
string clientCertPassword = GetRunnerContext("ClientCertPassword");
certConfig.ClientCertificatePrivateKeyFile = clientCertKey;
certConfig.ClientCertificateArchiveFile = clientCertArchive;
certConfig.ClientCertificatePassword = clientCertPassword;
certConfig.VssClientCertificateManager = new RunnerClientCertificateManager(clientCertArchive, clientCertPassword);
}
return certConfig;
}
else
{
return null;
}
}
private string Escape(string input)
{
foreach (var mapping in _commandEscapeMappings)

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Library</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>

View File

@@ -1,40 +0,0 @@

using System.Security.Cryptography.X509Certificates;
using GitHub.Services.Common;
namespace GitHub.Runner.Sdk
{
public class RunnerCertificateSettings
{
public bool SkipServerCertificateValidation { get; set; }
public string CACertificateFile { get; set; }
public string ClientCertificateFile { get; set; }
public string ClientCertificatePrivateKeyFile { get; set; }
public string ClientCertificateArchiveFile { get; set; }
public string ClientCertificatePassword { get; set; }
public IVssClientCertificateManager VssClientCertificateManager { get; set; }
}
public class RunnerClientCertificateManager : IVssClientCertificateManager
{
private readonly X509Certificate2Collection _clientCertificates = new X509Certificate2Collection();
public X509Certificate2Collection ClientCertificates => _clientCertificates;
public RunnerClientCertificateManager()
{
}
public RunnerClientCertificateManager(string clientCertificateArchiveFile, string clientCertificatePassword)
{
AddClientCertificate(clientCertificateArchiveFile, clientCertificatePassword);
}
public void AddClientCertificate(string clientCertificateArchiveFile, string clientCertificatePassword)
{
if (!string.IsNullOrEmpty(clientCertificateArchiveFile))
{
_clientCertificates.Add(new X509Certificate2(clientCertificateArchiveFile, clientCertificatePassword));
}
}
}
}

View File

@@ -14,7 +14,7 @@ namespace GitHub.Runner.Sdk
{
public static class VssUtil
{
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy, IVssClientCertificateManager clientCert)
public static void InitializeVssClientSettings(ProductInfoHeaderValue additionalUserAgent, IWebProxy proxy)
{
var headerValues = new List<ProductInfoHeaderValue>();
headerValues.Add(additionalUserAgent);
@@ -26,7 +26,6 @@ namespace GitHub.Runner.Sdk
}
VssClientHttpRequestSettings.Default.UserAgent = headerValues;
VssClientHttpRequestSettings.Default.ClientCertificateManager = clientCert;
VssHttpMessageHandler.DefaultWebProxy = proxy;
}
@@ -83,7 +82,7 @@ namespace GitHub.Runner.Sdk
if (serviceEndpoint.Authorization.Scheme == EndpointAuthorizationSchemes.OAuth &&
serviceEndpoint.Authorization.Parameters.TryGetValue(EndpointAuthorizationParameters.AccessToken, out accessToken))
{
credentials = new VssCredentials(null, new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
credentials = new VssCredentials(new VssOAuthAccessTokenCredential(accessToken), CredentialPromptType.DoNotPrompt);
}
return credentials;

View File

@@ -7,7 +7,7 @@ namespace GitHub.Runner.Sdk
{
public static class WhichUtil
{
public static string Which(string command, bool require = false, ITraceWriter trace = null)
public static string Which(string command, bool require = false, ITraceWriter trace = null, string prependPath = null)
{
ArgUtil.NotNullOrEmpty(command, nameof(command));
trace?.Info($"Which: '{command}'");
@@ -17,6 +17,10 @@ namespace GitHub.Runner.Sdk
trace?.Info("PATH environment variable not defined.");
path = path ?? string.Empty;
}
if (!string.IsNullOrEmpty(prependPath))
{
path = PathUtil.PrependPath(prependPath, path);
}
string[] pathSegments = path.Split(new Char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < pathSegments.Length; i++)

View File

@@ -276,9 +276,7 @@ namespace GitHub.Runner.Worker.Container
return await ExecuteDockerCommandAsync(context, "exec", $"{options} {containerId} {command}", context.CancellationToken);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms)
public async Task<int> DockerExec(IExecutionContext context, string containerId, string options, string command, List<string> output)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
ArgUtil.NotNull(output, nameof(output));
@@ -309,9 +307,10 @@ namespace GitHub.Runner.Worker.Container
}
};
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: HostContext.GetDirectory(WellKnownDirectory.Work),
fileName: DockerPath,
@@ -320,7 +319,6 @@ namespace GitHub.Runner.Worker.Container
requireExitCodeZero: false,
outputEncoding: null,
cancellationToken: CancellationToken.None);
#endif
}
public async Task<List<string>> DockerInspect(IExecutionContext context, string dockerObject, string options)
@@ -339,9 +337,7 @@ namespace GitHub.Runner.Worker.Container
return ExecuteDockerCommandAsync(context, command, options, null, cancellationToken);
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms)
private async Task<int> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options, IDictionary<string, string> environment, EventHandler<ProcessDataReceivedEventArgs> stdoutDataReceived, EventHandler<ProcessDataReceivedEventArgs> stderrDataReceived, CancellationToken cancellationToken = default(CancellationToken))
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
string arg = $"{command} {options}".Trim();
context.Command($"{DockerPath} {arg}");
@@ -351,9 +347,10 @@ namespace GitHub.Runner.Worker.Container
processInvoker.ErrorDataReceived += stderrDataReceived;
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: context.GetGitHubContext("workspace"),
fileName: DockerPath,
@@ -363,12 +360,9 @@ namespace GitHub.Runner.Worker.Container
outputEncoding: null,
killProcessOnCancel: false,
cancellationToken: cancellationToken);
#endif
}
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously (method has async logic on only certain platforms)
private async Task<int> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options, string workingDirectory, CancellationToken cancellationToken = default(CancellationToken))
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
string arg = $"{command} {options}".Trim();
context.Command($"{DockerPath} {arg}");
@@ -384,9 +378,10 @@ namespace GitHub.Runner.Worker.Container
context.Output(message.Data);
};
#if OS_WINDOWS || OS_OSX
throw new NotSupportedException($"Container operation is only supported on Linux");
#else
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
return await processInvoker.ExecuteAsync(
workingDirectory: workingDirectory ?? context.GetGitHubContext("workspace"),
fileName: DockerPath,
@@ -397,7 +392,6 @@ namespace GitHub.Runner.Worker.Container
killProcessOnCancel: false,
redirectStandardIn: null,
cancellationToken: cancellationToken);
#endif
}
private async Task<List<string>> ExecuteDockerCommandAsync(IExecutionContext context, string command, string options)

View File

@@ -35,6 +35,10 @@ namespace GitHub.Runner.Worker
public async Task StartContainersAsync(IExecutionContext executionContext, object data)
{
Trace.Entering();
if (!Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux))
{
throw new NotSupportedException("Container operations are only supported on Linux runners");
}
ArgUtil.NotNull(executionContext, nameof(executionContext));
List<ContainerInfo> containers = data as List<ContainerInfo>;
ArgUtil.NotNull(containers, nameof(containers));
@@ -44,7 +48,7 @@ namespace GitHub.Runner.Worker
displayName: "Stop containers",
data: data);
executionContext.Debug($"Register post job cleanup for stoping/deleting containers.");
executionContext.Debug($"Register post job cleanup for stopping/deleting containers.");
executionContext.RegisterPostJobStep(nameof(StopContainersAsync), postJobStep);
// Check whether we are inside a container.
@@ -125,7 +129,7 @@ namespace GitHub.Runner.Worker
executionContext.Warning($"Delete stale container networks failed, docker network prune fail with exit code {networkPruneExitCode}");
}
// Create local docker network for this job to avoid port conflict when multiple agents run on same machine.
// Create local docker network for this job to avoid port conflict when multiple runners run on same machine.
// All containers within a job join the same network
var containerNetwork = $"github_network_{Guid.NewGuid().ToString("N")}";
await CreateContainerNetworkAsync(executionContext, containerNetwork);

View File

@@ -41,7 +41,6 @@ namespace GitHub.Runner.Worker
TaskResult? CommandResult { get; set; }
CancellationToken CancellationToken { get; }
List<ServiceEndpoint> Endpoints { get; }
List<SecureFile> SecureFiles { get; }
PlanFeatures Features { get; }
Variables Variables { get; }
@@ -136,7 +135,6 @@ namespace GitHub.Runner.Worker
public Task ForceCompleted => _forceCompleted.Task;
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
public List<ServiceEndpoint> Endpoints { get; private set; }
public List<SecureFile> SecureFiles { get; private set; }
public Variables Variables { get; private set; }
public Dictionary<string, string> IntraActionState { get; private set; }
public HashSet<string> OutputVariables => _outputvariables;
@@ -257,7 +255,6 @@ namespace GitHub.Runner.Worker
child.Features = Features;
child.Variables = Variables;
child.Endpoints = Endpoints;
child.SecureFiles = SecureFiles;
if (intraActionState == null)
{
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -549,9 +546,6 @@ namespace GitHub.Runner.Worker
// Endpoints
Endpoints = message.Resources.Endpoints;
// SecureFiles
SecureFiles = message.Resources.SecureFiles;
// Variables
Variables = new Variables(HostContext, message.Variables);
@@ -616,44 +610,6 @@ namespace GitHub.Runner.Worker
// PostJobSteps for job ExecutionContext
PostJobSteps = new Stack<IStep>();
// // Certificate variables
// var agentCert = HostContext.GetService<IRunnerCertificateManager>();
// if (agentCert.SkipServerCertificateValidation)
// {
// SetRunnerContext("sslskipcertvalidation", bool.TrueString);
// }
// if (!string.IsNullOrEmpty(agentCert.CACertificateFile))
// {
// SetRunnerContext("sslcainfo", agentCert.CACertificateFile);
// }
// if (!string.IsNullOrEmpty(agentCert.ClientCertificateFile) &&
// !string.IsNullOrEmpty(agentCert.ClientCertificatePrivateKeyFile) &&
// !string.IsNullOrEmpty(agentCert.ClientCertificateArchiveFile))
// {
// SetRunnerContext("clientcertfile", agentCert.ClientCertificateFile);
// SetRunnerContext("clientcertprivatekey", agentCert.ClientCertificatePrivateKeyFile);
// SetRunnerContext("clientcertarchive", agentCert.ClientCertificateArchiveFile);
// if (!string.IsNullOrEmpty(agentCert.ClientCertificatePassword))
// {
// HostContext.SecretMasker.AddValue(agentCert.ClientCertificatePassword);
// SetRunnerContext("clientcertpassword", agentCert.ClientCertificatePassword);
// }
// }
// // Runtime option variables
// var runtimeOptions = HostContext.GetService<IConfigurationStore>().GetRunnerRuntimeOptions();
// if (runtimeOptions != null)
// {
// #if OS_WINDOWS
// if (runtimeOptions.GitUseSecureChannel)
// {
// SetRunnerContext("gituseschannel", runtimeOptions.GitUseSecureChannel.ToString());
// }
// #endif
// }
// Job timeline record.
InitializeTimelineRecord(

View File

@@ -16,6 +16,8 @@ namespace GitHub.Runner.Worker
"head_ref",
"ref",
"repository",
"run_id",
"run_number",
"sha",
"workflow",
"workspace",

View File

@@ -62,7 +62,7 @@ namespace GitHub.Runner.Worker.Handlers
}
else if (data.ExecutionType == ActionExecutionType.Plugin)
{
// Agent plugin
// Runner plugin
handler = HostContext.CreateService<IRunnerPluginHandler>();
(handler as IRunnerPluginHandler).Data = data as PluginActionExecutionData;
}

View File

@@ -2,6 +2,7 @@
using System.IO;
using System.Text;
using System.Threading.Tasks;
using System.Linq;
using GitHub.DistributedTask.Pipelines.ContextData;
using GitHub.Runner.Common;
using GitHub.Runner.Sdk;
@@ -56,6 +57,7 @@ namespace GitHub.Runner.Worker.Handlers
string shellCommand;
string shellCommandPath = null;
bool validateShellOnHost = !(StepHost is ContainerStepHost);
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
Inputs.TryGetValue("shell", out var shell);
if (string.IsNullOrEmpty(shell))
{
@@ -63,19 +65,19 @@ namespace GitHub.Runner.Worker.Handlers
shellCommand = "pwsh";
if(validateShellOnHost)
{
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace);
shellCommandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
if (string.IsNullOrEmpty(shellCommandPath))
{
shellCommand = "powershell";
Trace.Info($"Defaulting to {shellCommand}");
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace);
shellCommandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
}
}
#else
shellCommand = "sh";
if (validateShellOnHost)
{
shellCommandPath = WhichUtil.Which("bash") ?? WhichUtil.Which("sh", true, Trace);
shellCommandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
}
#endif
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
@@ -86,7 +88,7 @@ namespace GitHub.Runner.Worker.Handlers
shellCommand = parsed.shellCommand;
if (validateShellOnHost)
{
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace);
shellCommandPath = WhichUtil.Which(parsed.shellCommand, true, Trace, prependPath);
}
argFormat = $"{parsed.shellArgs}".TrimStart();
@@ -144,23 +146,24 @@ namespace GitHub.Runner.Worker.Handlers
Inputs.TryGetValue("shell", out var shell);
var isContainerStepHost = StepHost is ContainerStepHost;
string prependPath = string.Join(Path.PathSeparator.ToString(), ExecutionContext.PrependPath.Reverse<string>());
string commandPath, argFormat, shellCommand;
// Set up default command and arguments
if (string.IsNullOrEmpty(shell))
{
#if OS_WINDOWS
shellCommand = "pwsh";
commandPath = WhichUtil.Which(shellCommand, require: false, Trace);
commandPath = WhichUtil.Which(shellCommand, require: false, Trace, prependPath);
if (string.IsNullOrEmpty(commandPath))
{
shellCommand = "powershell";
Trace.Info($"Defaulting to {shellCommand}");
commandPath = WhichUtil.Which(shellCommand, require: true, Trace);
commandPath = WhichUtil.Which(shellCommand, require: true, Trace, prependPath);
}
ArgUtil.NotNullOrEmpty(commandPath, "Default Shell");
#else
shellCommand = "sh";
commandPath = WhichUtil.Which("bash", false, Trace) ?? WhichUtil.Which("sh", true, Trace);
commandPath = WhichUtil.Which("bash", false, Trace, prependPath) ?? WhichUtil.Which("sh", true, Trace, prependPath);
#endif
argFormat = ScriptHandlerHelpers.GetScriptArgumentsFormat(shellCommand);
}
@@ -169,7 +172,7 @@ namespace GitHub.Runner.Worker.Handlers
var parsed = ScriptHandlerHelpers.ParseShellOptionString(shell);
shellCommand = parsed.shellCommand;
// For non-ContainerStepHost, the command must be located on the host by Which
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace);
commandPath = WhichUtil.Which(parsed.shellCommand, !isContainerStepHost, Trace, prependPath);
argFormat = $"{parsed.shellArgs}".TrimStart();
if (string.IsNullOrEmpty(argFormat))
{

View File

@@ -141,6 +141,13 @@ namespace GitHub.Runner.Worker.Handlers
executionContext.Debug(line);
if (line.ToLower().Contains("alpine"))
{
if (!Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.X64))
{
var os = Constants.Runner.Platform.ToString();
var arch = Constants.Runner.PlatformArchitecture.ToString();
var msg = $"JavaScript Actions in Alpine containers are only supported on x64 Linux runners. Detected {os} {arch}";
throw new NotSupportedException(msg);
}
nodeExternal = "node12_alpine";
executionContext.Output($"Container distribution is alpine. Running JavaScript Action with external tool: {nodeExternal}");
return nodeExternal;

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
<TargetFramework>netcoreapp3.1</TargetFramework>
<OutputType>Exe</OutputType>
<RuntimeIdentifiers>win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64</RuntimeIdentifiers>
<TargetLatestRuntimePatch>true</TargetLatestRuntimePatch>

View File

@@ -40,8 +40,7 @@ namespace GitHub.Runner.Worker
// Validate args.
ArgUtil.NotNullOrEmpty(pipeIn, nameof(pipeIn));
ArgUtil.NotNullOrEmpty(pipeOut, nameof(pipeOut));
var runnerCertManager = HostContext.GetService<IRunnerCertificateManager>();
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy, runnerCertManager.VssClientCertificateManager);
VssUtil.InitializeVssClientSettings(HostContext.UserAgent, HostContext.WebProxy);
var jobRunner = HostContext.CreateService<IJobRunner>();
using (var channel = HostContext.CreateService<IProcessChannel>())
@@ -178,15 +177,6 @@ namespace GitHub.Runner.Worker
}
}
}
// Add masks for secure file download tickets
foreach (SecureFile file in message.Resources.SecureFiles ?? new List<SecureFile>())
{
if (!string.IsNullOrEmpty(file.Ticket))
{
HostContext.SecretMasker.AddValue(file.Ticket);
}
}
}
private void SetCulture(Pipelines.AgentJobRequestMessage message)

View File

@@ -1,264 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
internal static class CookieUtility
{
public static readonly String AcsMetadataRetrievalExceptionText = "Unable to retrieve ACS Metadata from '{0}'";
public static readonly String FedAuthCookieName = "FedAuth";
public static readonly String WindowsLiveSignOutUrl = "https://login.live.com/uilogout.srf";
public static readonly Uri WindowsLiveCookieDomain = new Uri("https://login.live.com/");
public static CookieCollection GetFederatedCookies(Uri cookieDomainAndPath)
{
CookieCollection result = null;
Cookie cookie = GetCookieEx(cookieDomainAndPath, FedAuthCookieName).FirstOrDefault();
if (cookie != null)
{
result = new CookieCollection();
result.Add(cookie);
for (Int32 x = 1; x < 50; x++)
{
String cookieName = FedAuthCookieName + x;
cookie = GetCookieEx(cookieDomainAndPath, cookieName).FirstOrDefault();
if (cookie != null)
{
result.Add(cookie);
}
else
{
break;
}
}
}
return result;
}
public static CookieCollection GetFederatedCookies(String[] token)
{
CookieCollection result = null;
if (token != null && token.Length > 0 && token[0] != null)
{
result = new CookieCollection();
result.Add(new Cookie(FedAuthCookieName, token[0]));
for (Int32 x = 1; x < token.Length; x++)
{
String cookieName = FedAuthCookieName + x;
if (token[x] != null)
{
Cookie cookie = new Cookie(cookieName, token[x]);
cookie.HttpOnly = true;
result.Add(cookie);
}
else
{
break;
}
}
}
return result;
}
public static CookieCollection GetFederatedCookies(IHttpResponse webResponse)
{
CookieCollection result = null;
IEnumerable<String> cookies = null;
if (webResponse.Headers.TryGetValues("Set-Cookie", out cookies))
{
foreach (String cookie in cookies)
{
if (cookie != null && cookie.StartsWith(CookieUtility.FedAuthCookieName, StringComparison.OrdinalIgnoreCase))
{
// Only take the security token field of the cookie, and discard the rest
String fedAuthToken = cookie.Split(';').FirstOrDefault();
Int32 index = fedAuthToken.IndexOf('=');
if (index > 0 && index < fedAuthToken.Length - 1)
{
String name = fedAuthToken.Substring(0, index);
String value = fedAuthToken.Substring(index + 1);
result = result ?? new CookieCollection();
result.Add(new Cookie(name, value));
}
}
}
}
return result;
}
public static CookieCollection GetAllCookies(Uri cookieDomainAndPath)
{
CookieCollection result = null;
List<Cookie> cookies = GetCookieEx(cookieDomainAndPath, null);
foreach (Cookie cookie in cookies)
{
if (result == null)
{
result = new CookieCollection();
}
result.Add(cookie);
}
return result;
}
public static void DeleteFederatedCookies(Uri cookieDomainAndPath)
{
CookieCollection cookies = GetFederatedCookies(cookieDomainAndPath);
if (cookies != null)
{
foreach (Cookie cookie in cookies)
{
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
}
}
}
public static void DeleteWindowsLiveCookies()
{
DeleteAllCookies(WindowsLiveCookieDomain);
}
public static void DeleteAllCookies(Uri cookieDomainAndPath)
{
CookieCollection cookies = GetAllCookies(cookieDomainAndPath);
if (cookies != null)
{
foreach (Cookie cookie in cookies)
{
DeleteCookieEx(cookieDomainAndPath, cookie.Name);
}
}
}
public const UInt32 INTERNET_COOKIE_HTTPONLY = 0x00002000;
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool InternetGetCookieEx(
String url, String cookieName, StringBuilder cookieData, ref Int32 size, UInt32 flags, IntPtr reserved);
[DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)]
static extern bool InternetSetCookieEx(
String url, String cookieName, String cookieData, UInt32 flags, IntPtr reserved);
public static Boolean DeleteCookieEx(Uri cookiePath, String cookieName)
{
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
DateTime expiration = DateTime.UtcNow.AddYears(-1);
String cookieData = String.Format(CultureInfo.InvariantCulture, "{0}=0;expires={1};path=/;domain={2};httponly", cookieName, expiration.ToString("R"), cookiePath.Host);
return InternetSetCookieEx(path, null, cookieData, flags, IntPtr.Zero);
}
public static Boolean SetCookiesEx(
Uri cookiePath,
CookieCollection cookies)
{
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
Boolean successful = true;
foreach (Cookie cookie in cookies)
{
// This means it doesn't expire
if (cookie.Expires.Year == 1)
{
continue;
}
String cookieData = String.Format(CultureInfo.InvariantCulture,
"{0}; path={1}; domain={2}; expires={3}; httponly",
cookie.Value,
cookie.Path,
cookie.Domain,
cookie.Expires.ToString("ddd, dd-MMM-yyyy HH:mm:ss 'GMT'"));
successful &= InternetSetCookieEx(path, cookie.Name, cookieData, INTERNET_COOKIE_HTTPONLY, IntPtr.Zero);
}
return successful;
}
public static List<Cookie> GetCookieEx(Uri cookiePath, String cookieName)
{
UInt32 flags = INTERNET_COOKIE_HTTPONLY;
List<Cookie> cookies = new List<Cookie>();
Int32 size = 256;
StringBuilder cookieData = new StringBuilder(size);
String path = cookiePath.ToString();
if (!path.EndsWith("/", StringComparison.Ordinal))
{
path = path + "/";
}
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
{
if (size < 0)
{
return cookies;
}
cookieData = new StringBuilder(size);
if (!InternetGetCookieEx(path, cookieName, cookieData, ref size, flags, IntPtr.Zero))
{
return cookies;
}
}
if (cookieData.Length > 0)
{
String[] cookieSections = cookieData.ToString().Split(new char[] { ';' });
foreach (String cookieSection in cookieSections)
{
String[] cookieParts = cookieSection.Split(new char[] { '=' }, 2);
if (cookieParts.Length == 2)
{
Cookie cookie = new Cookie();
cookie.Name = cookieParts[0].TrimStart();
cookie.Value = cookieParts[1];
cookie.HttpOnly = true;
cookies.Add(cookie);
}
}
}
return cookies;
}
}
}

View File

@@ -1,95 +0,0 @@
using System;
using System.Net.Http;
using System.Security;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
/// <summary>
/// Currently it is impossible to get whether prompting is allowed from the credential itself without reproducing the logic
/// used by VssClientCredentials. Since this is a stop gap solution to get Windows integrated authentication to work against
/// AAD via ADFS for now this class will only support that one, non-interactive flow. We need to assess how much we want to
/// invest in this legacy stack rather than recommending people move to the VssConnect API for future authentication needs.
/// </summary>
[Serializable]
public sealed class VssAadCredential : FederatedCredential
{
private string username;
private SecureString password;
public VssAadCredential()
: base(null)
{
}
public VssAadCredential(VssAadToken initialToken)
: base(initialToken)
{
}
public VssAadCredential(string username)
: base(null)
{
this.username = username;
}
public VssAadCredential(string username, string password)
: base(null)
{
this.username = username;
if (password != null)
{
this.password = new SecureString();
foreach (char character in password)
{
this.password.AppendChar(character);
}
}
}
public VssAadCredential(string username, SecureString password)
: base(null)
{
this.username = username;
this.password = password;
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Aad;
}
}
internal string Username
{
get
{
return username;
}
}
internal SecureString Password => password;
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
bool isNonAuthenticationChallenge = false;
return VssFederatedCredential.IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? false;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
if (response == null && base.InitialToken == null)
{
return null;
}
return new VssAadTokenProvider(this);
}
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.Diagnostics;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Internal;
namespace GitHub.Services.Client
{
internal static class VssAadSettings
{
public const string DefaultAadInstance = "https://login.microsoftonline.com/";
public const string CommonTenant = "common";
// VSTS service principal.
public const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
// Visual Studio IDE client ID originally provisioned by Azure Tools.
public const string Client = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
// AAD Production Application tenant.
private const string ApplicationTenantId = "f8cdef31-a31e-4b4a-93e4-5f571e91255a";
#if !NETSTANDARD
public static Uri NativeClientRedirectUri
{
get
{
Uri nativeClientRedirect = null;
try
{
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientRedirect);
if (!string.IsNullOrEmpty(nativeRedirect))
{
Uri.TryCreate(nativeRedirect, UriKind.RelativeOrAbsolute, out nativeClientRedirect);
}
}
catch (Exception e)
{
Debug.WriteLine(string.Format("NativeClientRedirectUri: {0}", e));
}
return nativeClientRedirect ?? new Uri("urn:ietf:wg:oauth:2.0:oob");
}
}
public static string ClientId
{
get
{
string nativeRedirect = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadNativeClientIdentifier);
return nativeRedirect ?? VssAadSettings.Client;
}
}
#endif
public static string AadInstance
{
get
{
#if !NETSTANDARD
string aadInstance = VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadInstance);
#else
string aadInstance = null;
#endif
if (string.IsNullOrWhiteSpace(aadInstance))
{
aadInstance = DefaultAadInstance;
}
else if (!aadInstance.EndsWith("/"))
{
aadInstance = aadInstance + "/";
}
return aadInstance;
}
}
#if !NETSTANDARD
/// <summary>
/// Application tenant either from a registry override or a constant
/// </summary>
public static string ApplicationTenant =>
VssClientEnvironment.GetSharedConnectedUserValue<string>(VssConnectionParameterOverrideKeys.AadApplicationTenant)
?? VssAadSettings.ApplicationTenantId;
#endif
}
}

View File

@@ -1,124 +0,0 @@
using System;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
[Serializable]
public class VssAadToken : IssuedToken
{
private string accessToken;
private string accessTokenType;
private AuthenticationContext authenticationContext;
private UserCredential userCredential;
private VssAadTokenOptions options;
public VssAadToken(AuthenticationResult authentication)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
if (!string.IsNullOrWhiteSpace(authentication.AccessToken))
{
this.Authenticated();
}
this.accessToken = authentication.AccessToken;
this.accessTokenType = authentication.AccessTokenType;
}
public VssAadToken(
string accessTokenType,
string accessToken)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
if (!string.IsNullOrWhiteSpace(accessToken) && !string.IsNullOrWhiteSpace(accessTokenType))
{
this.Authenticated();
}
this.accessToken = accessToken;
this.accessTokenType = accessTokenType;
}
public VssAadToken(
AuthenticationContext authenticationContext,
UserCredential userCredential = null,
VssAadTokenOptions options = VssAadTokenOptions.None)
{
// Prevent any attempt to store this token.
this.FromStorage = true;
this.authenticationContext = authenticationContext;
this.userCredential = userCredential;
this.options = options;
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Aad;
}
}
public AuthenticationResult AcquireToken()
{
if (this.authenticationContext == null)
{
return null;
}
AuthenticationResult authenticationResult = null;
for (int index = 0; index < 3; index++)
{
try
{
if (this.userCredential == null && !options.HasFlag(VssAadTokenOptions.AllowDialog))
{
authenticationResult = authenticationContext.AcquireTokenSilentAsync(VssAadSettings.Resource, VssAadSettings.Client).ConfigureAwait(false).GetAwaiter().GetResult();
}
else
{
authenticationResult = authenticationContext.AcquireTokenAsync(VssAadSettings.Resource, VssAadSettings.Client, this.userCredential).ConfigureAwait(false).GetAwaiter().GetResult();
}
if (authenticationResult != null)
{
break;
}
}
catch (Exception x)
{
System.Diagnostics.Debug.WriteLine("Failed to get ADFS token: " + x.ToString());
}
}
return authenticationResult;
}
internal override void ApplyTo(IHttpRequest request)
{
AuthenticationResult authenticationResult = AcquireToken();
if (authenticationResult != null)
{
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{authenticationResult.AccessTokenType} {authenticationResult.AccessToken}");
}
else if (!string.IsNullOrEmpty(this.accessTokenType) && !string.IsNullOrEmpty(this.accessToken))
{
request.Headers.SetValue(Common.Internal.HttpHeaders.Authorization, $"{this.accessTokenType} {this.accessToken}");
}
}
}
[Flags]
public enum VssAadTokenOptions
{
None = 0,
AllowDialog = 1
}
}

View File

@@ -1,77 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
internal sealed class VssAadTokenProvider : IssuedTokenProvider
{
public VssAadTokenProvider(VssAadCredential credential)
: base(credential, null, null)
{
}
public override bool GetTokenIsInteractive
{
get
{
return false;
}
}
private VssAadToken GetVssAadToken()
{
AuthenticationContext authenticationContext = new AuthenticationContext(string.Concat(VssAadSettings.AadInstance, VssAadSettings.CommonTenant));
UserCredential userCredential = null;
VssAadCredential credential = this.Credential as VssAadCredential;
if (credential?.Username != null)
{
#if NETSTANDARD
// UserPasswordCredential does not currently exist for ADAL 3.13.5 for any non-desktop build.
userCredential = new UserCredential(credential.Username);
#else
if (credential.Password != null)
{
userCredential = new UserPasswordCredential(credential.Username, credential.Password);
}
else
{
userCredential = new UserCredential(credential.Username);
}
#endif
}
else
{
userCredential = new UserCredential();
}
return new VssAadToken(authenticationContext, userCredential);
}
/// <summary>
/// Temporary implementation since we don't have a good configuration story here at the moment.
/// </summary>
protected override Task<IssuedToken> OnGetTokenAsync(IssuedToken failedToken, CancellationToken cancellationToken)
{
// If we have already tried to authenticate with an AAD token retrieved from Windows integrated authentication and it is not working, clear out state.
if (failedToken != null && failedToken.CredentialType == VssCredentialsType.Aad && failedToken.IsAuthenticated)
{
this.CurrentToken = null;
return Task.FromResult<IssuedToken>(null);
}
try
{
return Task.FromResult<IssuedToken>(GetVssAadToken());
}
catch
{ }
return Task.FromResult<IssuedToken>(null);
}
}
}

View File

@@ -1,172 +0,0 @@
using System;
using System.Linq;
using System.Net;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides federated authentication with a hosted <c>VssConnection</c> instance using cookies.
/// </summary>
[Serializable]
public sealed class VssFederatedCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
public VssFederatedCredential()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
public VssFederatedCredential(Boolean useCache)
: this(useCache, null)
{
}
/// <summary>
/// Initializes a new <c>VssFederatedCredential</c> instance.
/// </summary>
/// <param name="initialToken">The initial token if available</param>
public VssFederatedCredential(VssFederatedToken initialToken)
: this(false, initialToken)
{
}
public VssFederatedCredential(
Boolean useCache,
VssFederatedToken initialToken)
: base(initialToken)
{
#if !NETSTANDARD
if (useCache)
{
Storage = new VssClientCredentialStorage();
}
#endif
}
/// <summary>
///
/// </summary>
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Federated;
}
}
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
bool isNonAuthenticationChallenge = false;
return IsVssFederatedAuthenticationChallenge(webResponse, out isNonAuthenticationChallenge) ?? isNonAuthenticationChallenge;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// The response is only null when attempting to determine the most appropriate token provider to
// use for the connection. The only way we should do anything here is if we have an initial token
// since that means we can present something without making a server call.
if (response == null && base.InitialToken == null)
{
return null;
}
Uri signInUrl = null;
String realm = String.Empty;
String issuer = String.Empty;
if (response != null)
{
var location = response.Headers.GetValues(HttpHeaders.Location).FirstOrDefault();
if (location == null)
{
location = response.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).FirstOrDefault();
}
if (!String.IsNullOrEmpty(location))
{
signInUrl = new Uri(location);
}
// Inform the server that we support the javascript notify "smart client" pattern for ACS auth
AddParameter(ref signInUrl, "protocol", "javascriptnotify");
// Do not automatically sign in with existing FedAuth cookie
AddParameter(ref signInUrl, "force", "1");
GetRealmAndIssuer(response, out realm, out issuer);
}
return new VssFederatedTokenProvider(this, serverUrl, signInUrl, issuer, realm);
}
internal static void GetRealmAndIssuer(
IHttpResponse response,
out String realm,
out String issuer)
{
realm = response.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
issuer = response.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
if (!String.IsNullOrWhiteSpace(issuer))
{
issuer = new Uri(issuer).GetLeftPart(UriPartial.Authority);
}
}
internal static Boolean? IsVssFederatedAuthenticationChallenge(
IHttpResponse webResponse,
out Boolean isNonAuthenticationChallenge)
{
isNonAuthenticationChallenge = false;
if (webResponse == null)
{
return false;
}
// Check to make sure that the redirect was issued from the Tfs service. We include the TfsServiceError
// header to avoid the possibility that a redirect from a non-tfs service is issued and we incorrectly
// launch the credentials UI.
if (webResponse.StatusCode == HttpStatusCode.Found ||
webResponse.StatusCode == HttpStatusCode.Redirect)
{
return webResponse.Headers.GetValues(HttpHeaders.Location).Any() && webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).Any();
}
else if (webResponse.StatusCode == HttpStatusCode.Unauthorized)
{
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
}
else if (webResponse.StatusCode == HttpStatusCode.Forbidden)
{
// This is not strictly an "authentication challenge" but it is a state the user can do something about so they can get access to the resource
// they are attempting to access. Specifically, the user will hit this when they need to update or create a profile required by business policy.
isNonAuthenticationChallenge = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRedirect).Any();
if (isNonAuthenticationChallenge)
{
return null;
}
}
return false;
}
private static void AddParameter(ref Uri uri, String name, String value)
{
if (uri.Query.IndexOf(String.Concat(name, "="), StringComparison.OrdinalIgnoreCase) < 0)
{
UriBuilder builder = new UriBuilder(uri);
builder.Query = String.Concat(builder.Query.TrimStart('?'), "&", name, "=", value);
uri = builder.Uri;
}
}
}
}

View File

@@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Net;
using GitHub.Services.Common;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides a cookie-based authentication token.
/// </summary>
[Serializable]
public sealed class VssFederatedToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>VssFederatedToken</c> instance using the specified cookies.
/// </summary>
/// <param name="cookies"></param>
public VssFederatedToken(CookieCollection cookies)
{
ArgumentUtility.CheckForNull(cookies, "cookies");
m_cookies = cookies;
}
/// <summary>
/// Returns the CookieCollection contained within this token. For internal use only.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public CookieCollection CookieCollection
{
get
{
return m_cookies;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Federated;
}
}
internal override void ApplyTo(IHttpRequest request)
{
// From http://www.ietf.org/rfc/rfc2109.txt:
// Note: For backward compatibility, the separator in the Cookie header
// is semi-colon (;) everywhere.
//
// HttpRequestHeaders uses comma as the default separator, so instead of returning
// a list of cookies, the method returns one semicolon separated string.
IEnumerable<String> values = request.Headers.GetValues(s_cookieHeader);
request.Headers.SetValue(s_cookieHeader, GetHeaderValue(values));
}
private String GetHeaderValue(IEnumerable<String> cookieHeaders)
{
List<String> currentCookies = new List<String>();
if (cookieHeaders != null)
{
foreach (String value in cookieHeaders)
{
currentCookies.AddRange(value.Split(';').Select(x => x.Trim()));
}
}
currentCookies.RemoveAll(x => String.IsNullOrEmpty(x));
foreach (Cookie cookie in m_cookies)
{
// Remove all existing cookies that match the name of the cookie we are going to add.
currentCookies.RemoveAll(x => String.Equals(x.Substring(0, x.IndexOf('=')), cookie.Name, StringComparison.OrdinalIgnoreCase));
currentCookies.Add(String.Concat(cookie.Name, "=", cookie.Value));
}
return String.Join("; ", currentCookies);
}
private CookieCollection m_cookies;
private static readonly String s_cookieHeader = HttpRequestHeader.Cookie.ToString();
}
}

View File

@@ -1,157 +0,0 @@
using System;
using System.Net;
using System.Net.Http;
using GitHub.Services.Common;
using System.Globalization;
namespace GitHub.Services.Client
{
/// <summary>
/// Provides authentication for internet identities using single-sign-on cookies.
/// </summary>
internal sealed class VssFederatedTokenProvider : IssuedTokenProvider, ISupportSignOut
{
public VssFederatedTokenProvider(
VssFederatedCredential credential,
Uri serverUrl,
Uri signInUrl,
String issuer,
String realm)
: base(credential, serverUrl, signInUrl)
{
Issuer = issuer;
Realm = realm;
}
protected override String AuthenticationScheme
{
get
{
return "TFS-Federated";
}
}
protected override String AuthenticationParameter
{
get
{
if (String.IsNullOrEmpty(this.Issuer) && String.IsNullOrEmpty(this.Realm))
{
return String.Empty;
}
else
{
return String.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.Issuer, this.Realm);
}
}
}
/// <summary>
/// Gets the federated credential from which this provider was created.
/// </summary>
public new VssFederatedCredential Credential
{
get
{
return (VssFederatedCredential)base.Credential;
}
}
/// <summary>
/// Gets a value indicating whether or not a call to get token will require interactivity.
/// </summary>
public override Boolean GetTokenIsInteractive
{
get
{
return this.CurrentToken == null;
}
}
/// <summary>
/// Gets the issuer for the token provider.
/// </summary>
public String Issuer
{
get;
private set;
}
/// <summary>
/// Gets the realm for the token provider.
/// </summary>
public String Realm
{
get;
private set;
}
protected internal override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (!base.IsAuthenticationChallenge(webResponse))
{
return false;
}
// This means we were proactively constructed without any connection information. In this case
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
// to retrieve a new token.
if (this.SignInUrl == null)
{
return false;
}
String realm, issuer;
VssFederatedCredential.GetRealmAndIssuer(webResponse, out realm, out issuer);
return this.Realm.Equals(realm, StringComparison.OrdinalIgnoreCase) &&
this.Issuer.Equals(issuer, StringComparison.OrdinalIgnoreCase);
}
protected override IssuedToken OnValidatingToken(
IssuedToken token,
IHttpResponse webResponse)
{
// If the response has Set-Cookie headers, attempt to retrieve the FedAuth cookie from the response
// and replace the current token with the new FedAuth cookie. Note that the server only reissues the
// FedAuth cookie if it is issued for more than an hour.
CookieCollection fedAuthCookies = CookieUtility.GetFederatedCookies(webResponse);
if (fedAuthCookies != null)
{
// The reissued token should have the same user information as the previous one.
VssFederatedToken federatedToken = new VssFederatedToken(fedAuthCookies)
{
Properties = token.Properties,
UserId = token.UserId,
UserName = token.UserName
};
token = federatedToken;
}
return token;
}
public void SignOut(Uri signOutUrl, Uri replyToUrl, String identityProvider)
{
// The preferred implementation is to follow the signOutUrl with a browser and kill the browser whenever it
// arrives at the replyToUrl (or if it bombs out somewhere along the way).
// This will work for all Web-based identity providers (Live, Google, Yahoo, Facebook) supported by ACS provided that
// the TFS server has registered sign-out urls (in the TF Registry) for each of these.
// This is the long-term approach that should be pursued and probably the approach recommended to other
// clients which don't have direct access to the cookie store (TEE?)
// In the short term we are simply going to delete the TFS cookies and the Windows Live cookies that are exposed to this
// session. This has the drawback of not properly signing out of Live (you'd still be signed in to e.g. Hotmail, Xbox, MSN, etc.)
// but will allow the user to re-enter their live credentials and sign-in again to TFS.
// The other drawback is that the clients will have to be updated again when we pursue the implementation outlined above.
CookieUtility.DeleteFederatedCookies(replyToUrl);
if (!String.IsNullOrEmpty(identityProvider) && identityProvider.Equals("Windows Live ID", StringComparison.OrdinalIgnoreCase))
{
CookieUtility.DeleteWindowsLiveCookies();
}
}
}
}

View File

@@ -1,92 +0,0 @@
using System;
using System.Linq;
using System.Net;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a credential for basic authentication against a Visual Studio Service.
/// </summary>
public sealed class VssBasicCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with no token specified.
/// </summary>
public VssBasicCredential()
: this((VssBasicToken)null)
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified user name and password.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
public VssBasicCredential(
string userName,
string password)
: this(new VssBasicToken(new NetworkCredential(userName, password)))
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssBasicCredential(ICredentials initialToken)
: this(new VssBasicToken(initialToken))
{
}
/// <summary>
/// Initializes a new <c>VssBasicCredential</c> instance with the specified token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssBasicCredential(VssBasicToken initialToken)
: base(initialToken)
{
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Basic;
}
}
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode != HttpStatusCode.Found &&
webResponse.StatusCode != HttpStatusCode.Redirect &&
webResponse.StatusCode != HttpStatusCode.Unauthorized)
{
return false;
}
return webResponse.Headers.GetValues(HttpHeaders.WwwAuthenticate).Any(x => x.StartsWith("Basic", StringComparison.OrdinalIgnoreCase));
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
if (serverUrl.Scheme != "https")
{
String unsafeBasicAuthEnv = Environment.GetEnvironmentVariable("VSS_ALLOW_UNSAFE_BASICAUTH") ?? "false";
if (!Boolean.TryParse(unsafeBasicAuthEnv, out Boolean unsafeBasicAuth) || !unsafeBasicAuth)
{
throw new InvalidOperationException(CommonResources.BasicAuthenticationRequiresSsl());
}
}
return new BasicAuthTokenProvider(this, serverUrl);
}
}
}

View File

@@ -1,63 +0,0 @@
using System;
using System.Globalization;
using System.Net;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a token for basic authentication of internet identities.
/// </summary>
public sealed class VssBasicToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>BasicAuthToken</c> instance with the specified token value.
/// </summary>
/// <param name="credentials">The credentials which should be used for authentication</param>
public VssBasicToken(ICredentials credentials)
{
m_credentials = credentials;
}
internal ICredentials Credentials
{
get
{
return m_credentials;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Basic;
}
}
internal override void ApplyTo(IHttpRequest request)
{
var basicCredential = m_credentials.GetCredential(request.RequestUri, "Basic");
if (basicCredential != null)
{
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "Basic " + FormatBasicAuthHeader(basicCredential));
}
}
private static String FormatBasicAuthHeader(NetworkCredential credential)
{
String authHeader = String.Empty;
if (!String.IsNullOrEmpty(credential.Domain))
{
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}\\{1}:{2}", credential.Domain, credential.UserName, credential.Password);
}
else
{
authHeader = String.Format(CultureInfo.InvariantCulture, "{0}:{1}", credential.UserName, credential.Password);
}
return Convert.ToBase64String(VssHttpRequestSettings.Encoding.GetBytes(authHeader));
}
private readonly ICredentials m_credentials;
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Net;
namespace GitHub.Services.Common
{
internal sealed class BasicAuthTokenProvider : IssuedTokenProvider
{
public BasicAuthTokenProvider(
VssBasicCredential credential,
Uri serverUrl)
: base(credential, serverUrl, serverUrl)
{
}
protected override String AuthenticationScheme
{
get
{
return "Basic";
}
}
public new VssBasicCredential Credential
{
get
{
return (VssBasicCredential)base.Credential;
}
}
public override Boolean GetTokenIsInteractive
{
get
{
return base.CurrentToken == null;
}
}
}
}

View File

@@ -51,47 +51,7 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with default credentials.
/// </summary>
public VssCredentials()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with default credentials if specified.
/// </summary>
/// <param name="useDefaultCredentials">True to use default windows credentials; otherwise, false</param>
public VssCredentials(bool useDefaultCredentials)
: this(new WindowsCredential(useDefaultCredentials))
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
public VssCredentials(WindowsCredential windowsCredential)
: this(windowsCredential, null)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified windows credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
public VssCredentials(
WindowsCredential windowsCredential,
CredentialPromptType promptType)
: this(windowsCredential, null, promptType)
{
}
/// <summary>
/// Initializes a new <c>VssCredentials</c> instance with the specified issued token credential and
/// default windows credential.
/// </summary>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
public VssCredentials(FederatedCredential federatedCredential)
: this(new WindowsCredential(), federatedCredential)
: this(null)
{
}
@@ -99,12 +59,9 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential)
: this(windowsCredential, federatedCredential, EnvironmentUserInteractive
public VssCredentials(FederatedCredential federatedCredential)
: this(federatedCredential, EnvironmentUserInteractive
? CredentialPromptType.PromptIfNeeded : CredentialPromptType.DoNotPrompt)
{
}
@@ -113,14 +70,12 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed, otherwise CredentialProptType.DoNotPrompt</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType)
: this(windowsCredential, federatedCredential, promptType, null)
: this(federatedCredential, promptType, null)
{
}
@@ -128,16 +83,14 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType,
TaskScheduler scheduler)
: this(windowsCredential, federatedCredential, promptType, scheduler, null)
: this(federatedCredential, promptType, scheduler, null)
{
}
@@ -145,13 +98,11 @@ namespace GitHub.Services.Common
/// Initializes a new <c>VssCredentials</c> instance with the specified windows and issued token
/// credential.
/// </summary>
/// <param name="windowsCredential">The windows credential to use for authentication</param>
/// <param name="federatedCredential">The federated credential to use for authentication</param>
/// <param name="promptType">CredentialPromptType.PromptIfNeeded if interactive prompts are allowed; otherwise, CredentialProptType.DoNotPrompt</param>
/// <param name="scheduler">An optional <c>TaskScheduler</c> to ensure credentials prompting occurs on the UI thread</param>
/// <param name="credentialPrompt">An optional <c>IVssCredentialPrompt</c> to perform prompting for credentials</param>
public VssCredentials(
WindowsCredential windowsCredential,
FederatedCredential federatedCredential,
CredentialPromptType promptType,
TaskScheduler scheduler,
@@ -172,13 +123,6 @@ namespace GitHub.Services.Common
scheduler = TaskScheduler.Default;
}
if (windowsCredential != null)
{
m_windowsCredential = windowsCredential;
m_windowsCredential.Scheduler = scheduler;
m_windowsCredential.Prompt = credentialPrompt;
}
if (federatedCredential != null)
{
m_federatedCredential = federatedCredential;
@@ -199,16 +143,6 @@ namespace GitHub.Services.Common
return new VssCredentials(credential);
}
/// <summary>
/// Implicitly converts a <c>WindowsCredential</c> instance into a <c>VssCredentials</c> instance.
/// </summary>
/// <param name="credential">The windows credential instance</param>
/// <returns>A new <c>VssCredentials</c> instance which wraps the specified credential</returns>
public static implicit operator VssCredentials(WindowsCredential credential)
{
return new VssCredentials(credential);
}
/// <summary>
/// Gets or sets a value indicating whether or not interactive prompts are allowed.
/// </summary>
@@ -240,17 +174,6 @@ namespace GitHub.Services.Common
}
}
/// <summary>
/// Gets the windows credential to use for NTLM authentication with the server.
/// </summary>
public WindowsCredential Windows
{
get
{
return m_windowsCredential;
}
}
/// <summary>
/// A pluggable credential store.
/// Simply assign a storage implementation to this property
@@ -267,11 +190,6 @@ namespace GitHub.Services.Common
{
m_credentialStorage = value;
if (m_windowsCredential != null)
{
m_windowsCredential.Storage = value;
}
if (m_federatedCredential != null)
{
m_federatedCredential.Storage = value;
@@ -327,20 +245,6 @@ namespace GitHub.Services.Common
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
}
}
else if (m_windowsCredential != null && m_windowsCredential.IsAuthenticationChallenge(webResponse))
{
if (tokenProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderRemoved(traceActivity, tokenProvider);
}
tokenProvider = m_windowsCredential.CreateTokenProvider(serverUrl, webResponse, failedToken);
if (tokenProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
}
}
m_currentProvider = tokenProvider;
}
@@ -356,7 +260,7 @@ namespace GitHub.Services.Common
/// <param name="provider">Stores the active token provider, if one exists</param>
/// <returns>True if a token provider was found, false otherwise</returns>
public bool TryGetTokenProvider(
Uri serverUrl,
Uri serverUrl,
out IssuedTokenProvider provider)
{
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
@@ -371,11 +275,6 @@ namespace GitHub.Services.Common
m_currentProvider = m_federatedCredential.CreateTokenProvider(serverUrl, null, null);
}
if (m_currentProvider == null && m_windowsCredential != null)
{
m_currentProvider = m_windowsCredential.CreateTokenProvider(serverUrl, null, null);
}
if (m_currentProvider != null)
{
VssHttpEventSource.Log.IssuedTokenProviderCreated(VssTraceActivity.Current, m_currentProvider);
@@ -401,11 +300,6 @@ namespace GitHub.Services.Common
}
bool isChallenge = false;
if (m_windowsCredential != null)
{
isChallenge = m_windowsCredential.IsAuthenticationChallenge(webResponse);
}
if (!isChallenge && m_federatedCredential != null)
{
isChallenge = m_federatedCredential.IsAuthenticationChallenge(webResponse);
@@ -415,8 +309,8 @@ namespace GitHub.Services.Common
}
internal void SignOut(
Uri serverUrl,
Uri serviceLocation,
Uri serverUrl,
Uri serviceLocation,
string identityProvider)
{
// Remove the token in the storage and the current token provider. Note that we don't
@@ -450,110 +344,6 @@ namespace GitHub.Services.Common
tokenProviderWithSignOut.SignOut(serviceLocation, serverUrl, identityProvider);
}
#if !NETSTANDARD
/// <summary>
/// Loads stored credentials for the specified server if found. If no credentials are found in the windows
/// credential store for the specified server and options then default credentials are returned.
/// </summary>
/// <param name="serverUrl">The server location</param>
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
public static VssCredentials LoadCachedCredentials(
Uri serverUrl,
bool requireExactMatch)
{
return LoadCachedCredentials(null, serverUrl, requireExactMatch);
}
/// <summary>
/// Loads stored credentials for the specified server if found. If no credentials are found for the specified server and options then default credentials are returned.
/// This overload assumes that the credentials are to be stored under the TFS server's registry root
/// </summary>
/// <param name="featureRegistryKeyword">An optional application name for isolated credential storage in the registry</param>
/// <param name="serverUrl">The server location</param>
/// <param name="requireExactMatch">A value indicating whether or not an exact or partial match of the server is required</param>
/// <returns>A credentials object populated with stored credentials for the server if found</returns>
[EditorBrowsable(EditorBrowsableState.Never)]
public static VssCredentials LoadCachedCredentials(
string featureRegistryKeyword,
Uri serverUrl,
bool requireExactMatch)
{
ArgumentUtility.CheckForNull(serverUrl, "serverUrl");
bool uriKnownToCachedProvider = false;
VssCredentials cred = LoadCachedCredentialsFromRegisteredProviders(serverUrl, out uriKnownToCachedProvider);
// If one of the registered credential providers had the target URI in its cache but failed to return a valid credential it means
// we should have had a cred but something went wrong (user canceled, user failed, auth source unavailable, etc.). In that case
// we Do Not want to carry on with the fallback to the VS registry/windows store credential caches. Even if that worked to get a
// credential it would put the user in a bad state (having an active, authenticated connection with an unexpected credential type).
if (cred == null && !uriKnownToCachedProvider)
{
WindowsCredential windowsCredential = null;
FederatedCredential federatedCredential = null;
CredentialsCacheManager credentialsCacheManager = new CredentialsCacheManager();
TfsCredentialCacheEntry cacheEntry = credentialsCacheManager.GetCredentials(featureRegistryKeyword, serverUrl, requireExactMatch, null);
if (cacheEntry != null)
{
if (cacheEntry.NonInteractive)
{
switch (cacheEntry.Type)
{
case CachedCredentialsType.ServiceIdentity:
VssServiceIdentityToken initialToken = null;
string initialTokenValue = ReadAuthorizationToken(cacheEntry.Attributes);
if (!string.IsNullOrEmpty(initialTokenValue))
{
initialToken = new VssServiceIdentityToken(initialTokenValue);
}
// Initialize the issued token credential using the stored token if it exists
federatedCredential = new VssServiceIdentityCredential(cacheEntry.Credentials.UserName,
cacheEntry.Credentials.Password,
initialToken);
break;
case CachedCredentialsType.Windows:
windowsCredential = new WindowsCredential(cacheEntry.Credentials);
break;
}
}
}
cred = new VssCredentials(windowsCredential ?? new WindowsCredential(true), federatedCredential, CredentialPromptType.DoNotPrompt);
}
return cred ?? new VssCredentials(new WindowsCredential(true), null, CredentialPromptType.DoNotPrompt);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static VssCredentials LoadCachedCredentialsFromRegisteredProviders(Uri serverUri, out bool knownUri)
{
LoadRegisteredCachedVssCredentialProviders();
bool uriKnownByAnyProvider = false;
VssCredentials cred = null;
foreach (var pair in m_loadedCachedVssCredentialProviders)
{
bool uriKnownToProvider = false;
cred = pair.Value?.GetCachedCredentials(serverUri, out uriKnownToProvider);
if (cred != null || uriKnownToProvider)
{
uriKnownByAnyProvider |= uriKnownToProvider;
break;
}
}
knownUri = uriKnownByAnyProvider;
return cred;
}
private static void LoadRegisteredCachedVssCredentialProviders()
{
CredentialsProviderRegistryHelper.LoadCachedVssCredentialProviders(ref m_loadedCachedVssCredentialProviders);
}
private static ConcurrentDictionary<string, ICachedVssCredentialProvider> m_loadedCachedVssCredentialProviders = new ConcurrentDictionary<string, ICachedVssCredentialProvider>();
#endif
[EditorBrowsable(EditorBrowsableState.Never)]
public static void WriteAuthorizationToken(
string token,
@@ -604,7 +394,6 @@ namespace GitHub.Services.Common
private object m_thisLock;
private CredentialPromptType m_promptType;
private IssuedTokenProvider m_currentProvider;
protected WindowsCredential m_windowsCredential;
protected FederatedCredential m_federatedCredential;
private IVssCredentialStorage m_credentialStorage;
}

View File

@@ -1,164 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides federated authentication as a service identity with a Visual Studio Service.
/// </summary>
[Serializable]
public sealed class VssServiceIdentityCredential : FederatedCredential
{
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
public VssServiceIdentityCredential(
string userName,
string password)
: this(userName, password, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
/// provided token, if not null, will be used before attempting authentication with the credentials.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public VssServiceIdentityCredential(
string userName,
string password,
VssServiceIdentityToken initialToken)
: this(userName, password, initialToken, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified access token.
/// </summary>
/// <param name="token">A token which may be used for authorization as the desired service identity</param>
public VssServiceIdentityCredential(VssServiceIdentityToken token)
: this(null, null, token, null)
{
}
/// <summary>
/// Initializes a new <c>VssServiceIdentityCredential</c> instance with the specified user name and password. The
/// provided token, if not null, will be used before attempting authentication with the credentials.
/// </summary>
/// <param name="userName">The user name</param>
/// <param name="password">The password</param>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
/// <param name="innerHandler">An optional HttpMessageHandler which if passed will be passed along to the TokenProvider when executing OnCreateTokenProvider </param>
public VssServiceIdentityCredential(
string userName,
string password,
VssServiceIdentityToken initialToken,
DelegatingHandler innerHandler)
: base(initialToken)
{
m_userName = userName;
m_password = password;
m_innerHandler = innerHandler;
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.ServiceIdentity;
}
}
/// <summary>
/// Gets the user name.
/// </summary>
public String UserName
{
get
{
return m_userName;
}
}
/// <summary>
/// Gets the password.
/// </summary>
internal String Password
{
get
{
return m_password;
}
}
public override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode == HttpStatusCode.Found ||
webResponse.StatusCode == HttpStatusCode.Redirect ||
webResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
var wwwAuthenticate = webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate);
if (!String.IsNullOrEmpty(authIssuer) && !String.IsNullOrEmpty(authRealm))
{
return webResponse.StatusCode != HttpStatusCode.Unauthorized || wwwAuthenticate.Any(x => x.StartsWith("TFS-Federated", StringComparison.OrdinalIgnoreCase));
}
}
return false;
}
internal override string GetAuthenticationChallenge(IHttpResponse webResponse)
{
var authRealm = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
var authIssuer = webResponse.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
return string.Format(CultureInfo.InvariantCulture, "TFS-Federated realm={0}, issuer={1}", authRealm, authIssuer);
}
/// <summary>
/// Creates a provider for retrieving security tokens for the provided credentials.
/// </summary>
/// <returns>An issued token provider for the current credential</returns>
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// The response is only null when attempting to determine the most appropriate token provider to
// use for the connection. The only way we should do anything here is if we have an initial token
// since that means we can present something without making a server call.
if (response == null && base.InitialToken == null)
{
return null;
}
Uri signInUrl = null;
String realm = string.Empty;
if (response != null)
{
realm = response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
signInUrl = new Uri(new Uri(response.Headers.GetValues(Internal.HttpHeaders.TfsFedAuthIssuer).FirstOrDefault()).GetLeftPart(UriPartial.Authority));
}
return new VssServiceIdentityTokenProvider(this, serverUrl, signInUrl, realm, m_innerHandler);
}
private readonly String m_userName;
private readonly String m_password;
[NonSerialized]
private readonly DelegatingHandler m_innerHandler = null;
}
}

View File

@@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides simple web token used for OAuth authentication.
/// </summary>
[Serializable]
public sealed class VssServiceIdentityToken : IssuedToken
{
/// <summary>
/// Initializes a new <c>VssServiceIdentityToken</c> instance with the specified token value.
/// </summary>
/// <param name="token">The token value as a string</param>
public VssServiceIdentityToken(string token)
{
ArgumentUtility.CheckStringForNullOrEmpty(token, "token");
m_token = token;
//.ValidFrom = DateTime.UtcNow;
// Read out the expiration time for the ValidTo field if we can find it
Dictionary<string, string> nameValues;
if (TryGetNameValues(token, out nameValues))
{
string expiresOnValue;
if (nameValues.TryGetValue(c_expiresName, out expiresOnValue))
{
// The time is represented as standard epoch
// base.ValidTo = s_epoch.AddSeconds(Convert.ToUInt64(expiresOnValue, CultureInfo.CurrentCulture));
}
}
}
public String Token
{
get
{
return m_token;
}
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.ServiceIdentity;
}
}
internal override void ApplyTo(IHttpRequest request)
{
request.Headers.SetValue(Internal.HttpHeaders.Authorization, "WRAP access_token=\"" + m_token + "\"");
}
internal static VssServiceIdentityToken ExtractToken(string responseValue)
{
// Extract the actual token string
string token = UriUtility.UrlDecode(responseValue
.Split('&')
.Single(value => value.StartsWith("wrap_access_token=", StringComparison.OrdinalIgnoreCase))
.Split('=')[1], VssHttpRequestSettings.Encoding);
return new VssServiceIdentityToken(token);
}
internal static bool TryGetNameValues(
string token,
out Dictionary<string, string> tokenValues)
{
tokenValues = null;
if (string.IsNullOrEmpty(token))
{
return false;
}
tokenValues =
token
.Split('&')
.Aggregate(
new Dictionary<string, string>(),
(dict, rawNameValue) =>
{
if (rawNameValue == string.Empty)
{
return dict;
}
string[] nameValue = rawNameValue.Split('=');
if (nameValue.Length != 2)
{
return dict;
}
if (dict.ContainsKey(nameValue[0]) == true)
{
return dict;
}
dict.Add(UriUtility.UrlDecode(nameValue[0]), UriUtility.UrlDecode(nameValue[1]));
return dict;
});
return true;
}
private string m_token;
private const string c_expiresName = "ExpiresOn";
}
}

View File

@@ -1,201 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using GitHub.Services.Common.Diagnostics;
using GitHub.Services.Common.Internal;
namespace GitHub.Services.Common
{
internal sealed class VssServiceIdentityTokenProvider : IssuedTokenProvider
{
public VssServiceIdentityTokenProvider(
VssServiceIdentityCredential credential,
Uri serverUrl,
Uri signInUrl,
string realm,
DelegatingHandler innerHandler)
: this(credential, serverUrl, signInUrl, realm)
{
m_innerHandler = innerHandler;
}
public VssServiceIdentityTokenProvider(
VssServiceIdentityCredential credential,
Uri serverUrl,
Uri signInUrl,
string realm)
: base(credential, serverUrl, signInUrl)
{
Realm = realm;
}
protected override string AuthenticationParameter
{
get
{
if (string.IsNullOrEmpty(this.Realm) && this.SignInUrl == null)
{
return string.Empty;
}
else
{
return string.Format(CultureInfo.InvariantCulture, "issuer=\"{0}\", realm=\"{1}\"", this.SignInUrl, this.Realm);
}
}
}
protected override String AuthenticationScheme
{
get
{
return "TFS-Federated";
}
}
/// <summary>
/// Gets the simple web token credential from which this provider was created.
/// </summary>
public new VssServiceIdentityCredential Credential
{
get
{
return (VssServiceIdentityCredential)base.Credential;
}
}
/// <summary>
/// Gets a value indicating whether or not a call to get token will require interactivity.
/// </summary>
public override Boolean GetTokenIsInteractive
{
get
{
return false;
}
}
/// <summary>
/// Gets the realm for the token provider.
/// </summary>
public String Realm
{
get;
}
protected internal override bool IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (!base.IsAuthenticationChallenge(webResponse))
{
return false;
}
// This means we were proactively constructed without any connection information. In this case
// we return false to ensure that a new provider is reconstructed with all appropriate configuration
// to retrieve a new token.
if (this.SignInUrl == null)
{
return false;
}
string authRealm = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthRealm).FirstOrDefault();
string authIssuer = webResponse.Headers.GetValues(HttpHeaders.TfsFedAuthIssuer).FirstOrDefault();
Uri signInUrl = new Uri(new Uri(authIssuer).GetLeftPart(UriPartial.Authority), UriKind.Absolute);
// Make sure that the values match our stored values. This way if the values change we will be thrown
// away and a new instance with correct values will be constructed.
return this.Realm.Equals(authRealm, StringComparison.OrdinalIgnoreCase) &&
Uri.Compare(this.SignInUrl, signInUrl, UriComponents.AbsoluteUri, UriFormat.Unescaped, StringComparison.OrdinalIgnoreCase) == 0;
}
/// <summary>
/// Issues a request to synchronously retrieve a token for the associated credential.
/// </summary>
/// <param name="failedToken"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override async Task<IssuedToken> OnGetTokenAsync(
IssuedToken failedToken,
CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(this.Credential.UserName) ||
string.IsNullOrEmpty(this.Credential.Password))
{
return null;
}
VssTraceActivity traceActivity = VssTraceActivity.Current;
using (HttpClient client = new HttpClient(CreateMessageHandler(), false))
{
client.BaseAddress = this.SignInUrl;
KeyValuePair<string, string>[] values = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("wrap_name", this.Credential.UserName),
new KeyValuePair<string, string>("wrap_password", this.Credential.Password),
new KeyValuePair<string, string>("wrap_scope", this.Realm),
};
Uri url = new Uri("WRAPv0.9/", UriKind.Relative);
FormUrlEncodedContent content = new FormUrlEncodedContent(values);
using (HttpResponseMessage response = await client.PostAsync(url, content, cancellationToken).ConfigureAwait(false))
{
string responseValue = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
if (response.IsSuccessStatusCode)
{
return VssServiceIdentityToken.ExtractToken(responseValue);
}
else
{
VssHttpEventSource.Log.AuthenticationError(traceActivity, this, responseValue);
return null;
}
}
}
}
private HttpMessageHandler CreateMessageHandler()
{
var retryOptions = new VssHttpRetryOptions()
{
RetryableStatusCodes =
{
VssNetworkHelper.TooManyRequests,
HttpStatusCode.InternalServerError,
},
};
HttpMessageHandler innerHandler;
if (m_innerHandler != null)
{
if (m_innerHandler.InnerHandler == null)
{
m_innerHandler.InnerHandler = new HttpClientHandler();
}
innerHandler = m_innerHandler;
}
else
{
innerHandler = new HttpClientHandler();
}
// Inherit proxy setting from VssHttpMessageHandler
var httpClientHandler = innerHandler as HttpClientHandler;
if (httpClientHandler != null && VssHttpMessageHandler.DefaultWebProxy != null)
{
httpClientHandler.Proxy = VssHttpMessageHandler.DefaultWebProxy;
httpClientHandler.UseProxy = true;
}
return new VssHttpRetryMessageHandler(retryOptions, innerHandler);
}
private DelegatingHandler m_innerHandler = null;
}
}

View File

@@ -1,137 +0,0 @@
using System;
using System.Linq;
using System.Net;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides a credential for windows authentication against a Visual Studio Service.
/// </summary>
public sealed class WindowsCredential : IssuedTokenCredential
{
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the default network credentials.
/// </summary>
public WindowsCredential()
: this(true)
{
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the default network credentials, if specified.
/// </summary>
/// <param name="useDefaultCredentials">True if the default credentials should be used; otherwise, false</param>
public WindowsCredential(bool useDefaultCredentials)
: this(useDefaultCredentials ? CredentialCache.DefaultCredentials : null)
{
UseDefaultCredentials = useDefaultCredentials;
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using a default user interface provider implementation
/// and the specified network credentials.
/// </summary>
/// <param name="credentials">The windows credentials which should be used for authentication</param>
public WindowsCredential(ICredentials credentials)
: this(null)
{
m_credentials = credentials;
UseDefaultCredentials = credentials == CredentialCache.DefaultCredentials;
}
/// <summary>
/// Initializes a new <c>WindowsCredential</c> instance using the specified initial token.
/// </summary>
/// <param name="initialToken">An optional token which, if present, should be used before obtaining a new token</param>
public WindowsCredential(WindowsToken initialToken)
: base(initialToken)
{
}
/// <summary>
/// Gets the credentials associated with this windows credential.
/// </summary>
public ICredentials Credentials
{
get
{
return m_credentials;
}
set
{
m_credentials = value;
UseDefaultCredentials = Credentials == CredentialCache.DefaultCredentials;
}
}
public override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Windows;
}
}
/// <summary>
/// Gets a value indicating what value was passed to WindowsCredential(bool useDefaultCredentials) constructor
/// </summary>
public Boolean UseDefaultCredentials
{
get;
private set;
}
public override Boolean IsAuthenticationChallenge(IHttpResponse webResponse)
{
if (webResponse == null)
{
return false;
}
if (webResponse.StatusCode == HttpStatusCode.Unauthorized &&
webResponse.Headers.GetValues(Internal.HttpHeaders.WwwAuthenticate).Any(x => AuthenticationSchemeValid(x)))
{
return true;
}
if (webResponse.StatusCode == HttpStatusCode.ProxyAuthenticationRequired &&
webResponse.Headers.GetValues(Internal.HttpHeaders.ProxyAuthenticate).Any(x => AuthenticationSchemeValid(x)))
{
return true;
}
return false;
}
protected override IssuedTokenProvider OnCreateTokenProvider(
Uri serverUrl,
IHttpResponse response)
{
// If we have no idea what kind of credentials we are supposed to be using, don't play a windows token on
// the first request.
if (response == null)
{
return null;
}
if (m_credentials != null)
{
this.InitialToken = new WindowsToken(m_credentials);
}
return new WindowsTokenProvider(this, serverUrl);
}
private static Boolean AuthenticationSchemeValid(String authenticateHeader)
{
return authenticateHeader.StartsWith("Basic", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Digest", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Negotiate", StringComparison.OrdinalIgnoreCase) ||
authenticateHeader.StartsWith("Ntlm", StringComparison.OrdinalIgnoreCase);
}
private ICredentials m_credentials;
}
}

View File

@@ -1,39 +0,0 @@
using System;
using System.Net;
namespace GitHub.Services.Common
{
public sealed class WindowsToken : IssuedToken, ICredentials
{
internal WindowsToken(ICredentials credentials)
{
this.Credentials = credentials;
}
public ICredentials Credentials
{
get;
}
protected internal override VssCredentialsType CredentialType
{
get
{
return VssCredentialsType.Windows;
}
}
internal override void ApplyTo(IHttpRequest request)
{
// Special-cased by the caller because we implement ICredentials
throw new InvalidOperationException();
}
NetworkCredential ICredentials.GetCredential(
Uri uri,
String authType)
{
return this.Credentials?.GetCredential(uri, authType);
}
}
}

View File

@@ -1,40 +0,0 @@
using System;
using System.Globalization;
using System.Net;
namespace GitHub.Services.Common
{
internal sealed class WindowsTokenProvider : IssuedTokenProvider
{
public WindowsTokenProvider(
WindowsCredential credential,
Uri serverUrl)
: base(credential, serverUrl, serverUrl)
{
}
protected override String AuthenticationScheme
{
get
{
return String.Format(CultureInfo.InvariantCulture, "{0} {1} {2} {3}", AuthenticationSchemes.Negotiate, AuthenticationSchemes.Ntlm, AuthenticationSchemes.Digest, AuthenticationSchemes.Basic);
}
}
public new WindowsCredential Credential
{
get
{
return (WindowsCredential)base.Credential;
}
}
public override Boolean GetTokenIsInteractive
{
get
{
return base.CurrentToken == null;
}
}
}
}

View File

@@ -88,7 +88,7 @@ namespace GitHub.Services.Common.ClientStorage
public string PathKeyCombine(params string[] paths)
{
StringBuilder combinedPath = new StringBuilder();
foreach(string segment in paths)
foreach (string segment in paths)
{
if (segment != null)
{
@@ -152,8 +152,8 @@ namespace GitHub.Services.Common.ClientStorage
/// <summary>
/// Gets an instance of a VssLocalFileStorage under the current user directory.
/// </summary>
/// <param name="pathSuffix">This pathSuffix will be combined at the end of the current user data directory for VSS to make a full path. Something like: "%localappdata%\Microsoft\VisualStudio Services\[pathSuffix]"</param>
/// <param name="storeByVssVersion">Adds the current product version as a path segment. ...\Microsoft\VisualStudio Services\v[GeneratedVersionInfo.ProductVersion]\[pathSuffix]"</param>
/// <param name="pathSuffix">This pathSuffix will be combined at the end of the current user data directory for VSS to make a full path. Something like: "%localappdata%\GitHub\ActionsService\[pathSuffix]"</param>
/// <param name="storeByVssVersion">Adds the current product version as a path segment. ...\GitHub\ActionsService\v[GeneratedVersionInfo.ProductVersion]\[pathSuffix]"</param>
/// <param name="pathSeparatorForKeys">The separator to use between the path segments of the storage keys.</param>
/// <param name="ignoreCaseInPaths">If true the dictionary will use the OrdinalIgnoreCase StringComparer to compare keys.</param>
/// <returns></returns>
@@ -166,7 +166,7 @@ namespace GitHub.Services.Common.ClientStorage
/// Directory containing the client settings files.
///
/// This will look something like this:
/// C:\Users\[user]\AppData\Local\Microsoft\VisualStudio Services\v[GeneratedVersionInfo.ProductVersion]
/// C:\Users\[user]\AppData\Local\GitHub\ActionsService\v[GeneratedVersionInfo.ProductVersion]
/// </summary>
internal static string ClientSettingsDirectoryByVersion
{
@@ -182,7 +182,7 @@ namespace GitHub.Services.Common.ClientStorage
/// Directory containing the client settings files.
///
/// This will look something like this:
/// C:\Users\[user]\AppData\Local\Microsoft\VisualStudio Services
/// C:\Users\[user]\AppData\Local\GitHub\ActionsService
/// </summary>
internal static string ClientSettingsDirectory
{
@@ -192,7 +192,7 @@ namespace GitHub.Services.Common.ClientStorage
// Windows Impersonation is being used.
// Check to see if we can find the user's local application data directory.
string subDir = "Microsoft\\VisualStudio Services";
string subDir = "GitHub\\ActionsService";
string path = Environment.GetEnvironmentVariable("localappdata");
SafeGetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (string.IsNullOrEmpty(path))
@@ -204,10 +204,10 @@ namespace GitHub.Services.Common.ClientStorage
{
// The user does not have a roaming network directory either. Just place the cache in the
// common area.
// If we are using the common dir, we might not have access to create a folder under "Microsoft"
// If we are using the common dir, we might not have access to create a folder under "GitHub"
// so we just create a top level folder.
path = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
subDir = "Microsoft VisualStudio Services";
subDir = "GitHubActionsService";
}
}

View File

@@ -5,9 +5,6 @@ using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Net.Sockets;
#if !NETSTANDARD
using System.Diagnostics.Eventing;
#endif
namespace GitHub.Services.Common.Diagnostics
{
@@ -838,13 +835,6 @@ namespace GitHub.Services.Common.Diagnostics
[NonEvent]
private void SetActivityId(VssTraceActivity activity)
{
#if !NETSTANDARD
if (activity != null)
{
Guid activityId = activity.Id;
EventProvider.SetActivityId(ref activityId);
}
#endif
}
[NonEvent]
@@ -876,15 +866,6 @@ namespace GitHub.Services.Common.Diagnostics
Action<Int32, String> writeEvent)
{
writeEvent(param0, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, messagePart);
}
}
#endif
}
[NonEvent]
@@ -895,15 +876,6 @@ namespace GitHub.Services.Common.Diagnostics
Action<VssCredentialsType, Int32, String> writeEvent)
{
writeEvent(param0, param1, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, param1, messagePart);
}
}
#endif
}
[NonEvent]
@@ -914,23 +886,10 @@ namespace GitHub.Services.Common.Diagnostics
Action<VssHttpMethod, String, String> writeEvent)
{
writeEvent(param0, param1, message);
#if !NETSTANDARD
if (EventProvider.GetLastWriteEventError() == EventProvider.WriteEventErrorCode.EventTooBig)
{
foreach (String messagePart in SplitMessage(message))
{
writeEvent(param0, param1, messagePart);
}
}
#endif
}
[NonEvent]
#if !NETSTANDARD
private unsafe void WriteEvent(
#else
private new unsafe void WriteEvent(
#endif
Int32 eventId,
Int32 param0,
String param1)
@@ -1168,6 +1127,6 @@ namespace GitHub.Services.Common.Diagnostics
public static class VssEventSources
{
public const String Http = "Microsoft-VSS-Http";
public const String Http = "GitHub-Actions-Http";
}
}

View File

@@ -1,8 +1,5 @@
using System;
using System.Diagnostics;
#if !NETSTANDARD
using System.Runtime.Remoting.Messaging;
#endif
using System.Runtime.Serialization;
namespace GitHub.Services.Common.Diagnostics
@@ -38,22 +35,11 @@ namespace GitHub.Services.Common.Diagnostics
/// </summary>
public static VssTraceActivity Current
{
#if !NETSTANDARD
get
{
return CallContext.LogicalGetData(VssTraceActivity.PropertyName) as VssTraceActivity;
}
private set
{
CallContext.LogicalSetData(VssTraceActivity.PropertyName, value);
}
#else
get
{
return null;
}
set { }
#endif
}
/// <summary>

View File

@@ -8,84 +8,28 @@ namespace GitHub.Services.Common.Internal
public static class HttpHeaders
{
public const String ActivityId = "ActivityId";
public const String ETag = "ETag";
public const String TfsVersion = "X-TFS-Version";
public const String TfsRedirect = "X-TFS-Redirect";
public const String TfsException = "X-TFS-Exception";
public const String TfsServiceError = "X-TFS-ServiceError";
public const String TfsSessionHeader = "X-TFS-Session";
public const String TfsSoapException = "X-TFS-SoapException";
public const String TfsFedAuthRealm = "X-TFS-FedAuthRealm";
public const String TfsFedAuthIssuer = "X-TFS-FedAuthIssuer";
public const String TfsFedAuthRedirect = "X-TFS-FedAuthRedirect";
public const String VssAuthorizationEndpoint = "X-VSS-AuthorizationEndpoint";
public const String VssPageHandlers = "X-VSS-PageHandlers";
public const String VssE2EID = "X-VSS-E2EID";
public const String VssOrchestrationId = "X-VSS-OrchestrationId";
public const String AuditCorrelationId = "X-VSS-Audit-CorrelationId";
public const String VssOriginUserAgent = "X-VSS-OriginUserAgent";
// Internal Headers that we use in our client.
public const string TfsInstanceHeader = "X-TFS-Instance";
public const string TfsVersionOneHeader = "X-VersionControl-Instance";
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tfs")]
public const string TfsImpersonate = "X-TFS-Impersonate";
public const string TfsSubjectDescriptorImpersonate = "X-TFS-SubjectDescriptorImpersonate";
public const string MsContinuationToken = "X-MS-ContinuationToken";
public const String VssUserData = "X-VSS-UserData";
public const String VssAgentHeader = "X-VSS-Agent";
public const String VssAuthenticateError = "X-VSS-AuthenticateError";
public const string VssReauthenticationAction = "X-VSS-ReauthenticationAction";
public const string RequestedWith = "X-Requested-With";
public const String VssRateLimitResource = "X-RateLimit-Resource";
public const String VssRateLimitDelay = "X-RateLimit-Delay";
public const String VssRateLimitLimit = "X-RateLimit-Limit";
public const String VssRateLimitRemaining = "X-RateLimit-Remaining";
public const String VssRateLimitReset = "X-RateLimit-Reset";
public const String RetryAfter = "Retry-After";
public const String VssGlobalMessage = "X-VSS-GlobalMessage";
public const String VssRequestRouted = "X-VSS-RequestRouted";
public const String VssUseRequestRouting = "X-VSS-UseRequestRouting";
public const string VssResourceTenant = "X-VSS-ResourceTenant";
public const String VssOverridePrompt = "X-VSS-OverridePrompt";
public const String VssOAuthS2STargetService = "X-VSS-S2STargetService";
public const String VssHostOfflineError = "X-VSS-HostOfflineError";
public const string VssForceMsaPassThrough = "X-VSS-ForceMsaPassThrough";
public const string VssRequestPriority = "X-VSS-RequestPriority";
// This header represents set of ';' delimited mappings (usually one) that are considered by DetermineAccessMapping API
public const string VssClientAccessMapping = "X-VSS-ClientAccessMapping";
// This header is used to download artifacts anonymously.
// N.B. Some resources secured with download tickets (e.g. TFVC files) are still retrieved with the download
// ticket in the query string.
public const string VssDownloadTicket = "X-VSS-DownloadTicket";
public const string IfModifiedSince = "If-Modified-Since";
public const string Authorization = "Authorization";
public const string Location = "Location";
public const string ProxyAuthenticate = "Proxy-Authenticate";
public const string WwwAuthenticate = "WWW-Authenticate";
public const string AfdIncomingRouteKey = "X-FD-RouteKey";
public const string AfdOutgoingRouteKey = "X-AS-RouteKey";
public const string AfdIncomingEndpointList = "X-FD-RouteKeyApplicationEndpointList";
public const string AfdOutgoingEndpointList = "X-AS-RouteKeyApplicationEndpointList";
public const string AfdResponseRef = "X-MSEdge-Ref";
public const string AfdIncomingClientIp = "X-FD-ClientIP";
public const string AfdIncomingSocketIp = "X-FD-SocketIP";
public const string AfdIncomingRef = "X-FD-Ref";
public const string AfdIncomingEventId = "X-FD-EventId";
public const string AfdIncomingEdgeEnvironment = "X-FD-EdgeEnvironment";
public const string AfdOutgoingQualityOfResponse = "X-AS-QualityOfResponse";
public const string AfdOutgoingClientIp = "X-MSEdge-ClientIP";
}
}

View File

@@ -1,553 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace GitHub.Services.Common
{
/// <summary>
/// Provides path normalization/expansion for absolute, relative and UNC-style paths
/// and supports paths that contain more than 248 characters.
/// </summary>
/// <remarks>
/// This utility class can be used in place of the .NET Path and Directory classes
/// that throw System.IO.PathTooLongException when paths are longer than 248 characters
/// </remarks>
public static class LongPathUtility
{
private static Regex AbsolutePathRegEx = new Regex(@"^([a-zA-Z]:\\|\\\\)", RegexOptions.CultureInvariant | RegexOptions.Compiled);
private const int ERROR_FILE_NOT_FOUND = 2;
/// <summary>
/// Returns a list of directory names under the path specified, and optionally all subdirectories
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the currect directory or all subdirectories</param>
/// <returns>A list of all subdirectories</returns>
public static IEnumerable<string> EnumerateDirectories(string path, bool recursiveSearch)
{
var directoryPaths = new List<string>();
EnumerateDirectoriesInternal(directoryPaths, path, recursiveSearch);
return directoryPaths;
}
/// <summary>
/// Returns a list of file names under the path specified, and optionally within all subdirectories.
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
/// <returns>
/// A list of full file names(including path) contained in the directory specified that match the specified search pattern.</returns>
public static IEnumerable<string> EnumerateFiles(string path, bool recursiveSearch)
{
return EnumerateFiles(path, "*", recursiveSearch);
}
/// <summary>
/// Returns an enumerable collection of file names that match a search pattern in a specified path,
/// and optionally searches subdirectories.
/// </summary>
/// <param name="path">The directory to search</param>
/// <param name="matchPattern">The search string to match against the names of the files</param>
/// <param name="recursiveSearch">Specifies whether the search operation should include only the current directory or all subdirectories</param>
/// <returns>
/// A list of full file names(including path) contained in the directory specified (and subdirectories optionally) that match the specified pattern.
/// </returns>
public static IEnumerable<string> EnumerateFiles(string path, string matchPattern, bool recursiveSearch)
{
if (!DirectoryExists(path))
{
throw new DirectoryNotFoundException($"The path '{path}' is not a valid directory.");
}
var filePaths = new List<string>();
EnumerateFilesInternal(filePaths, path, matchPattern, recursiveSearch);
return filePaths;
}
/// <summary>
/// Returns true/false whether the file exists. This method inspects the
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
/// supports file paths that are longer than 260 characters.
/// </summary>
/// <param name="filePath">The file path to inspect</param>
/// <returns>
/// True if the file exists or false if not
/// </returns>
public static bool FileExists(string filePath)
{
return FileOrDirectoryExists(filePath, isDirectory: false);
}
/// <summary>
/// Returns true/false whether the directory exists. This method inspects the
/// file system attributes and supports files without extensions (ex: DIRS, Sources). This method
/// supports file paths that are longer than 260 characters.
/// </summary>
/// <param name="directoryPath">The file path to inspect</param>
/// <returns>
/// True if the directory exists or false if not
/// </returns>
public static bool DirectoryExists(string directoryPath)
{
return FileOrDirectoryExists(directoryPath, isDirectory: true);
}
private static bool FileOrDirectoryExists(string filePath, bool isDirectory)
{
if (String.IsNullOrWhiteSpace(filePath))
{
throw new ArgumentException("A path to the file is required and cannot be null, empty or whitespace", "filePath");
}
bool pathExists = false;
// File names may or may not include an extension (ex: DIRS, Sources). We have to look at the attributes
// on the file system object in order to distinguish a directory from a file
var attributes = (FlagsAndAttributes)NativeMethods.GetFileAttributes(filePath);
if (attributes != FlagsAndAttributes.InvalidFileAttributes)
{
bool pathIsDirectory = (attributes & FlagsAndAttributes.Directory) == FlagsAndAttributes.Directory;
if (pathIsDirectory == isDirectory)
{
pathExists = true;
}
}
return pathExists;
}
/// <summary>
/// Returns the fully expanded/normalized path. This method supports paths that are
/// longer than 248 characters.
/// </summary>
/// <param name="path">The file or directory path</param>
/// <returns></returns>
public static string GetFullNormalizedPath(string path)
{
if (String.IsNullOrWhiteSpace(path))
{
throw new ArgumentException("A path is required and cannot be null, empty or whitespace", "path");
}
string outPath = path;
// We need the length of the absolute path in order to prepare a buffer of
// the correct size
uint bufferSize = NativeMethods.GetFullPathName(path, 0, null, null);
int lastWin32Error = Marshal.GetLastWin32Error();
if (bufferSize > 0)
{
var absolutePath = new StringBuilder((int)bufferSize);
uint length = NativeMethods.GetFullPathName(path, bufferSize, absolutePath, null);
lastWin32Error = Marshal.GetLastWin32Error();
if (length > 0)
{
outPath = absolutePath.ToString();
}
else
{
// Path resolution failed
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Path normalization/expansion failed. The path length was not returned by the Kernel32 subsystem for '{0}'.",
path
)
);
}
}
else
{
// Path resolution failed and the path length could not
// be determined
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Path normalization/expansion failed. A full path was not returned by the Kernel32 subsystem for '{0}'.",
path
)
);
}
return outPath != null ? outPath.TrimEnd('\\') : null;
}
/// <summary>
/// Determines whether the specified path is an absolute path or not.
/// </summary>
/// <param name="path">The path to be tested.</param>
/// <returns>
/// <c>true</c> if the path is absolute; otherwise, <c>false</c>.
/// </returns>
public static bool IsAbsolutePath(string path)
{
return LongPathUtility.AbsolutePathRegEx.Match(path).Success;
}
public static string RemoveExtendedLengthPathPrefix(string inPath)
{
string outPath = inPath;
if (!String.IsNullOrWhiteSpace(inPath))
{
if (inPath.StartsWith("\\", StringComparison.OrdinalIgnoreCase))
{
// ex: \\?\UNC\server\share to \\server\share
outPath = inPath.Replace(@"\\?\UNC", @"\");
// ex: \\?\c:\windows to c:\windows
outPath = outPath.Replace(@"\\?\", String.Empty);
}
}
return outPath;
}
private static string CombinePaths(string pathA, string pathB)
{
if (pathA == null)
{
throw new ArgumentNullException("pathA");
}
if (pathB == null)
{
throw new ArgumentNullException("pathB");
}
// The Path class does not suffer from the 248/260 character limitation
// that the File and Directory classes do.
return Path.Combine(
pathA.TrimEnd('\\'),
pathB.TrimStart('\\')
);
}
private static string ConvertToExtendedLengthPath(string path)
{
string extendedLengthPath = GetFullNormalizedPath(path);
if (!String.IsNullOrWhiteSpace(extendedLengthPath))
{
//no need to modify- it's already unicode
if (!extendedLengthPath.StartsWith(@"\\?", StringComparison.OrdinalIgnoreCase))
{
// ex: \\server\share
if (extendedLengthPath.StartsWith(@"\\", StringComparison.OrdinalIgnoreCase))
{
// make it \\?\UNC\server\share
extendedLengthPath = String.Format(
CultureInfo.InvariantCulture,
@"\\?\UNC{0}",
extendedLengthPath.Substring(1)
);
}
else //not unicode already, and not UNC
{
extendedLengthPath = String.Format(
CultureInfo.InvariantCulture,
@"\\?\{0}",
extendedLengthPath
);
}
}
}
return extendedLengthPath;
}
private static IEnumerable<string> EnumerateDirectoriesInPath(string path)
{
SafeFindHandle handle = null;
var findData = new FindData();
var childDirectories = new List<string>();
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), "*"), findData))
{
if (!handle.IsInvalid)
{
bool searchComplete = false;
do
{
// skip the dot directories
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
{
if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0)
{
childDirectories.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
}
}
if (NativeMethods.FindNextFile(handle, findData))
{
if (handle.IsInvalid)
{
throw new Win32Exception(
Marshal.GetLastWin32Error(),
String.Format(
CultureInfo.InvariantCulture,
"Enumerating subdirectories for path '{0}' failed.",
path
)
);
}
}
else
{
searchComplete = true;
}
} while (!searchComplete);
}
}
return childDirectories;
}
private static IEnumerable<string> EnumerateFilesInPath(string path, string matchPattern)
{
SafeFindHandle handle = null;
var findData = new FindData();
var fullFilePaths = new List<string>();
using (handle = NativeMethods.FindFirstFile(CombinePaths(ConvertToExtendedLengthPath(path), matchPattern), findData))
{
int lastWin32Error = Marshal.GetLastWin32Error();
if (handle.IsInvalid)
{
if (lastWin32Error != ERROR_FILE_NOT_FOUND)
{
throw new Win32Exception(
lastWin32Error,
String.Format(CultureInfo.InvariantCulture, "Enumerating files for path '{0}' failed.", path)
);
}
}
else
{
bool searchComplete = false;
do
{
// skip the dot directories
if (!findData.fileName.Equals(@".") && !findData.fileName.Equals(@".."))
{
if ((findData.fileAttributes & (int)FileAttributes.Directory) == 0)
{
fullFilePaths.Add(RemoveExtendedLengthPathPrefix(CombinePaths(path, findData.fileName)));
}
}
if (NativeMethods.FindNextFile(handle, findData))
{
lastWin32Error = Marshal.GetLastWin32Error();
if (handle.IsInvalid)
{
throw new Win32Exception(
lastWin32Error,
String.Format(
CultureInfo.InvariantCulture,
"Enumerating subdirectories for path '{0}' failed.",
path
)
);
}
}
else
{
searchComplete = true;
}
} while (!searchComplete);
}
}
return fullFilePaths;
}
private static void EnumerateFilesInternal(List<string> filePaths, string path, string matchPattern, bool recursiveSearch)
{
var fullFilePaths = EnumerateFilesInPath(path, matchPattern);
if (fullFilePaths.Any())
{
lock (filePaths)
{
filePaths.AddRange(fullFilePaths);
}
}
if (recursiveSearch)
{
var directorySearchPaths = EnumerateDirectoriesInPath(path);
if (directorySearchPaths.Any())
{
Parallel.ForEach(
directorySearchPaths,
(searchPath) =>
{
EnumerateFilesInternal(filePaths, searchPath, matchPattern, recursiveSearch);
}
);
}
}
}
public static void EnumerateDirectoriesInternal(List<string> directoryPaths, string path, bool recursiveSearch)
{
var directorySearchPaths = EnumerateDirectoriesInPath(path);
if (directorySearchPaths.Any())
{
lock (directoryPaths)
{
directoryPaths.AddRange(directorySearchPaths);
}
if (recursiveSearch)
{
// This will not ensure that the directory paths are added to the list
// in alphabetical order but does provide performance 2 - 4 times better than the
// canonical Directory.GetDirectories() method.
Parallel.ForEach(
directorySearchPaths,
(searchPath) =>
{
EnumerateDirectoriesInternal(directoryPaths, searchPath, recursiveSearch);
}
);
}
}
}
/// <summary>
/// Kernel32.dll native interop methods for use with utility file/path parsing
/// operations
/// </summary>
private static class NativeMethods
{
private const string Kernel32Dll = "kernel32.dll";
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindClose(IntPtr hFindFile);
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern SafeFindHandle FindFirstFile(
[MarshalAs(UnmanagedType.LPTStr)]
string fileName,
[In, Out] FindData findFileData
);
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.alternateFileName")]
[SuppressMessage("Microsoft.Globalization", "CA2101:SpecifyMarshalingForPInvokeStringArguments", MessageId = "FindData.fileName")]
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern int GetFileAttributes(string lpFileName);
[DllImport(Kernel32Dll, CharSet = CharSet.Unicode, BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
public static extern uint GetFullPathName(
[MarshalAs(UnmanagedType.LPTStr)]
string lpFileName,
uint nBufferLength,
[Out]
StringBuilder lpBuffer,
StringBuilder lpFilePart
);
}
//for mapping to the WIN32_FIND_DATA native structure
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter", Justification = "Reviewed.")]
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:FieldsMustBePrivate", Justification = "Reviewed.")]
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private sealed class FindData
{
// NOTE:
// Although it may seem correct to Marshal the string members of this class as UnmanagedType.LPWStr, they
// must explicitly remain UnmanagedType.ByValTStr with the size constraints noted. Otherwise we end up with
// COM Interop exceptions while trying to marshal the data across the PInvoke boundaries. We thus require the StyleCop
// suppressions on the NativeMethods.FindNextFile() method above.
public int fileAttributes;
public System.Runtime.InteropServices.ComTypes.FILETIME creationTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastAccessTime;
public System.Runtime.InteropServices.ComTypes.FILETIME lastWriteTime;
public int nFileSizeHigh;
public int nFileSizeLow;
public int dwReserved0;
public int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
public string fileName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
public string alternateFileName;
}
//A Win32 safe find handle in which a return value of -1 indicates it's invalid
private sealed class SafeFindHandle : Microsoft.Win32.SafeHandles.SafeHandleMinusOneIsInvalid
{
public SafeFindHandle()
: base(true)
{
return;
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
protected override bool ReleaseHandle()
{
return NativeMethods.FindClose(handle);
}
}
[Flags]
private enum FlagsAndAttributes : uint
{
None = 0x00000000,
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000,
InvalidFileAttributes = 0xFFFFFFFF // Returned by GetFileAttributes on Non existant path
}
}
}

View File

@@ -218,7 +218,6 @@ namespace GitHub.Services.Common
}
}
#if NETSTANDARD
/// <summary>
/// Portable compliant way to get a constructor with specified arguments. This will return a constructor that is public or private as long as the arguments match. NULL will be returned if there is no match.
/// Note that it will pick the first one it finds that matches, which is not necesarily the best match.
@@ -263,7 +262,6 @@ namespace GitHub.Services.Common
}
return null;
}
#endif
private static PropertyInfo GetPublicInstancePropertyInfo(Type type, string name)
{

View File

@@ -1,18 +1,4 @@
// ************************************************************************************************
// Microsoft Team Foundation
//
// Microsoft Confidential
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: VssStringComparer.cs
// Area: Team Foundation
// Classes: VssStringComparer
// Contents: The Team Foundation string comparison class provides inner classes
// that are used to provide semantic-specific Equals and Compare methods
// and a semantic-specific StringComparer instance. New semantics should
// be added on an as-needed basis.
// ************************************************************************************************
using System;
using System;
using System.Diagnostics;
namespace GitHub.Services.Common

View File

@@ -13,9 +13,6 @@ namespace GitHub.Services.Common.Internal
{
[EditorBrowsable(EditorBrowsableState.Never)]
#if !NETSTANDARD
[CLSCompliant(false)]
#endif
public static class XmlUtility
{
internal static FileStream OpenFile(String path, FileShare sharing, Boolean saveFile)

View File

@@ -5,16 +5,6 @@ using System.Diagnostics.CodeAnalysis;
namespace GitHub.Services.Common
{
public static class AdminConstants
{
/// <summary>
/// Each incoming web request is assigned a server process id, this constant defines
/// an element within the Context.Items[] to hold that value.
/// </summary>
public const String ServerProcessID = "serverProcessID";
public const String ApplicationName = "ApplicationName";
}
[GenerateSpecificConstants]
public static class IdentityConstants
{
@@ -352,370 +342,6 @@ namespace GitHub.Services.Common
public static readonly ISet<string> WhiteListedProperties = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
public static class DirectoryRoleConstants
{
/// Name of the directory role that represents "Company Administrator/Global Admin"
public const string CompanyAdministrator = "Company Administrator";
}
// Used with Registration entries
[GenerateSpecificConstants]
public static class ToolNames
{
public const string Framework = "Framework";
[GenerateConstant]
public const string VersionControl = "VersionControl";
[GenerateConstant]
public const string WorkItemTracking = "WorkItemTracking";
[GenerateConstant]
public const string RemoteWorkItemTracking = "RemoteWorkItemTracking";
public const string CoreServices = "vstfs";
public const string Warehouse = "Reports";
[GenerateConstant]
public const string TeamBuild = "Build";
public const string ProxyServer = "ps";
public const string TeamFoundation = "vstfs";
public const string SharePoint = "Wss";
[GenerateConstant]
public const string TestManagement = "TestManagement";
public const string LabManagement = "LabManagement";
public const string ReleaseManagement = "ReleaseManagement";
public const string SyncService = "SyncService";
public const string TestRig = "TestRig";
public const string TSWebAccess = "TSWebAccess";
public const string ProjectServer = "ProjectServer";
public const string DeploymentRig = "DeploymentRig";
public const string TeamProjects = "TeamProjects"; // contains specific project registration entries (project portal, process guidance and doc url)
public const string Discussion = "Discussion";
[GenerateConstant]
public const string Requirements = "Requirements";
[GenerateConstant]
public const string Hyperlink = "Hyperlink";
public const string Classification = "Classification";
[GenerateConstant]
public const string Legacy = "Legacy";
[GenerateConstant]
public const string CodeSense = "CodeSense";
[GenerateConstant]
public const string Git = "Git";
[GenerateConstant]
public const string CodeReview = "CodeReview";
[GenerateConstant]
public const string ProjectDownload = "ProjectDownload";
public const string DistributedTask = "DistributedTask";
[GenerateConstant]
public const string Wiki = "Wiki";
public const string Search = "Search";
[GenerateConstant]
public const string GitHub = "GitHub";
}
// Artifact types
[GenerateSpecificConstants]
public static class ArtifactTypeNames
{
public const string Project = "TeamProject";
public const string Node = "Node";
public const string Collector = "Collector";
public const string TestResult = "TestResult";
[GenerateConstant]
public const string TcmResult = "TcmResult";
[GenerateConstant]
public const string TcmResultAttachment = "TcmResultAttachment";
[GenerateConstant]
public const string TcmTest = "TcmTest";
[GenerateConstant]
public const string Build = "Build";
public const string BuildAgent = "Agent";
public const string BuildDefinition = "Definition";
public const string BuildController = "Controller";
public const string BuildGroup = "Group";
public const string BuildRequest = "Request";
public const string BuildServiceHost = "ServiceHost";
[GenerateConstant]
public const string VersionedItem = "VersionedItem";
[GenerateConstant]
public const string LatestItemVersion = "LatestItemVersion";
[GenerateConstant]
public const string Changeset = "Changeset";
public const string Label = "Label";
[GenerateConstant]
public const string Shelveset = "Shelveset";
public const string ShelvedItem = "ShelvedItem";
[GenerateConstant]
public const string WorkItem = "WorkItem";
public const string Query = "Query";
public const string Results = "Results";
public const string LabEnvironment = "LabEnvironment";
public const string LabTemplate = "LabTemplate";
public const string LabSystem = "LabSystem";
public const string TeamProjectHostGroup = "TeamProjectHostGroup";
public const string TeamProjectLibraryShare = "TeamProjectLibraryShare";
public const string TeamProjectCollectionLibraryShare = "TeamProjectCollectionLibraryShare";
public const string TeamProjectCollectionHostGroup = "TeamProjectCollectionHostGroup";
public const string TestMachine = "TestMachine";
[GenerateConstant]
public const string Storyboard = "Storyboard";
[GenerateConstant]
public const string Commit = "Commit";
public const string LaunchLatestVersionedItem = "LaunchLatestVersionedItem";
[GenerateConstant]
public const string CodeReviewId = "CodeReviewId";
[GenerateConstant]
public const string CodeReviewSdkId = "ReviewId";
[GenerateConstant]
public const string PullRequestId = "PullRequestId";
[GenerateConstant]
public const string ProjectDownloadProject = "Project";
/// <summary>
/// A Git Ref
/// </summary>
[GenerateConstant]
public const string Ref = "Ref";
public const string TaskAgentPoolMaintenance = "PoolMaintenance";
[GenerateConstant]
public const string WikiPage = "WikiPage";
// GitHub
[GenerateConstant]
public const string PullRequest = "PullRequest";
[GenerateConstant]
public const string Issue = "Issue";
}
/// <summary>
/// Constant strings used in Notifications
/// </summary>
public static class NotificationConstants
{
/// <summary>
/// Macro used in subscriptions which will be replaced by the project name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyProjectNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyProjectNameMacro = "@@MyProjectName@@";
/// <summary>
/// Macro used in subscriptions which will be replaced by the subscriber's Display Name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyDisplayNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyDisplayNameMacro = "@@MyDisplayName@@";
/// <summary>
/// Macro used in subscriptions which will be replaced by the subscriber's Unique User Name when evaluated
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.MyUniqueNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String MyUniqueNameMacro = "@@MyUniqueName@@";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.SingleQuoteNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String SingleQuoteNameMacro = "@@SQBDQ@@"; //SingleQuoteBetweenDoubleQuotes
[Obsolete]
public const String SingleQuoteValue = "\"'\""; //"'"
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteNameMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteNameMacro = "@@DQBSQ@@"; //DoubleQuoteBetweenSingleQuotes
[Obsolete]
public const String DoubleQuoteValue = "'\"'"; //'"'
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.SingleQuoteCharMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String SingleQuoteCharMacro = "@@SingleQuote@@";
[Obsolete]
public const String SingleQuoteCharValue = "'";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteCharMacro in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteCharMacro = "@@DoubleQuote@@";
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DoubleQuoteCharValue in assembly MS.VS.Services.Notifications.WebApi")]
public const String DoubleQuoteCharValue = "\"";
/// <summary>
/// Token used in subscription addresses to identify dynamic delivery targets computed from the source event
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DynamicTargetsToken in assembly MS.VS.Services.Notifications.WebApi")]
public const String DynamicTargetsToken = "@@";
/// <summary>
/// TeamFoundationIdentity property name for a user's custom list of Email addresses to receive notifications at
/// </summary>
public const String CustomNotificationAddressesIdentityProperty = "CustomNotificationAddresses";
/// <summary>
/// TeamFoundationIdentity propery name for a user's confirmed Email address to receive notifications. This is used in Hosted environments only.
/// </summary>
public const string ConfirmedNotificationAddressIdentityProperty = "ConfirmedNotificationAddress";
/// <summary>
/// The name of the WorkItemChangedEvent
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.WorkItemChangedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const string WorkItemChangedEventTypeName = "WorkItemChangedEvent";
/// <summary>
/// The name of the BuildStatusChangedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.BuildStatusChangeEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String BuildStatusChangeEventName = "BuildStatusChangeEvent";
/// <summary>
/// The name of the BuildCompletedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.BuildCompletedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String BuildCompletedEventName = "BuildCompletedEvent";
/// <summary>
/// The name of the CheckinEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.CheckinEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String CheckinEventName = "CheckinEvent";
/// <summary>
/// The name of the CodeReviewChangedEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.CodeReviewChangedEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String CodeReviewChangedEventName = "CodeReviewChangedEvent";
/// <summary>
/// The name of the GitPushEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.GitPushEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String GitPushEventName = "GitPushEvent";
/// <summary>
/// The name of the GitPullRequestEvent type
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.LegacyNames.GitPullRequestEvent in assembly MS.VS.Services.Notifications.WebApi")]
public const String GitPullRequestEventName = "GitPullRequestEvent";
/// <summary>
/// The relative path to the alerts admin web page
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsPageRelativePath in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsPageRelativePath = "{0}#id={1}&showteams={2}";
/// <summary>
/// The alerts page name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsPage in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsPage = "_Alerts";
/// <summary>
/// The admin alerts page
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationUrlConstants.AlertsAdminPage in assembly MS.VS.Services.Notifications.WebApi")]
public const String AlertsAdminPage = "_admin/_Alerts";
/// <summary>
/// Property used to keep track of how many confirmations were sent for this user. Used to limit the number
/// of confirmations a single user is allowed to send out for their account.
/// The value is updated and monitored by the SendEmailConfirmationJob.
/// </summary>
public const string EmailConfirmationSendDates = "EmailConfirmationSendDates";
/// <summary>
/// Prefix to denote that identity field value have been processed
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.ProcessedFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char ProcessedFlagCharacter = (Char)7;
/// <summary>
/// Prefix to denote that identity field value have been processed and converted to TFID
/// </summary>
/// [Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.ProcessedTfIdFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char ProcessedTfIdFlagCharacter = (Char)11;
/// <summary>
/// Prefix to denote that this is the start of displayname value for this identity field
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.DisplayNameFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char DisplayNameFlagCharacter = '|';
/// <summary>
/// Prefix to denote that this is the start of TFID value for this identity field
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.TfIdFlagCharacter in assembly MS.VS.Services.Notifications.WebApi")]
public const Char TfIdFlagCharacter = '%';
/// <summary>
/// Optional Feature flag to enable escaping Regex expressions when creating Notification subscriptions.
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.FeatureFlags.AllowUserRegexInMatchConditionFeatureFlag in assembly MS.VS.Services.Notifications.WebApi")]
public const string AllowUserRegexInMatchConditionFeatureFlag = "VisualStudio.Services.Notifications.AllowUserRegexInMatchCondition";
/// <summary>
/// The MDM scope name for the notification job
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.MDMNotificationJobScope in assembly MS.VS.Services.Notifications.WebApi")]
public const string MDMNotificationJobScope = "NotificationJob";
/// <summary>
/// Event processing delay KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.EventProcessingDelayKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string EventProcessingDelayKPI = "EventProcessingDelayInMs";
/// <summary>
/// Event processing delay KPI description
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.EventProcessingDelayKPIDesc in assembly MS.VS.Services.Notifications.WebApi")]
public const string EventProcessingDelayKPIDesc = "Time taken to start processing an event";
/// <summary>
/// The MDM scope name for the delivery job
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.MDMDeliveryJobscope in assembly MS.VS.Services.Notifications.WebApi")]
public const string MDMDeliveryJobscope = "NotificationDeliveryJob";
/// <summary>
/// Notification delivery delay KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayKPI = "NotificationDeliveryDelayInMs";
/// <summary>
/// Notification delivery delay with retries KPI name
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayWithRetriesKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayWithRetriesKPI = "NotificationDeliveryDelayWithRetriesInMs";
/// <summary>
/// Total time taken between the event creation till the notification delivery
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.TotalProcessingTimeKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string TotalProcessingTimeKPI = "EventProcessingTimeInMs";
/// <summary>
/// Total time taken between the event creation till the notification delivery
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.TotalProcessingTimeWithRetriesKPI in assembly MS.VS.Services.Notifications.WebApi")]
public const string TotalProcessingTimeWithRetriesKPI = "EventProcessingTimeWithRetriesInMs";
/// <summary>
/// Notification delivery delay KPI description
/// </summary>
[Obsolete("Moved to GitHub.Services.Notifications.Common.MDMConstants.DeliveryDelayKPIDesc in assembly MS.VS.Services.Notifications.WebApi")]
public const string DeliveryDelayKPIDesc = "Time taken to start deliverying a notification";
// caching key for our notification bridge interface
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.BridgeKey in assembly MS.VS.Services.Notifications.WebApi")]
public const String BridgeKey = "@NotifBridge";
// delivery retry count registryKey
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.RetryCountRegistryKey in assembly MS.VS.Services.Notifications.WebApi")]
public const string RetryCountRegistryKey = "NotificationRetryCount";
// delivery retry count default value
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.RetryCountDefaultValue in assembly MS.VS.Services.Notifications.WebApi")]
public const Int32 RetryCountDefaultValue = 5;
// the collection scope Guid
[Obsolete("Moved to GitHub.Services.Notifications.Common.NotificationFrameworkConstants.CollectionScope in assembly MS.VS.Services.Notifications.WebApi")]
public static Guid CollectionScope = new Guid("00000000-0000-636f-6c6c-656374696f6e");
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class LocationSecurityConstants
{
@@ -732,16 +358,6 @@ namespace GitHub.Services.Common
public const Int32 AllPermissions = Read | Write;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class SecuritySecurityConstants
{
public static readonly Guid NamespaceId = new Guid("9A82C708-BFBE-4F31-984C-E860C2196781");
public const char Separator = '/';
public const String RootToken = "";
public const int Read = 1;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public class GraphSecurityConstants
{
@@ -753,158 +369,6 @@ namespace GitHub.Services.Common
public const int ReadByPersonalIdentifier = 2;
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static class TeamProjectSecurityConstants
{
public static readonly Guid NamespaceId = new Guid("52D39943-CB85-4d7f-8FA8-C6BAAC873819");
// Existed in Orcas
public static readonly Int32 GenericRead = 1;
public static readonly Int32 GenericWrite = 2;
public static readonly Int32 Delete = 4;
public static readonly Int32 PublishTestResults = 8;
public static readonly Int32 AdministerBuild = 16;
public static readonly Int32 StartBuild = 32;
public static readonly Int32 EditBuildStatus = 64;
public static readonly Int32 UpdateBuild = 128;
public static readonly Int32 DeleteTestResults = 256;
public static readonly Int32 ViewTestResults = 512;
// Dev10 Beta1
public static readonly Int32 ManageTestEnvironments = 2048;
// Dev10 Beta2
public static readonly Int32 ManageTestConfigurations = 4096;
// Dev14 Update 2 / VSO (M91)
public static readonly Int32 WorkItemDelete = 8192;
// Dev14 Update 2 / VSO (M92)
public static readonly Int32 WorkItemMove = 16384;
// Dev14 Update 2 / VSO (M94)
public static readonly Int32 WorkItemPermanentlyDelete = 32768;
// Dev15 / VSO (M99)
public static readonly Int32 Rename = 65536;
/// <summary>
/// The permission required for setting project properties.
/// Introduced in Dev15 Update 2 / VSO (M116).
/// </summary>
public static readonly Int32 ManageProperties = 131072;
/// <summary>
/// The permission required for setting system project properties.
/// Introduced in Dev15 Update 2 / VSO (M116).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 ManageSystemProperties = 262144;
/// <summary>
/// The permission required for bypassing the project property cache.
/// Introduced in Dev16 / VSO (M118).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 BypassPropertyCache = 524288;
/// <summary>
/// The permission required for bypassing the rules while updating work items.
/// Introduced in Dev16 / VSO (M126).
/// </summary>
public static readonly Int32 BypassRules= 1048576;
/// <summary>
/// The permission required for suppressing notifications for work item updates.
/// Introduced in Dev16 / VSO (M126).
/// </summary>
public static readonly Int32 SuppressNotifications= 2097152;
/// <summary>
/// The permission required for updating project visibility.
/// Introduced in Dev16 / VSO (M131).
/// </summary>
public static readonly Int32 UpdateVisibility = 4194304;
/// <summary>
/// The permission required for changing the process of the team project
/// Introduced in Dev17 / VSO (M136).
/// </summary>
public static readonly Int32 ChangeProjectsProcess = 8388608;
/// <summary>
/// The permission required for granting access to backlog management. For stakeholder, this would disabled for private project and enabled for public project.
/// Introduced in Dev17 / VSO (M137).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 AgileToolsBacklogManagement = 16777216;
/// <summary>
/// The permission required for granting access to backlog management. For stakeholder, this is always disabled.
/// Introduced in Dev17 / VSO (M150).
/// </summary>
/// <remarks>
/// This permission was excluded from AllPermissions to avoid being unintentionally granted.
/// </remarks>
public static readonly Int32 AgileToolsPlans = 33554432;
public static readonly Int32 AllPermissions =
GenericRead |
GenericWrite |
Delete |
PublishTestResults |
AdministerBuild |
StartBuild |
EditBuildStatus |
UpdateBuild |
DeleteTestResults |
ViewTestResults |
ManageTestEnvironments |
ManageTestConfigurations |
WorkItemDelete |
WorkItemMove |
WorkItemPermanentlyDelete |
Rename |
ManageProperties |
BypassRules |
SuppressNotifications |
UpdateVisibility |
ChangeProjectsProcess;
public const String ProjectTokenPrefix = "$PROJECT:";
public static String GetToken(String projectUri)
{
if (String.IsNullOrEmpty(projectUri) || !projectUri.StartsWith(ProjectTokenPrefix, StringComparison.OrdinalIgnoreCase))
{
if (projectUri == null)
{
projectUri = String.Empty;
}
return ProjectTokenPrefix + projectUri + ":";
}
return projectUri + ":";
}
}
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ContentValidationSecurityConstants
{
public static readonly Guid NamespaceId = new Guid("B1982126-CB90-4479-BDFD-CBF193241CB8");
public static readonly string ViolationsToken = "Violations";
public const int Read = 1;
public const int Write = 2;
}
public enum WinHttpErrorCode
{
WINHTTP_ERROR_BASE = 12000,

View File

@@ -124,9 +124,6 @@ namespace GitHub.Services.Common
LogException = (bool)info.GetValue("m_logException", typeof(bool));
ReportException = (bool)info.GetValue("m_reportException", typeof(bool));
ErrorCode = (int)info.GetValue("m_errorCode", typeof(int));
#if !NETSTANDARD
LogLevel = (EventLogEntryType)info.GetValue("m_logLevel", typeof(EventLogEntryType));
#endif
EventId = (int)info.GetValue("m_eventId", typeof(int));
}
@@ -137,9 +134,6 @@ namespace GitHub.Services.Common
info.AddValue("m_logException", LogException);
info.AddValue("m_reportException", ReportException);
info.AddValue("m_errorCode", ErrorCode);
#if !NETSTANDARD
info.AddValue("m_logLevel", LogLevel);
#endif
info.AddValue("m_eventId", EventId);
}
@@ -157,22 +151,6 @@ namespace GitHub.Services.Common
}
}
#if !NETSTANDARD
/// <summary>The event log entry type to use when logging the exception</summary>
/// <value>One of the event log entry types: </value>
public EventLogEntryType LogLevel
{
get
{
return m_logLevel;
}
set
{
m_logLevel = value;
}
}
#endif
/// <summary>A user-defined error code.</summary>
public int ErrorCode
{
@@ -279,10 +257,6 @@ namespace GitHub.Services.Common
private bool m_reportException;
private int m_errorCode;
#if !NETSTANDARD
private EventLogEntryType m_logLevel = EventLogEntryType.Warning;
#endif
private int m_eventId = DefaultExceptionEventId;
//From EventLog.cs in Framework.

View File

@@ -33,13 +33,7 @@ namespace GitHub.Services.Common
public VssHttpMessageHandler(
VssCredentials credentials,
VssHttpRequestSettings settings)
: this(credentials, settings,
#if !NETSTANDARD
new WebRequestHandler()
#else
new HttpClientHandler()
#endif
)
: this(credentials, settings, new HttpClientHandler())
{
}
@@ -76,13 +70,7 @@ namespace GitHub.Services.Common
m_transportHandler = transportHandler;
}
#if NETSTANDARD
//.Net Core does not recognize CredentialCache.DefaultCredentials if we wrap them with CredentialWrapper
bool isDefaultCredentials = credentials != null && credentials.Windows != null && credentials.Windows.UseDefaultCredentials;
ApplySettings(m_transportHandler, isDefaultCredentials ? CredentialCache.DefaultCredentials : m_credentialWrapper, this.Settings);
#else
ApplySettings(m_transportHandler, m_credentialWrapper, this.Settings);
#endif
}
/// <summary>
@@ -139,35 +127,6 @@ namespace GitHub.Services.Common
var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request);
traceInfo?.TraceHandlerStartTime();
#if !NETSTANDARD
// This action is deferred from ApplySettings because we want don't want to do it if we aren't
// talking to an HTTPS endpoint.
if (!m_appliedClientCertificatesToTransportHandler &&
request.RequestUri.Scheme == "https")
{
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
if (webRequestHandler != null &&
this.Settings.ClientCertificateManager != null &&
this.Settings.ClientCertificateManager.ClientCertificates != null &&
this.Settings.ClientCertificateManager.ClientCertificates.Count > 0)
{
webRequestHandler.ClientCertificates.AddRange(this.Settings.ClientCertificateManager.ClientCertificates);
}
m_appliedClientCertificatesToTransportHandler = true;
}
if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
request.RequestUri.Scheme == "https")
{
WebRequestHandler webRequestHandler = m_transportHandler as WebRequestHandler;
if (webRequestHandler != null &&
this.Settings.ServerCertificateValidationCallback != null)
{
webRequestHandler.ServerCertificateValidationCallback = this.Settings.ServerCertificateValidationCallback;
}
m_appliedServerCertificateValidationCallbackToTransportHandler = true;
}
#else
if (!m_appliedClientCertificatesToTransportHandler &&
request.RequestUri.Scheme == "https")
{
@@ -201,7 +160,6 @@ namespace GitHub.Services.Common
{
request.Version = HttpVersion.Version11;
}
#endif
IssuedToken token = null;
IssuedTokenProvider provider;
@@ -540,16 +498,11 @@ namespace GitHub.Services.Common
}
}
private static IWebProxy s_defaultWebProxy =
#if !NETSTANDARD
WebRequest.DefaultWebProxy;
#else
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
// This needs to be investigated further.
null;
#endif
// setting this to WebRequest.DefaultWebProxy in NETSTANDARD is causing a System.PlatformNotSupportedException
//.in System.Net.SystemWebProxy.IsBypassed. Comment in IsBypassed method indicates ".NET Core and .NET Native
// code will handle this exception and call into WinInet/WinHttp as appropriate to use the system proxy."
// This needs to be investigated further.
private static IWebProxy s_defaultWebProxy = null;
/// <summary>
/// Allows you to set a proxy to be used by all VssHttpMessageHandler requests without affecting the global WebRequest.DefaultWebProxy. If not set it returns the WebRequest.DefaultWebProxy.
@@ -570,8 +523,6 @@ namespace GitHub.Services.Common
}
set
{
// requested by Insights team to be able to set a default Proxy that only affects this handler.
// see following bug for details: https://mseng.visualstudio.com/DefaultCollection/VSOnline/_workitems#_a=edit&id=425575&triage=true
s_defaultWebProxy = value;
}
}
@@ -585,13 +536,9 @@ namespace GitHub.Services.Common
private bool m_appliedServerCertificateValidationCallbackToTransportHandler;
private readonly HttpMessageHandler m_transportHandler;
#if NETSTANDARD
//.Net Core does not attempt NTLM schema on Linux, unless ICredentials is a CredentialCache instance
//This workaround may not be needed after this corefx fix is consumed: https://github.com/dotnet/corefx/pull/7923
private sealed class CredentialWrapper : CredentialCache, ICredentials
#else
private sealed class CredentialWrapper : ICredentials
#endif
{
public ICredentials InnerCredentials
{

View File

@@ -44,9 +44,7 @@ namespace GitHub.Services.Common
this.SuppressFedAuthRedirects = true;
this.ClientCertificateManager = null;
this.ServerCertificateValidationCallback = null;
#if NETSTANDARD
this.UseHttp11 = false;
#endif
// If different, we'll also add CurrentCulture to the request headers,
// but UICulture was added first, so it gets first preference
@@ -99,9 +97,7 @@ namespace GitHub.Services.Common
this.ClientCertificateManager = copy.ClientCertificateManager;
this.ServerCertificateValidationCallback = copy.ServerCertificateValidationCallback;
this.MaxRetryRequest = copy.MaxRetryRequest;
#if NETSTANDARD
this.UseHttp11 = copy.UseHttp11;
#endif
}
/// <summary>
@@ -144,7 +140,6 @@ namespace GitHub.Services.Common
set;
}
#if NETSTANDARD
/// <summary>
/// The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
/// This causes problems with some versions of the Curl handler on Linux.
@@ -156,7 +151,6 @@ namespace GitHub.Services.Common
get;
set;
}
#endif
/// <summary>
/// Gets or sets the maximum size allowed for response content buffering.
@@ -266,15 +260,6 @@ namespace GitHub.Services.Common
set;
}
#if !NETSTANDARD
/// <summary>
/// Optional implementation used to validate server certificate validation
/// </summary>
public RemoteCertificateValidationCallback ServerCertificateValidationCallback
{
get; set;
}
#else
/// <summary>
/// Optional implementation used to validate server certificate validation
/// </summary>
@@ -283,7 +268,6 @@ namespace GitHub.Services.Common
get;
set;
}
#endif
/// <summary>
/// Number of times to retry a request that has an ambient failure
@@ -359,13 +343,11 @@ namespace GitHub.Services.Common
request.Headers.Add(Internal.HttpHeaders.VssAgentHeader, this.AgentId);
}
#if NETSTANDARD
// Content is being sent as chunked by default in dotnet5.4, which differs than the .net 4.5 behaviour.
if (request.Content != null && !request.Content.Headers.ContentLength.HasValue && !request.Headers.TransferEncodingChunked.HasValue)
{
request.Content.Headers.ContentLength = request.Content.ReadAsByteArrayAsync().Result.Length;
}
#endif
return true;
}

View File

@@ -216,21 +216,13 @@ namespace GitHub.Services.Common
}
}
}
#if !NETSTANDARD
else if (ex is System.Data.Services.Client.DataServiceRequestException ||
ex is System.Data.Services.Client.DataServiceClientException)
{
// WCF exceptions
return true;
}
#endif
return false;
}
/// <summary>
/// Gets the HttpStatusCode which represents a throttling error.
/// </summary>
/// <summary>
/// Gets the HttpStatusCode which represents a throttling error.
/// </summary>
public const HttpStatusCode TooManyRequests = (HttpStatusCode)429;
}
}

View File

@@ -10,30 +10,12 @@ namespace GitHub.Services.Common
// See Toolsets\Version\Version.props for more details.
public const String MajorVersion = "16";
public const String MinorVersion = "0";
public const String BuildVersion = "65000";
public const String PatchVersion = "0";
public const String ProductVersion = MajorVersion + "." + MinorVersion;
// Assembly version (i.e. strong name)
public const String AssemblyMajorVersion = "16";
public const String AssemblyMinorVersion = "0";
public const String AssemblyBuildVersion = "0";
public const String AssemblyPatchVersion = "0";
public const String AssemblyVersion = AssemblyMajorVersion + "." + AssemblyMinorVersion + "." + AssemblyBuildVersion + "." + AssemblyPatchVersion;
// File version
public const String FileMajorVersion = "16";
public const String FileMinorVersion = "255";
public const String FileBuildVersion = "65000";
public const String FilePatchVersion = "0";
public const String FileVersion = FileMajorVersion + "." + FileMinorVersion + "." + FileBuildVersion + "." + FilePatchVersion;
// Derived versions
public const String TfsMajorVersion = "8";
public const String TfsMinorVersion = "0";
public const String TfsProductVersion = TfsMajorVersion + "." + TfsMinorVersion;
// On-premises TFS install folder
public const String TfsInstallDirectory = "Azure DevOps Server 2019";
public const String ActionsProductVersion = "8.0";
}
}

View File

@@ -1,80 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.Common.Internal;
using GitHub.Services.WebApi;
using GitHub.Services.WebApi.Patch;
using GitHub.Services.WebApi.Patch.Json;
namespace GitHub.Core.WebApi
{
[GenerateAllConstants]
public enum ProjectState
{
/// <summary>
/// Project is in the process of being deleted.
/// </summary>
[EnumMember]
Deleting = 2,
/// <summary>
/// Project is in the process of being created.
/// </summary>
[EnumMember]
New = 0,
/// <summary>
/// Project is completely created and ready to use.
/// </summary>
[EnumMember]
WellFormed = 1,
/// <summary>
/// Project has been queued for creation, but the process has not yet started.
/// </summary>
[EnumMember]
CreatePending = 3,
/// <summary>
/// All projects regardless of state.
/// </summary>
[EnumMember]
All = -1, // Used for filtering.
/// <summary>
/// Project has not been changed.
/// </summary>
[EnumMember]
Unchanged = -2, // Used for updating projects.
/// <summary>
/// Project has been deleted.
/// </summary>
[EnumMember]
Deleted = 4, // Used for the project history.
}
public enum ProjectVisibility // Stored as a TINYINT
{
[ClientInternalUseOnly]
Unchanged = -1, // Used for updating projects.
/// <summary>
/// The project is only visible to users with explicit access.
/// </summary>
Private = 0,
/// <summary>
/// Enterprise level project visibility
/// </summary>
[ClientInternalUseOnly(omitFromTypeScriptDeclareFile: false)]
Organization = 1,
/// <summary>
/// The project is visible to all.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
Public = 2,
[ClientInternalUseOnly]
SystemPrivate = 3 // Soft-deleted projects
}
}

View File

@@ -1,105 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.Core.WebApi
{
/// <summary>
/// Represents a shallow reference to a TeamProject.
/// </summary>
[DataContract]
public class TeamProjectReference : ISecuredObject
{
/// <summary>
/// Default constructor to ensure we set up the project state correctly for serialization.
/// </summary>
public TeamProjectReference()
{
State = ProjectState.Unchanged;
Visibility = ProjectVisibility.Unchanged;
}
/// <summary>
/// Project identifier.
/// </summary>
[DataMember(Order = 0, EmitDefaultValue = false)]
public Guid Id { get; set; }
/// <summary>
/// Project abbreviation.
/// </summary>
[DataMember(Order = 1, EmitDefaultValue = false)]
public string Abbreviation { get; set; }
/// <summary>
/// Project name.
/// </summary>
[DataMember(Order = 2, EmitDefaultValue = false)]
public string Name { get; set; }
/// <summary>
/// The project's description (if any).
/// </summary>
[DataMember(Order = 3, EmitDefaultValue = false)]
public string Description { get; set; }
/// <summary>
/// Url to the full version of the object.
/// </summary>
[DataMember(Order = 4, EmitDefaultValue = false)]
public string Url { get; set; }
/// <summary>
/// Project state.
/// </summary>
[DataMember(Order = 5)]
public ProjectState State { get; set; }
/// <summary>
/// Project revision.
/// </summary>
[DataMember(Order = 6, EmitDefaultValue = false)]
public Int64 Revision { get; set; }
/// <summary>
/// Project visibility.
/// </summary>
[DataMember(Order = 7)]
public ProjectVisibility Visibility { get; set; }
/// <summary>
/// Url to default team identity image.
/// </summary>
[DataMember(Order = 8, EmitDefaultValue = false)]
public String DefaultTeamImageUrl { get; set; }
/// <summary>
/// Project last update time.
/// </summary>
[DataMember(Order = 9)]
public DateTime LastUpdateTime { get; set; }
#region ISecuredObject
Guid ISecuredObject.NamespaceId => NamespaceId;
int ISecuredObject.RequiredPermissions => RequiredPermissions;
string ISecuredObject.GetToken()
{
return GetToken();
}
protected virtual Guid NamespaceId => TeamProjectSecurityConstants.NamespaceId;
protected virtual int RequiredPermissions => TeamProjectSecurityConstants.GenericRead;
protected virtual string GetToken()
{
// WE DON'T CARE THIS FOR NOW
return TeamProjectSecurityConstants.GetToken(Id.ToString("D"));
}
#endregion
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class AuthorizationHeader : BaseSecuredObject
{
[DataMember(EmitDefaultValue = false)]
public String Name { get; set; }
[DataMember(EmitDefaultValue = false)]
public String Value { get; set; }
}
}

View File

@@ -1,149 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
/// <summary>
/// Represents binding of data source for the service endpoint request.
/// </summary>
[DataContract]
public class DataSourceBindingBase : BaseSecuredObject
{
public DataSourceBindingBase()
{
}
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected DataSourceBindingBase(DataSourceBindingBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.DataSourceName = inputDefinitionToClone.DataSourceName;
this.EndpointId = inputDefinitionToClone.EndpointId;
this.Target = inputDefinitionToClone.Target;
this.ResultTemplate = inputDefinitionToClone.ResultTemplate;
this.EndpointUrl = inputDefinitionToClone.EndpointUrl;
this.ResultSelector = inputDefinitionToClone.ResultSelector;
this.RequestVerb = inputDefinitionToClone.RequestVerb;
this.RequestContent = inputDefinitionToClone.RequestContent;
this.CallbackContextTemplate = inputDefinitionToClone.CallbackContextTemplate;
this.CallbackRequiredTemplate = inputDefinitionToClone.CallbackRequiredTemplate;
this.InitialContextTemplate = inputDefinitionToClone.InitialContextTemplate;
inputDefinitionToClone.Parameters.Copy(this.Parameters);
this.CloneHeaders(inputDefinitionToClone.Headers);
}
/// <summary>
/// Gets or sets the name of the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public string DataSourceName { get; set; }
/// <summary>
/// Gets or sets the parameters for the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public Dictionary<string, string> Parameters
{
get
{
if (m_parameters == null)
{
m_parameters = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
return m_parameters;
}
}
public DataSourceBindingBase Clone(ISecuredObject securedObject)
{
return new DataSourceBindingBase(this, securedObject);
}
private void CloneHeaders(List<AuthorizationHeader> headers)
{
if (headers == null)
{
return;
}
this.Headers = headers.Select(header => new AuthorizationHeader { Name = header.Name, Value = header.Value }).ToList();
}
/// <summary>
/// Gets or sets the endpoint Id.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String EndpointId { get; set; }
/// <summary>
/// Gets or sets the target of the data source.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Target { get; set; }
/// <summary>
/// Gets or sets the result template.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String ResultTemplate { get; set; }
/// <summary>
/// Gets or sets http request verb
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String RequestVerb { get; set; }
/// <summary>
/// Gets or sets http request body
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String RequestContent { get; set; }
/// <summary>
/// Gets or sets the url of the service endpoint.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String EndpointUrl { get; set; }
/// <summary>
/// Gets or sets the result selector.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String ResultSelector { get; set; }
/// <summary>
/// Pagination format supported by this data source(ContinuationToken/SkipTop).
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String CallbackContextTemplate { get; set; }
/// <summary>
/// Subsequent calls needed?
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String CallbackRequiredTemplate { get; set; }
/// <summary>
/// Defines the initial value of the query params
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String InitialContextTemplate { get; set; }
/// <summary>
/// Gets or sets the authorization headers.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public List<AuthorizationHeader> Headers { get; set; }
private Dictionary<String, String> m_parameters;
}
}

View File

@@ -1,163 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using GitHub.DistributedTask.WebApi;
using GitHub.Services.Common;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class ProcessParameters : BaseSecuredObject
{
public ProcessParameters()
: this(null)
{
}
public ProcessParameters(ISecuredObject securedObject)
: this(null, securedObject)
{
}
private ProcessParameters(ProcessParameters toClone, ISecuredObject securedObject)
: base(securedObject)
{
if (toClone != null)
{
if (toClone.Inputs.Count > 0)
{
Inputs.AddRange(toClone.Inputs.Select(i => i.Clone(securedObject)));
}
if (toClone.SourceDefinitions.Count > 0)
{
SourceDefinitions.AddRange(toClone.SourceDefinitions.Select(sd => sd.Clone(securedObject)));
}
if (toClone.DataSourceBindings.Count > 0)
{
DataSourceBindings.AddRange(toClone.DataSourceBindings.Select(dsb => dsb.Clone(securedObject)));
}
}
}
public IList<TaskInputDefinitionBase> Inputs
{
get
{
if (m_inputs == null)
{
m_inputs = new List<TaskInputDefinitionBase>();
}
return m_inputs;
}
}
public IList<TaskSourceDefinitionBase> SourceDefinitions
{
get
{
if (m_sourceDefinitions == null)
{
m_sourceDefinitions = new List<TaskSourceDefinitionBase>();
}
return m_sourceDefinitions;
}
}
public IList<DataSourceBindingBase> DataSourceBindings
{
get
{
if (m_dataSourceBindings == null)
{
m_dataSourceBindings = new List<DataSourceBindingBase>();
}
return m_dataSourceBindings;
}
}
public override int GetHashCode()
{
return base.GetHashCode();
}
public override bool Equals(object obj)
{
var processParameters2 = obj as ProcessParameters;
if (processParameters2 == null)
{
return false;
}
if (this.Inputs == null && processParameters2.Inputs == null)
{
return true;
}
if ((this.Inputs != null && processParameters2.Inputs == null)
|| (this.Inputs == null && processParameters2.Inputs != null))
{
return false;
}
if (this.Inputs.Count != processParameters2.Inputs.Count)
{
return false;
}
var orderedProcessParameters1 = this.Inputs.Where(i => i != null).OrderBy(i => i.Name);
var orderedProcessParameters2 = processParameters2.Inputs.Where(i => i != null).OrderBy(i => i.Name);
if (!orderedProcessParameters1.OrderBy(i => i.Name).SequenceEqual(orderedProcessParameters2))
{
return false;
}
return true;
}
public ProcessParameters Clone(ISecuredObject securedObject = null)
{
return new ProcessParameters(this, securedObject);
}
[OnDeserialized]
private void OnDeserialized(StreamingContext context)
{
SerializationHelper.Copy(ref m_serializedInputs, ref m_inputs, true);
SerializationHelper.Copy(ref m_serializedSourceDefinitions, ref m_sourceDefinitions, true);
SerializationHelper.Copy(ref m_serializedDataSourceBindings, ref m_dataSourceBindings, true);
}
[OnSerializing]
private void OnSerializing(StreamingContext context)
{
SerializationHelper.Copy(ref m_inputs, ref m_serializedInputs);
SerializationHelper.Copy(ref m_sourceDefinitions, ref m_serializedSourceDefinitions);
SerializationHelper.Copy(ref m_dataSourceBindings, ref m_serializedDataSourceBindings);
}
[OnSerialized]
private void OnSerialized(StreamingContext context)
{
m_serializedInputs = null;
m_serializedSourceDefinitions = null;
m_serializedDataSourceBindings = null;
}
[DataMember(Name = "Inputs", EmitDefaultValue = false)]
private List<TaskInputDefinitionBase> m_serializedInputs;
[DataMember(Name = "SourceDefinitions", EmitDefaultValue = false)]
private List<TaskSourceDefinitionBase> m_serializedSourceDefinitions;
[DataMember(Name = "DataSourceBindings", EmitDefaultValue = false)]
private List<DataSourceBindingBase> m_serializedDataSourceBindings;
private List<TaskInputDefinitionBase> m_inputs;
private List<TaskSourceDefinitionBase> m_sourceDefinitions;
private List<DataSourceBindingBase> m_dataSourceBindings;
}
}

View File

@@ -1,254 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskInputDefinitionBase : BaseSecuredObject
{
public TaskInputDefinitionBase()
{
InputType = TaskInputType.String;
DefaultValue = String.Empty;
Required = false;
HelpMarkDown = String.Empty;
}
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected TaskInputDefinitionBase(TaskInputDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.DefaultValue = inputDefinitionToClone.DefaultValue;
this.InputType = inputDefinitionToClone.InputType;
this.Label = inputDefinitionToClone.Label;
this.Name = inputDefinitionToClone.Name;
this.Required = inputDefinitionToClone.Required;
this.HelpMarkDown = inputDefinitionToClone.HelpMarkDown;
this.VisibleRule = inputDefinitionToClone.VisibleRule;
this.GroupName = inputDefinitionToClone.GroupName;
if (inputDefinitionToClone.Validation != null)
{
this.Validation = inputDefinitionToClone.Validation.Clone(securedObject);
}
if (inputDefinitionToClone.m_aliases != null)
{
this.m_aliases = new List<String>(inputDefinitionToClone.m_aliases);
}
if (inputDefinitionToClone.m_options != null)
{
this.m_options = new Dictionary<String, String>(inputDefinitionToClone.m_options);
}
if (inputDefinitionToClone.m_properties != null)
{
this.m_properties = new Dictionary<String, String>(inputDefinitionToClone.m_properties);
}
}
public IList<String> Aliases
{
get
{
if (m_aliases == null)
{
m_aliases = new List<String>();
}
return m_aliases;
}
}
[DataMember(EmitDefaultValue = false)]
public String Name
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Label
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String DefaultValue
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public Boolean Required
{
get;
set;
}
[DataMember(Name = "Type")]
public String InputType
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String HelpMarkDown
{
get;
set;
}
// VisibleRule should specify the condition at which this input is to be shown/displayed
// Typical format is "NAME OF THE DEPENDENT INPUT = VALUE TOBE BOUND"
[DataMember(EmitDefaultValue = false)]
public string VisibleRule
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public string GroupName
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public TaskInputValidation Validation
{
get;
set;
}
public Dictionary<String, String> Options
{
get
{
if (m_options == null)
{
m_options = new Dictionary<String, String>();
}
return m_options;
}
}
public Dictionary<String, String> Properties
{
get
{
if (m_properties == null)
{
m_properties = new Dictionary<String, String>();
}
return m_properties;
}
}
public virtual TaskInputDefinitionBase Clone(
ISecuredObject securedObject)
{
return new TaskInputDefinitionBase(this, securedObject);
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ this.DefaultValue.GetHashCode() ^ this.Label.GetHashCode();
}
public override bool Equals(object obj)
{
var taskInput2 = obj as TaskInputDefinitionBase;
if (taskInput2 == null
|| !string.Equals(InputType, taskInput2.InputType, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(Label, taskInput2.Label, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(Name, taskInput2.Name, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(GroupName, taskInput2.GroupName, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(DefaultValue, taskInput2.DefaultValue, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(HelpMarkDown, taskInput2.HelpMarkDown, StringComparison.OrdinalIgnoreCase)
|| !string.Equals(VisibleRule, taskInput2.VisibleRule, StringComparison.OrdinalIgnoreCase)
|| !this.Required.Equals(taskInput2.Required))
{
return false;
}
if (!AreListsEqual(Aliases, taskInput2.Aliases)
|| !AreDictionariesEqual(Properties, taskInput2.Properties)
|| !AreDictionariesEqual(Options, taskInput2.Options))
{
return false;
}
if ((Validation != null && taskInput2.Validation == null)
|| (Validation == null && taskInput2.Validation != null)
|| ((Validation != null && taskInput2.Validation != null)
&& !Validation.Equals(taskInput2.Validation)))
{
return false;
}
return true;
}
private bool AreDictionariesEqual(Dictionary<String, String> input1, Dictionary<String, String> input2)
{
if (input1 == null && input2 == null)
{
return true;
}
if ((input1 == null && input2 != null)
|| (input1 != null && input2 == null)
|| (input1.Count != input2.Count))
{
return false;
}
foreach (var key in input1.Keys)
{
if (!(input2.ContainsKey(key) && String.Equals(input1[key], input2[key], StringComparison.OrdinalIgnoreCase)))
{
return false;
}
}
return true;
}
private Boolean AreListsEqual(IList<String> list1, IList<String> list2)
{
if (list1.Count != list2.Count)
{
return false;
}
for (Int32 i = 0; i < list1.Count; i++)
{
if (!String.Equals(list1[i], list2[i], StringComparison.OrdinalIgnoreCase))
{
return false;
}
}
return true;
}
[DataMember(Name = "Aliases", EmitDefaultValue = false)]
private List<String> m_aliases;
[DataMember(Name = "Options", EmitDefaultValue = false)]
private Dictionary<String, String> m_options;
[DataMember(Name = "Properties", EmitDefaultValue = false)]
private Dictionary<String, String> m_properties;
}
}

View File

@@ -1,14 +0,0 @@
using System.Runtime.Serialization;
using System;
namespace GitHub.DistributedTask.Common.Contracts
{
public static class TaskInputType
{
public const String String = "string";
public const String Repository = "repository";
public const String Boolean = "boolean";
public const String KeyValue = "keyvalue";
public const String FilePath = "filepath";
}
}

View File

@@ -1,59 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskInputValidation : BaseSecuredObject
{
public TaskInputValidation()
{
}
private TaskInputValidation(TaskInputValidation toClone, ISecuredObject securedObject)
: base(securedObject)
{
if (toClone != null)
{
this.Expression = toClone.Expression;
this.Message = toClone.Message;
}
}
/// <summary>
/// Conditional expression
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Expression
{
get;
set;
}
/// <summary>
/// Message explaining how user can correct if validation fails
/// </summary>
[DataMember(EmitDefaultValue = false)]
public String Message
{
get;
set;
}
public override int GetHashCode()
{
return Expression.GetHashCode() ^ Message.GetHashCode();
}
public TaskInputValidation Clone()
{
return this.Clone(null);
}
public TaskInputValidation Clone(ISecuredObject securedObject)
{
return new TaskInputValidation(this, securedObject);
}
}
}

View File

@@ -1,74 +0,0 @@
using System;
using System.Runtime.Serialization;
using GitHub.Services.WebApi;
namespace GitHub.DistributedTask.Common.Contracts
{
[DataContract]
public class TaskSourceDefinitionBase : BaseSecuredObject
{
public TaskSourceDefinitionBase()
{
AuthKey = String.Empty;
Endpoint = String.Empty;
Selector = String.Empty;
Target = String.Empty;
KeySelector = String.Empty;
}
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone)
: this(inputDefinitionToClone, null)
{
}
protected TaskSourceDefinitionBase(TaskSourceDefinitionBase inputDefinitionToClone, ISecuredObject securedObject)
: base(securedObject)
{
this.Endpoint = inputDefinitionToClone.Endpoint;
this.Target = inputDefinitionToClone.Target;
this.AuthKey = inputDefinitionToClone.AuthKey;
this.Selector = inputDefinitionToClone.Selector;
this.KeySelector = inputDefinitionToClone.KeySelector;
}
public virtual TaskSourceDefinitionBase Clone(ISecuredObject securedObject)
{
return new TaskSourceDefinitionBase(this, securedObject);
}
[DataMember(EmitDefaultValue = false)]
public String Endpoint
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Target
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String AuthKey
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String Selector
{
get;
set;
}
[DataMember(EmitDefaultValue = false)]
public String KeySelector
{
get;
set;
}
}
}

View File

@@ -1,22 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class AndNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
foreach (ExpressionNode parameter in Parameters)
{
if (!parameter.EvaluateBoolean(context))
{
return false;
}
}
return true;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
namespace GitHub.DistributedTask.Expressions
{
internal sealed class CoalesceNode : FunctionNode
{
protected sealed override Boolean TraceFullyRealized => false;
protected sealed override Object EvaluateCore(EvaluationContext context)
{
EvaluationResult result = null;
foreach (ExpressionNode parameter in Parameters)
{
result = parameter.Evaluate(context);
if (result.Kind == ValueKind.Null)
{
continue;
}
if (result.Kind == ValueKind.String && String.IsNullOrEmpty(result.Value as String))
{
continue;
}
break;
}
return result?.Value;
}
}
}

View File

@@ -1,31 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JArrayAccessor : IReadOnlyArray
{
public JArrayAccessor(JArray jarray)
{
m_jarray = jarray;
}
public Int32 Count => m_jarray.Count;
public Object this[Int32 index] => m_jarray[index];
public IEnumerator<Object> GetEnumerator()
{
return m_jarray.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_jarray.GetEnumerator();
}
private readonly JArray m_jarray;
}
}

View File

@@ -1,56 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JObjectAccessor : IReadOnlyObject
{
public JObjectAccessor(JObject jobject)
{
m_jobject = jobject;
}
public Int32 Count => m_jobject.Count;
public IEnumerable<String> Keys => (m_jobject as IDictionary<String, JToken>).Keys;
// This uses Select. Calling .Values directly throws an exception.
public IEnumerable<Object> Values => (m_jobject as IDictionary<String, JToken>).Select(x => x.Value);
public Object this[String key] => m_jobject[key];
public Boolean ContainsKey(String key)
{
return (m_jobject as IDictionary<String, JToken>).ContainsKey(key);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return (m_jobject as IDictionary<String, JToken>).Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if ((m_jobject as IDictionary<String, JToken>).TryGetValue(key, out JToken val))
{
value = val;
return true;
}
value = null;
return false;
}
private readonly JObject m_jobject;
}
}

View File

@@ -1,106 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Serialization;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JsonDictionaryContractAccessor : IReadOnlyObject
{
public JsonDictionaryContractAccessor(
JsonDictionaryContract contract,
Object obj)
{
m_contract = contract;
m_obj = obj;
}
public Int32 Count
{
get
{
var genericMethod = s_getCountTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
return (Int32)genericMethod.Invoke(null, new[] { m_obj });
}
}
public IEnumerable<String> Keys
{
get
{
var genericMethod = s_getKeysTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
return genericMethod.Invoke(null, new[] { m_obj }) as IEnumerable<String>;
}
}
public IEnumerable<Object> Values => Keys.Select(x => this[x]);
public Object this[String key]
{
get
{
if (TryGetValue(key, out Object value))
{
return value;
}
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
}
}
public Boolean ContainsKey(String key)
{
return TryGetValue(key, out _);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
var genericMethod = s_tryGetValueTemplate.Value.MakeGenericMethod(m_contract.DictionaryValueType);
var tuple = genericMethod.Invoke(null, new[] { m_obj, key }) as Tuple<Boolean, Object>;
value = tuple.Item2;
return tuple.Item1;
}
private static Int32 GetCount<TValue>(IDictionary<String, TValue> dictionary)
{
return dictionary.Count;
}
private static IEnumerable<String> GetKeys<TValue>(IDictionary<String, TValue> dictionary)
{
return dictionary.Keys;
}
private static Tuple<Boolean, Object> TryGetValue<TValue>(
IDictionary<String, TValue> dictionary,
String key)
{
if (dictionary.TryGetValue(key, out TValue value))
{
return new Tuple<Boolean, Object>(true, value);
}
return new Tuple<Boolean, Object>(false, null);
}
private static Lazy<MethodInfo> s_getCountTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetCount), BindingFlags.NonPublic | BindingFlags.Static));
private static Lazy<MethodInfo> s_getKeysTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(GetKeys), BindingFlags.NonPublic | BindingFlags.Static));
private static Lazy<MethodInfo> s_tryGetValueTemplate = new Lazy<MethodInfo>(() => typeof(JsonDictionaryContractAccessor).GetTypeInfo().GetMethod(nameof(TryGetValue), BindingFlags.NonPublic | BindingFlags.Static));
private readonly JsonDictionaryContract m_contract;
private readonly Object m_obj;
}
}

View File

@@ -1,89 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Serialization;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class JsonObjectContractAccessor : IReadOnlyObject
{
public JsonObjectContractAccessor(
JsonObjectContract contract,
Object obj)
{
m_contract = contract;
m_obj = obj;
}
public Int32 Count => GetProperties().Count();
public IEnumerable<String> Keys => GetProperties().Select(x => x.PropertyName);
public IEnumerable<Object> Values => GetProperties().Select(x => x.ValueProvider.GetValue(m_obj));
public Object this[String key]
{
get
{
if (TryGetValue(key, out Object value))
{
return value;
}
throw new KeyNotFoundException(ExpressionResources.KeyNotFound(key));
}
}
public Boolean ContainsKey(String key)
{
return TryGetProperty(key, out _);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return Keys.Select(x => new KeyValuePair<String, Object>(x, this[x])).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if (TryGetProperty(key, out JsonProperty property))
{
value = property.ValueProvider.GetValue(m_obj);
return true;
}
value = null;
return false;
}
private IEnumerable<JsonProperty> GetProperties()
{
return m_contract.Properties.Where(x => !x.Ignored);
}
private Boolean TryGetProperty(
String key,
out JsonProperty property)
{
property = m_contract.Properties.GetClosestMatchProperty(key);
if (property != null && !property.Ignored)
{
return true;
}
property = null;
return false;
}
private readonly JsonObjectContract m_contract;
private readonly Object m_obj;
}
}

View File

@@ -1,30 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ListOfObjectAccessor : IReadOnlyArray
{
public ListOfObjectAccessor(IList<Object> list)
{
m_list = list;
}
public Int32 Count => m_list.Count;
public Object this[Int32 index] => m_list[index];
public IEnumerator<Object> GetEnumerator()
{
return m_list.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_list.GetEnumerator();
}
private readonly IList<Object> m_list;
}
}

View File

@@ -1,37 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ReadOnlyDictionaryOfStringObjectAccessor : IReadOnlyObject
{
public ReadOnlyDictionaryOfStringObjectAccessor(IReadOnlyDictionary<String, Object> dictionary)
{
m_dictionary = dictionary;
}
public Int32 Count => m_dictionary.Count;
public IEnumerable<String> Keys => m_dictionary.Keys;
public IEnumerable<Object> Values => m_dictionary.Values;
public Object this[String key] => m_dictionary[key];
public Boolean ContainsKey(String key) => m_dictionary.ContainsKey(key);
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator() => m_dictionary.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => m_dictionary.GetEnumerator();
public Boolean TryGetValue(
String key,
out Object value)
{
return m_dictionary.TryGetValue(key, out value);
}
private readonly IReadOnlyDictionary<String, Object> m_dictionary;
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace GitHub.DistributedTask.Expressions.CollectionAccessors
{
internal sealed class ReadOnlyDictionaryOfStringStringAccessor : IReadOnlyObject
{
public ReadOnlyDictionaryOfStringStringAccessor(IReadOnlyDictionary<String, String> dictionary)
{
m_dictionary = dictionary;
}
public Int32 Count => m_dictionary.Count;
public IEnumerable<String> Keys => m_dictionary.Keys;
public IEnumerable<Object> Values => m_dictionary.Values.OfType<Object>();
public Object this[String key] => m_dictionary[key];
public Boolean ContainsKey(String key)
{
return m_dictionary.ContainsKey(key);
}
public IEnumerator<KeyValuePair<String, Object>> GetEnumerator()
{
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return m_dictionary.Select(x => new KeyValuePair<String, Object>(x.Key, x.Value)).GetEnumerator();
}
public Boolean TryGetValue(
String key,
out Object value)
{
if (m_dictionary.TryGetValue(key, out String val))
{
value = val;
return true;
}
value = default;
return false;
}
private readonly IReadOnlyDictionary<String, String> m_dictionary;
}
}

Some files were not shown because too many files have changed in this diff Show More