File: Resources\PluginResource.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Protocol\NuGet.Protocol.csproj (NuGet.Protocol)
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

#nullable disable

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using NuGet.Configuration;
using NuGet.Protocol.Plugins;

namespace NuGet.Protocol.Core.Types
{
    /// <summary>
    /// Represents a plugin resource.
    /// </summary>
    public sealed class PluginResource : INuGetResource
    {
        private const string _basicAuthenticationType = "Basic";

        private readonly ICredentialService _credentialService;
        private readonly PackageSource _packageSource;
        private readonly IReadOnlyList<PluginCreationResult> _pluginCreationResults;

        /// <summary>
        /// Instantiates a new <see cref="PluginResource" /> class.
        /// </summary>
        /// <param name="pluginCreationResults">Plugin creation results.</param>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="pluginCreationResults" />
        /// is <see langword="null" />.</exception>
        /// <exception cref="ArgumentNullException">Thrown if <paramref name="packageSource" />
        /// is <see langword="null" />.</exception>
        public PluginResource(
            IEnumerable<PluginCreationResult> pluginCreationResults,
            PackageSource packageSource,
            ICredentialService credentialService)
        {
            if (pluginCreationResults == null)
            {
                throw new ArgumentNullException(nameof(pluginCreationResults));
            }

            if (packageSource == null)
            {
                throw new ArgumentNullException(nameof(packageSource));
            }

            _pluginCreationResults = pluginCreationResults.ToArray();
            _packageSource = packageSource;
            _credentialService = credentialService;
        }

        /// <summary>
        /// Gets the first plugin satisfying the required operation claims for the current package source.
        /// </summary>
        /// <param name="requiredClaim">The required operation claim.</param>
        /// <param name="cancellationToken">A cancellation token.</param>
        /// <returns>A task that represents the asynchronous operation.
        /// The task result (<see cref="Task{TResult}.Result" />) returns a <see cref="GetPluginResult" />.</returns>
        /// <exception cref="OperationCanceledException">Thrown if <paramref name="cancellationToken" />
        /// is cancelled.</exception>
        public async Task<GetPluginResult> GetPluginAsync(
            OperationClaim requiredClaim,
            CancellationToken cancellationToken)
        {
            cancellationToken.ThrowIfCancellationRequested();

            foreach (var result in _pluginCreationResults)
            {
                if (!string.IsNullOrEmpty(result.Message))
                {
                    throw new PluginException(result.Message);
                }

                if (result.Claims.Contains(requiredClaim))
                {
                    var key = $"{MessageMethod.SetCredentials}.{_packageSource.SourceUri}";

                    await result.PluginMulticlientUtilities.DoOncePerPluginLifetimeAsync(
                        key,
                        () => SetPackageSourceCredentialsAsync(result.Plugin, cancellationToken),
                        cancellationToken);

                    return new GetPluginResult(result.Plugin, result.PluginMulticlientUtilities);
                }
            }

            return null;
        }

        private async Task SetPackageSourceCredentialsAsync(IPlugin plugin, CancellationToken cancellationToken)
        {
            var payload = CreateRequest();

            await plugin.Connection.SendRequestAndReceiveResponseAsync<SetCredentialsRequest, SetCredentialsResponse>(
                MessageMethod.SetCredentials,
                payload,
                cancellationToken);
        }

        private SetCredentialsRequest CreateRequest()
        {
            var sourceUri = _packageSource.SourceUri;
            string proxyUsername = null;
            string proxyPassword = null;
            string username = null;
            string password = null;
            ICredentials credentials;

            if (TryGetCachedCredentials(sourceUri, isProxy: true, credentials: out credentials))
            {
                var proxyCredential = credentials.GetCredential(sourceUri, _basicAuthenticationType);

                if (proxyCredential != null)
                {
                    proxyUsername = proxyCredential.UserName;
                    proxyPassword = proxyCredential.Password;
                }
            }

            if (TryGetCachedCredentials(sourceUri, isProxy: false, credentials: out credentials))
            {
                var packageSourceCredential = credentials.GetCredential(sourceUri, authType: null);

                if (packageSourceCredential != null)
                {
                    username = packageSourceCredential.UserName;
                    password = packageSourceCredential.Password;
                }
            }

            return new SetCredentialsRequest(
                _packageSource.Source,
                proxyUsername,
                proxyPassword,
                username,
                password);
        }

        private bool TryGetCachedCredentials(Uri uri, bool isProxy, out ICredentials credentials)
        {
            credentials = null;

            if (_credentialService == null)
            {
                return false;
            }

            return _credentialService.TryGetLastKnownGoodCredentialsFromCache(uri, isProxy, out credentials);
        }

        public sealed class GetPluginResult
        {
            public IPlugin Plugin { get; }
            public IPluginMulticlientUtilities PluginMulticlientUtilities { get; }

            internal GetPluginResult(IPlugin plugin, IPluginMulticlientUtilities utilities)
            {
                Plugin = plugin;
                PluginMulticlientUtilities = utilities;
            }
        }
    }
}