diff --git a/CHANGELOG.md b/CHANGELOG.md index f14fc3e6..b8df86f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +## [1.0.0-preview.15] - 2022-10-18 + +### Added + +- Adds an API key authentication provider. + ## [1.0.0-preview.14] - 2022-10-17 ### Changed diff --git a/Microsoft.Kiota.Abstractions.Tests/Authentication/ApiKeyAuthenticationProviderTests.cs b/Microsoft.Kiota.Abstractions.Tests/Authentication/ApiKeyAuthenticationProviderTests.cs new file mode 100644 index 00000000..3de8e508 --- /dev/null +++ b/Microsoft.Kiota.Abstractions.Tests/Authentication/ApiKeyAuthenticationProviderTests.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Kiota.Abstractions.Authentication; +using Xunit; + +namespace Microsoft.Kiota.Abstractions.Tests; + +public class ApiKeyAuthenticationProviderTests { + [Fact] + public void DefensiveProgramming() { + Assert.Throws(() => new ApiKeyAuthenticationProvider(null, "param", ApiKeyAuthenticationProvider.KeyLocation.Header)); + Assert.Throws(() => new ApiKeyAuthenticationProvider("key", null, ApiKeyAuthenticationProvider.KeyLocation.Header)); + + var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.Header); + Assert.Throws(() => value.AuthenticateRequestAsync(null).GetAwaiter().GetResult()); + } + [Fact] + public async Task AddsInHeader() { + var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.Header); + var request = new RequestInformation { + UrlTemplate = "https://localhost{?param1}", + }; + await value.AuthenticateRequestAsync(request); + Assert.False(request.URI.ToString().EndsWith("param=key", StringComparison.OrdinalIgnoreCase)); + Assert.Contains("param", request.Headers.Keys); + } + [Fact] + public async Task AddsInQueryParameters() { + var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.QueryParameter); + var request = new RequestInformation { + UrlTemplate = "https://localhost{?param1}", + }; + await value.AuthenticateRequestAsync(request); + Assert.EndsWith("?param=key", request.URI.ToString()); + Assert.DoesNotContain("param", request.Headers.Keys); + } + [Fact] + public async Task AddsInQueryParametersWithOtherParameters() { + var value = new ApiKeyAuthenticationProvider("key", "param", ApiKeyAuthenticationProvider.KeyLocation.QueryParameter); + var request = new RequestInformation { + UrlTemplate = "https://localhost{?param1}", + }; + request.QueryParameters.Add("param1", "value1"); + await value.AuthenticateRequestAsync(request); + Assert.EndsWith("¶m=key", request.URI.ToString()); + Assert.DoesNotContain("param", request.Headers.Keys); + } +} \ No newline at end of file diff --git a/src/Microsoft.Kiota.Abstractions.csproj b/src/Microsoft.Kiota.Abstractions.csproj index bfb7ef44..6afc8c3b 100644 --- a/src/Microsoft.Kiota.Abstractions.csproj +++ b/src/Microsoft.Kiota.Abstractions.csproj @@ -14,7 +14,7 @@ true true 1.0.0 - preview.14 + preview.15 true false false @@ -23,7 +23,7 @@ - - Changes the ResponeHandler parameter in IRequestAdapter to be a RequestOption + - Adds an API key authentication provider true LICENSE diff --git a/src/authentication/ApiKeyAuthenticationProvider.cs b/src/authentication/ApiKeyAuthenticationProvider.cs new file mode 100644 index 00000000..30bfffac --- /dev/null +++ b/src/authentication/ApiKeyAuthenticationProvider.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.Kiota.Abstractions.Authentication; + +/// +/// This authentication provider authenticates requests using an API key. +/// +public class ApiKeyAuthenticationProvider : IAuthenticationProvider +{ + private readonly string ApiKey; + private readonly string ParameterName; + private readonly KeyLocation KeyLoc; + private readonly AllowedHostsValidator AllowedHostsValidator; + + /// + /// Instantiates a new using the provided parameters. + /// + /// The API key to use for authentication. + /// The name of the query parameter or header to use for authentication. + /// The location of the API key. + /// The hosts that are allowed to use the provided API key. + public ApiKeyAuthenticationProvider(string apiKey, string parameterName, KeyLocation keyLocation, params string[] allowedHosts) + { + if (string.IsNullOrEmpty(apiKey)) + throw new ArgumentNullException(nameof(apiKey)); + if (string.IsNullOrEmpty(parameterName)) + throw new ArgumentNullException(nameof(parameterName)); + if (allowedHosts == null) + throw new ArgumentNullException(nameof(allowedHosts)); + ApiKey = apiKey; + ParameterName = parameterName; + KeyLoc = keyLocation; + AllowedHostsValidator = new AllowedHostsValidator(allowedHosts); + } + private static ActivitySource _activitySource = new(typeof(RequestInformation).Namespace); + /// + public Task AuthenticateRequestAsync(RequestInformation request, Dictionary additionalAuthenticationContext = null, CancellationToken cancellationToken = default) { + if (request == null) + throw new ArgumentNullException(nameof(request)); + using var span = _activitySource?.StartActivity(nameof(AuthenticateRequestAsync)); + if (!AllowedHostsValidator.IsUrlHostValid(request.URI)) { + span?.SetTag("com.microsoft.kiota.authentication.is_url_valid", false); + return Task.CompletedTask; + } + + var uri = request.URI; + if(!uri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase)) { + span?.SetTag("com.microsoft.kiota.authentication.is_url_valid", false); + throw new ArgumentException("Only https is supported"); + } + + switch(KeyLoc) { + case KeyLocation.QueryParameter: + var uriString = uri.OriginalString + (uri.Query != string.Empty ? "&" : "?") + $"{ParameterName}={ApiKey}"; + request.URI = new Uri(uriString); + break; + case KeyLocation.Header: + request.Headers.Add(ParameterName, ApiKey); + break; + default: + throw new ArgumentOutOfRangeException(nameof(KeyLoc)); + } + span?.SetTag("com.microsoft.kiota.authentication.is_url_valid", true); + return Task.CompletedTask; + } + /// + /// The location of the API key parameter. + /// + public enum KeyLocation { + /// + /// The API key is passed as a query parameter. + /// + QueryParameter, + /// + /// The API key is passed as a header. + /// + Header + } +}