diff --git a/CHANGELOG.md b/CHANGELOG.md index 797af6deba..702475a074 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,6 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Suppress CS1591 when generating CSharp code and documentation is not available - Added file name suffix escaping in Go to avoid generating files with reserved suffixes. [#4407](https://github.com/microsoft/kiota/issues/4407) - Added `KIOTA_OFFLINE_ENABLED` environment variable to disable checking for updates before each command. [#4556](https://github.com/microsoft/kiota/issues/4556) +- Added support for plugin auth options in the CLI. [#5072](https://github.com/microsoft/kiota/issues/5072) ### Changed diff --git a/src/Kiota.Builder/Configuration/PluginAuthConfiguration.cs b/src/Kiota.Builder/Configuration/PluginAuthConfiguration.cs index cce36655b3..3351146592 100644 --- a/src/Kiota.Builder/Configuration/PluginAuthConfiguration.cs +++ b/src/Kiota.Builder/Configuration/PluginAuthConfiguration.cs @@ -1,6 +1,8 @@ using System; +using Microsoft.OpenApi.Models; using Microsoft.Plugins.Manifest; + namespace Kiota.Builder.Configuration; /// @@ -44,4 +46,37 @@ internal Auth ToPluginManifestAuth() _ => throw new ArgumentOutOfRangeException(nameof(AuthType), $"Unknown plugin auth type '{AuthType}'") }; } + + /// + /// Constructs a PluginAuthConfiguration object from SecuritySchemeType and reference id. + /// + /// The SecuritySchemeType. + /// The reference id. + /// A PluginAuthConfiguration object. + /// If the reference id is null or contains only whitespaces. + /// If the SecuritySchemeType is unknown. + public static PluginAuthConfiguration FromParameters(SecuritySchemeType? pluginAuthType, string pluginAuthRefId) + { + if (!pluginAuthType.HasValue) + { + throw new ArgumentNullException(nameof(pluginAuthType), "Missing plugin auth type"); + } + + var pluginAuthConfig = new PluginAuthConfiguration(pluginAuthRefId); + switch (pluginAuthType) + { + case SecuritySchemeType.ApiKey: + case SecuritySchemeType.Http: + case SecuritySchemeType.OpenIdConnect: + pluginAuthConfig.AuthType = PluginAuthType.ApiKeyPluginVault; + break; + case SecuritySchemeType.OAuth2: + pluginAuthConfig.AuthType = PluginAuthType.OAuthPluginVault; + break; + default: + throw new ArgumentOutOfRangeException(nameof(pluginAuthType), $"Unknown plugin auth type '{pluginAuthType}'"); + } + + return pluginAuthConfig; + } } diff --git a/src/kiota/Handlers/Plugin/AddHandler.cs b/src/kiota/Handlers/Plugin/AddHandler.cs index e6bfc11e91..3bc30b5a3b 100644 --- a/src/kiota/Handlers/Plugin/AddHandler.cs +++ b/src/kiota/Handlers/Plugin/AddHandler.cs @@ -6,6 +6,7 @@ using Kiota.Builder.Extensions; using Kiota.Builder.WorkspaceManagement; using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; namespace kiota.Handlers.Plugin; @@ -23,6 +24,16 @@ public required Option> PluginTypesOption { get; init; } + + public required Option PluginAuthTypeOption + { + get; init; + } + + public required Option PluginAuthRefIdOption + { + get; init; + } public required Option DescriptionOption { get; init; @@ -43,6 +54,8 @@ public override async Task InvokeAsync(InvocationContext context) { string output = context.ParseResult.GetValueForOption(OutputOption) ?? string.Empty; List pluginTypes = context.ParseResult.GetValueForOption(PluginTypesOption) ?? []; + SecuritySchemeType? pluginAuthType = context.ParseResult.GetValueForOption(PluginAuthTypeOption); + string pluginAuthRefId = context.ParseResult.GetValueForOption(PluginAuthRefIdOption) ?? string.Empty; string openapi = context.ParseResult.GetValueForOption(DescriptionOption) ?? string.Empty; bool skipGeneration = context.ParseResult.GetValueForOption(SkipGenerationOption); string className = context.ParseResult.GetValueForOption(ClassOption) ?? string.Empty; @@ -56,6 +69,7 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.Operation = ConsumerOperation.Add; if (pluginTypes.Count != 0) Configuration.Generation.PluginTypes = pluginTypes.ToHashSet(); + Configuration.Generation.PluginAuthInformation = PluginAuthConfiguration.FromParameters(pluginAuthType, pluginAuthRefId); if (includePatterns.Count != 0) Configuration.Generation.IncludePatterns = includePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if (excludePatterns.Count != 0) diff --git a/src/kiota/Handlers/Plugin/EditHandler.cs b/src/kiota/Handlers/Plugin/EditHandler.cs index 895c48504d..927664345f 100644 --- a/src/kiota/Handlers/Plugin/EditHandler.cs +++ b/src/kiota/Handlers/Plugin/EditHandler.cs @@ -6,6 +6,7 @@ using Kiota.Builder.Extensions; using Kiota.Builder.WorkspaceManagement; using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; namespace kiota.Handlers.Plugin; @@ -40,10 +41,24 @@ public required Option> PluginTypesOption get; init; } + public required Option PluginAuthTypeOption + { + get; init; + } + + public required Option PluginAuthRefIdOption + { + get; init; + } + + + public override async Task InvokeAsync(InvocationContext context) { string output = context.ParseResult.GetValueForOption(OutputOption) ?? string.Empty; List? pluginTypes = context.ParseResult.GetValueForOption(PluginTypesOption); + SecuritySchemeType? pluginAuthType = context.ParseResult.GetValueForOption(PluginAuthTypeOption); + string pluginAuthRefId = context.ParseResult.GetValueForOption(PluginAuthRefIdOption) ?? string.Empty; string openapi = context.ParseResult.GetValueForOption(DescriptionOption) ?? string.Empty; bool skipGeneration = context.ParseResult.GetValueForOption(SkipGenerationOption); string className = context.ParseResult.GetValueForOption(ClassOption) ?? string.Empty; @@ -53,7 +68,7 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.SkipGeneration = skipGeneration; Configuration.Generation.Operation = ConsumerOperation.Edit; - + Configuration.Generation.PluginAuthInformation = PluginAuthConfiguration.FromParameters(pluginAuthType, pluginAuthRefId); var (loggerFactory, logger) = GetLoggerAndFactory(context, Configuration.Generation.OutputPath); using (loggerFactory) { diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index c4188bc567..40a6fd37b8 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -576,6 +576,18 @@ private static void AddStringRegexValidator(Option option, Regex validat input.ErrorMessage = $"{value} is not a valid {parameterName} for the client, the {parameterName} must conform to {validator}"; }); } + internal static void ValidateAllOrNoneOptions(CommandResult commandResult, params Option[] options) + { + var optionResults = options.Select(option => commandResult.Children.FirstOrDefault(c => c.Symbol == option) as OptionResult); + var optionsWithValue = optionResults.Where(result => result?.Tokens.Any() ?? false).ToList(); + + // If not all options are set and at least one is set, it's an error + if (optionsWithValue.Count > 0 && optionsWithValue.Count < options.Length) + { + var optionNames = options.Select(option => option.Aliases.FirstOrDefault() ?? "unknown option").ToArray(); + commandResult.ErrorMessage = $"Either all of {string.Join(", ", optionNames)} must be provided or none."; + } + } internal static void ValidateKnownValues(OptionResult input, string parameterName, IEnumerable knownValues) { var knownValuesHash = new HashSet(knownValues, StringComparer.OrdinalIgnoreCase); diff --git a/src/kiota/KiotaPluginCommands.cs b/src/kiota/KiotaPluginCommands.cs index acd2d23739..1067db2781 100644 --- a/src/kiota/KiotaPluginCommands.cs +++ b/src/kiota/KiotaPluginCommands.cs @@ -2,6 +2,7 @@ using kiota.Handlers.Plugin; using Kiota.Builder; using Kiota.Builder.Configuration; +using Microsoft.OpenApi.Models; namespace kiota; public static class KiotaPluginCommands @@ -37,6 +38,28 @@ internal static Option> GetPluginTypeOption(bool isRequired = t typeOption.AddValidator(x => KiotaHost.ValidateKnownValues(x, "type", Enum.GetNames())); return typeOption; } + + internal static Option GetPluginAuthenticationTypeOption(bool isRequired = false) + { + var authTypeOption = new Option("--authentication-type", "The authentication type for the plugin. Should be a valid OpenAPI security scheme."); + authTypeOption.AddAlias("--at"); + { + authTypeOption.IsRequired = isRequired; + authTypeOption.Arity = ArgumentArity.ZeroOrOne; + } + authTypeOption.AddValidator(x => KiotaHost.ValidateKnownValues(x, "authentication-type", Enum.GetNames())); + return authTypeOption; + } + + internal static Option GetPluginAuthenticationReferenceIdOption(bool required = false) + { + var authRefIdOption = new Option("--authentication-ref-id", "The authentication reference id for the plugin.") + { + IsRequired = required, + }; + authRefIdOption.AddAlias("--refid"); + return authRefIdOption; + } public static Command GetAddCommand() { var defaultConfiguration = new GenerationConfiguration(); @@ -47,6 +70,8 @@ public static Command GetAddCommand() var skipGenerationOption = KiotaClientCommands.GetSkipGenerationOption(); var pluginNameOption = GetPluginNameOption(); var pluginType = GetPluginTypeOption(); + var pluginAuthTypeOption = GetPluginAuthenticationTypeOption(); + var pluginAuthRefIdOption = GetPluginAuthenticationReferenceIdOption(); var command = new Command("add", "Adds a new plugin to the Kiota configuration"){ descriptionOption, includePatterns, @@ -56,13 +81,21 @@ public static Command GetAddCommand() outputOption, pluginNameOption, pluginType, + pluginAuthTypeOption, + pluginAuthRefIdOption, //TODO overlay when we have support for it in OAI.net }; + command.AddValidator(commandResult => + { + KiotaHost.ValidateAllOrNoneOptions(commandResult, pluginAuthTypeOption, pluginAuthRefIdOption); + }); command.Handler = new AddHandler { ClassOption = pluginNameOption, OutputOption = outputOption, PluginTypesOption = pluginType, + PluginAuthTypeOption = pluginAuthTypeOption, + PluginAuthRefIdOption = pluginAuthRefIdOption, DescriptionOption = descriptionOption, IncludePatternsOption = includePatterns, ExcludePatternsOption = excludePatterns, @@ -80,6 +113,8 @@ public static Command GetEditCommand() var skipGenerationOption = KiotaClientCommands.GetSkipGenerationOption(); var pluginNameOption = GetPluginNameOption(); var pluginTypes = GetPluginTypeOption(false); + var pluginAuthTypeOption = GetPluginAuthenticationTypeOption(); + var pluginAuthRefIdOption = GetPluginAuthenticationReferenceIdOption(); var command = new Command("edit", "Edits a plugin configuration and updates the Kiota configuration"){ descriptionOption, includePatterns, @@ -89,13 +124,21 @@ public static Command GetEditCommand() outputOption, pluginNameOption, pluginTypes, + pluginAuthTypeOption, + pluginAuthRefIdOption, //TODO overlay when we have support for it in OAI.net }; + command.AddValidator(commandResult => + { + KiotaHost.ValidateAllOrNoneOptions(commandResult, pluginAuthTypeOption, pluginAuthRefIdOption); + }); command.Handler = new EditHandler { ClassOption = pluginNameOption, OutputOption = outputOption, PluginTypesOption = pluginTypes, + PluginAuthTypeOption = pluginAuthTypeOption, + PluginAuthRefIdOption = pluginAuthRefIdOption, DescriptionOption = descriptionOption, IncludePatternsOption = includePatterns, ExcludePatternsOption = excludePatterns, diff --git a/tests/Kiota.Builder.Tests/Plugins/PluginAuthConfigurationTests.cs b/tests/Kiota.Builder.Tests/Plugins/PluginAuthConfigurationTests.cs index 2c6d9b50e1..d5f4c0f491 100644 --- a/tests/Kiota.Builder.Tests/Plugins/PluginAuthConfigurationTests.cs +++ b/tests/Kiota.Builder.Tests/Plugins/PluginAuthConfigurationTests.cs @@ -1,6 +1,7 @@ using System; using Kiota.Builder.Configuration; using Kiota.Builder.Plugins; +using Microsoft.OpenApi.Models; using Xunit; namespace Kiota.Builder.Tests.Plugins; @@ -44,4 +45,42 @@ public void AddCoverageOnUnsupportedException() var b = new UnsupportedSecuritySchemeException(["t0"], "msg", new Exception()); Assert.NotEmpty(b.SupportedTypes); } + + [Fact] + public void FromParametersThrowArgumentNullExceptionIfSecuritySchemeTypeHasNoValue() + { + Assert.Throws(() => + { + _ = PluginAuthConfiguration.FromParameters(null, "reference"); + }); + } + + [Fact] + public void FromParametersThrowsExceptionIfSecuritySchemeTypeIsUnknown() + { + Assert.Throws(() => + { + _ = PluginAuthConfiguration.FromParameters((SecuritySchemeType)10, "reference"); + }); + } + + [Fact] + public void FromParametersReturnsCorrectPluginAuthConfiguration() + { + var pluginAuthConfig = PluginAuthConfiguration.FromParameters(SecuritySchemeType.ApiKey, "reference"); + Assert.Equal(PluginAuthType.ApiKeyPluginVault, pluginAuthConfig.AuthType); + Assert.Equal("reference", pluginAuthConfig.ReferenceId); + + pluginAuthConfig = PluginAuthConfiguration.FromParameters(SecuritySchemeType.Http, "reference"); + Assert.Equal(PluginAuthType.ApiKeyPluginVault, pluginAuthConfig.AuthType); + Assert.Equal("reference", pluginAuthConfig.ReferenceId); + + pluginAuthConfig = PluginAuthConfiguration.FromParameters(SecuritySchemeType.OpenIdConnect, "reference"); + Assert.Equal(PluginAuthType.ApiKeyPluginVault, pluginAuthConfig.AuthType); + Assert.Equal("reference", pluginAuthConfig.ReferenceId); + + pluginAuthConfig = PluginAuthConfiguration.FromParameters(SecuritySchemeType.OAuth2, "reference"); + Assert.Equal(PluginAuthType.OAuthPluginVault, pluginAuthConfig.AuthType); + Assert.Equal("reference", pluginAuthConfig.ReferenceId); + } }