├── .devcontainer
└── devcontainer.json
├── .github
└── workflows
│ └── stale.yml
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── .vsts-ci.yml
├── .vsts-pr.yml
├── Build.props
├── CONTRIBUTING.md
├── CredentialProvider.Microsoft.Tests
├── CredentialProvider.Microsoft.Tests.csproj
├── CredentialProviders
│ ├── Vsts
│ │ ├── AuthUtilTests.cs
│ │ ├── EnvironmentLock.cs
│ │ └── VstsCredentialProviderTests.cs
│ ├── VstsBuildTask
│ │ └── VstsBuildTaskCredentialProviderTests.cs
│ └── VstsBuildTaskServiceEndpoint
│ │ └── VstsBuildTaskServiceEndpointCredentialProviderTests.cs
├── Logging
│ └── LoggingTests.cs
└── Util
│ ├── FeedEndpointCredentialParserTests.cs
│ └── HttpClientFactoryTests.cs
├── CredentialProvider.Microsoft.VSIX
├── Microsoft.CredentialProvider.swixproj
├── Microsoft.CredentialProvider.swr
├── Microsoft.CredentialProvider.vsmanproj
├── nuget.config
└── packages.config
├── CredentialProvider.Microsoft
├── CredentialProvider.Microsoft.csproj
├── CredentialProvider.Microsoft.nuspec
├── CredentialProviderArgs.cs
├── CredentialProviders
│ ├── CredentialProviderBase.cs
│ ├── ICredentialProvider.cs
│ ├── Vsts
│ │ ├── IAuthUtil.cs
│ │ ├── IVstsSessionTokenClient.cs
│ │ ├── IVstsSessionTokenFromBearerTokenProvider.cs
│ │ ├── MsalTokenProvidersFactory.cs
│ │ ├── VstsCredentialProvider.cs
│ │ ├── VstsSessionToken.cs
│ │ ├── VstsSessionTokenClient.cs
│ │ └── VstsSessionTokenFromBearerTokenProvider.cs
│ ├── VstsBuildTask
│ │ └── VstsBuildTaskCredentialProvider.cs
│ └── VstsBuildTaskServiceEndpoint
│ │ ├── VstsBuildTaskMsalTokenProvidersFactory.cs
│ │ └── VstsBuildTaskServiceEndpointCredentialProvider.cs
├── EULA_Microsoft Visual Studio Team Services Credential Provider.docx
├── Logging
│ ├── ConsoleLoggers.cs
│ ├── CredentialResult.cs
│ ├── ILogger.cs
│ ├── LogEveryMessageFileLogger.cs
│ ├── LoggerBase.cs
│ ├── MultiLogger.cs
│ ├── NuGetLoggerAdapter.cs
│ └── PluginConnectionLogger.cs
├── Program.cs
├── RequestHandlers
│ ├── GetAuthenticationCredentialsRequestHandler.cs
│ ├── GetOperationClaimsRequestHandler.cs
│ ├── InitializeRequestHandler.cs
│ ├── RequestHandlerBase.cs
│ ├── RequestHandlerCollection.cs
│ ├── SetCredentialsRequestHandler.cs
│ └── SetLogLevelRequestHandler.cs
├── Resources.resx
├── ThirdPartyNotices.txt
├── Util
│ ├── CertificateUtil.cs
│ ├── EncryptedFileWithPermissions.cs
│ ├── EnvUtil.cs
│ ├── ExtensionMethods.cs
│ ├── FeedEndpointCredentialsParser.cs
│ ├── HttpClientFactory.cs
│ ├── ICache.cs
│ └── SessionTokenCache.cs
├── helpericons.ico
└── runtimeconfig.template.json
├── Directory.Build.props
├── Directory.Build.targets
├── Directory.Packages.props
├── LICENSE
├── MicrosoftCredentialProvider.sln
├── README.md
├── SECURITY.md
├── build
├── build.yml
├── validate-install-script-bash.yml
├── validate-install-script-powershell.yml
└── validate-install-script.yml
├── helpers
├── installcredprovider.ps1
└── installcredprovider.sh
├── nuget.config
├── samples
└── dockerfile.sample.txt
├── src
├── Authentication.Tests
│ ├── AccountPriorityTests.cs
│ ├── Microsoft.Artifacts.Authentication.Tests.csproj
│ ├── MsalAuthenticationTests.cs
│ ├── MsalHttpClientFactoryTests.cs
│ ├── TokenProviderTests.cs
│ └── Usings.cs
├── Authentication
│ ├── AzureArtifacts.cs
│ ├── ITokenProvider.cs
│ ├── ITokenProvidersFactory.cs
│ ├── Microsoft.Artifacts.Authentication.csproj
│ ├── MsalCache.cs
│ ├── MsalConstants.cs
│ ├── MsalDeviceCodeTokenProvider.cs
│ ├── MsalExtensions.cs
│ ├── MsalHttpClientFactory.cs
│ ├── MsalIntegratedWindowsAuthTokenProvider.cs
│ ├── MsalInteractiveTokenProvider.cs
│ ├── MsalManagedIdentityTokenProvider.cs
│ ├── MsalServicePrincipalTokenProvider.cs
│ ├── MsalSilentTokenProvider.cs
│ ├── MsalTokenProviders.cs
│ ├── PlatformInformation.cs
│ ├── README.md
│ ├── Resources.resx
│ ├── TokenRequest.cs
│ ├── WindowsIntegratedAuth.cs
│ └── artifacts-icon.png
└── artifacts-credprovider-conda
│ ├── LICENSE
│ ├── README.md
│ ├── recipe
│ ├── bld.bat
│ ├── build.sh
│ └── meta.yaml
│ └── src
│ ├── activate
│ ├── artifacts-cred-activate.bat
│ ├── artifacts-cred-activate.ps1
│ ├── artifacts-cred-activate.sh
│ └── artifacts-cred.py
│ └── deactivate
│ ├── artifacts-cred-deactivate.bat
│ ├── artifacts-cred-deactivate.ps1
│ └── artifacts-cred-deactivate.sh
└── test.bat
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "image": "mcr.microsoft.com/devcontainers/dotnet",
3 | "features": {
4 | "ghcr.io/devcontainers/features/dotnet:1": {
5 | "version": "6.0"
6 | },
7 | "ghcr.io/microsoft/codespace-features/artifacts-helper:1": {
8 | "dotnet6": true
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: Mark stale issues
2 |
3 | on:
4 | schedule:
5 | - cron: "0 */12 * * *"
6 |
7 | jobs:
8 | stale:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | issues: write
12 | steps:
13 | - uses: actions/stale@v3
14 | with:
15 | repo-token: ${{ secrets.GITHUB_TOKEN }}
16 | stale-issue-message: 'This issue has had no activity in 90 days. Please comment if it is not actually stale.'
17 | stale-issue-label: 'stale'
18 | exempt-issue-labels: 'keep'
19 | days-before-stale: 90
20 | days-before-close: 7
21 | days-before-pr-close: -1
22 | enable-statistics: true
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
332 | .vscode
333 | !.vscode/launch.json
334 | !.vscode/tasks.json
335 |
336 | # MicroBuild build output
337 | MicroBuild
338 | keys
339 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": ".NET Core Launch (console)",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${workspaceFolder}/CredentialProvider.Microsoft/bin/Debug/net6.0/CredentialProvider.Microsoft.dll",
10 | "args": [
11 | "-Uri", "https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/artifacts-credprovider/nuget/v3/index.json",
12 | "-Verbosity", "Debug"
13 | ],
14 | "cwd": "${workspaceFolder}/CredentialProvider.Microsoft",
15 | "console": "integratedTerminal",
16 | "stopAtEntry": false
17 | },
18 | {
19 | "name": ".NET Core Attach",
20 | "type": "coreclr",
21 | "request": "attach"
22 | }
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/CredentialProvider.Microsoft/CredentialProvider.Microsoft.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "build sln",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "build",
22 | "${workspaceFolder}/CredentialProvider.Microsoft.sln",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/.vsts-ci.yml:
--------------------------------------------------------------------------------
1 | resources:
2 | - repo: self
3 |
4 | pool:
5 | vmImage: windows-latest
6 |
7 | trigger:
8 | - master
9 |
10 | variables:
11 | BuildConfiguration: 'Release'
12 | TeamName: 'Package Experience'
13 |
14 | stages:
15 | - stage: Build
16 | dependsOn: []
17 | jobs:
18 | - job: build
19 | steps:
20 | - template: build/build.yml
21 | parameters:
22 | nuspecProperties: 'VersionSuffix=CI-$(Build.BuildNumber)'
23 |
24 | - stage: Validate
25 | dependsOn: []
26 | jobs:
27 | - template: build/validate-install-script.yml
28 |
--------------------------------------------------------------------------------
/.vsts-pr.yml:
--------------------------------------------------------------------------------
1 | resources:
2 | - repo: self
3 |
4 | pool:
5 | vmImage: windows-latest
6 |
7 | trigger:
8 | - master
9 |
10 | variables:
11 | BuildConfiguration: 'Debug'
12 | TeamName: 'Package Experience'
13 |
14 | stages:
15 | - stage: Build
16 | dependsOn: []
17 | jobs:
18 | - job: build
19 | steps:
20 | - template: build/build.yml
21 | parameters:
22 | nuspecProperties: 'VersionSuffix=CI-$(Build.BuildNumber)'
23 |
24 | - stage: Validate
25 | dependsOn: []
26 | jobs:
27 | - template: build/validate-install-script.yml
28 |
--------------------------------------------------------------------------------
/Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 1.4.1
5 |
6 | netcoreapp3.1;net461;net481;net6.0;net8.0
7 |
8 |
9 |
10 | net8.0
11 |
12 |
13 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to the Azure Artifacts Credential Provider
2 |
3 | ### Building
4 |
5 | ```shell
6 | dotnet build CredentialProvider.Microsoft --configuration Release
7 | ```
8 |
9 | In this and subsequent examples, configuration can be either `debug` or `release`.
10 |
11 | ### Publishing
12 |
13 | ```shell
14 | dotnet publish CredentialProvider.Microsoft --configuration Release --framework net8.0
15 | ```
16 |
17 | ### Packing
18 |
19 | ```shell
20 | dotnet pack CredentialProvider.Microsoft --configuration Release
21 | ```
22 |
23 | For CI builds, you can append a pre-release version:
24 |
25 | ```shell
26 | dotnet pack CredentialProvider.Microsoft --configuration Release /p:NuspecProperties=VersionSuffix=MyCustomVersion-2
27 | ```
28 |
29 | ### Versioning
30 |
31 | When releasing a new version, update the CredentialProviderVersion property in Build.props
32 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.Tests/CredentialProvider.Microsoft.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | latest
6 | false
7 | true
8 | true
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.Tests/CredentialProviders/Vsts/EnvironmentLock.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts
11 | {
12 | internal static class EnvironmentLock
13 | {
14 | private static readonly Semaphore _lock = new Semaphore(1, 1);
15 | private static readonly Dictionary savedEnvVars = new Dictionary();
16 |
17 | public static async Task WaitAsync()
18 | {
19 | var tcs = new TaskCompletionSource();
20 | var waiter = new Thread(() => {
21 | try
22 | {
23 | tcs.SetResult(_lock.WaitOne(TimeSpan.FromSeconds(60)));
24 | }
25 | catch(Exception e)
26 | {
27 | tcs.SetException(e);
28 | }
29 | });
30 | waiter.Start();
31 | await tcs.Task;
32 |
33 | foreach(object nameObj in Environment.GetEnvironmentVariables().Keys)
34 | {
35 | string name = (string)nameObj;
36 | if (name.Contains("NUGET"))
37 | {
38 | savedEnvVars[name] = Environment.GetEnvironmentVariable(name);
39 | Environment.SetEnvironmentVariable(name, null);
40 | }
41 | }
42 |
43 | return new Releaser();
44 | }
45 |
46 | private sealed class Releaser : IDisposable
47 | {
48 | public void Dispose()
49 | {
50 | foreach(var envVar in savedEnvVars)
51 | {
52 | Environment.SetEnvironmentVariable(envVar.Key, envVar.Value);
53 | }
54 | savedEnvVars.Clear();
55 | _lock.Release();
56 | }
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.Tests/CredentialProviders/VstsBuildTask/VstsBuildTaskCredentialProviderTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using CredentialProvider.Microsoft.Tests.CredentialProviders.Vsts;
9 | using Microsoft.VisualStudio.TestTools.UnitTesting;
10 | using Moq;
11 | using NuGet.Protocol.Plugins;
12 | using NuGetCredentialProvider.CredentialProviders.VstsBuildTask;
13 | using NuGetCredentialProvider.Logging;
14 | using NuGetCredentialProvider.Util;
15 |
16 | namespace CredentialProvider.Microsoft.Tests.CredentialProviders.VstsBuildTask
17 | {
18 | [TestClass]
19 | public class VstsBuildTaskCredentialProviderTests
20 | {
21 | private readonly Uri testAuthority = new Uri("https://example.aad.authority.com");
22 |
23 | private Mock mockLogger;
24 |
25 | private VstsBuildTaskCredentialProvider vstsCredentialProvider;
26 | private IDisposable environmentLock;
27 |
28 | [TestInitialize]
29 | public void TestInitialize()
30 | {
31 | mockLogger = new Mock();
32 |
33 | vstsCredentialProvider = new VstsBuildTaskCredentialProvider(mockLogger.Object);
34 | environmentLock = EnvironmentLock.WaitAsync().Result;
35 | ResetEnvVars();
36 | }
37 |
38 | [TestCleanup]
39 | public virtual void TestCleanup()
40 | {
41 | ResetEnvVars();
42 | environmentLock?.Dispose();
43 | }
44 |
45 | private void ResetEnvVars()
46 | {
47 | Environment.SetEnvironmentVariable(EnvUtil.BuildTaskUriPrefixes, null);
48 | Environment.SetEnvironmentVariable(EnvUtil.BuildTaskAccessToken, null);
49 | }
50 |
51 | [TestMethod]
52 | public async Task CanProvideCredentials_ReturnsFalseWhenEnvironmentVariablesAreNotSet()
53 | {
54 | Uri sourceUri = new Uri(@"http://example.pkgs.vsts.me/_packaging/TestFeed/nuget/v3/index.json");
55 | string uriPrefixesEnvVar = EnvUtil.BuildTaskUriPrefixes;
56 | string accessTokenEnvVar = EnvUtil.BuildTaskAccessToken;
57 |
58 | // Setting environment variables to null
59 | Environment.SetEnvironmentVariable(uriPrefixesEnvVar, null);
60 | Environment.SetEnvironmentVariable(accessTokenEnvVar, null);
61 |
62 | var result = await vstsCredentialProvider.CanProvideCredentialsAsync(sourceUri);
63 | Assert.AreEqual(false, result);
64 | }
65 |
66 | [TestMethod]
67 | public async Task CanProvideCredentials_ReturnsTrueForCorrectEnvironmentVariableAndMatchingSourceUri()
68 | {
69 | Uri sourceUri = new Uri(@"http://example.pkgs.vsts.me/_packaging/TestFeed/nuget/v3/index.json");
70 | string uriPrefixesEnvVar = EnvUtil.BuildTaskUriPrefixes;
71 | string accessTokenEnvVar = EnvUtil.BuildTaskAccessToken;
72 |
73 | Environment.SetEnvironmentVariable(uriPrefixesEnvVar, "http://example.pkgs.vsts.me/");
74 | Environment.SetEnvironmentVariable(accessTokenEnvVar, "accessToken");
75 |
76 | var result = await vstsCredentialProvider.CanProvideCredentialsAsync(sourceUri);
77 | Assert.AreEqual(true, result);
78 | }
79 |
80 | [TestMethod]
81 | public async Task HandleRequestAsync_ReturnsSuccess()
82 | {
83 | Uri sourceUri = new Uri(@"http://example.pkgs.vsts.me/_packaging/TestFeed/nuget/v3/index.json");
84 | string uriPrefixesEnvVar = EnvUtil.BuildTaskUriPrefixes;
85 | string accessTokenEnvVar = EnvUtil.BuildTaskAccessToken;
86 |
87 | Environment.SetEnvironmentVariable(uriPrefixesEnvVar, "http://example.pkgs.vsts.me/_packaging/TestFeed/nuget/v3/index.json");
88 | Environment.SetEnvironmentVariable(accessTokenEnvVar, "accessToken");
89 |
90 | var result = await vstsCredentialProvider.HandleRequestAsync(new GetAuthenticationCredentialsRequest(sourceUri, false, false, false), CancellationToken.None);
91 | Assert.AreEqual(result.ResponseCode, MessageResponseCode.Success);
92 | }
93 |
94 | [TestMethod]
95 | public async Task HandleRequestAsync_ReturnsErrorWhenPrefixDoesNotMatch()
96 | {
97 | Uri sourceUri = new Uri(@"http://example.pkgs.vsts.me/_packaging/TestFeed/nuget/v3/index.json");
98 | string uriPrefixesEnvVar = EnvUtil.BuildTaskUriPrefixes;
99 | string accessTokenEnvVar = EnvUtil.BuildTaskAccessToken;
100 |
101 | Environment.SetEnvironmentVariable(uriPrefixesEnvVar, "http://urlThatDoesNotMatch");
102 | Environment.SetEnvironmentVariable(accessTokenEnvVar, "accessToken");
103 |
104 | var result = await vstsCredentialProvider.HandleRequestAsync(new GetAuthenticationCredentialsRequest(sourceUri, false, false, false), CancellationToken.None);
105 | Assert.AreEqual(result.ResponseCode, MessageResponseCode.Error);
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.Tests/Logging/LoggingTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.VisualStudio.TestTools.UnitTesting;
6 | using Moq;
7 | using NuGet.Common;
8 | using NuGetCredentialProvider.Logging;
9 | using System.IO;
10 |
11 | namespace CredentialProvider.Microsoft.Tests.Logging
12 | {
13 | [TestClass]
14 | public class LoggingTests
15 | {
16 | Mock mockWriter = new Mock();
17 |
18 | [TestMethod]
19 | public void HumanFriendlyTextWriterLogger_EmitsLogLevelAndMessage()
20 | {
21 | mockWriter.Setup(x => x.WriteLine(It.IsAny()));
22 | HumanFriendlyTextWriterLogger logger = new HumanFriendlyTextWriterLogger(mockWriter.Object, writesToConsole: false);
23 | logger.SetLogLevel(LogLevel.Error);
24 | logger.Log(LogLevel.Error, allowOnConsole: true, message: "Something bad happened");
25 | mockWriter.Verify(x => x.WriteLine("[Error] [CredentialProvider]Something bad happened"));
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.Tests/Util/HttpClientFactoryTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Linq;
6 | using Microsoft.Artifacts.Authentication;
7 | using Microsoft.VisualStudio.TestTools.UnitTesting;
8 | using NuGetCredentialProvider.Util;
9 |
10 | namespace CredentialProvider.Microsoft.Tests.Util;
11 |
12 | [TestClass]
13 | public class HttpClientFactoryTests
14 | {
15 | [TestMethod]
16 | public void HttpClientFactory_UserAgent()
17 | {
18 | var httpClientFactory = HttpClientFactory.Default;
19 | var httpClient = httpClientFactory.GetHttpClient();
20 | var userAgent = httpClient.DefaultRequestHeaders.UserAgent;
21 |
22 | Assert.AreEqual(4, userAgent.Count);
23 | Assert.AreEqual(MsalHttpClientFactory.ProgramProduct, userAgent.ElementAt(0));
24 | Assert.AreEqual(MsalHttpClientFactory.ProgramComment, userAgent.ElementAt(1));
25 | Assert.AreEqual(MsalHttpClientFactory.ClrProduct, userAgent.ElementAt(2));
26 | Assert.AreEqual(MsalHttpClientFactory.ClrComment, userAgent.ElementAt(3));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.VSIX/Microsoft.CredentialProvider.swixproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(CredentialProviderVersion)
8 | 4966bd57-3289-44a3-9698-f750a35b726b
9 | 2.0
10 | neutral
11 | false
12 | Microsoft.CredentialProvider
13 | vsix
14 |
15 |
16 | true
17 |
18 |
19 |
20 | .\bin\$(Configuration)\
21 | $(OutputPath)
22 | ..\CredentialProvider.Microsoft\bin\$(Configuration)\net461\
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 | $(PackagePreprocessorDefinitions);PluginBinPath=$(PluginBinPath);SourceDir=$(SourceDir);Version=$(BuildVersion);ThirdPartyNotice=$(ThirdPartyNotice);ProductVersion=$(ProductVersion)
35 | $(BaseIntermediateOutputPath)$(Configuration)\
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.VSIX/Microsoft.CredentialProvider.vsmanproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | true
8 | true
9 | v4.8
10 |
11 |
12 |
13 | .\bin\$(Configuration)\
14 | $(OutputPath)
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.VSIX/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft.VSIX/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProvider.Microsoft.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | NuGetCredentialProvider
6 | latest
7 | helpericons.ico
8 | $(CredentialProviderVersion)
9 | CredentialProvider.Microsoft.nuspec
10 |
11 | $(NuspecProperties);Configuration=$(Configuration);Version=$(VersionPrefix)
12 | $(NuspecProperties);Configuration=$(Configuration);Version=$(VersionPrefix)-$(VersionSuffix)
13 | AnyCPU
14 | prompt
15 | 4
16 | $(NoWarn);NU5100
17 |
18 |
19 |
20 | true
21 | portable
22 | false
23 | $(DefineConstants);DEBUG;TRACE
24 |
25 |
26 | pdbonly
27 | true
28 | $(DefineConstants);TRACE
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProvider.Microsoft.nuspec:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.NuGet.CredentialProvider
5 | $Version$
6 | Microsoft Credential Provider for NuGet
7 | Microsoft
8 | microsoft,nugetvss
9 | http://go.microsoft.com/fwlink/?LinkId=329770
10 | http://go.microsoft.com/fwlink/?LinkId=623217
11 | http://go.microsoft.com/fwlink/?LinkId=615568
12 | true
13 | The Microsoft Credential Provider enables NuGet.exe 4.8+ to connect to Visual Studio Team Services Package Management NuGet feeds. See the NuGet docs for more info: http://docs.nuget.org/Consume/Credential-Providers
14 | © Microsoft Corporation. All rights reserved.
15 | Microsoft Visual Studio Team Services VSTS VSO VisualStudio Package Management NuGet
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviderArgs.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using NuGet.Common;
7 | using PowerArgs;
8 |
9 | namespace NuGetCredentialProvider
10 | {
11 | [ArgDescription("The Azure Artifacts credential provider can be used to acquire credentials for Azure Artifacts.\n" +
12 | "\n" +
13 | "Note: The Azure Artifacts Credential Provider is mainly intended for use via integrations with development tools such as .NET Core and nuget.exe.\n" +
14 | "While it can be used via this CLI in \"stand-alone mode\", please pay special attention to certain options such as -IsRetry below.\n" +
15 | "Failing to do so may result in obtaining invalid credentials.")]
16 | internal class CredentialProviderArgs
17 | {
18 | [ArgDescription("Used by nuget to run the credential helper in plugin mode")]
19 | public bool Plugin { get; set; }
20 |
21 | [ArgDescription("The package source URI for which credentials will be filled")]
22 | public Uri Uri { get; set; }
23 |
24 | [ArgDescription("If present and true, providers will not issue interactive prompts")]
25 | public bool NonInteractive { get; set; }
26 |
27 | [ArgDescription("If false / unset, INVALID CREDENTIALS MAY BE RETURNED. The caller is required to validate returned credentials themselves, and if invalid, should call the credential provider again with -IsRetry set. If true, the credential provider will obtain new credentials instead of returning potentially invalid credentials from the cache.")]
28 | public bool IsRetry { get; set; }
29 |
30 | [ArgDefaultValue(LogLevel.Information)]
31 | [ArgDescription("Display this amount of detail in the output")]
32 | public LogLevel Verbosity { get; set; }
33 |
34 | [ArgDescription("Prevents writing the password to standard output (for troubleshooting purposes)")]
35 | public bool RedactPassword { get; set; }
36 |
37 | [ArgShortcut("?")]
38 | [ArgShortcut("h")]
39 | [ArgDescription("Prints this help message")]
40 | public bool Help { get; set; }
41 |
42 | [ArgDefaultValue(true)]
43 | [ArgDescription("If true, user can be prompted with credentials through UI, if false, device flow must be used")]
44 | public bool CanShowDialog { get; set; }
45 |
46 | [ArgDescription("In standalone mode, format the results for human readability or as JSON. If JSON is selected, then logging (which may include Device Code instructions) will be logged to standard error instead of standard output.")]
47 | [ArgShortcut("F")]
48 | public OutputFormat OutputFormat { get; set; }
49 | }
50 |
51 | public enum OutputFormat
52 | {
53 | HumanReadable = 0,
54 | Json = 1
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/CredentialProviderBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using NuGet.Protocol.Plugins;
9 | using NuGetCredentialProvider.Logging;
10 | using NuGetCredentialProvider.Util;
11 |
12 | namespace NuGetCredentialProvider.CredentialProviders
13 | {
14 | ///
15 | /// Represents a base class for credential providers.
16 | ///
17 | public abstract class CredentialProviderBase : ICredentialProvider
18 | {
19 | ///
20 | /// A for when credentials could not be retrieved.
21 | ///
22 | protected static readonly GetAuthenticationCredentialsResponse NotFoundGetAuthenticationCredentialsResponse = new GetAuthenticationCredentialsResponse(
23 | username: null,
24 | password: null,
25 | message: null,
26 | authenticationTypes: null,
27 | responseCode: MessageResponseCode.NotFound);
28 |
29 | ///
30 | /// Initializes a new instance of the class.
31 | ///
32 | /// A to use for logging.
33 | protected CredentialProviderBase(ILogger logger)
34 | {
35 | Logger = logger;
36 | }
37 |
38 | public virtual bool IsCachable { get { return true; } }
39 |
40 | protected abstract string LoggingName { get; }
41 |
42 | ///
43 | /// Gets a to use for logging.
44 | ///
45 | protected ILogger Logger { get; }
46 |
47 | ///
48 | public abstract Task CanProvideCredentialsAsync(Uri uri);
49 |
50 | ///
51 | public virtual void Dispose()
52 | {
53 | }
54 |
55 | ///
56 | public abstract Task HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancellationToken);
57 |
58 | protected void Error(string message)
59 | {
60 | Logger.Error($"{LoggingName} - {message}");
61 | }
62 |
63 | protected void Warning(string message)
64 | {
65 | Logger.Warning($"{LoggingName} - {message}");
66 | }
67 |
68 | protected void Info(string message)
69 | {
70 | Logger.Info($"{LoggingName} - {message}");
71 | }
72 |
73 | protected void Verbose(string message)
74 | {
75 | Logger.Verbose($"{LoggingName} - {message}");
76 | }
77 | }
78 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/ICredentialProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using NuGet.Protocol.Plugins;
9 |
10 | namespace NuGetCredentialProvider.CredentialProviders
11 | {
12 | ///
13 | /// Represents an interface for implementation of credential providers.
14 | ///
15 | internal interface ICredentialProvider : IDisposable
16 | {
17 | ///
18 | /// Determines whether this implementation can provide credentials for the specified .
19 | ///
20 | /// The of the package feed.
21 | /// true
if this implementation can provide credentials, otherwise false
.
22 | Task CanProvideCredentialsAsync(Uri uri);
23 |
24 | ///
25 | /// Handles a .
26 | ///
27 | /// A object containing details about the request.
28 | /// A used for signaling cancellation.
29 | /// A object containg details about a response.
30 | Task HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancellationToken);
31 |
32 | bool IsCachable { get; }
33 | }
34 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/IVstsSessionTokenClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
10 | {
11 | public interface IVstsSessionTokenClient
12 | {
13 | Task CreateSessionTokenAsync(VstsTokenType tokenType, DateTime validTo, CancellationToken cancellationToken);
14 | }
15 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/IVstsSessionTokenFromBearerTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using NuGet.Protocol.Plugins;
7 |
8 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
9 | {
10 | public interface IAzureDevOpsSessionTokenFromBearerTokenProvider
11 | {
12 | Task GetAzureDevOpsSessionTokenFromBearerToken(
13 | GetAuthenticationCredentialsRequest request,
14 | string bearerToken,
15 | bool bearerTokenObtainedInteractively,
16 | CancellationToken cancellationToken);
17 | }
18 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/MsalTokenProvidersFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 | using Microsoft.Artifacts.Authentication;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Identity.Client.Extensions.Msal;
11 | using NuGetCredentialProvider.Util;
12 |
13 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
14 | {
15 | internal class MsalTokenProvidersFactory : ITokenProvidersFactory
16 | {
17 | private readonly ILogger logger;
18 | private MsalCacheHelper cache;
19 |
20 | public MsalTokenProvidersFactory(ILogger logger)
21 | {
22 | this.logger = logger;
23 | }
24 |
25 | public async Task> GetAsync(Uri authority)
26 | {
27 | if (cache == null && EnvUtil.MsalFileCacheEnabled())
28 | {
29 | cache = await MsalCache.GetMsalCacheHelperAsync(EnvUtil.GetMsalCacheLocation(), logger);
30 | }
31 |
32 | var app = AzureArtifacts.CreateDefaultBuilder(authority)
33 | .WithBroker(EnvUtil.MsalAllowBrokerEnabled(), logger)
34 | .WithHttpClientFactory(HttpClientFactory.Default)
35 | .WithLogging(
36 | (Microsoft.Identity.Client.LogLevel level, string message, bool containsPii) =>
37 | {
38 | // We ignore containsPii param because we are passing in enablePiiLogging below.
39 | logger.LogTrace("MSAL Log ({level}): {message}", level, message);
40 | },
41 | enablePiiLogging: EnvUtil.GetLogPIIEnabled()
42 | )
43 | .Build();
44 |
45 | cache?.RegisterCache(app.UserTokenCache);
46 |
47 | return MsalTokenProviders.Get(app, logger);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/VstsSessionToken.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Runtime.Serialization;
7 |
8 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
9 | {
10 | [DataContract]
11 | public class VstsSessionToken
12 | {
13 | [DataMember]
14 | public string DisplayName { get; set; }
15 |
16 | [DataMember]
17 | public string Scope { get; set; }
18 |
19 | [DataMember]
20 | public DateTime? ValidTo { get; set; }
21 |
22 | [DataMember]
23 | public string Token { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/VstsSessionTokenClient.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Net.Http;
7 | using System.Net.Http.Headers;
8 | using System.Text;
9 | using System.Text.Json;
10 | using System.Threading;
11 | using System.Threading.Tasks;
12 | using NuGetCredentialProvider.Logging;
13 | using NuGetCredentialProvider.Util;
14 |
15 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
16 | {
17 | public class VstsSessionTokenClient : IVstsSessionTokenClient
18 | {
19 | private const string TokenScope = "vso.packaging_write vso.drop_write";
20 |
21 | private static readonly JsonSerializerOptions options = new JsonSerializerOptions
22 | {
23 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
24 | PropertyNameCaseInsensitive = true
25 | };
26 |
27 | private readonly Uri vstsUri;
28 | private readonly string bearerToken;
29 | private readonly IAuthUtil authUtil;
30 | private readonly ILogger logger;
31 |
32 | public VstsSessionTokenClient(Uri vstsUri, string bearerToken, IAuthUtil authUtil, ILogger logger)
33 | {
34 | this.vstsUri = vstsUri ?? throw new ArgumentNullException(nameof(vstsUri));
35 | this.bearerToken = bearerToken ?? throw new ArgumentNullException(nameof(bearerToken));
36 | this.authUtil = authUtil ?? throw new ArgumentNullException(nameof(authUtil));
37 | this.logger = logger ?? throw new ArgumentException(nameof(logger));
38 | }
39 |
40 | private HttpRequestMessage CreateRequest(Uri uri, DateTime? validTo)
41 | {
42 | var request = new HttpRequestMessage(HttpMethod.Post, uri);
43 | request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
44 | request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken);
45 |
46 | var tokenRequest = new VstsSessionToken()
47 | {
48 | DisplayName = "Azure DevOps Artifacts Credential Provider",
49 | Scope = TokenScope,
50 | ValidTo = validTo
51 | };
52 |
53 | request.Content = new StringContent(
54 | JsonSerializer.Serialize(tokenRequest, options),
55 | Encoding.UTF8,
56 | "application/json");
57 |
58 | return request;
59 | }
60 |
61 | public async Task CreateSessionTokenAsync(VstsTokenType tokenType, DateTime validTo, CancellationToken cancellationToken)
62 | {
63 | var spsEndpoint = await authUtil.GetAuthorizationEndpoint(vstsUri, cancellationToken);
64 | if (spsEndpoint == null)
65 | {
66 | return null;
67 | }
68 |
69 | var uriBuilder = new UriBuilder(spsEndpoint)
70 | {
71 | Query = $"tokenType={tokenType}&api-version=5.0-preview.1"
72 | };
73 |
74 | uriBuilder.Path = uriBuilder.Path.TrimEnd('/') + "/_apis/Token/SessionTokens";
75 |
76 | var httpClient = HttpClientFactory.Default.GetHttpClient();
77 |
78 | using (var request = CreateRequest(uriBuilder.Uri, validTo))
79 | using (var response = await httpClient.SendAsync(request, cancellationToken))
80 | {
81 | logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response);
82 |
83 | string serializedResponse;
84 | if (response.StatusCode == System.Net.HttpStatusCode.BadRequest)
85 | {
86 | request.Dispose();
87 | response.Dispose();
88 |
89 | logger.Log(NuGet.Common.LogLevel.Verbose, true, "Re-trying with service-defined valid-time.");
90 | using (var request2 = CreateRequest(uriBuilder.Uri, validTo: null))
91 | using(var response2 = await httpClient.SendAsync(request2, cancellationToken))
92 | {
93 | response2.EnsureSuccessStatusCode();
94 | logger.LogResponse(NuGet.Common.LogLevel.Verbose, true, response2);
95 |
96 | serializedResponse = await response2.Content.ReadAsStringAsync();
97 | }
98 | }
99 | else
100 | {
101 | response.EnsureSuccessStatusCode();
102 | serializedResponse = await response.Content.ReadAsStringAsync();
103 | }
104 |
105 | var responseToken = JsonSerializer.Deserialize(serializedResponse, options);
106 |
107 | if (validTo.Subtract(responseToken.ValidTo.Value).TotalHours > 1.0)
108 | {
109 | logger.Log(NuGet.Common.LogLevel.Information, true, $"Requested {validTo} but received {responseToken.ValidTo}");
110 | }
111 |
112 | return responseToken.Token;
113 | }
114 | }
115 | }
116 |
117 | public enum VstsTokenType
118 | {
119 | Compact, // Personal Access Token (PAT)
120 | SelfDescribing // Session Token (JWT)
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/Vsts/VstsSessionTokenFromBearerTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 | using NuGet.Protocol.Plugins;
9 | using NuGetCredentialProvider.Logging;
10 | using NuGetCredentialProvider.Util;
11 |
12 | namespace NuGetCredentialProvider.CredentialProviders.Vsts
13 | {
14 | public class VstsSessionTokenFromBearerTokenProvider : IAzureDevOpsSessionTokenFromBearerTokenProvider
15 | {
16 | private const double DefaultSessionTimeHours = 4;
17 | private const double DefaultPersonalAccessTimeHours = 2160; // 90 days
18 | private readonly IAuthUtil authUtil;
19 | private readonly ILogger logger;
20 |
21 | public VstsSessionTokenFromBearerTokenProvider(IAuthUtil authUtil, ILogger logger)
22 | {
23 | this.authUtil = authUtil;
24 | this.logger = logger;
25 | }
26 |
27 | public async Task GetAzureDevOpsSessionTokenFromBearerToken(
28 | GetAuthenticationCredentialsRequest request,
29 | string bearerToken,
30 | bool bearerTokenObtainedInteractively,
31 | CancellationToken cancellationToken)
32 | {
33 | // Allow the user to choose their token type
34 | // If they don't and interactive auth was required, then prefer a PAT so we can safely default to a much longer validity period
35 | VstsTokenType tokenType = EnvUtil.GetVstsTokenType() ??
36 | (bearerTokenObtainedInteractively
37 | ? VstsTokenType.Compact
38 | : VstsTokenType.SelfDescribing);
39 |
40 | // Allow the user to override the validity period
41 | TimeSpan? preferredTokenTime = EnvUtil.GetSessionTimeFromEnvironment(logger);
42 | TimeSpan sessionTimeSpan;
43 | if (tokenType == VstsTokenType.Compact)
44 | {
45 | // Allow Personal Access Tokens to be as long as SPS will grant, since they're easily revokable
46 | sessionTimeSpan = preferredTokenTime ?? TimeSpan.FromHours(DefaultPersonalAccessTimeHours);
47 | }
48 | else
49 | {
50 | // But limit self-describing session tokens to a strict 24 hours, since they're harder to revoke
51 | sessionTimeSpan = preferredTokenTime ?? TimeSpan.FromHours(DefaultSessionTimeHours);
52 | if (sessionTimeSpan >= TimeSpan.FromHours(24))
53 | {
54 | sessionTimeSpan = TimeSpan.FromHours(24);
55 | }
56 | }
57 |
58 | DateTime endTime = DateTime.UtcNow + sessionTimeSpan;
59 | logger.Verbose(string.Format(Resources.VSTSSessionTokenValidity, tokenType.ToString(), sessionTimeSpan.ToString(), endTime.ToUniversalTime().ToString()));
60 | VstsSessionTokenClient sessionTokenClient = new VstsSessionTokenClient(request.Uri, bearerToken, authUtil, this.logger);
61 | return await sessionTokenClient.CreateSessionTokenAsync(tokenType, endTime, cancellationToken);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/VstsBuildTask/VstsBuildTaskCredentialProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using NuGet.Protocol.Plugins;
11 | using NuGetCredentialProvider.Logging;
12 | using NuGetCredentialProvider.Util;
13 | using ILogger = NuGetCredentialProvider.Logging.ILogger;
14 |
15 | namespace NuGetCredentialProvider.CredentialProviders.VstsBuildTask
16 | {
17 | public sealed class VstsBuildTaskCredentialProvider : CredentialProviderBase
18 | {
19 | private const string Username = "VssSessionToken";
20 |
21 | public VstsBuildTaskCredentialProvider(ILogger logger)
22 | : base(logger)
23 | {
24 | }
25 |
26 | protected override string LoggingName => nameof(VstsBuildTaskCredentialProvider);
27 |
28 | public override bool IsCachable { get { return false; } }
29 |
30 | public override Task CanProvideCredentialsAsync(Uri uri)
31 | {
32 | string uriPrefixesString = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskUriPrefixes);
33 | string accessToken = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskAccessToken);
34 |
35 | bool useBuildTaskCredProvider = string.IsNullOrWhiteSpace(uriPrefixesString) == false && string.IsNullOrWhiteSpace(accessToken) == false;
36 | if (useBuildTaskCredProvider == true)
37 | {
38 | return Task.FromResult(true);
39 | }
40 |
41 | Verbose(Resources.BuildTaskEnvVarError);
42 | return Task.FromResult(false);
43 | }
44 |
45 | public override Task HandleRequestAsync(GetAuthenticationCredentialsRequest request, CancellationToken cancellationToken)
46 | {
47 | cancellationToken.ThrowIfCancellationRequested();
48 |
49 | string uriPrefixesString = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskUriPrefixes);
50 | string accessToken = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskAccessToken);
51 |
52 | Verbose(string.Format(Resources.IsRetry, request.IsRetry));
53 |
54 | string[] uriPrefixes = uriPrefixesString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
55 | Verbose(Resources.BuildTaskUriPrefixes);
56 | foreach (var uriPrefix in uriPrefixes)
57 | {
58 | Verbose($"{uriPrefix}");
59 | }
60 |
61 | string uriString = request.Uri.AbsoluteUri;
62 | string matchedPrefix = uriPrefixes.FirstOrDefault(prefix => uriString.StartsWith(prefix, StringComparison.OrdinalIgnoreCase));
63 | Verbose(string.Format(Resources.BuildTaskMatchedPrefix, matchedPrefix != null ? matchedPrefix : Resources.BuildTaskNoMatchingPrefixes));
64 |
65 | if (matchedPrefix == null)
66 | {
67 | Verbose(Resources.BuildTaskNoPrefixMatch);
68 | return this.GetResponse(
69 | null,
70 | null,
71 | Resources.BuildTaskNoPrefixMatch,
72 | MessageResponseCode.Error);
73 | }
74 |
75 | Verbose(string.Format(Resources.BuildTaskEndpointMatchingUrlFound, uriString));
76 | return this.GetResponse(
77 | Username,
78 | accessToken,
79 | null,
80 | MessageResponseCode.Success);
81 | }
82 |
83 | private Task GetResponse(string username, string password, string message, MessageResponseCode responseCode)
84 | {
85 | return Task.FromResult(new GetAuthenticationCredentialsResponse(
86 | username: username,
87 | password: password,
88 | message: message,
89 | authenticationTypes: new List
90 | {
91 | "Basic"
92 | },
93 | responseCode: responseCode));
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/CredentialProviders/VstsBuildTaskServiceEndpoint/VstsBuildTaskMsalTokenProvidersFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 | using Microsoft.Artifacts.Authentication;
9 | using Microsoft.Extensions.Logging;
10 | using Microsoft.Identity.Client;
11 | using NuGetCredentialProvider.Util;
12 |
13 | namespace NuGetCredentialProvider.CredentialProviders.VstsBuildTaskServiceEndpoint;
14 |
15 | internal class VstsBuildTaskMsalTokenProvidersFactory : ITokenProvidersFactory
16 | {
17 | private readonly ILogger logger;
18 |
19 | public VstsBuildTaskMsalTokenProvidersFactory(ILogger logger)
20 | {
21 | this.logger = logger;
22 | }
23 |
24 | public Task> GetAsync(Uri authority)
25 | {
26 | var app = AzureArtifacts.CreateDefaultBuilder(authority)
27 | .WithBroker(EnvUtil.MsalAllowBrokerEnabled(), logger)
28 | .WithHttpClientFactory(HttpClientFactory.Default)
29 | .WithLogging(
30 | (level, message, containsPii) =>
31 | {
32 | // We ignore containsPii param because we are passing in enablePiiLogging below.
33 | logger.LogTrace("MSAL Log ({level}): {message}", level, message);
34 | },
35 | enablePiiLogging: EnvUtil.GetLogPIIEnabled()
36 | )
37 | .Build();
38 |
39 | return Task.FromResult(ConstructTokenProvidersList(app));
40 | }
41 |
42 | private IEnumerable ConstructTokenProvidersList(IPublicClientApplication app)
43 | {
44 | yield return new MsalServicePrincipalTokenProvider(app, logger);
45 | yield return new MsalManagedIdentityTokenProvider(app, logger);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/EULA_Microsoft Visual Studio Team Services Credential Provider.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/artifacts-credprovider/107acd99393a8ba94be8a3280be18773c0549de4/CredentialProvider.Microsoft/EULA_Microsoft Visual Studio Team Services Credential Provider.docx
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/ConsoleLoggers.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.IO;
7 | using NuGet.Common;
8 |
9 | namespace NuGetCredentialProvider.Logging
10 | {
11 | ///
12 | /// Logs messages to standard output, with log level included
13 | ///
14 | public class StandardOutputLogger : HumanFriendlyTextWriterLogger
15 | {
16 | public StandardOutputLogger() : base(Console.Out, writesToConsole: true) { }
17 | }
18 |
19 | ///
20 | /// Logs messages to standard error, with log level included
21 | ///
22 | public class StandardErrorLogger : HumanFriendlyTextWriterLogger
23 | {
24 | public StandardErrorLogger() : base(Console.Error, writesToConsole: true) { }
25 | }
26 |
27 | ///
28 | /// Emits a log message in a human-readable format to a TextWriter, including the log level
29 | ///
30 | public class HumanFriendlyTextWriterLogger : LoggerBase
31 | {
32 | private readonly TextWriter writer;
33 |
34 | public HumanFriendlyTextWriterLogger(TextWriter writer, bool writesToConsole)
35 | : base(writesToConsole)
36 | {
37 | this.writer = writer;
38 | }
39 |
40 | protected override void WriteLog(LogLevel logLevel, string message)
41 | {
42 | writer.WriteLine($"[{logLevel}] {message}");
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/CredentialResult.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.Serialization;
2 |
3 | namespace NuGetCredentialProvider.Logging
4 | {
5 | [DataContract]
6 | public class CredentialResult
7 | {
8 | public CredentialResult(string username, string password)
9 | {
10 | Username = username;
11 | Password = password;
12 | }
13 |
14 | [DataMember]
15 | public string Username { get; }
16 |
17 | [DataMember]
18 | public string Password { get; }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/ILogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Linq;
6 | using System.Net.Http;
7 | using NuGet.Common;
8 |
9 | namespace NuGetCredentialProvider.Logging
10 | {
11 | public interface ILogger
12 | {
13 | void Log(LogLevel level, bool allowOnConsole, string message);
14 |
15 | void SetLogLevel(LogLevel newLogLevel);
16 | }
17 |
18 | public static class LoggerExtensions
19 | {
20 | public static void LogResponse(this ILogger logger, LogLevel level, bool allowOnConsole, HttpResponseMessage response)
21 | {
22 | logger.Log(NuGet.Common.LogLevel.Verbose, true, $"Response: {response.StatusCode}");
23 | if (response.Headers.TryGetValues("ActivityId", out var activityIds))
24 | {
25 | string activityId = activityIds.FirstOrDefault();
26 | if (activityId != null)
27 | {
28 | logger.Log(NuGet.Common.LogLevel.Verbose, true, $" ActivityId: {activityId}");
29 | }
30 | }
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/LogEveryMessageFileLogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Diagnostics;
7 | using System.IO;
8 | using System.Threading;
9 | using NuGet.Common;
10 |
11 | namespace NuGetCredentialProvider.Logging
12 | {
13 | internal class LogEveryMessageFileLogger : ILogger
14 | {
15 | private static readonly int Pid = Process.GetCurrentProcess().Id;
16 |
17 | private readonly string filePath;
18 |
19 | internal LogEveryMessageFileLogger(string filePath)
20 | {
21 | this.filePath = filePath;
22 | Log(LogLevel.Minimal, allowOnConsole: false, string.Format(Resources.LogStartsAt, DateTime.UtcNow.ToString("u")));
23 | }
24 |
25 | public void Log(LogLevel level, bool allowOnConsole, string message)
26 | {
27 | try
28 | {
29 | for (int i = 0; i < 3; ++i)
30 | {
31 | try
32 | {
33 | File.AppendAllText(
34 | filePath,
35 | $"[{DateTime.UtcNow:HH:mm:ss.fff} {Pid,5} {GetLevelKeyword(level),7}] {message}\n");
36 | return;
37 | }
38 | catch (IOException)
39 | {
40 | // retry IOExceptions a couple of times. Could be another instance (or thread) of the plugin locking the file
41 | Thread.Sleep(TimeSpan.FromMilliseconds(20));
42 | }
43 | }
44 | }
45 | catch
46 | {
47 | // don't crash the process just because logging failed.
48 | }
49 | }
50 |
51 | private string GetLevelKeyword(LogLevel level)
52 | {
53 | switch (level)
54 | {
55 | case LogLevel.Debug:
56 | return "Debug";
57 |
58 | case LogLevel.Verbose:
59 | return "Verbose";
60 |
61 | case LogLevel.Information:
62 | return "Info";
63 |
64 | case LogLevel.Minimal:
65 | return "Minimal";
66 |
67 | case LogLevel.Warning:
68 | return "Warning";
69 |
70 | case LogLevel.Error:
71 | return "Error";
72 |
73 | default:
74 | throw new ArgumentOutOfRangeException(nameof(level), level, null);
75 | }
76 | }
77 |
78 | void ILogger.SetLogLevel(LogLevel newLogLevel)
79 | {
80 | // no-op. This logger always logs all messages.
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/LoggerBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Concurrent;
7 | using System.Threading;
8 | using NuGet.Common;
9 |
10 | namespace NuGetCredentialProvider.Logging
11 | {
12 | public abstract class LoggerBase : ILogger
13 | {
14 | private LogLevel minLogLevel = LogLevel.Debug;
15 | private bool allowLogWrites = false;
16 | private ConcurrentQueue> bufferedLogs = new ConcurrentQueue>();
17 | private readonly bool writesToConsole;
18 |
19 | protected LoggerBase(bool writesToConsole)
20 | {
21 | this.writesToConsole = writesToConsole;
22 | }
23 |
24 | public void Log(LogLevel level, bool allowOnConsole, string message)
25 | {
26 | if (writesToConsole && !allowOnConsole)
27 | {
28 | return;
29 | }
30 |
31 | if (!allowLogWrites)
32 | {
33 | // cheap reserve, if it swaps out after we add, meh, we miss one log
34 | var buffer = bufferedLogs;
35 | if (buffer != null)
36 | {
37 | buffer.Enqueue(Tuple.Create(level, message, DateTime.Now));
38 | }
39 |
40 | // we could pass this through if buffer is null since the Set message has already come through to unblock us, but
41 | // the race should be rare and we don't know exactly how nuget will behave with the parallelism so
42 | // opt to ignore this one racing log message.
43 | return;
44 | }
45 | else if (bufferedLogs != null)
46 | {
47 | ConcurrentQueue> buffer = null;
48 | buffer = Interlocked.CompareExchange(ref bufferedLogs, null, bufferedLogs);
49 |
50 | if (buffer != null)
51 | {
52 | foreach (var log in buffer)
53 | {
54 | if (log.Item1 >= minLogLevel)
55 | {
56 | WriteLog(log.Item1, GetLogPrefix(log.Item3) + log.Item2);
57 | }
58 | }
59 | }
60 | }
61 |
62 | if (level >= minLogLevel)
63 | {
64 | WriteLog(level, GetLogPrefix(null) + message);
65 | }
66 | }
67 |
68 | public void SetLogLevel(LogLevel newLogLevel)
69 | {
70 | minLogLevel = newLogLevel;
71 | allowLogWrites = true;
72 | }
73 |
74 | protected abstract void WriteLog(LogLevel logLevel, string message);
75 |
76 | private string GetLogPrefix(DateTime? bufferedLogTime)
77 | {
78 | string timeString = bufferedLogTime.HasValue
79 | ? bufferedLogTime.Value.ToString(".HHmmss")
80 | : null;
81 |
82 | return $"[CredentialProvider{timeString}]";
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/MultiLogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Collections.Generic;
6 | using NuGet.Common;
7 |
8 | namespace NuGetCredentialProvider.Logging
9 | {
10 | internal class MultiLogger : List, ILogger
11 | {
12 | private LogLevel? minLogLevel = null;
13 |
14 | public void Log(LogLevel level, bool allowOnConsole, string message)
15 | {
16 | foreach (var logger in this)
17 | {
18 | logger.Log(level, allowOnConsole, message);
19 | }
20 | }
21 |
22 | public void SetLogLevel(LogLevel newLogLevel)
23 | {
24 | minLogLevel = newLogLevel;
25 |
26 | foreach (var logger in this)
27 | {
28 | logger.SetLogLevel(newLogLevel);
29 | }
30 | }
31 |
32 | public new void Add(ILogger logger)
33 | {
34 | if (minLogLevel.HasValue)
35 | {
36 | logger.SetLogLevel(minLogLevel.Value);
37 | }
38 |
39 | base.Add(logger);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/NuGetLoggerAdapter.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using NuGet.Common;
7 |
8 | namespace NuGetCredentialProvider.Logging;
9 |
10 | internal class NuGetLoggerAdapter : Microsoft.Extensions.Logging.ILogger
11 | {
12 | private readonly ILogger logger;
13 | private readonly LogLevel logLevel;
14 |
15 | public NuGetLoggerAdapter(ILogger logger, LogLevel logLevel)
16 | {
17 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
18 | this.logLevel = logLevel;
19 | }
20 |
21 | public IDisposable BeginScope(TState state)
22 | {
23 | throw new NotImplementedException();
24 | }
25 |
26 | public bool IsEnabled(Microsoft.Extensions.Logging.LogLevel logLevel)
27 | {
28 | return GetNuGetLogLevel(logLevel) >= this.logLevel;
29 | }
30 |
31 | public void Log(Microsoft.Extensions.Logging.LogLevel logLevel, Microsoft.Extensions.Logging.EventId eventId, TState state, Exception exception, Func formatter)
32 | {
33 | logger.Log(GetNuGetLogLevel(logLevel), true, formatter(state, exception));
34 | }
35 |
36 | private LogLevel GetNuGetLogLevel(Microsoft.Extensions.Logging.LogLevel logLevel)
37 | {
38 | switch (logLevel)
39 | {
40 | // Seems backwards, but NuGet defines a different order for log levels
41 | case Microsoft.Extensions.Logging.LogLevel.Trace:
42 | return LogLevel.Debug;
43 | case Microsoft.Extensions.Logging.LogLevel.Debug:
44 | return LogLevel.Verbose;
45 | case Microsoft.Extensions.Logging.LogLevel.Information:
46 | return LogLevel.Information;
47 | case Microsoft.Extensions.Logging.LogLevel.Warning:
48 | return LogLevel.Warning;
49 | case Microsoft.Extensions.Logging.LogLevel.Error:
50 | return LogLevel.Error;
51 | case Microsoft.Extensions.Logging.LogLevel.Critical:
52 | return LogLevel.Error;
53 | case Microsoft.Extensions.Logging.LogLevel.None:
54 | return LogLevel.Error;
55 | default:
56 | return LogLevel.Minimal;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Logging/PluginConnectionLogger.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using NuGet.Common;
8 | using NuGet.Protocol.Plugins;
9 |
10 | namespace NuGetCredentialProvider.Logging
11 | {
12 | internal class PluginConnectionLogger : LoggerBase
13 | {
14 | private readonly IConnection connection;
15 |
16 | internal PluginConnectionLogger(IConnection connection)
17 | : base(writesToConsole: true)
18 | {
19 | this.connection = connection;
20 | }
21 |
22 | protected override void WriteLog(LogLevel logLevel, string message)
23 | {
24 | // intentionally not awaiting here -- don't want to block forward progress just because we tried to log.
25 | connection.SendRequestAndReceiveResponseAsync(
26 | MessageMethod.Log,
27 | new LogRequest(logLevel, $" {message}"),
28 | CancellationToken.None)
29 | // "observe" any exceptions to avoid unobserved exception escalation, which may terminate the process
30 | .ContinueWith(x => x.Exception, TaskContinuationOptions.OnlyOnFaulted);
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/GetOperationClaimsRequestHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Threading.Tasks;
8 | using NuGet.Protocol.Plugins;
9 | using NuGetCredentialProvider.CredentialProviders;
10 | using NuGetCredentialProvider.Logging;
11 |
12 | namespace NuGetCredentialProvider.RequestHandlers
13 | {
14 | ///
15 | /// Handles a and replies with the supported operations.
16 | ///
17 | internal class GetOperationClaimsRequestHandler : RequestHandlerBase
18 | {
19 | ///
20 | /// A when a registered credential provider can provide credentials for the current request.
21 | ///
22 | private static readonly GetOperationClaimsResponse CanProvideCredentialsResponse = new GetOperationClaimsResponse(new List
23 | {
24 | OperationClaim.Authentication
25 | });
26 |
27 | ///
28 | /// A when no registered credential providers can provide credentials for the current request.
29 | ///
30 | private static readonly GetOperationClaimsResponse EmptyGetOperationClaimsResponse = new GetOperationClaimsResponse(new List());
31 |
32 | private readonly IReadOnlyCollection _credentialProviders;
33 |
34 | ///
35 | /// Initializes a new instance of the class.
36 | ///
37 | /// A to use for logging.
38 | /// An containing credential providers.
39 | public GetOperationClaimsRequestHandler(ILogger logger, IReadOnlyCollection credentialProviders)
40 | : base(logger)
41 | {
42 | _credentialProviders = credentialProviders ?? throw new ArgumentNullException(nameof(credentialProviders));
43 | }
44 |
45 | public override Task HandleRequestAsync(GetOperationClaimsRequest request)
46 | {
47 | if (request.PackageSourceRepository != null || request.ServiceIndex != null)
48 | {
49 | return Task.FromResult(EmptyGetOperationClaimsResponse);
50 | }
51 |
52 | return Task.FromResult(CanProvideCredentialsResponse);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/InitializeRequestHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Threading.Tasks;
6 | using NuGet.Protocol.Plugins;
7 | using NuGetCredentialProvider.Logging;
8 |
9 | namespace NuGetCredentialProvider.RequestHandlers
10 | {
11 | ///
12 | /// Handles an .
13 | ///
14 | internal class InitializeRequestHandler : RequestHandlerBase
15 | {
16 | ///
17 | /// Initializes a new instance of the class.
18 | ///
19 | /// A to use for logging.
20 | public InitializeRequestHandler(ILogger logger)
21 | : base(logger)
22 | {
23 | }
24 |
25 | public override Task HandleRequestAsync(InitializeRequest request)
26 | {
27 | return Task.FromResult(new InitializeResponse(MessageResponseCode.Success));
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/RequestHandlerBase.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Diagnostics;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 | using Newtonsoft.Json;
10 | using NuGet.Common;
11 | using NuGet.Protocol.Plugins;
12 | using NuGetCredentialProvider.Util;
13 | using ILogger = NuGetCredentialProvider.Logging.ILogger;
14 |
15 | namespace NuGetCredentialProvider.RequestHandlers
16 | {
17 | ///
18 | /// A base class for implementations of .
19 | ///
20 | /// The type of the request message.
21 | /// The type of the response message.
22 | internal abstract class RequestHandlerBase : IRequestHandler
23 | where TResponse : class
24 | {
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// A to use for logging.
29 | protected RequestHandlerBase(ILogger logger)
30 | {
31 | Logger = logger ?? throw new ArgumentNullException(nameof(logger));
32 | }
33 |
34 | ///
35 | /// Gets a to use.
36 | ///
37 | public virtual CancellationToken CancellationToken { get; private set; } = CancellationToken.None;
38 |
39 | ///
40 | /// Gets the current .
41 | ///
42 | public IConnection Connection { get; private set; }
43 |
44 | ///
45 | /// Gets the current to use for logging.
46 | ///
47 | public ILogger Logger { get; }
48 |
49 | ///
50 | public async Task HandleResponseAsync(IConnection connection, Message message, IResponseHandler responseHandler, CancellationToken cancellationToken)
51 | {
52 | Stopwatch timer = new Stopwatch();
53 | timer.Start();
54 |
55 | Connection = connection;
56 |
57 | TRequest request = MessageUtilities.DeserializePayload(message);
58 |
59 | try {
60 | TResponse response = null;
61 | Logger.Verbose(string.Format(Resources.HandlingRequest, message.Type, message.Method, timer.ElapsedMilliseconds, message.Payload.ToString(Formatting.None)));
62 | try {
63 | using (GetProgressReporter(connection, message, cancellationToken))
64 | {
65 | response = await HandleRequestAsync(request).ConfigureAwait(continueOnCapturedContext: false);
66 | }
67 | }
68 | catch (Exception ex) when (cancellationToken.IsCancellationRequested)
69 | {
70 | // NuGet will handle canceling event but verbose logs in this case might be interesting.
71 | Logger.Verbose(string.Format(Resources.RequestHandlerCancelingExceptionMessage, ex.InnerException, ex.Message));
72 | return;
73 | }
74 | Logger.Verbose(string.Format(Resources.SendingResponse, message.Type, message.Method, timer.ElapsedMilliseconds));
75 | // If we did not send a cancel message, we must submit the response even if cancellationToken is canceled.
76 | await responseHandler.SendResponseAsync(message, response, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false);
77 |
78 | Logger.Verbose(string.Format(Resources.TimeElapsedAfterSendingResponse, message.Type, message.Method, timer.ElapsedMilliseconds));
79 | }
80 | catch (Exception ex)
81 | {
82 | // don't report cancellations to the console during shutdown, they're most likely not interesting.
83 | bool cancelingDuringShutdown = ex is OperationCanceledException && Program.IsShuttingDown;
84 |
85 | if (cancelingDuringShutdown)
86 | {
87 | Logger.Log(LogLevel.Verbose, allowOnConsole: false, Resources.WhileShuttingDown);
88 | }
89 |
90 | Logger.Log(LogLevel.Verbose, allowOnConsole: !cancelingDuringShutdown, string.Format(Resources.ResponseHandlerException, message.Method, message.RequestId));
91 | Logger.Log(LogLevel.Verbose, allowOnConsole: !cancelingDuringShutdown, ex.ToString());
92 |
93 | throw;
94 | }
95 |
96 | timer.Stop();
97 | }
98 |
99 | public abstract Task HandleRequestAsync(TRequest request);
100 |
101 | protected virtual AutomaticProgressReporter GetProgressReporter(IConnection connection, Message message, CancellationToken cancellationToken)
102 | {
103 | return null;
104 | }
105 | }
106 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/RequestHandlerCollection.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Collections.Concurrent;
7 | using NuGet.Protocol.Plugins;
8 |
9 | namespace NuGetCredentialProvider.RequestHandlers
10 | {
11 | ///
12 | /// Represents a collection of NuGet plug-in request handlers.
13 | ///
14 | /// This custom collection is used instead of because it inherits
15 | /// which allows for the initializer syntax.
16 | internal class RequestHandlerCollection : ConcurrentDictionary, IRequestHandlers
17 | {
18 | public void Add(MessageMethod method, IRequestHandler handler)
19 | {
20 | TryAdd(method, handler);
21 | }
22 |
23 | public void AddOrUpdate(MessageMethod method, Func addHandlerFunc, Func updateHandlerFunc)
24 | {
25 | AddOrUpdate(method, messageMethod => addHandlerFunc(), (messageMethod, requestHandler) => updateHandlerFunc(requestHandler));
26 | }
27 |
28 | public bool TryGet(MessageMethod method, out IRequestHandler requestHandler)
29 | {
30 | return TryGetValue(method, out requestHandler);
31 | }
32 |
33 | public bool TryRemove(MessageMethod method)
34 | {
35 | return TryRemove(method, out IRequestHandler _);
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/SetCredentialsRequestHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Threading.Tasks;
6 | using NuGet.Protocol.Plugins;
7 | using NuGetCredentialProvider.Logging;
8 |
9 | namespace NuGetCredentialProvider.RequestHandlers
10 | {
11 | ///
12 | /// Handles a
13 | ///
14 | internal class SetCredentialsRequestHandler : RequestHandlerBase
15 | {
16 | private static readonly SetCredentialsResponse SuccessResponse = new SetCredentialsResponse(MessageResponseCode.Success);
17 |
18 | public SetCredentialsRequestHandler(ILogger logger)
19 | : base(logger)
20 | {
21 | }
22 |
23 | public override Task HandleRequestAsync(SetCredentialsRequest request)
24 | {
25 | // There's currently no way to handle proxies, so nothing we can do here
26 | return Task.FromResult(SuccessResponse);
27 | }
28 | }
29 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/RequestHandlers/SetLogLevelRequestHandler.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Threading.Tasks;
6 | using NuGet.Protocol.Plugins;
7 | using NuGetCredentialProvider.Logging;
8 |
9 | namespace NuGetCredentialProvider.RequestHandlers
10 | {
11 | internal class SetLogLevelRequestHandler : RequestHandlerBase
12 | {
13 | private static readonly SetLogLevelResponse SuccessResponse = new SetLogLevelResponse(MessageResponseCode.Success);
14 |
15 | public SetLogLevelRequestHandler(ILogger logger)
16 | : base(logger)
17 | {
18 | }
19 |
20 | public override Task HandleRequestAsync(SetLogLevelRequest request)
21 | {
22 | Logger.SetLogLevel(request.LogLevel);
23 | return Task.FromResult(SuccessResponse);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/ThirdPartyNotices.txt:
--------------------------------------------------------------------------------
1 |
2 | THIRD-PARTY SOFTWARE NOTICES AND INFORMATION
3 | Do Not Translate or Localize
4 |
5 | Microsoft Visual Studio Team Services Credential Provider for NuGet is based on or incorporates material from the projects listed below (Third Party OSS). The original copyright notice and the license under which Microsoft received such Third Party OSS, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party OSS to you under the licensing terms for the Microsoft product or service. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise.
6 |
7 |
8 | 1. Json.NET (Newtonsoft.Json) (https://github.com/JamesNK/Newtonsoft.Json)
9 | 2. Powerargs (https://github.com/adamabdelhamed/PowerArgs)
10 |
11 |
12 | %% Json.NET (Newtonsoft.Json) NOTICES AND INFORMATION BEGIN HERE
13 | =========================================
14 | The MIT License (MIT)
15 |
16 | Copyright (c) 2007 James Newton-King
17 |
18 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
19 |
20 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
21 |
22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | =========================================
25 | END OF Json.NET (Newtonsoft.Json) NOTICES AND INFORMATION
26 |
27 | %% Powerargs NOTICES AND INFORMATION BEGIN HERE
28 | =========================================
29 | Copyright (c) 2013 Adam Abdelhamed
30 |
31 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
32 |
33 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
34 |
35 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
36 | =========================================
37 | END OF Powerargs NOTICES AND INFORMATION
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/CertificateUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Security.Cryptography.X509Certificates;
4 | using ILogger = NuGetCredentialProvider.Logging.ILogger;
5 |
6 | namespace NuGetCredentialProvider.Util;
7 |
8 | internal static class CertificateUtil
9 | {
10 | public static X509Certificate2 GetCertificateBySubjectName(ILogger logger, string subjectName)
11 | {
12 | if (string.IsNullOrWhiteSpace(subjectName))
13 | {
14 | logger.Info(message: Resources.InvalidCertificateInput);
15 | return null;
16 | }
17 |
18 | var locations = new []{ StoreLocation.CurrentUser, StoreLocation.LocalMachine };
19 | foreach (var location in locations)
20 | {
21 | var store = new X509Store(StoreName.My, location);
22 | try
23 | {
24 | store.Open(OpenFlags.ReadOnly);
25 | var cert = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName , subjectName, false);
26 |
27 | if (cert.Count > 0)
28 | {
29 | logger.Verbose(string.Format(Resources.ClientCertificateFound, subjectName));
30 | return cert[0];
31 | }
32 | }
33 | catch (Exception ex)
34 | {
35 | logger.Error(string.Format(Resources.ClientCertificateError, ex, ex.Message));
36 | continue;
37 | }
38 | finally
39 | {
40 | store.Close();
41 | }
42 | }
43 |
44 | logger.Info(string.Format(Resources.ClientCertificateSubjectNameNotFound, subjectName));
45 | return null;
46 | }
47 |
48 | public static X509Certificate2 GetCertificateByFilePath(ILogger logger, string filePath)
49 | {
50 | if (string.IsNullOrWhiteSpace(filePath))
51 | {
52 | logger.Info(message: Resources.InvalidCertificateInput);
53 | return null;
54 | }
55 |
56 | try
57 | {
58 | var fileType = Path.GetExtension(filePath);
59 | X509Certificate2 certificate;
60 | switch (fileType)
61 | {
62 | case ".pfx":
63 | certificate = new X509Certificate2(filePath);
64 | break;
65 | case ".pem":
66 | #if NET6_0_OR_GREATER
67 | certificate= X509Certificate2.CreateFromPemFile(filePath);
68 | break;
69 | #endif
70 | throw new NotSupportedException(Resources.ClientCertificatePemFilesNotSupported);
71 | default:
72 | throw new NotSupportedException(Resources.ClientCertificateFileTypeNotSupported);
73 | }
74 |
75 | if (certificate == null)
76 | {
77 | logger.Verbose(string.Format(Resources.ClientCertificateFilePathNotFound, filePath));
78 | return null;
79 | }
80 |
81 | logger.Verbose(string.Format(Resources.ClientCertificateFound, filePath));
82 | return certificate;
83 | }
84 | catch (Exception ex)
85 | {
86 | logger.Error(string.Format(Resources.ClientCertificateError, ex, ex.Message));
87 | return null;
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/EncryptedFileWithPermissions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.IO;
7 | using System.Runtime.InteropServices;
8 | using System.Security.AccessControl;
9 | using System.Security.Cryptography;
10 | using System.Security.Principal;
11 | using Microsoft.Identity.Client.Extensions.Msal;
12 | using Microsoft.Win32.SafeHandles;
13 |
14 | namespace NuGetCredentialProvider.Util
15 | {
16 | public class EncryptedFileWithPermissions
17 | {
18 | #region Unix specific
19 |
20 | ///
21 | /// Equivalent to calling open() with flags O_CREAT|O_WRONLY|O_TRUNC. O_TRUNC will truncate the file.
22 | /// See https://man7.org/linux/man-pages/man2/open.2.html
23 | ///
24 | [DllImport("libc", EntryPoint = "creat", SetLastError = true)]
25 | private static extern int PosixCreate([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode);
26 |
27 | [DllImport("libc", EntryPoint = "chmod", SetLastError = true)]
28 | private static extern int PosixChmod([MarshalAs(UnmanagedType.LPStr)] string pathname, int mode);
29 |
30 | #endregion
31 |
32 | public static byte[] ReadFileBytes(string filePath, bool readUnencrypted = false)
33 | {
34 | try
35 | {
36 | return File.Exists(filePath) ? ProtectedData.Unprotect(File.ReadAllBytes(filePath), null, DataProtectionScope.CurrentUser) : null;
37 | }
38 | catch (NotSupportedException)
39 | {
40 | if (readUnencrypted)
41 | {
42 | return File.Exists(filePath) ? File.ReadAllBytes(filePath) : null;
43 | }
44 |
45 | throw;
46 | }
47 | }
48 |
49 | public static void WriteFileBytes(string filePath, byte[] bytes, bool writeUnencrypted = false)
50 | {
51 | try
52 | {
53 | EnsureDirectoryExists(filePath);
54 |
55 | WriteToNewFileWithOwnerRWPermissions(filePath, ProtectedData.Protect(bytes, null, DataProtectionScope.CurrentUser));
56 | }
57 | catch (NotSupportedException)
58 | {
59 | if (writeUnencrypted)
60 | {
61 | WriteToNewFileWithOwnerRWPermissions(filePath, bytes);
62 | return;
63 | }
64 |
65 | throw;
66 | }
67 | }
68 |
69 | private static void EnsureDirectoryExists(string filePath)
70 | {
71 | var directory = Path.GetDirectoryName(filePath);
72 |
73 | if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
74 | {
75 | Directory.CreateDirectory(directory);
76 | }
77 | }
78 |
79 | ///
80 | /// Based on https://stackoverflow.com/questions/45132081/file-permissions-on-linux-unix-with-net-core and on
81 | /// https://github.com/NuGet/NuGet.Client/commit/d62db666c710bf95121fe8f5c6a6cbe01985456f and
82 | /// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/b299b2581da87af50fde751e689f1bd4114516ce/src/client/Microsoft.Identity.Client.Extensions.Msal/Accessors/FileWithPermissions.cs
83 | ///
84 | private static void WriteToNewFileWithOwnerRWPermissions(string path, byte[] bytes)
85 | {
86 |
87 | if (SharedUtilities.IsWindowsPlatform())
88 | {
89 | WriteToNewFileWithOwnerRWPermissionsWindows(path, bytes);
90 | }
91 | else if (SharedUtilities.IsMacPlatform() || SharedUtilities.IsLinuxPlatform())
92 | {
93 | WriteToNewFileWithOwnerRWPermissionsUnix(path, bytes);
94 | }
95 | else
96 | {
97 | throw new PlatformNotSupportedException();
98 | }
99 | }
100 |
101 | private static void WriteToNewFileWithOwnerRWPermissionsUnix(string path, byte[] bytes)
102 | {
103 | int _0600 = Convert.ToInt32("600", 8);
104 |
105 | int fileDescriptor = PosixCreate(path, _0600);
106 |
107 | // if creat() fails, then try to use File.Create because it will throw a meaningful exception.
108 | if (fileDescriptor == -1)
109 | {
110 | int posixCreateError = Marshal.GetLastWin32Error();
111 | using (File.Create(path))
112 | {
113 | // File.Create() should have thrown an exception with an appropriate error message
114 | }
115 | File.Delete(path);
116 | throw new InvalidOperationException($"libc creat() failed with last error code {posixCreateError}, but File.Create did not");
117 | }
118 |
119 | var safeFileHandle = new SafeFileHandle((IntPtr)fileDescriptor, ownsHandle: true);
120 | using var fileStream = new FileStream(safeFileHandle, FileAccess.ReadWrite);
121 | fileStream.Write(bytes, 0, bytes.Length);
122 | }
123 |
124 | #pragma warning disable CA1416 // Validate platform compatibility
125 | private static void WriteToNewFileWithOwnerRWPermissionsWindows(string filePath, byte[] bytes)
126 | {
127 | FileSecurity security = new();
128 |
129 | var rights = FileSystemRights.Read | FileSystemRights.Write;
130 |
131 | security.AddAccessRule(
132 | new FileSystemAccessRule(
133 | WindowsIdentity.GetCurrent().Name,
134 | rights,
135 | InheritanceFlags.None,
136 | PropagationFlags.NoPropagateInherit,
137 | AccessControlType.Allow));
138 |
139 | security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false);
140 |
141 | FileStream fs = null;
142 |
143 | try
144 | {
145 | #if NET45_OR_GREATER
146 | if (File.Exists(filePath))
147 | {
148 | File.Delete(filePath);
149 | }
150 |
151 | fs = File.Create(filePath, bytes.Length, FileOptions.None, security);
152 | #else
153 | FileInfo info = new FileInfo(filePath);
154 | fs = info.Create(FileMode.Create, rights, FileShare.Read, bytes.Length, FileOptions.None, security);
155 | #endif
156 |
157 | fs.Write(bytes, 0, bytes.Length);
158 | }
159 | finally
160 | {
161 | fs?.Dispose();
162 | }
163 | }
164 | #pragma warning restore CA1416 // Validate platform compatibility
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/ExtensionMethods.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System;
6 | using System.Text;
7 | using NuGet.Common;
8 | using ILogger = NuGetCredentialProvider.Logging.ILogger;
9 |
10 | namespace NuGetCredentialProvider.Util
11 | {
12 | ///
13 | /// Represents the set of extension methods used by this project.
14 | ///
15 | internal static class ExtensionMethods
16 | {
17 | ///
18 | /// Writes a event message to the using the specified message.
19 | ///
20 | /// A instance to write the message to.
21 | /// The level to log at.
22 | /// The message.
23 | public static void Log(this ILogger logger, LogLevel logLevel, string message)
24 | {
25 | logger.Log(logLevel, true, message);
26 | }
27 |
28 | ///
29 | /// Writes a event message to the using the specified message.
30 | ///
31 | /// A instance to write the message to.
32 | /// The message.
33 | public static void Error(this ILogger logger, string message)
34 | {
35 | logger.Log(LogLevel.Error, message);
36 | }
37 |
38 | ///
39 | /// Writes a event message to the using the specified message.
40 | ///
41 | /// A instance to write the message to.
42 | /// The message.
43 | public static void Warning(this ILogger logger, string message)
44 | {
45 | logger.Log(LogLevel.Warning, message);
46 | }
47 |
48 | ///
49 | /// Writes a event message to the using the specified message.
50 | ///
51 | /// A instance to write the message to.
52 | /// The message.
53 | public static void Minimal(this ILogger logger, string message)
54 | {
55 | logger.Log(LogLevel.Minimal, message);
56 | }
57 |
58 | ///
59 | /// Writes a event message to the using the specified message.
60 | ///
61 | /// A instance to write the message to.
62 | /// The message.
63 | public static void Info(this ILogger logger, string message)
64 | {
65 | logger.Log(LogLevel.Information, message);
66 | }
67 |
68 | ///
69 | /// Writes a event message to the using the specified message.
70 | ///
71 | /// A instance to write the message to.
72 | /// The message.
73 | public static void Verbose(this ILogger logger, string message)
74 | {
75 | logger.Log(LogLevel.Verbose, message);
76 | }
77 |
78 | ///
79 | /// Converts the current with just the host by discarding the other parts like path and querystrings.
80 | ///
81 | /// The current to convert.
82 | /// A with only the host.
83 | public static Uri ToHostOnly(this Uri uri)
84 | {
85 | return uri.Segments.Length > 1
86 | ? new Uri($"{uri.Scheme}://{uri.Host}")
87 | : uri;
88 | }
89 |
90 | ///
91 | /// Converts the current string to a JSON web access token (JWT) as a string.
92 | ///
93 | /// The current access token as a string.
94 | /// A JWT as a JSON string.
95 | public static string ToJsonWebTokenString(this string accessToken)
96 | {
97 | // Effictively this splits by '.' and converts from a base-64 encoded string. Splitting creates new strings so this just calculates
98 | // a substring instead to reduce memory overhead.
99 | int start = accessToken.IndexOf(".", StringComparison.Ordinal) + 1;
100 |
101 | if (start < 0)
102 | {
103 | return null;
104 | }
105 |
106 | int length = accessToken.IndexOf(".", start, StringComparison.Ordinal) - start;
107 |
108 | return start > 0 && length < accessToken.Length
109 | ? Encoding.UTF8.GetString(
110 | Convert.FromBase64String(
111 | accessToken.Substring(start, length)
112 | .PadRight(length + (length % 4), '=')))
113 | : null;
114 | }
115 | }
116 | }
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/FeedEndpointCredentialsParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using Newtonsoft.Json;
6 | using ILogger = NuGetCredentialProvider.Logging.ILogger;
7 |
8 | namespace NuGetCredentialProvider.Util;
9 | public class ExternalEndpointCredentials
10 | {
11 | [JsonProperty("endpoint")]
12 | public string Endpoint { get; set; }
13 | [JsonProperty("username")]
14 | public string Username { get; set; }
15 | [JsonProperty("password")]
16 | public string Password { get; set; }
17 | }
18 |
19 | public class ExternalEndpointCredentialsContainer
20 | {
21 | [JsonProperty("endpointCredentials")]
22 | public ExternalEndpointCredentials[] EndpointCredentials { get; set; }
23 | }
24 |
25 | public class EndpointCredentials
26 | {
27 | [JsonPropertyName("endpoint")]
28 | public string Endpoint { get; set; }
29 | [JsonPropertyName("clientId")]
30 | public string ClientId { get; set; }
31 | [JsonPropertyName("clientCertificateFilePath")]
32 | public string CertificateFilePath { get; set; }
33 | [JsonPropertyName("clientCertificateSubjectName")]
34 | public string CertificateSubjectName { get; set; }
35 | }
36 |
37 | public class EndpointCredentialsContainer
38 | {
39 | [JsonPropertyName("endpointCredentials")]
40 | public EndpointCredentials[] EndpointCredentials { get; set; }
41 | }
42 |
43 | public static class FeedEndpointCredentialsParser
44 | {
45 | private static readonly JsonSerializerOptions options = new JsonSerializerOptions
46 | {
47 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
48 | PropertyNameCaseInsensitive = true,
49 | };
50 |
51 | public static Dictionary ParseFeedEndpointsJsonToDictionary(ILogger logger)
52 | {
53 | string feedEndpointsJson = Environment.GetEnvironmentVariable(EnvUtil.EndpointCredentials);
54 | if (string.IsNullOrWhiteSpace(feedEndpointsJson))
55 | {
56 | return new Dictionary(StringComparer.OrdinalIgnoreCase);
57 | }
58 |
59 | try
60 | {
61 | logger.Verbose(Resources.ParsingJson);
62 | Dictionary credsResult = new Dictionary(StringComparer.OrdinalIgnoreCase);
63 | EndpointCredentialsContainer endpointCredentials = System.Text.Json.JsonSerializer.Deserialize(feedEndpointsJson, options);
64 | if (endpointCredentials == null)
65 | {
66 | logger.Verbose(Resources.NoEndpointsFound);
67 | return credsResult;
68 | }
69 |
70 | foreach (var credentials in endpointCredentials.EndpointCredentials)
71 | {
72 | if (credentials == null)
73 | {
74 | logger.Verbose(Resources.EndpointParseFailure);
75 | break;
76 | }
77 |
78 | if (credentials.ClientId == null)
79 | {
80 | logger.Verbose(Resources.EndpointParseFailure);
81 | break;
82 | }
83 |
84 | if (credentials.CertificateSubjectName != null && credentials.CertificateFilePath != null)
85 | {
86 | logger.Verbose(Resources.EndpointParseFailure);
87 | break;
88 | }
89 |
90 | if (!Uri.TryCreate(credentials.Endpoint, UriKind.Absolute, out var endpointUri))
91 | {
92 | logger.Verbose(Resources.EndpointParseFailure);
93 | break;
94 | }
95 |
96 | var urlEncodedEndpoint = endpointUri.AbsoluteUri;
97 | if (!credsResult.ContainsKey(urlEncodedEndpoint))
98 | {
99 | credsResult.Add(urlEncodedEndpoint, credentials);
100 | }
101 | }
102 |
103 | return credsResult;
104 | }
105 | catch (Exception ex)
106 | {
107 | logger.Verbose(string.Format(Resources.VstsBuildTaskExternalCredentialCredentialProviderError, ex));
108 | return new Dictionary(StringComparer.OrdinalIgnoreCase); ;
109 | }
110 | }
111 |
112 | public static Dictionary ParseExternalFeedEndpointsJsonToDictionary(ILogger logger)
113 | {
114 | string feedEndpointsJson = Environment.GetEnvironmentVariable(EnvUtil.BuildTaskExternalEndpoints);
115 | if (string.IsNullOrWhiteSpace(feedEndpointsJson))
116 | {
117 | return new Dictionary(StringComparer.OrdinalIgnoreCase);
118 | }
119 |
120 | try
121 | {
122 | logger.Verbose(Resources.ParsingJson);
123 | if (feedEndpointsJson.Contains("':"))
124 | {
125 | logger.Warning(Resources.InvalidJsonWarning);
126 | }
127 |
128 | Dictionary credsResult = new Dictionary(StringComparer.OrdinalIgnoreCase);
129 | ExternalEndpointCredentialsContainer endpointCredentials = JsonConvert.DeserializeObject(feedEndpointsJson);
130 | if (endpointCredentials == null)
131 | {
132 | logger.Verbose(Resources.NoEndpointsFound);
133 | return credsResult;
134 | }
135 |
136 | foreach (var credentials in endpointCredentials.EndpointCredentials)
137 | {
138 | if (credentials == null)
139 | {
140 | logger.Verbose(Resources.EndpointParseFailure);
141 | break;
142 | }
143 |
144 | if (credentials.Username == null)
145 | {
146 | credentials.Username = "VssSessionToken";
147 | }
148 |
149 | if (!Uri.TryCreate(credentials.Endpoint, UriKind.Absolute, out var endpointUri))
150 | {
151 | logger.Verbose(Resources.EndpointParseFailure);
152 | break;
153 | }
154 |
155 | var urlEncodedEndpoint = endpointUri.AbsoluteUri;
156 | if (!credsResult.ContainsKey(urlEncodedEndpoint))
157 | {
158 | credsResult.Add(urlEncodedEndpoint, credentials);
159 | }
160 | }
161 |
162 | return credsResult;
163 | }
164 | catch (Exception ex)
165 | {
166 | logger.Verbose(string.Format(Resources.VstsBuildTaskExternalCredentialCredentialProviderError, ex));
167 | return new Dictionary(StringComparer.OrdinalIgnoreCase);
168 | }
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/HttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Net.Http;
6 | using System.Net.Http.Headers;
7 | using Microsoft.Artifacts.Authentication;
8 |
9 | namespace NuGetCredentialProvider.Util
10 | {
11 | public class HttpClientFactory : MsalHttpClientFactory
12 | {
13 | private static readonly HttpClientFactory httpClientFactory;
14 |
15 | public static HttpClientFactory Default => httpClientFactory;
16 |
17 | public HttpClientFactory(HttpClient httpClient) : base(httpClient)
18 | {
19 | }
20 |
21 | static HttpClientFactory()
22 | {
23 | var httpClient = new HttpClient(new HttpClientHandler
24 | {
25 | // This is needed to make IWA work. See:
26 | // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/blob/e15befb5f0a6cf4757c5abcaa6487e28e7ebd1bb/src/client/Microsoft.Identity.Client/PlatformsCommon/Shared/SimpleHttpClientFactory.cs#LL26C1-L27C73
27 | UseDefaultCredentials = true
28 | });
29 |
30 | // Add program context to headers if available
31 | if (ProgramContext != null)
32 | {
33 | httpClient.DefaultRequestHeaders.UserAgent.Add(ProgramContext);
34 | }
35 |
36 | httpClientFactory = new(httpClient);
37 | }
38 |
39 | private static ProductInfoHeaderValue ProgramContext
40 | {
41 | get
42 | {
43 | var context = EnvUtil.GetProgramContextFromEnvironment();
44 | return context != null
45 | ? new ProductInfoHeaderValue($"({context})")
46 | : null;
47 | }
48 | }
49 | }
50 |
51 | public enum Context
52 | {
53 | Maven,
54 | NuGet,
55 | Pip,
56 | Conda,
57 | Npm
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/Util/ICache.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace NuGetCredentialProvider.Util
6 | {
7 | public interface ICache
8 | {
9 | TValue this[TKey key] { get; set; }
10 |
11 | bool ContainsKey(TKey key);
12 |
13 | bool TryGetValue(TKey key, out TValue value);
14 |
15 | void Remove(TKey key);
16 | }
17 |
18 | public class NoOpCache : ICache
19 | {
20 | public TValue this[TKey key]
21 | {
22 | get => default;
23 | set
24 | {
25 | }
26 | }
27 |
28 | public bool ContainsKey(TKey key)
29 | {
30 | return false;
31 | }
32 |
33 | public void Remove(TKey key)
34 | {
35 | }
36 |
37 | public bool TryGetValue(TKey key, out TValue value)
38 | {
39 | value = default;
40 |
41 | return false;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/helpericons.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/artifacts-credprovider/107acd99393a8ba94be8a3280be18773c0549de4/CredentialProvider.Microsoft/helpericons.ico
--------------------------------------------------------------------------------
/CredentialProvider.Microsoft/runtimeconfig.template.json:
--------------------------------------------------------------------------------
1 | {
2 | "rollForward": "Major"
3 | }
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 | true
8 | $(NoWarn);CS1574;CS1591
9 |
10 |
11 |
12 | true
13 | true
14 | $(MSBuildThisFileDirectory)keys\FinalPublicKey.snk
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 | PrepareResources;$(CoreCompileDependsOn)
13 |
14 |
15 |
16 |
17 | MSBuild:Compile
18 | $(RootNamespace)$([MSBuild]::ValueOrDefault('.%(RelativeDir)', '').Replace('\', '.').Replace('/', '.').TrimEnd('.'))
19 | $(IntermediateOutputPath)%(ResourceNamespace).%(Filename).g$(DefaultLanguageSourceExtension)
20 | $(Language)
21 | %(ResourceNamespace)
22 | %(Filename)
23 |
24 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/Directory.Packages.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation. All rights reserved.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/MicrosoftCredentialProvider.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29411.138
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CredentialProvider.Microsoft", "CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj", "{E991747F-A42E-4502-A797-7886FFB14841}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CredentialProvider.Microsoft.Tests", "CredentialProvider.Microsoft.Tests\CredentialProvider.Microsoft.Tests.csproj", "{22ACC99A-B915-4DEC-A525-0DB6C8171639}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3A10D4CB-5F08-4267-9199-B1C811498912}"
11 | ProjectSection(SolutionItems) = preProject
12 | Build.props = Build.props
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{37ABB2C5-BCCA-4760-BA5A-2171606062D4}"
16 | EndProject
17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Artifacts.Authentication", "src\Authentication\Microsoft.Artifacts.Authentication.csproj", "{505FEC6B-0A7E-45C1-A78A-71C7694F3CAD}"
18 | EndProject
19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Artifacts.Authentication.Tests", "src\Authentication.Tests\Microsoft.Artifacts.Authentication.Tests.csproj", "{7B44129A-0105-445E-8620-567EE7E8F815}"
20 | EndProject
21 | Global
22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 | Debug|Any CPU = Debug|Any CPU
24 | Release|Any CPU = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {E991747F-A42E-4502-A797-7886FFB14841}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {E991747F-A42E-4502-A797-7886FFB14841}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {E991747F-A42E-4502-A797-7886FFB14841}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {E991747F-A42E-4502-A797-7886FFB14841}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {22ACC99A-B915-4DEC-A525-0DB6C8171639}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {22ACC99A-B915-4DEC-A525-0DB6C8171639}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {22ACC99A-B915-4DEC-A525-0DB6C8171639}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {22ACC99A-B915-4DEC-A525-0DB6C8171639}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {505FEC6B-0A7E-45C1-A78A-71C7694F3CAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {505FEC6B-0A7E-45C1-A78A-71C7694F3CAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {505FEC6B-0A7E-45C1-A78A-71C7694F3CAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {505FEC6B-0A7E-45C1-A78A-71C7694F3CAD}.Release|Any CPU.Build.0 = Release|Any CPU
39 | {7B44129A-0105-445E-8620-567EE7E8F815}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
40 | {7B44129A-0105-445E-8620-567EE7E8F815}.Debug|Any CPU.Build.0 = Debug|Any CPU
41 | {7B44129A-0105-445E-8620-567EE7E8F815}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {7B44129A-0105-445E-8620-567EE7E8F815}.Release|Any CPU.Build.0 = Release|Any CPU
43 | EndGlobalSection
44 | GlobalSection(SolutionProperties) = preSolution
45 | HideSolutionNode = FALSE
46 | EndGlobalSection
47 | GlobalSection(ExtensibilityGlobals) = postSolution
48 | SolutionGuid = {B8C4B4A0-5840-4475-86EA-05AF868057AF}
49 | EndGlobalSection
50 | GlobalSection(NestedProjects) = preSolution
51 | {505FEC6B-0A7E-45C1-A78A-71C7694F3CAD} = {37ABB2C5-BCCA-4760-BA5A-2171606062D4}
52 | {7B44129A-0105-445E-8620-567EE7E8F815} = {37ABB2C5-BCCA-4760-BA5A-2171606062D4}
53 | EndGlobalSection
54 | EndGlobal
55 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
6 |
7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report).
14 |
15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey).
16 |
17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc).
18 |
19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/build/validate-install-script-bash.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | - name: repo
3 | type: string
4 | - name: scriptEnvVariables
5 | type: string
6 | - name: expectedCredentialProviderVersion
7 | type: string
8 |
9 | steps:
10 | - checkout: ${{ parameters.repo }}
11 | - bash: |
12 | ${{ parameters.scriptEnvVariables }}
13 | ./helpers/installcredprovider.sh -Force >> ./output.log
14 | cat ./output.log
15 |
16 | if ! grep ${{ parameters.expectedCredentialProviderVersion }} ./output.log; then
17 | echo "Expected credential provider not found"
18 | exit 1
19 | fi
20 | if [ ! -d "$HOME/.nuget/plugins/netcore/CredentialProvider.Microsoft" ]; then
21 | echo "Credential provider plugin directory not found"
22 | exit 1
23 | fi
24 |
25 | echo "Credential provider installed successfully"
26 | workingDirectory: $(Build.SourcesDirectory)
27 | displayName: Validate Install Script
28 | - bash: |
29 | if grep $'\r' ./helpers/installcredprovider.sh; then
30 | echo "CRLF line ending found"
31 | exit 1
32 | fi
33 | workingDirectory: $(Build.SourcesDirectory)
34 | displayName: Check Line Endings
--------------------------------------------------------------------------------
/build/validate-install-script-powershell.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | - name: repo
3 | type: string
4 | - name: scriptInputs
5 | type: string
6 | - name: expectedCredentialProviderVersion
7 | type: string
8 |
9 | steps:
10 | - checkout: ${{ parameters.repo }}
11 | - task: PowerShell@2
12 | displayName: Validate Install Script
13 | inputs:
14 | targetType: 'inline'
15 | script: |
16 | ./helpers/installcredprovider.ps1 ${{ parameters.scriptInputs }} -Force 6>> ./output.log
17 | cat ./output.log
18 | if( (Select-String -Path .\output.log -Pattern ${{ parameters.expectedCredentialProviderVersion }}) -eq $null) {echo "Expected credential provider file not found."; exit 1}
19 | workingDirectory: $(Build.SourcesDirectory)
--------------------------------------------------------------------------------
/build/validate-install-script.yml:
--------------------------------------------------------------------------------
1 | jobs:
2 | - job: WindowsInstallDefault
3 | pool:
4 | vmImage: windows-latest
5 | steps:
6 | - template: validate-install-script-powershell.yml@self
7 | parameters:
8 | repo: self
9 | scriptInputs: ''
10 | expectedCredentialProviderVersion: 'Microsoft.Net6.NuGet.CredentialProvider'
11 | - job: WindowsInstallNet8
12 | pool:
13 | vmImage: windows-latest
14 | steps:
15 | - template: validate-install-script-powershell.yml@self
16 | parameters:
17 | repo: self
18 | scriptInputs: '-InstallNet8'
19 | expectedCredentialProviderVersion: 'Microsoft.Net8.NuGet.CredentialProvider'
20 | - job: WindowsInstallNet8winx64
21 | pool:
22 | vmImage: windows-latest
23 | steps:
24 | - template: validate-install-script-powershell.yml@self
25 | parameters:
26 | repo: self
27 | scriptInputs: '-InstallNet8 -RuntimeIdentifier win-x64'
28 | expectedCredentialProviderVersion: 'Microsoft.Net8.win-x64.NuGet.CredentialProvider'
29 | - job: WindowsInstallNet8winx64NoParams
30 | pool:
31 | vmImage: windows-latest
32 | steps:
33 | - template: validate-install-script-powershell.yml@self
34 | parameters:
35 | repo: self
36 | scriptInputs: '-RuntimeIdentifier win-x64'
37 | expectedCredentialProviderVersion: 'Microsoft.Net8.win-x64.NuGet.CredentialProvider'
38 | - job: WindowsInstallNet8winx64Net6Params
39 | pool:
40 | vmImage: windows-latest
41 | steps:
42 | - template: validate-install-script-powershell.yml@self
43 | parameters:
44 | repo: self
45 | scriptInputs: '-InstallNet6 -RuntimeIdentifier win-x64'
46 | expectedCredentialProviderVersion: 'Microsoft.Net8.win-x64.NuGet.CredentialProvider'
47 | - job: WindowsInstallNetfxDefault
48 | pool:
49 | vmImage: windows-latest
50 | steps:
51 | - template: validate-install-script-powershell.yml@self
52 | parameters:
53 | repo: self
54 | scriptInputs: '-AddNetfx'
55 | expectedCredentialProviderVersion: 'Microsoft.NuGet.CredentialProvider'
56 | - job: WindowsInstallNetfx48
57 | pool:
58 | vmImage: windows-latest
59 | steps:
60 | - template: validate-install-script-powershell.yml@self
61 | parameters:
62 | repo: self
63 | scriptInputs: '-AddNetFx48'
64 | expectedCredentialProviderVersion: 'Microsoft.NetFx48.NuGet.CredentialProvider'
65 | - job: LinuxInstallDefault
66 | pool:
67 | vmImage: ubuntu-latest
68 | steps:
69 | - template: validate-install-script-bash.yml@self
70 | parameters:
71 | repo: self
72 | scriptEnvVariables: ''
73 | expectedCredentialProviderVersion: 'Microsoft.Net6.NuGet.CredentialProvider'
74 | - job: LinuxInstallNet8
75 | pool:
76 | vmImage: ubuntu-latest
77 | steps:
78 | - template: validate-install-script-bash.yml@self
79 | parameters:
80 | repo: self
81 | scriptEnvVariables: |
82 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
83 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
84 | expectedCredentialProviderVersion: 'Microsoft.Net8.NuGet.CredentialProvider'
85 | - job: LinuxInstallNet8Net6Param
86 | pool:
87 | vmImage: ubuntu-latest
88 | steps:
89 | - template: validate-install-script-bash.yml@self
90 | parameters:
91 | repo: self
92 | scriptEnvVariables: |
93 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=true
94 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
95 | expectedCredentialProviderVersion: 'Microsoft.Net8.NuGet.CredentialProvider'
96 | - job: LinuxInstallNetFx
97 | pool:
98 | vmImage: ubuntu-latest
99 | steps:
100 | - template: validate-install-script-bash.yml@self
101 | parameters:
102 | repo: self
103 | scriptEnvVariables: |
104 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
105 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=false
106 | expectedCredentialProviderVersion: 'Microsoft.NuGet.CredentialProvider'
107 | - job: LinuxInstallNet8linuxx64
108 | pool:
109 | vmImage: ubuntu-latest
110 | steps:
111 | - template: validate-install-script-bash.yml@self
112 | parameters:
113 | repo: self
114 | scriptEnvVariables: |
115 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
116 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
117 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=linux-x64
118 | expectedCredentialProviderVersion: 'Microsoft.Net8.linux-x64.NuGet.CredentialProvider'
119 | - job: LinuxInstallNet8linuxarm64
120 | pool:
121 | vmImage: ubuntu-latest
122 | steps:
123 | - template: validate-install-script-bash.yml@self
124 | parameters:
125 | repo: self
126 | scriptEnvVariables: |
127 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
128 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
129 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=linux-arm64
130 | expectedCredentialProviderVersion: 'Microsoft.Net8.linux-arm64.NuGet.CredentialProvider'
131 | - job: macOSInstallNet8osxarm64
132 | pool:
133 | vmImage: macOS-latest
134 | steps:
135 | - template: validate-install-script-bash.yml@self
136 | parameters:
137 | repo: self
138 | scriptEnvVariables: |
139 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
140 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
141 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=osx-arm64
142 | expectedCredentialProviderVersion: 'Microsoft.Net8.osx-arm64.NuGet.CredentialProvider'
143 | - job: macOSnstallNet8osxx64
144 | pool:
145 | vmImage: macOS-latest
146 | steps:
147 | - template: validate-install-script-bash.yml@self
148 | parameters:
149 | repo: self
150 | scriptEnvVariables: |
151 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=false
152 | export USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER=true
153 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=osx-x64
154 | expectedCredentialProviderVersion: 'Microsoft.Net8.osx-x64.NuGet.CredentialProvider'
155 | - job: macOSInstallNet8osxx64NoParams
156 | pool:
157 | vmImage: macOS-latest
158 | steps:
159 | - template: validate-install-script-bash.yml@self
160 | parameters:
161 | repo: self
162 | scriptEnvVariables: |
163 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=osx-x64
164 | expectedCredentialProviderVersion: 'Microsoft.Net8.osx-x64.NuGet.CredentialProvider'
165 | - job: macOSnstallNet8osxx64Net6Params
166 | pool:
167 | vmImage: macOS-latest
168 | steps:
169 | - template: validate-install-script-bash.yml@self
170 | parameters:
171 | repo: self
172 | scriptEnvVariables: |
173 | export USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER=true
174 | export ARTIFACTS_CREDENTIAL_PROVIDER_RID=osx-x64
175 | expectedCredentialProviderVersion: 'Microsoft.Net8.osx-x64.NuGet.CredentialProvider'
176 |
--------------------------------------------------------------------------------
/helpers/installcredprovider.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # DESCRIPTION: A simple shell script designed to fetch a version
3 | # of the artifacts credential provider plugin and install it into $HOME/.nuget/plugins.
4 | # Readme: https://github.com/Microsoft/artifacts-credprovider/blob/master/README.md
5 |
6 | # Default version to install is the latest version.
7 | # To install a release other than `latest`, set the `AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION` environment
8 | # variable to match the TAG NAME of a supported release, e.g. "v1.0.1".
9 | # Releases: https://github.com/microsoft/artifacts-credprovider/releases
10 |
11 | REPO="Microsoft/artifacts-credprovider"
12 | NUGET_PLUGIN_DIR="$HOME/.nuget/plugins"
13 |
14 | # If a RuntimeID (RID) is set, install the self-contained version of the .NET 8 credential provider.
15 | # To install a release with a specific runtime version set the `ARTIFACTS_CREDENTIAL_PROVIDER_RID` enviornment variable.
16 | if [ ! -z ${ARTIFACTS_CREDENTIAL_PROVIDER_RID} ]; then
17 | echo "INFO: ARTIFACTS_CREDENTIAL_PROVIDER_RID variable set, defaulting to NET8 installation."
18 |
19 | # If the RID is osx-*, use the zip file otherwise use the tar.gz file.
20 | case ${ARTIFACTS_CREDENTIAL_PROVIDER_RID} in osx-* )
21 | FILE="Microsoft.Net8.${ARTIFACTS_CREDENTIAL_PROVIDER_RID}.NuGet.CredentialProvider.zip"
22 | ;;
23 | *)
24 | FILE="Microsoft.Net8.${ARTIFACTS_CREDENTIAL_PROVIDER_RID}.NuGet.CredentialProvider.tar.gz"
25 | ;;
26 | esac
27 |
28 | if [ -z ${USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER} ]; then
29 | echo "WARNING: The USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER variable is set, but ARTIFACTS_CREDENTIAL_PROVIDER_RID variable is defined. The NET8 version of the credential provider will be installed."
30 | fi
31 |
32 | # throw if version starts < 1.4.0. (self-contained not supported)
33 | case ${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION} in
34 | 0.*|v0.*|1.0.*|v1.0.*|1.1.*|v1.1.*|1.2.*|v1.2.*|1.3.*|v1.3.*)
35 | echo "ERROR: To install NET8 cred provider using the ARTIFACTS_CREDENTIAL_PROVIDER_RID variable, version to be installed must be 1.4.0 or greater. Check your AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION variable."
36 | exit 1
37 | ;;
38 | esac
39 | # If .NET 8 variable is set, install the .NET 8 version of the credential provider even if USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER is true.
40 | elif [ ! -z ${USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER} ] && [ ${USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER} != "false" ]; then
41 | # Default to the full zip file since ARTIFACTS_CREDENTIAL_PROVIDER_RID is not specified.
42 | FILE="Microsoft.Net8.NuGet.CredentialProvider.tar.gz"
43 |
44 | if [ -z ${USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER} ]; then
45 | echo "WARNING: The USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER variable is set, but USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER variable is true. The NET8 version of the credential provider will be installed."
46 | fi
47 |
48 | # throw if version starts < 1.3.0. (net8 not supported)
49 | case ${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION} in
50 | 0.*|v0.*|1.0.*|v1.0.*|1.1.*|v1.1.*|1.2.*|v1.2.*)
51 | echo "ERROR: To install NET8 cred provider using the USE_NET8_ARTIFACTS_CREDENTIAL_PROVIDER variable, version to be installed must be 1.3.0 or greater. Check your AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION variable."
52 | exit 1
53 | ;;
54 | esac
55 | # .NET 6 is the default installation, attempt to install unless set to false.
56 | elif [ -z ${USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER} ] || [ ${USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER} != "false" ]; then
57 | FILE="Microsoft.Net6.NuGet.CredentialProvider.tar.gz"
58 |
59 | # throw if version starts with 0. (net6 not supported)
60 | case ${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION} in
61 | 0.*|v0.*)
62 | echo "ERROR: To install NET6 cred provider using the USE_NET6_ARTIFACTS_CREDENTIAL_PROVIDER variable, version to be installed must be 1.0.0 or greater. Check your AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION variable."
63 | exit 1
64 | ;;
65 | esac
66 | # If .NET 6 is disabled and .NET 8 isn't explicitly enabled, fall back to the legacy .NET Framework.
67 | else
68 | echo "WARNING: The .Net Framework 3.1 version of the Credential Provider is deprecated and will be removed in the next major release. Please migrate to the .Net Framework 4.8 or .Net Core versions."
69 | FILE="Microsoft.NuGet.CredentialProvider.tar.gz"
70 | fi
71 |
72 | # If AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION is set, install the version specified, otherwise install latest
73 | if [ ! -z ${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION} ] && [ ${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION} != "latest" ]; then
74 | # browser_download_url from https://api.github.com/repos/Microsoft/artifacts-credprovider/releases/latest
75 | URI="https://github.com/$REPO/releases/download/${AZURE_ARTIFACTS_CREDENTIAL_PROVIDER_VERSION}/$FILE"
76 | else
77 | # URL pattern to get latest documented at https://help.github.com/en/articles/linking-to-releases as of 2019-03-29
78 | URI="https://github.com/$REPO/releases/latest/download/$FILE"
79 | fi
80 |
81 | # Ensure plugin directory exists
82 | if [ ! -d "${NUGET_PLUGIN_DIR}" ]; then
83 | echo "INFO: Creating the nuget plugin directory (i.e. ${NUGET_PLUGIN_DIR}). "
84 | if ! mkdir -p "${NUGET_PLUGIN_DIR}"; then
85 | echo "ERROR: Unable to create nuget plugins directory (i.e. ${NUGET_PLUGIN_DIR})."
86 | exit 1
87 | fi
88 | fi
89 |
90 | echo "Downloading from $URI"
91 | # Extract netcore from the .tar.gz into the plugin directory
92 |
93 | #Fetch the file
94 | if ! curl -H "Accept: application/octet-stream" \
95 | -s \
96 | -S \
97 | -L \
98 | "$URI" | tar xz -C "$HOME/.nuget/" "plugins/netcore"; then
99 | exit 1
100 | fi
101 |
102 | echo "INFO: credential provider netcore plugin extracted to $HOME/.nuget/"
--------------------------------------------------------------------------------
/nuget.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/dockerfile.sample.txt:
--------------------------------------------------------------------------------
1 | # This sample is for Artifacts Credential Provider version 0.1.28 and it is incompatible with greater versions due to the .NET Core version this sample uses.
2 |
3 | # downloading the dotnet sdk image. Could be any docker sdk image with sdk > 2.1.500
4 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS dotnet-builder
5 | ARG FEED_URL
6 | ARG PAT
7 |
8 | # download and install latest credential provider. Not required after https://github.com/dotnet/dotnet-docker/issues/878
9 | RUN wget -qO- https://aka.ms/install-artifacts-credprovider.sh | bash
10 |
11 | # Optional
12 | WORKDIR /workdir
13 | COPY ./ .
14 |
15 | # Optional: Sometimes the http client hangs because of a .NET issue. Setting this in dockerfile helps
16 | ENV DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER=0
17 |
18 | # Environment variable to enable seesion token cache. More on this here: https://github.com/Microsoft/artifacts-credprovider#help
19 | ENV NUGET_CREDENTIALPROVIDER_SESSIONTOKENCACHE_ENABLED true
20 |
21 | # Environment variable for adding endpoint credentials. More on this here: https://github.com/Microsoft/artifacts-credprovider#help
22 | # Add "FEED_URL" AND "PAT" using --build-arg in docker build step. "endpointCredentials" field is an array, you can add multiple endpoint configurations.
23 | # Make sure that you *do not* hard code the "PAT" here. That is a sensitive information and must not be checked in.
24 | ENV VSS_NUGET_EXTERNAL_FEED_ENDPOINTS {\"endpointCredentials\": [{\"endpoint\":\"${FEED_URL}\", \"username\":\"ArtifactsDocker\", \"password\":\"${PAT}\"}]}
25 |
26 | # Use this if you have a nuget.config file with all the endpoints.
27 | RUN dotnet restore
28 |
29 | # Optional: Extended step to build the app using dotnet msbuild.
30 | RUN dotnet build dirs.proj
31 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/AccountPriorityTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication.Tests;
6 |
7 | [TestClass]
8 | public class AccountPriorityTests
9 | {
10 | private class TestAccount : IAccount
11 | {
12 | public string? Username { get; set; }
13 | public string? Environment { get; set; }
14 | public AccountId? HomeAccountId { get; set; }
15 | }
16 |
17 | private static readonly Guid ContosoTenant = Guid.NewGuid();
18 | private static readonly Guid FabrikamTenant = Guid.NewGuid();
19 |
20 | private static readonly IAccount FabrikamUser = new TestAccount
21 | {
22 | Username = "billg@fabrikam.com",
23 | Environment = string.Empty,
24 | HomeAccountId = new AccountId(string.Empty, string.Empty, FabrikamTenant.ToString()),
25 | };
26 |
27 | private static readonly IAccount ContosoUser = new TestAccount
28 | {
29 | Username = "billg@contoso.com",
30 | Environment = string.Empty,
31 | HomeAccountId = new AccountId(string.Empty, string.Empty, ContosoTenant.ToString()),
32 | };
33 |
34 | private static readonly IAccount MsaUser = new TestAccount
35 | {
36 | Username = "bill.gates@live.com",
37 | Environment = string.Empty,
38 | HomeAccountId = new AccountId(string.Empty, string.Empty, MsalConstants.MsaAccountTenant.ToString()),
39 | };
40 |
41 | private static readonly List> Permutations = new List>()
42 | {
43 | new List { ContosoUser, MsaUser, FabrikamUser },
44 | new List { ContosoUser, FabrikamUser, MsaUser },
45 | new List { MsaUser, ContosoUser, FabrikamUser },
46 | new List { MsaUser, FabrikamUser, ContosoUser },
47 | new List { FabrikamUser, MsaUser, ContosoUser },
48 | new List { FabrikamUser, ContosoUser, MsaUser },
49 | };
50 |
51 | [TestMethod]
52 | public void MsaMatchesMsa()
53 | {
54 | foreach (var accounts in Permutations)
55 | {
56 | var applicable = MsalExtensions.GetApplicableAccounts(accounts, MsalConstants.FirstPartyTenant, loginHint: null);
57 | Assert.AreEqual(applicable[0].Item1.Username, MsaUser.Username);
58 | }
59 | }
60 |
61 | [TestMethod]
62 | public void ContosoMatchesContoso()
63 | {
64 | foreach (var accounts in Permutations)
65 | {
66 | var applicable = MsalExtensions.GetApplicableAccounts(accounts, ContosoTenant, loginHint: null);
67 | Assert.AreEqual(applicable[0].Item1.Username, ContosoUser.Username);
68 | }
69 | }
70 |
71 | [TestMethod]
72 | public void FabrikamMatchesFabrikam()
73 | {
74 | foreach (var accounts in Permutations)
75 | {
76 | var applicable = MsalExtensions.GetApplicableAccounts(accounts, FabrikamTenant, loginHint: null);
77 | Assert.AreEqual(applicable[0].Item1.Username, FabrikamUser.Username);
78 | }
79 | }
80 |
81 | [TestMethod]
82 | public void LoginHintOverride()
83 | {
84 | foreach (var accounts in Permutations)
85 | {
86 | foreach (var tenantId in Permutations[0].Select(a => Guid.Parse(a.HomeAccountId.TenantId)))
87 | {
88 | foreach (var loginHint in Permutations[0].Select(a => a.Username))
89 | {
90 | var applicable = MsalExtensions.GetApplicableAccounts(accounts, tenantId, loginHint);
91 | Assert.AreEqual(applicable[0].Item1.Username, loginHint);
92 | }
93 | }
94 | }
95 | }
96 |
97 | [TestMethod]
98 | public void UnknownAuthorityTenantPrefersMsa()
99 | {
100 | foreach (var accounts in Permutations)
101 | {
102 | var applicable = MsalExtensions.GetApplicableAccounts(accounts, Guid.Empty, loginHint: null);
103 | Assert.AreEqual(applicable[0].Item1.Username, MsaUser.Username);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/Microsoft.Artifacts.Authentication.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.Artifacts.Authentication.Tests
5 | Microsoft.Artifacts.Authentication.Tests
6 | net6.0
7 | enable
8 | enable
9 |
10 | false
11 | true
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/MsalAuthenticationTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication.Tests;
6 |
7 | // Tests are meant to be run manually as it requires interactive logins, multiple accounts, and domain joined machines.
8 | [Ignore]
9 | [TestClass]
10 | public class MsalAuthenticationTests
11 | {
12 | private static readonly Uri PackageUri = new Uri("https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/artifacts-credprovider/nuget/v3/index.json");
13 | private static readonly Uri AuthorityUri = new Uri("https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47");
14 | private static ILogger logger = LoggerFactory
15 | .Create(builder =>
16 | {
17 | builder.SetMinimumLevel(Extensions.Logging.LogLevel.Trace);
18 | builder.AddDebug();
19 | builder.AddConsole();
20 | })
21 | .CreateLogger(nameof(MsalAuthenticationTests));
22 |
23 | private static MsalCacheHelper cache = MsalCache
24 | .GetMsalCacheHelperAsync(MsalCache.DefaultMsalCacheLocation, logger).GetAwaiter().GetResult();
25 |
26 | private IPublicClientApplication app = AzureArtifacts
27 | .CreateDefaultBuilder(AuthorityUri)
28 | .WithBroker(true, logger)
29 | // The test hosting process (testhost.exe) may not have an associated console window, so use
30 | // the foreground window which is correct enough when debugging and running tests locally.
31 | .WithParentActivityOrWindow(() => GetForegroundWindow())
32 | .Build(cache);
33 |
34 | [TestMethod]
35 | public async Task MsalAcquireTokenSilentTest()
36 | {
37 | var tokenProvider = new MsalSilentTokenProvider(app, logger);
38 | var tokenRequest = new TokenRequest();
39 |
40 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
41 |
42 | Assert.IsNotNull(result);
43 | Assert.AreEqual(TokenSource.Broker, result.AuthenticationResultMetadata.TokenSource);
44 | }
45 |
46 | [TestMethod]
47 | public async Task MsalAcquireTokenByIntegratedWindowsAuthTest()
48 | {
49 | var tokenProvider = new MsalIntegratedWindowsAuthTokenProvider(app, logger);
50 | var tokenRequest = new TokenRequest();
51 |
52 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
53 |
54 | Assert.IsNotNull(result);
55 | Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
56 | }
57 |
58 | [TestMethod]
59 | public async Task MsalAcquireTokenInteractiveTest()
60 | {
61 | var tokenProvider = new MsalInteractiveTokenProvider(app, logger);
62 | var tokenRequest = new TokenRequest();
63 |
64 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
65 |
66 | Assert.IsNotNull(result);
67 | Assert.AreEqual(TokenSource.Broker, result.AuthenticationResultMetadata.TokenSource);
68 | }
69 |
70 | [TestMethod]
71 | public async Task MsalAcquireTokenWithDeviceCodeTest()
72 | {
73 | var tokenProvider = new MsalDeviceCodeTokenProvider(app, logger);
74 | var tokenRequest = new TokenRequest();
75 |
76 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
77 |
78 | Assert.IsNotNull(result);
79 | Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
80 | }
81 |
82 | [TestMethod]
83 | public async Task MsalAquireTokenWithManagedIdentity()
84 | {
85 | var tokenProvider = new MsalManagedIdentityTokenProvider(app, logger);
86 | var tokenRequest = new TokenRequest();
87 | tokenRequest.ClientId = Environment.GetEnvironmentVariable("ARTIFACTS_CREDENTIALPROVIDER_TEST_CLIENTID");
88 |
89 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
90 |
91 | Assert.IsNotNull(result);
92 | Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
93 | }
94 |
95 | [TestMethod]
96 | public async Task MsalAquireTokenWithServicePrincipal()
97 | {
98 | var tokenProvider = new MsalServicePrincipalTokenProvider(app, logger);
99 | var tokenRequest = new TokenRequest();
100 | tokenRequest.ClientId = Environment.GetEnvironmentVariable("ARTIFACTS_CREDENTIALPROVIDER_TEST_CLIENTID");
101 | tokenRequest.ClientCertificate = new X509Certificate2(Environment.GetEnvironmentVariable("ARTIFACTS_CREDENTIALPROVIDER_TEST_CERT_PATH") ?? string.Empty);
102 |
103 | var result = await tokenProvider.GetTokenAsync(tokenRequest);
104 |
105 | Assert.IsNotNull(result);
106 | Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource);
107 | }
108 |
109 | [System.Runtime.InteropServices.DllImport("user32.dll")]
110 | private static extern IntPtr GetForegroundWindow();
111 | }
112 |
113 | internal static class MsalCacheExtensions
114 | {
115 | public static IPublicClientApplication Build(this PublicClientApplicationBuilder builder, MsalCacheHelper? cache = null)
116 | {
117 | IPublicClientApplication app = builder.Build();
118 |
119 | cache?.RegisterCache(app.UserTokenCache);
120 |
121 | return app;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/MsalHttpClientFactoryTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication.Tests;
6 |
7 | [TestClass]
8 | public class MsalHttpClientFactoryTests
9 | {
10 | [TestMethod]
11 | public void GetHttpClientTest()
12 | {
13 | var httpClient = new HttpClient();
14 | var httpClientFactory = new MsalHttpClientFactory(httpClient);
15 |
16 | Assert.AreEqual(httpClient, httpClientFactory.GetHttpClient());
17 | }
18 |
19 | [TestMethod]
20 | public void UserAgentTest()
21 | {
22 | var httpClientFactory = new MsalHttpClientFactory(new HttpClient());
23 | var httpClient = httpClientFactory.GetHttpClient();
24 | var userAgent = httpClient.DefaultRequestHeaders.UserAgent;
25 |
26 | Assert.AreEqual(4, userAgent.Count);
27 |
28 | var programProduct = userAgent.ElementAt(0);
29 | Assert.IsNotNull(programProduct.Product);
30 | Assert.AreEqual(MsalHttpClientFactory.ProgramProduct, programProduct);
31 | Assert.AreEqual(PlatformInformation.GetProgramName(), programProduct.Product.Name);
32 | Assert.AreEqual(PlatformInformation.GetProgramVersion(), programProduct.Product.Version);
33 |
34 | var programComment = userAgent.ElementAt(1);
35 | Assert.AreEqual(MsalHttpClientFactory.ProgramComment, programComment);
36 | Assert.AreEqual($"({PlatformInformation.GetOSType()}; {PlatformInformation.GetCpuArchitecture()}; {PlatformInformation.GetOsDescription()})", programComment.Comment);
37 |
38 | var clrProduct = userAgent.ElementAt(2);
39 | Assert.IsNotNull(clrProduct.Product);
40 | Assert.AreEqual(MsalHttpClientFactory.ClrProduct, clrProduct);
41 | Assert.AreEqual("CLR", clrProduct.Product.Name);
42 | Assert.AreEqual(PlatformInformation.GetClrVersion(), clrProduct.Product.Version);
43 |
44 | var clrComment = userAgent.ElementAt(3);
45 | Assert.AreEqual(MsalHttpClientFactory.ClrComment, clrComment);
46 | Assert.AreEqual($"({PlatformInformation.GetClrFramework()}; {PlatformInformation.GetClrRuntime()}; {PlatformInformation.GetClrDescription()})", clrComment.Comment);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/TokenProviderTests.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication.Tests;
6 |
7 | [TestClass]
8 | public class TokenProviderTests
9 | {
10 | private static readonly Uri PackageUri = new Uri("https://pkgs.dev.azure.com/mseng/PipelineTools/_packaging/artifacts-credprovider/nuget/v3/index.json");
11 |
12 | private Mock appMock = new Mock(MockBehavior.Strict);
13 | private Mock loggerMock = new Mock();
14 |
15 | [TestMethod]
16 | public void MsalSilentContractTest()
17 | {
18 | var tokenProvider = new MsalSilentTokenProvider(appMock.Object, loggerMock.Object);
19 | var tokenRequest = new TokenRequest();
20 |
21 | Assert.IsNotNull(tokenProvider.Name);
22 | Assert.IsFalse(tokenProvider.IsInteractive);
23 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
24 |
25 | tokenRequest.IsRetry = true;
26 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
27 | }
28 |
29 | [TestMethod]
30 | public void MsalIntegratedWindowsAuthContractTest()
31 | {
32 | var tokenProvider = new MsalIntegratedWindowsAuthTokenProvider(appMock.Object, loggerMock.Object);
33 | var tokenRequest = new TokenRequest();
34 | var windowsIntegratedAuthSupported = Environment.OSVersion.Platform == PlatformID.Win32NT;
35 |
36 | Assert.IsNotNull(tokenProvider.Name);
37 | Assert.IsFalse(tokenProvider.IsInteractive);
38 |
39 | tokenRequest.IsWindowsIntegratedAuthEnabled = false;
40 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
41 |
42 | tokenRequest.IsWindowsIntegratedAuthEnabled = true;
43 | Assert.AreEqual(windowsIntegratedAuthSupported, tokenProvider.CanGetToken(tokenRequest));
44 | }
45 |
46 | [TestMethod]
47 | public void MsalInteractiveContractTest()
48 | {
49 | var tokenProvider = new MsalInteractiveTokenProvider(appMock.Object, loggerMock.Object);
50 | var tokenRequest = new TokenRequest();
51 |
52 | Assert.IsNotNull(tokenProvider.Name);
53 | Assert.IsTrue(tokenProvider.IsInteractive);
54 |
55 | tokenRequest.IsInteractive = false;
56 | tokenRequest.CanShowDialog = false;
57 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
58 |
59 | tokenRequest.IsInteractive = false;
60 | tokenRequest.CanShowDialog = true;
61 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
62 |
63 | tokenRequest.IsInteractive = true;
64 | tokenRequest.CanShowDialog = false;
65 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
66 |
67 | tokenRequest.IsInteractive = true;
68 | tokenRequest.CanShowDialog = true;
69 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
70 |
71 | // non-interactive variants for those who don't not avoid framework guidelines
72 | tokenRequest.IsNonInteractive = false;
73 | tokenRequest.CanShowDialog = true;
74 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
75 |
76 | tokenRequest.IsNonInteractive = true;
77 | tokenRequest.CanShowDialog = true;
78 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
79 | }
80 |
81 | [TestMethod]
82 | public void MsalDeviceCodeFlowContractTest()
83 | {
84 | var tokenProvider = new MsalDeviceCodeTokenProvider(appMock.Object, loggerMock.Object);
85 | var tokenRequest = new TokenRequest();
86 |
87 | Assert.IsNotNull(tokenProvider.Name);
88 | Assert.IsTrue(tokenProvider.IsInteractive);
89 |
90 | tokenRequest.IsInteractive = false;
91 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
92 |
93 | tokenRequest.IsInteractive = true;
94 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
95 | }
96 |
97 | [TestMethod]
98 | public void MsalServicePrincipalContractTest()
99 | {
100 | appMock.Setup(x => x.AppConfig)
101 | .Returns(new Mock().Object);
102 |
103 | var tokenProvider = new MsalServicePrincipalTokenProvider(appMock.Object, loggerMock.Object);
104 | var tokenRequest = new TokenRequest();
105 |
106 | Assert.IsNotNull(tokenProvider.Name);
107 | Assert.IsFalse(tokenProvider.IsInteractive);
108 |
109 | tokenRequest.IsInteractive = true;
110 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
111 |
112 | tokenRequest.IsInteractive = false;
113 | tokenRequest.ClientId = "clientId";
114 | tokenRequest.ClientCertificate = Mock.Of();
115 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
116 |
117 | tokenRequest.IsInteractive = false;
118 | tokenRequest.ClientId = null;
119 | tokenRequest.ClientCertificate = Mock.Of();
120 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
121 |
122 | tokenRequest.IsInteractive = false;
123 | tokenRequest.ClientId = "clientId";
124 | tokenRequest.ClientCertificate = null;
125 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
126 | }
127 |
128 |
129 | [TestMethod]
130 | public void MsalManagedIdentityContractTest()
131 | {
132 | appMock.Setup(x => x.AppConfig)
133 | .Returns(new Mock().Object);
134 |
135 | var tokenProvider = new MsalManagedIdentityTokenProvider(appMock.Object, loggerMock.Object);
136 | var tokenRequest = new TokenRequest();
137 |
138 | Assert.IsNotNull(tokenProvider.Name);
139 | Assert.IsFalse(tokenProvider.IsInteractive);
140 |
141 | tokenRequest.IsInteractive = true;
142 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
143 |
144 | tokenRequest.IsInteractive = false;
145 | tokenRequest.ClientId = "clientId";
146 | Assert.IsTrue(tokenProvider.CanGetToken(tokenRequest));
147 |
148 | tokenRequest.IsInteractive = false;
149 | tokenRequest.ClientId = null;
150 | Assert.IsFalse(tokenProvider.CanGetToken(tokenRequest));
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Authentication.Tests/Usings.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | global using System.Security.Cryptography.X509Certificates;
6 | global using Microsoft.Extensions.Logging;
7 | global using Microsoft.Identity.Client;
8 | global using Microsoft.Identity.Client.Extensions.Msal;
9 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
10 | global using Moq;
11 |
--------------------------------------------------------------------------------
/src/Authentication/AzureArtifacts.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Runtime.InteropServices;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Identity.Client;
8 | using Microsoft.Identity.Client.Broker;
9 |
10 | namespace Microsoft.Artifacts.Authentication;
11 |
12 | public static class AzureArtifacts
13 | {
14 | ///
15 | /// Azure Artifacts application ID.
16 | ///
17 | public const string ClientId = "d5a56ea4-7369-46b8-a538-c370805301bf";
18 |
19 | ///
20 | /// Visual Studio application ID.
21 | ///
22 | private const string LegacyClientId = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
23 |
24 | public static PublicClientApplicationBuilder CreateDefaultBuilder(Uri authority)
25 | {
26 | // Azure Artifacts is not yet present in PPE, so revert to the old app in that case
27 | bool prod = !authority.Host.Equals("login.windows-ppe.net", StringComparison.OrdinalIgnoreCase);
28 |
29 | var builder = PublicClientApplicationBuilder.Create(prod ? AzureArtifacts.ClientId : AzureArtifacts.LegacyClientId)
30 | .WithAuthority(authority)
31 | .WithRedirectUri("http://localhost");
32 |
33 | return builder;
34 | }
35 |
36 | public static PublicClientApplicationBuilder WithBroker(this PublicClientApplicationBuilder builder, bool enableBroker, ILogger logger)
37 | {
38 | // Eventually will be rolled into CreateDefaultBuilder as using the brokers is desirable
39 | if (!enableBroker || !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
40 | {
41 | return builder;
42 | }
43 |
44 | logger.LogTrace(Resources.MsalUsingWamBroker);
45 |
46 | return builder
47 | .WithBroker(
48 | new BrokerOptions(BrokerOptions.OperatingSystems.Windows)
49 | {
50 | Title = "Azure DevOps Artifacts",
51 | ListOperatingSystemAccounts = true,
52 | MsaPassthrough = true
53 | })
54 | .WithParentActivityOrWindow(() => GetConsoleOrTerminalWindow());
55 | }
56 |
57 | public static PublicClientApplicationBuilder WithHttpClient(this PublicClientApplicationBuilder builder, HttpClient? httpClient = null)
58 | {
59 | // Default HttpClient is only meant for .NET Framework clients that can't use the SocketsHttpHandler
60 | return builder.WithHttpClientFactory(new MsalHttpClientFactory(httpClient ?? new HttpClient(new HttpClientHandler
61 | {
62 | // Important for IWA
63 | UseDefaultCredentials = true
64 | })));
65 | }
66 |
67 | #region https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3590
68 | enum GetAncestorFlags
69 | {
70 | GetParent = 1,
71 | GetRoot = 2,
72 | ///
73 | /// Retrieves the owned root window by walking the chain of parent and owner windows returned by GetParent.
74 | ///
75 | GetRootOwner = 3
76 | }
77 |
78 | ///
79 | /// Retrieves the handle to the ancestor of the specified window.
80 | ///
81 | /// A handle to the window whose ancestor is to be retrieved.
82 | /// If this parameter is the desktop window, the function returns NULL.
83 | /// The ancestor to be retrieved.
84 | /// The return value is the handle to the ancestor window.
85 | [DllImport("user32.dll", ExactSpelling = true)]
86 | static extern IntPtr GetAncestor(IntPtr hwnd, GetAncestorFlags flags);
87 |
88 | [DllImport("kernel32.dll")]
89 | static extern IntPtr GetConsoleWindow();
90 |
91 | private static IntPtr GetConsoleOrTerminalWindow()
92 | {
93 | IntPtr consoleHandle = GetConsoleWindow();
94 | IntPtr handle = GetAncestor(consoleHandle, GetAncestorFlags.GetRootOwner);
95 |
96 | return handle;
97 | }
98 | #endregion
99 | }
100 |
--------------------------------------------------------------------------------
/src/Authentication/ITokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.Identity.Client;
6 |
7 | namespace Microsoft.Artifacts.Authentication;
8 |
9 | public interface ITokenProvider
10 | {
11 | string Name { get; }
12 |
13 | bool IsInteractive { get; }
14 |
15 | bool CanGetToken(TokenRequest tokenRequest);
16 |
17 | Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default);
18 | }
19 |
--------------------------------------------------------------------------------
/src/Authentication/ITokenProvidersFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication;
6 |
7 | public interface ITokenProvidersFactory
8 | {
9 | Task> GetAsync(Uri authority);
10 | }
11 |
--------------------------------------------------------------------------------
/src/Authentication/Microsoft.Artifacts.Authentication.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Microsoft.Artifacts.Authentication
5 | Microsoft.Artifacts.Authentication
6 | netstandard2.0
7 | latest
8 | enable
9 | enable
10 | 0.2.3
11 | Microsoft
12 | Microsoft
13 | Azure Artifacts authentication library for credential providers.
14 | © Microsoft Corporation. All rights reserved.
15 | artifacts-icon.png
16 | README.md
17 | https://github.com/microsoft/artifacts-credprovider
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/Authentication/MsalCache.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Runtime.InteropServices;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Identity.Client.Extensions.Msal;
8 |
9 | namespace Microsoft.Artifacts.Authentication;
10 |
11 | public static class MsalCache
12 | {
13 | private static readonly string LocalAppDataLocation = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.Create);
14 |
15 | // from https://github.com/GitCredentialManager/git-credential-manager/blob/df90676d1249759eef8cec57155c27e869503225/src/shared/Microsoft.Git.CredentialManager/Authentication/MicrosoftAuthentication.cs#L277
16 | // The Visual Studio MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
17 | // We use the MSAL extension library to provide us consistent cache file access semantics (synchronization, etc)
18 | // as Visual Studio itself follows, as well as other Microsoft developer tools such as the Azure PowerShell CLI.
19 | public static string DefaultMsalCacheLocation
20 | {
21 | get
22 | {
23 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
24 | {
25 | // The shared MSAL cache is located at "%LocalAppData%\.IdentityService\msal.cache" on Windows.
26 | return Path.Combine(LocalAppDataLocation, ".IdentityService", "msal.cache");
27 | }
28 | else
29 | {
30 | // The shared MSAL cache metadata is located at "~/.local/.IdentityService/msal.cache" on UNIX.
31 | return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", ".IdentityService", "msal.cache");
32 | }
33 | }
34 | }
35 |
36 | public static async Task GetMsalCacheHelperAsync(string cacheLocation, ILogger logger)
37 | {
38 | MsalCacheHelper? helper = null;
39 |
40 | logger.LogTrace(Resources.MsalCacheLocation, cacheLocation);
41 |
42 | var fileName = Path.GetFileName(cacheLocation);
43 | var directory = Path.GetDirectoryName(cacheLocation);
44 |
45 | // Copied from GCM https://github.com/GitCredentialManager/git-credential-manager/blob/bdc20d91d325d66647f2837ffb4e2b2fe98d7e70/src/shared/Core/Authentication/MicrosoftAuthentication.cs#L371-L407
46 | try
47 | {
48 | var storageProps = CreateTokenCacheProperties(useLinuxFallback: false);
49 |
50 | helper = await MsalCacheHelper.CreateAsync(storageProps);
51 |
52 | helper.VerifyPersistence();
53 | }
54 | catch (MsalCachePersistenceException ex)
55 | {
56 | logger.LogWarning(Resources.MsalCachePersistenceWarning);
57 | logger.LogTrace(ex.ToString());
58 |
59 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
60 | {
61 | // On macOS sometimes the Keychain returns the "errSecAuthFailed" error - we don't know why
62 | // but it appears to be something to do with not being able to access the keychain.
63 | // Locking and unlocking (or restarting) often fixes this.
64 | logger.LogError(Resources.MsalCachePersistenceMacOsWarning);
65 | }
66 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
67 | {
68 | // On Linux the SecretService/keyring might not be available so we must fall-back to a plaintext file.
69 | logger.LogWarning(Resources.MsalCacheUnprotectedFileWarning);
70 |
71 | var storageProps = CreateTokenCacheProperties(useLinuxFallback: true);
72 | helper = await MsalCacheHelper.CreateAsync(storageProps);
73 | }
74 | }
75 |
76 | StorageCreationProperties CreateTokenCacheProperties(bool useLinuxFallback)
77 | {
78 | var builder = new StorageCreationPropertiesBuilder(fileName, directory)
79 | .WithMacKeyChain("Microsoft.Developer.IdentityService", "MSALCache");
80 |
81 | if (useLinuxFallback)
82 | {
83 | builder.WithLinuxUnprotectedFile();
84 | }
85 | else
86 | {
87 | // The SecretService/keyring is used on Linux with the following collection name and attributes
88 | builder.WithLinuxKeyring(fileName,
89 | "default", "MSALCache",
90 | new KeyValuePair("MsalClientID", "Microsoft.Developer.IdentityService"),
91 | new KeyValuePair("Microsoft.Developer.IdentityService", "1.0.0.0"));
92 | }
93 |
94 | return builder.Build();
95 | }
96 |
97 | return helper!;
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Authentication/MsalConstants.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | namespace Microsoft.Artifacts.Authentication;
6 |
7 | public static class MsalConstants
8 | {
9 | public const string AzureDevOpsResource = "499b84ac-1321-427f-aa17-267ca6975798/.default";
10 | public static readonly IEnumerable AzureDevOpsScopes = Array.AsReadOnly(new[] { AzureDevOpsResource });
11 |
12 | public static readonly Guid FirstPartyTenant = Guid.Parse("f8cdef31-a31e-4b4a-93e4-5f571e91255a");
13 | public static readonly Guid MsaAccountTenant = Guid.Parse("9188040d-6c67-4c5b-b112-36a304b66dad");
14 | }
15 |
--------------------------------------------------------------------------------
/src/Authentication/MsalDeviceCodeTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Identity.Client;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public class MsalDeviceCodeTokenProvider : ITokenProvider
11 | {
12 | private readonly IPublicClientApplication app;
13 | private readonly ILogger logger;
14 |
15 | public MsalDeviceCodeTokenProvider(IPublicClientApplication app, ILogger logger)
16 | {
17 | this.app = app ?? throw new ArgumentNullException(nameof(app));
18 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
19 | }
20 |
21 | public string Name => "MSAL Device Code";
22 |
23 | public bool IsInteractive => true;
24 |
25 | public bool CanGetToken(TokenRequest tokenRequest)
26 | {
27 | return tokenRequest.IsInteractive;
28 | }
29 |
30 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
31 | {
32 | using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
33 | cts.CancelAfter(tokenRequest.InteractiveTimeout);
34 |
35 | try
36 | {
37 | var result = await app.AcquireTokenWithDeviceCode(MsalConstants.AzureDevOpsScopes, tokenRequest.DeviceCodeResultCallback ?? ((DeviceCodeResult deviceCodeResult) =>
38 | {
39 | logger.LogInformation(deviceCodeResult.Message);
40 |
41 | return Task.CompletedTask;
42 | }))
43 | .ExecuteAsync(cts.Token);
44 |
45 | return result;
46 | }
47 | catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
48 | {
49 | logger.LogWarning(ex.Message);
50 | return null;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Authentication/MsalExtensions.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.Identity.Client;
6 |
7 | namespace Microsoft.Artifacts.Authentication;
8 |
9 | public static partial class MsalExtensions
10 | {
11 | public static List<(IAccount Account, string CanonicalName)> GetApplicableAccounts(IEnumerable accounts, Guid authorityTenantId, string? loginHint)
12 | {
13 | var applicableAccounts = new List<(IAccount, string)>();
14 |
15 | foreach (var account in accounts)
16 | {
17 | string canonicalName = $"{account.HomeAccountId?.TenantId}\\{account.Username}";
18 |
19 | // If a login hint is provided and matches, try that first
20 | if (!string.IsNullOrEmpty(loginHint) && account.Username == loginHint)
21 | {
22 | applicableAccounts.Insert(0, (account, canonicalName));
23 | continue;
24 | }
25 |
26 | if (Guid.TryParse(account.HomeAccountId?.TenantId, out Guid accountTenantId))
27 | {
28 | if (accountTenantId == authorityTenantId)
29 | {
30 | applicableAccounts.Add((account, canonicalName));
31 | }
32 | else if (accountTenantId == MsalConstants.MsaAccountTenant && (authorityTenantId == MsalConstants.FirstPartyTenant || authorityTenantId == Guid.Empty))
33 | {
34 | applicableAccounts.Add((account, canonicalName));
35 | }
36 | }
37 | }
38 |
39 | return applicableAccounts;
40 | }
41 |
42 | public static AcquireTokenSilentParameterBuilder WithAccountTenantId(this AcquireTokenSilentParameterBuilder builder, IAccount account)
43 | {
44 | // Workaround for https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3077
45 | // Even if using the organizations tenant the presence of an MSA will attempt to use the consumers tenant
46 | // which is not supported by the Azure DevOps application. Detect this case and use the first party tenant.
47 |
48 | if (Guid.TryParse(account.HomeAccountId?.TenantId, out Guid accountTenantId) && accountTenantId == MsalConstants.MsaAccountTenant)
49 | {
50 | builder = builder.WithTenantId(MsalConstants.FirstPartyTenant.ToString());
51 | }
52 |
53 | return builder;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Authentication/MsalHttpClientFactory.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Net.Http.Headers;
6 | using Microsoft.Identity.Client;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public class MsalHttpClientFactory : IMsalHttpClientFactory
11 | {
12 | private readonly HttpClient httpClient;
13 |
14 | public MsalHttpClientFactory(HttpClient httpClient)
15 | {
16 | this.httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
17 |
18 | var userAgent = this.httpClient.DefaultRequestHeaders.UserAgent;
19 | userAgent.Add(ProgramProduct);
20 | userAgent.Add(ProgramComment);
21 | userAgent.Add(ClrProduct);
22 | userAgent.Add(ClrComment);
23 | }
24 |
25 | public static ProductInfoHeaderValue ProgramProduct =>
26 | new ProductInfoHeaderValue(PlatformInformation.GetProgramName(), PlatformInformation.GetProgramVersion());
27 |
28 | public static ProductInfoHeaderValue ProgramComment =>
29 | new ProductInfoHeaderValue($"({PlatformInformation.GetOSType()}; {PlatformInformation.GetCpuArchitecture()}; {PlatformInformation.GetOsDescription()})");
30 |
31 | public static ProductInfoHeaderValue ClrProduct =>
32 | new ProductInfoHeaderValue("CLR", PlatformInformation.GetClrVersion());
33 |
34 | public static ProductInfoHeaderValue ClrComment =>
35 | new ProductInfoHeaderValue($"({PlatformInformation.GetClrFramework()}; {PlatformInformation.GetClrRuntime()}; {PlatformInformation.GetClrDescription()})");
36 |
37 | // Produces a value similar to the following:
38 | // CredentialProvider.Microsoft/1.0.4+aae4981de95d543b7935811c05474e393dd9e144 (Windows; X64; Microsoft Windows 10.0.19045) CLR/6.0.16 (.NETCoreApp,Version=v6.0; win10-x64; .NET 6.0.16)
39 | public static IEnumerable UserAgent =>
40 | Array.AsReadOnly(new[]
41 | {
42 | ProgramProduct,
43 | ProgramComment,
44 | ClrProduct,
45 | ClrComment
46 | });
47 |
48 | public HttpClient GetHttpClient()
49 | {
50 | return httpClient;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Authentication/MsalIntegratedWindowsAuthTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Runtime.InteropServices;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Identity.Client;
8 |
9 | namespace Microsoft.Artifacts.Authentication;
10 |
11 | public class MsalIntegratedWindowsAuthTokenProvider : ITokenProvider
12 | {
13 | private readonly IPublicClientApplication app;
14 | private readonly ILogger logger;
15 |
16 | public MsalIntegratedWindowsAuthTokenProvider(IPublicClientApplication app, ILogger logger)
17 | {
18 | this.app = app ?? throw new ArgumentNullException(nameof(app));
19 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
20 | }
21 |
22 | public string Name => "MSAL Windows Integrated Authentication";
23 |
24 | public bool IsInteractive => false;
25 |
26 | public bool CanGetToken(TokenRequest tokenRequest)
27 | {
28 | return WindowsIntegratedAuth.IsSupported() && tokenRequest.IsWindowsIntegratedAuthEnabled;
29 | }
30 |
31 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
32 | {
33 | try
34 | {
35 | string? upn = WindowsIntegratedAuth.GetUserPrincipalName();
36 | if (upn == null)
37 | {
38 | logger.LogTrace(Resources.MsalUserPrincipalNameError, Marshal.GetLastWin32Error());
39 | return null;
40 | }
41 |
42 | var result = await app.AcquireTokenByIntegratedWindowsAuth(MsalConstants.AzureDevOpsScopes)
43 | .WithUsername(upn)
44 | .ExecuteAsync(cancellationToken);
45 |
46 | return result;
47 | }
48 | catch (MsalClientException ex) when (ex.ErrorCode is MsalError.WsTrustEndpointNotFoundInMetadataDocument or MsalError.IntegratedWindowsAuthNotSupportedForManagedUser)
49 | {
50 | logger.LogTrace(ex.Message);
51 | return null;
52 | }
53 | catch (MsalUiRequiredException ex)
54 | {
55 | logger.LogTrace(ex.Message);
56 | return null;
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Authentication/MsalInteractiveTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Identity.Client;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public class MsalInteractiveTokenProvider : ITokenProvider
11 | {
12 | private readonly IPublicClientApplication app;
13 | private readonly ILogger logger;
14 |
15 | public MsalInteractiveTokenProvider(IPublicClientApplication app, ILogger logger)
16 | {
17 | this.app = app ?? throw new ArgumentNullException(nameof(app));
18 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
19 | }
20 |
21 | public string Name => "MSAL Interactive";
22 |
23 | public bool IsInteractive => true;
24 |
25 | public bool CanGetToken(TokenRequest tokenRequest)
26 | {
27 | // MSAL will use the system browser, this will work on all OS's
28 | return tokenRequest.IsInteractive && tokenRequest.CanShowDialog;
29 | }
30 |
31 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
32 | {
33 | if (!app.IsUserInteractive())
34 | {
35 | logger.LogTrace(Resources.MsalNotUserInteractive);
36 | return null;
37 | }
38 |
39 | using CancellationTokenSource cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
40 | cts.CancelAfter(tokenRequest.InteractiveTimeout);
41 |
42 | try
43 | {
44 | logger.LogInformation(Resources.MsalInteractivePrompt);
45 | var result = await app.AcquireTokenInteractive(MsalConstants.AzureDevOpsScopes)
46 | .WithPrompt(Prompt.SelectAccount)
47 | .WithUseEmbeddedWebView(false)
48 | .ExecuteAsync(cts.Token);
49 |
50 | return result;
51 | }
52 | catch (MsalClientException ex) when (ex.ErrorCode == MsalError.AuthenticationCanceledError)
53 | {
54 | logger.LogWarning(ex.Message);
55 | return null;
56 | }
57 | catch (OperationCanceledException ex) when (cts.IsCancellationRequested)
58 | {
59 | logger.LogWarning(ex.Message);
60 | return null;
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Authentication/MsalManagedIdentityTokenProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Microsoft.Identity.Client;
3 | using Microsoft.Identity.Client.AppConfig;
4 |
5 | namespace Microsoft.Artifacts.Authentication;
6 |
7 | public class MsalManagedIdentityTokenProvider : ITokenProvider
8 | {
9 | private readonly ILogger logger;
10 | private readonly IAppConfig appConfig;
11 |
12 | public MsalManagedIdentityTokenProvider(IPublicClientApplication app, ILogger logger)
13 | {
14 | this.appConfig = app.AppConfig;
15 | this.logger = logger;
16 | }
17 |
18 | public string Name => "MSAL Managed Identity";
19 |
20 | public bool IsInteractive => false;
21 |
22 | public bool CanGetToken(TokenRequest tokenRequest) =>
23 | !string.IsNullOrWhiteSpace(tokenRequest.ClientId);
24 |
25 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
26 | {
27 | try
28 | {
29 | if (string.IsNullOrWhiteSpace(tokenRequest.ClientId))
30 | {
31 | logger.LogTrace(string.Format(Resources.MsalClientIdError, tokenRequest.ClientId));
32 | return null;
33 | }
34 |
35 | IManagedIdentityApplication app = ManagedIdentityApplicationBuilder.Create(CreateManagedIdentityId(tokenRequest.ClientId!))
36 | .WithHttpClientFactory(appConfig.HttpClientFactory)
37 | .WithLogging(appConfig.LoggingCallback, appConfig.LogLevel, appConfig.EnablePiiLogging, appConfig.IsDefaultPlatformLoggingEnabled)
38 | .Build();
39 |
40 | AuthenticationResult result = await app.AcquireTokenForManagedIdentity(MsalConstants.AzureDevOpsResource)
41 | .ExecuteAsync()
42 | .ConfigureAwait(false);
43 |
44 | return result;
45 | }
46 | catch (MsalServiceException ex) when (ex.ErrorCode is MsalError.ManagedIdentityRequestFailed)
47 | {
48 | logger.LogTrace(ex.Message);
49 | return null;
50 | }
51 | catch (MsalServiceException ex) when (ex.ErrorCode is MsalError.ManagedIdentityUnreachableNetwork)
52 | {
53 | logger.LogTrace(ex.Message);
54 | return null;
55 | }
56 | }
57 |
58 | private ManagedIdentityId CreateManagedIdentityId(string clientId)
59 | {
60 | return Guid.TryParse(clientId, out var id)
61 | ? ManagedIdentityId.WithUserAssignedClientId(id.ToString())
62 | : ManagedIdentityId.SystemAssigned;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Authentication/MsalServicePrincipalTokenProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Logging;
2 | using Microsoft.Identity.Client;
3 |
4 | namespace Microsoft.Artifacts.Authentication
5 | {
6 | public class MsalServicePrincipalTokenProvider : ITokenProvider
7 | {
8 | public string Name => "MSAL Service Principal";
9 |
10 | public bool IsInteractive => false;
11 |
12 | private readonly ILogger logger;
13 | private readonly IAppConfig appConfig;
14 |
15 | public MsalServicePrincipalTokenProvider(IPublicClientApplication app, ILogger logger)
16 | {
17 | this.appConfig = app.AppConfig;
18 | this.logger = logger;
19 | }
20 |
21 | public bool CanGetToken(TokenRequest tokenRequest)
22 | {
23 | return !string.IsNullOrWhiteSpace(tokenRequest.ClientId)
24 | && tokenRequest.ClientCertificate != null;
25 | }
26 |
27 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
28 | {
29 | try
30 | {
31 | if (!CanGetToken(tokenRequest))
32 | {
33 | logger.LogTrace("InvalidInputs");
34 | return null;
35 | }
36 |
37 | var app = ConfidentialClientApplicationBuilder.Create(tokenRequest.ClientId)
38 | .WithHttpClientFactory(appConfig.HttpClientFactory)
39 | .WithLogging(appConfig.LoggingCallback, appConfig.LogLevel, appConfig.EnablePiiLogging, appConfig.IsDefaultPlatformLoggingEnabled)
40 | .WithCertificate(tokenRequest.ClientCertificate, sendX5C: true)
41 | .WithTenantId(tokenRequest.TenantId)
42 | .Build();
43 |
44 | var result = await app.AcquireTokenForClient(MsalConstants.AzureDevOpsScopes)
45 | .ExecuteAsync()
46 | .ConfigureAwait(false);
47 |
48 | return result;
49 | }
50 | catch (Exception ex)
51 | {
52 | logger.LogTrace(ex.Message);
53 | return null;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Authentication/MsalSilentTokenProvider.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Runtime.InteropServices;
6 | using Microsoft.Extensions.Logging;
7 | using Microsoft.Identity.Client;
8 |
9 | namespace Microsoft.Artifacts.Authentication;
10 |
11 | public class MsalSilentTokenProvider : ITokenProvider
12 | {
13 | private readonly IPublicClientApplication app;
14 | private readonly ILogger logger;
15 |
16 | public MsalSilentTokenProvider(IPublicClientApplication app, ILogger logger)
17 | {
18 | this.app = app ?? throw new ArgumentNullException(nameof(app));
19 | this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
20 | }
21 |
22 | public string Name => "MSAL Silent";
23 |
24 | public bool IsInteractive => false;
25 |
26 | public bool CanGetToken(TokenRequest tokenRequest)
27 | {
28 | // Always run this and rely on MSAL to return a valid token. Previously, AcquireTokenByIntegratedWindowsAuth
29 | // would return cached tokens based on the user principal name, so this token provider could be skipped. Now,
30 | // cached and broker accounts and any cached tokens are returned via AcquireTokenSilent. MSAL will ensure any
31 | // returned token is refreshed as needed to be valid upon return.
32 | return true;
33 | }
34 |
35 | public async Task GetTokenAsync(TokenRequest tokenRequest, CancellationToken cancellationToken = default)
36 | {
37 | var accounts = await app.GetAccountsAsync();
38 |
39 | foreach (var account in accounts)
40 | {
41 | this.logger.LogTrace(Resources.MsalAccountInCache, $"{account.HomeAccountId?.TenantId}\\{account.Username}");
42 | }
43 |
44 | var authority = new Uri(app.Authority);
45 |
46 | if (!Guid.TryParse(authority.AbsolutePath.Trim('/'), out Guid authorityTenantId))
47 | {
48 | this.logger.LogTrace(Resources.MsalNoAuthorityTenant, authority);
49 | }
50 |
51 | var applicableAccounts = MsalExtensions.GetApplicableAccounts(accounts, authorityTenantId, tokenRequest.LoginHint);
52 |
53 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && app.AppConfig.IsBrokerEnabled)
54 | {
55 | applicableAccounts.Add((PublicClientApplication.OperatingSystemAccount, PublicClientApplication.OperatingSystemAccount.HomeAccountId.Identifier));
56 | }
57 |
58 | foreach ((IAccount account, string canonicalName) in applicableAccounts)
59 | {
60 | try
61 | {
62 | this.logger.LogTrace(Resources.MsalAccountAttempt, canonicalName);
63 |
64 | var result = await app.AcquireTokenSilent(MsalConstants.AzureDevOpsScopes, account)
65 | .WithAccountTenantId(account)
66 | .ExecuteAsync(cancellationToken);
67 |
68 | return result;
69 | }
70 | catch (MsalUiRequiredException ex)
71 | {
72 | this.logger.LogTrace(ex.Message);
73 | }
74 | catch (MsalServiceException ex)
75 | {
76 | this.logger.LogWarning(ex.Message);
77 | }
78 | }
79 |
80 | return null;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Authentication/MsalTokenProviders.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using Microsoft.Extensions.Logging;
6 | using Microsoft.Identity.Client;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public class MsalTokenProviders
11 | {
12 | public static IEnumerable Get(IPublicClientApplication app, ILogger logger)
13 | {
14 | yield return new MsalServicePrincipalTokenProvider(app, logger);
15 | yield return new MsalManagedIdentityTokenProvider(app, logger);
16 |
17 | // TODO: Would be more useful if MsalSilentTokenProvider enumerated over each account from the outside
18 | yield return new MsalSilentTokenProvider(app, logger);
19 |
20 | if (WindowsIntegratedAuth.IsSupported())
21 | {
22 | yield return new MsalIntegratedWindowsAuthTokenProvider(app, logger);
23 | }
24 |
25 | yield return new MsalInteractiveTokenProvider(app, logger);
26 | yield return new MsalDeviceCodeTokenProvider(app, logger);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Authentication/PlatformInformation.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Reflection;
6 | using System.Runtime.InteropServices;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public static class PlatformInformation
11 | {
12 | private static string? programName = null;
13 | private static string? programVersion = null;
14 | private static string? runtimeIdentifier = null;
15 |
16 | private static AssemblyName CurrentAssemblyName => typeof(PlatformInformation).Assembly.GetName();
17 |
18 | public static string GetProgramName()
19 | {
20 | return programName ??= Assembly
21 | .GetEntryAssembly()?
22 | .GetCustomAttribute()?.Title ?? CurrentAssemblyName.Name;
23 | }
24 |
25 | public static string GetProgramVersion()
26 | {
27 | return programVersion ??= Assembly
28 | .GetEntryAssembly()?
29 | .GetCustomAttribute()?.InformationalVersion ?? CurrentAssemblyName.Version.ToString();
30 | }
31 |
32 | public static string GetOSType()
33 | {
34 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
35 | {
36 | return nameof(OSPlatform.Windows);
37 | }
38 |
39 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
40 | {
41 | return nameof(OSPlatform.Linux);
42 | }
43 |
44 | if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
45 | {
46 | return nameof(OSPlatform.OSX);
47 | }
48 |
49 | return "Unknown";
50 | }
51 |
52 | public static string GetCpuArchitecture()
53 | {
54 | return RuntimeInformation.OSArchitecture.ToString();
55 | }
56 |
57 | public static string GetOsDescription()
58 | {
59 | return RuntimeInformation.OSDescription;
60 | }
61 |
62 | public static string GetClrVersion()
63 | {
64 | return Environment.Version.ToString();
65 | }
66 |
67 | public static string GetClrFramework()
68 | {
69 | return AppContext.TargetFrameworkName;
70 | }
71 |
72 | public static string GetClrRuntime()
73 | {
74 | // RuntimeInformation.RuntimeIdentifier not available on .NET Standard
75 | return runtimeIdentifier ??= AppContext.GetData("RUNTIME_IDENTIFIER") as string ?? "win-x64";
76 | }
77 |
78 | public static string GetClrDescription()
79 | {
80 | return RuntimeInformation.FrameworkDescription;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/Authentication/README.md:
--------------------------------------------------------------------------------
1 | ## Azure Artifacts authentication library for credential providers
2 |
3 | The Azure Artifacts credential provider authentication library provides extension methods and defaults for using the Microsoft Authentication Library (MSAL) for Azure Artifacts.
4 |
5 | ## Examples
6 |
7 | ### Basic usage
8 |
9 | Example usage to create a PublicClientApplication with recommended settings and defaults for Azure Artifacts and enumerate providers:
10 |
11 | ```csharp
12 | var app = AzureArtifacts.CreateDefaultBuilder(authority)
13 | .WithBroker(true, logger)
14 | .WithLogging((LogLevel level, string message, bool containsPii) =>
15 | {
16 | // Application specific logging
17 | })
18 | .Build();
19 |
20 | // Can use MsalTokenProviders which works for most cases, or compose the token providers manually
21 | var providers = MsalTokenProviders.Get(app, logger);
22 |
23 | var tokenRequest = new TokenRequest
24 | {
25 | IsInteractive = true
26 | };
27 |
28 | foreach (var provider in providers)
29 | {
30 | if (!provider.CanGetToken(tokenRequest))
31 | continue;
32 |
33 | var result = await provider.GetTokenAsync(tokenRequest);
34 | }
35 | ```
36 |
37 | ### Token cache
38 |
39 | The MSAL cache must be initialized for the WAM Broker to return cached accounts, and for non-Windows the token cache ensures users are not prompted when the session token expires.
40 |
41 | ```csharp
42 | var cacheLocation = MsalCache.DefaultMsalCacheLocation;
43 | var cache = await MsalCache.GetMsalCacheHelperAsync(cacheLocation, logger);
44 |
45 | var app = AzureArtifacts.CreateDefaultBuilder(authority, logger).Build();
46 |
47 | cache.RegisterCache(app.UserTokenCache);
48 | ```
49 |
--------------------------------------------------------------------------------
/src/Authentication/TokenRequest.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Security.Cryptography.X509Certificates;
6 | using Microsoft.Identity.Client;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | public class TokenRequest
11 | {
12 | [Obsolete($"The uri parameter is unused and unnecessary. Use the parameterless constructor instead.")]
13 | public TokenRequest(Uri uri)
14 | {
15 | }
16 |
17 | public TokenRequest()
18 | {
19 | }
20 |
21 | public bool IsRetry { get; set; }
22 |
23 | public bool IsInteractive { get; set; }
24 |
25 | // Provided for back-compat to make migration easier
26 | public bool IsNonInteractive { get => !IsInteractive; set => IsInteractive = !value; }
27 |
28 | public bool CanShowDialog { get; set; } = true;
29 |
30 | public bool IsWindowsIntegratedAuthEnabled { get; set; } = true;
31 |
32 | public string? LoginHint { get; set; } = null;
33 |
34 | public TimeSpan InteractiveTimeout { get; set; } = TimeSpan.FromMinutes(2);
35 |
36 | public Func? DeviceCodeResultCallback { get; set; } = null;
37 |
38 | public string? ClientId { get; set; } = null;
39 |
40 | public string? TenantId { get; set; } = null;
41 |
42 | public X509Certificate2? ClientCertificate { get; set; } = null;
43 | }
44 |
--------------------------------------------------------------------------------
/src/Authentication/WindowsIntegratedAuth.cs:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft. All rights reserved.
2 | //
3 | // Licensed under the MIT license.
4 |
5 | using System.Runtime.InteropServices;
6 | using System.Text;
7 |
8 | namespace Microsoft.Artifacts.Authentication;
9 |
10 | internal static class WindowsIntegratedAuth
11 | {
12 | // Adapted from https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/blob/dev/src/Microsoft.IdentityModel.Clients.ActiveDirectory/Platforms/net45/NetDesktopPlatformProxy.cs
13 | [DllImport("secur32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
14 | [return: MarshalAs(UnmanagedType.U1)]
15 | private static extern bool GetUserNameEx(int nameFormat, StringBuilder? userName, ref uint userNameSize);
16 |
17 | public static bool IsSupported()
18 | {
19 | return Environment.OSVersion.Platform == PlatformID.Win32NT;
20 | }
21 |
22 | public static string? GetUserPrincipalName()
23 | {
24 | try
25 | {
26 | const int NameUserPrincipal = 8;
27 | uint userNameSize = 0;
28 | GetUserNameEx(NameUserPrincipal, null, ref userNameSize);
29 | if (userNameSize == 0)
30 | {
31 | return null;
32 | }
33 |
34 | StringBuilder sb = new StringBuilder((int)userNameSize);
35 | if (!GetUserNameEx(NameUserPrincipal, sb, ref userNameSize))
36 | {
37 | return null;
38 | }
39 |
40 | return sb.ToString();
41 | }
42 | catch
43 | {
44 | return null;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Authentication/artifacts-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/artifacts-credprovider/107acd99393a8ba94be8a3280be18773c0549de4/src/Authentication/artifacts-icon.png
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Microsoft
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/README.md:
--------------------------------------------------------------------------------
1 | # artifacts-credprovider-conda
2 | Provide conda protocol authentication support for Azure Artifacts
3 |
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/recipe/bld.bat:
--------------------------------------------------------------------------------
1 | for %%F in (activate deactivate) DO (
2 | if not exist %PREFIX%\etc\conda\%%F.d mkdir %PREFIX%\etc\conda\%%F.d
3 | copy %SRC_DIR%\src\%%F\*.* %PREFIX%\etc\conda\%%F.d\
4 | )
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/recipe/build.sh:
--------------------------------------------------------------------------------
1 | for CHANGE in "activate" "deactivate"
2 | do
3 | mkdir -p "${PREFIX}/etc/conda/${CHANGE}.d"
4 | cp "${SRC_DIR}/src/${CHANGE}/*" "${PREFIX}/etc/conda/${CHANGE}.d/"
5 | done
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/recipe/meta.yaml:
--------------------------------------------------------------------------------
1 | package:
2 | name: artifacts-credprovider-conda
3 | version: 0.0.1
4 |
5 | source:
6 | path: ../src
7 | folder: src
8 |
9 | build:
10 | noarch: generic
11 |
12 | about:
13 | home: https://github.com/microsoft/artifacts-credprovider
14 | license: MIT
15 | summary: Conda credential provider wrapper to authenticate against Azure Artifacts feeds.
16 | description: |
17 | The artifacts-credprovider-conda package provides authentication to consume
18 | Conda pacakges from Azure Artifacts feed within Azure DevOps.
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/activate/artifacts-cred-activate.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | @for /f %%i in ('conda config --show --json ^| conda run --name base --no-capture-output %CONDA_PYTHON_EXE% %~dp0artifacts-cred.py') do set ARTIFACTS_CONDA_TOKEN=%%i
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/activate/artifacts-cred-activate.ps1:
--------------------------------------------------------------------------------
1 | $token = conda config --show --json | & $Env:CONDA_EXE run --name base --no-capture-output python (Join-Path $PSScriptRoot 'artifacts-cred.py')
2 | $env:ARTIFACTS_CONDA_TOKEN = $token
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/activate/artifacts-cred-activate.sh:
--------------------------------------------------------------------------------
1 | export ARTIFACTS_CONDA_TOKEN=$(eval conda config --show --json | eval $CONDA_PYTHON_EXE $(dirname $BASH_SOURCE)/artifacts-cred.py)
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/activate/artifacts-cred.py:
--------------------------------------------------------------------------------
1 | from __future__ import absolute_import
2 |
3 | import os
4 | import requests
5 | import subprocess
6 | import sys
7 | import warnings
8 | import json
9 |
10 | from subprocess import Popen
11 |
12 | if not hasattr(Popen, "__enter__"):
13 | # Handle Python 2.x not making Popen a context manager
14 | class Popen(Popen):
15 | def __enter__(self):
16 | return self
17 |
18 | def __exit__(self, ex_type, ex_value, ex_tb):
19 | pass
20 |
21 | try:
22 | from urllib.parse import urlsplit
23 | except ImportError:
24 | from urlparse import urlsplit
25 |
26 | # A wrapper similar as https://github.com/microsoft/artifacts-keyring
27 | class CredentialProvider(object):
28 | _NON_INTERACTIVE_VAR_NAME = "ARTIFACTS_CONDA_NONINTERACTIVE_MODE"
29 |
30 | def __init__(self):
31 | if sys.platform.startswith("win"):
32 | tool_path = os.path.join(
33 | os.path.abspath(os.environ['UserProfile']),
34 | ".nuget",
35 | "plugins",
36 | "netfx",
37 | "CredentialProvider.Microsoft",
38 | "CredentialProvider.Microsoft.exe",
39 | )
40 | self.exe = [tool_path]
41 | else:
42 | try:
43 | sys_version = tuple(int(i) for i in
44 | subprocess.check_output(["dotnet", "--version"]).decode().strip().partition("-")[0].split("."))
45 | get_runtime_path = lambda: "dotnet"
46 | except Exception as e:
47 | message = (
48 | "Unable to find dependency dotnet, please manually install"
49 | " the .NET SDK and ensure 'dotnet' is in your PATH. Error: "
50 | )
51 | raise Exception(message + str(e))
52 |
53 | tool_path = os.path.join(
54 | os.path.abspath(os.environ['HOME']),
55 | ".nuget",
56 | "plugins",
57 | "netcore",
58 | "CredentialProvider.Microsoft",
59 | "CredentialProvider.Microsoft.dll",
60 | )
61 | self.exe = [get_runtime_path(), "exec", tool_path]
62 |
63 | if not os.path.exists(tool_path):
64 | raise RuntimeError("Unable to find credential provider in the expected path: " + tool_path)
65 |
66 |
67 | def get_credentials(self, url):
68 | # Public feed short circuit: return nothing if the endpoint is public (can authenticate without credentials).
69 | if self._can_authenticate(url, None):
70 | return None, None
71 |
72 | # Getting credentials with IsRetry=false; the credentials may come from the cache
73 | username, password = self._get_credentials_from_credential_provider(url, is_retry=False)
74 |
75 | # Do not attempt to validate if the credentials could not be obtained
76 | if username is None or password is None:
77 | return username, password
78 |
79 | # Make sure the credentials are still valid (i.e. not expired)
80 | if self._can_authenticate(url, (username, password)):
81 | return username, password
82 |
83 | # The cached credentials are expired; get fresh ones with IsRetry=true
84 | return self._get_credentials_from_credential_provider(url, is_retry=True)
85 |
86 | def _can_authenticate(self, url, auth):
87 | response = requests.get(url, auth=auth)
88 |
89 | return response.status_code < 500 and \
90 | response.status_code != 401 and \
91 | response.status_code != 403
92 |
93 |
94 | def _get_credentials_from_credential_provider(self, url, is_retry):
95 | non_interactive = self._NON_INTERACTIVE_VAR_NAME in os.environ and \
96 | os.environ[self._NON_INTERACTIVE_VAR_NAME] and \
97 | str(os.environ[self._NON_INTERACTIVE_VAR_NAME]).lower() == "true"
98 |
99 | proc = Popen(
100 | self.exe + [
101 | "-Uri", url,
102 | "-IsRetry", str(is_retry),
103 | "-NonInteractive", str(non_interactive),
104 | "-CanShowDialog", str(non_interactive),
105 | "-OutputFormat", "Json"
106 | ],
107 | stdin=subprocess.PIPE,
108 | stdout=subprocess.PIPE,
109 | stderr=subprocess.PIPE
110 | )
111 |
112 | # Read all standard error first, which may either display
113 | # errors from the credential provider or instructions
114 | # from it for Device Flow authentication.
115 | for stderr_line in iter(proc.stderr.readline, b''):
116 | line = stderr_line.decode("utf-8", "ignore")
117 | sys.stderr.write(line)
118 | sys.stderr.flush()
119 |
120 | proc.wait()
121 |
122 | if proc.returncode != 0:
123 | stderr = proc.stderr.read().decode("utf-8", "ignore")
124 | raise RuntimeError("Failed to get credentials: process with PID {pid} exited with code {code}; additional error message: {error}"
125 | .format(pid=proc.pid, code=proc.returncode, error=stderr))
126 |
127 | try:
128 | # stdout is expected to be UTF-8 encoded JSON, so decoding errors are not ignored here.
129 | payload = proc.stdout.read().decode("utf-8")
130 | except ValueError:
131 | raise RuntimeError("Failed to get credentials: the Credential Provider's output could not be decoded using UTF-8.")
132 |
133 | try:
134 | parsed = json.loads(payload)
135 | return parsed["Username"], parsed["Password"]
136 | except ValueError:
137 | raise RuntimeError("Failed to get credentials: the Credential Provider's output could not be parsed as JSON.")
138 |
139 | class ArtifactsKeyringBackend():
140 | SUPPORTED_NETLOC = (
141 | "pkgs.dev.azure.com",
142 | "pkgs.visualstudio.com",
143 | "pkgs.codedev.ms",
144 | "pkgs.vsts.me"
145 | )
146 | _PROVIDER = CredentialProvider
147 |
148 | priority = 9.9
149 |
150 | def get_credential(self, service, username):
151 | try:
152 | parsed = urlsplit(service)
153 | except Exception as exc:
154 | warnings.warn(str(exc))
155 | return None
156 |
157 | netloc = parsed.netloc.rpartition("@")[-1]
158 |
159 | if netloc is None or not netloc.endswith(self.SUPPORTED_NETLOC):
160 | return None
161 |
162 | provider = self._PROVIDER()
163 |
164 | username, password = provider.get_credentials(service)
165 |
166 | if username and password:
167 | return password
168 |
169 | result = sys.stdin.buffer.read()
170 | jsonResult = json.loads(result)
171 | resultUrl = jsonResult['channel_alias']['scheme'] + "://" + jsonResult['channel_alias']['location']
172 | cred = ArtifactsKeyringBackend()
173 | token = cred.get_credential(resultUrl,None)
174 | print(token) # pipe the result back to the shell script
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/deactivate/artifacts-cred-deactivate.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set ARTIFACTS_CONDA_TOKEN=
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/deactivate/artifacts-cred-deactivate.ps1:
--------------------------------------------------------------------------------
1 | $env:ARTIFACTS_CONDA_TOKEN = $null
--------------------------------------------------------------------------------
/src/artifacts-credprovider-conda/src/deactivate/artifacts-cred-deactivate.sh:
--------------------------------------------------------------------------------
1 | unset ARTIFACTS_CONDA_TOKEN
--------------------------------------------------------------------------------
/test.bat:
--------------------------------------------------------------------------------
1 | @echo OFF
2 | SETLOCAL EnableDelayedExpansion
3 |
4 | @REM A Windows domain user should be able to run this against a feed in an AAD-back AzDO org
5 | @REM and all scenarios should succeed non-interactively.
6 |
7 | IF "%~1" == "" (
8 | echo "Please specify an AzDO organization package feed URL as the first parameter."
9 | exit /b 1
10 | )
11 |
12 | set TEST_FEED=%1
13 | set NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_ENABLED=true
14 | set NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION=%TEMP%\msal.cache
15 | IF EXIST %NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION% (del /q %NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION%)
16 |
17 | echo "Testing MSAL with broker"
18 | set NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=true
19 | CALL :TEST_FRAMEWORKS
20 | IF %ERRORLEVEL% NEQ 0 (
21 | echo "Failed: %ERRORLEVEL%"
22 | exit /b %ERRORLEVEL%
23 | )
24 |
25 | echo "Testing MSAL without broker"
26 | set NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=false
27 | CALL :TEST_FRAMEWORKS
28 | IF %ERRORLEVEL% NEQ 0 (
29 | echo "Failed: %ERRORLEVEL%"
30 | exit /b %ERRORLEVEL%
31 | )
32 |
33 | echo "All tests passed!"
34 | exit /b 0
35 |
36 |
37 | :TEST_FRAMEWORKS
38 | for %%I in ("netcoreapp3.1","net461","net481","net6.0","net8.0") DO (
39 | del /q "!UserProfile!\AppData\Local\MicrosoftCredentialProvider\*.dat" 2>NUL
40 | del /q "%NUGET_CREDENTIALPROVIDER_MSAL_FILECACHE_LOCATION%" 2>NUL
41 | echo Testing %%I with NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER=!NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER!
42 | echo dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug
43 | dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug ^
44 | > test.%%I.%NUGET_CREDENTIALPROVIDER_MSAL_ALLOW_BROKER%.log
45 | IF !ERRORLEVEL! NEQ 0 (
46 | echo "Previous command execution failed: !ERRORLEVEL!"
47 | dotnet run --no-restore --no-build -f %%I --project CredentialProvider.Microsoft\CredentialProvider.Microsoft.csproj -- -C -U !TEST_FEED! -V Debug
48 | exit /b !ERRORLEVEL!
49 | )
50 | )
51 |
--------------------------------------------------------------------------------