Skip to content

Commit

Permalink
Merge pull request #5267 from microsoft/samwelkanda/feat/kiota-plugin…
Browse files Browse the repository at this point in the history
…-auth-options

Samwelkanda/feat/kiota plugin auth options
  • Loading branch information
samwelkanda authored Sep 3, 2024
2 parents ba708a6 + a70925c commit 81ce751
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions src/Kiota.Builder/Configuration/PluginAuthConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using Microsoft.OpenApi.Models;
using Microsoft.Plugins.Manifest;


namespace Kiota.Builder.Configuration;

/// <summary>
Expand Down Expand Up @@ -44,4 +46,37 @@ internal Auth ToPluginManifestAuth()
_ => throw new ArgumentOutOfRangeException(nameof(AuthType), $"Unknown plugin auth type '{AuthType}'")
};
}

/// <summary>
/// Constructs a PluginAuthConfiguration object from SecuritySchemeType and reference id.
/// </summary>
/// <param name="pluginAuthType">The SecuritySchemeType.</param>
/// <param name="pluginAuthRefId">The reference id.</param>
/// <returns>A PluginAuthConfiguration object.</returns>
/// <exception cref="ArgumentException">If the reference id is null or contains only whitespaces.</exception>
/// <exception cref="ArgumentOutOfRangeException">If the SecuritySchemeType is unknown.</exception>
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;
}
}
14 changes: 14 additions & 0 deletions src/kiota/Handlers/Plugin/AddHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Kiota.Builder.Extensions;
using Kiota.Builder.WorkspaceManagement;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

namespace kiota.Handlers.Plugin;

Expand All @@ -23,6 +24,16 @@ public required Option<List<PluginType>> PluginTypesOption
{
get; init;
}

public required Option<SecuritySchemeType> PluginAuthTypeOption
{
get; init;
}

public required Option<string> PluginAuthRefIdOption
{
get; init;
}
public required Option<string> DescriptionOption
{
get; init;
Expand All @@ -43,6 +54,8 @@ public override async Task<int> InvokeAsync(InvocationContext context)
{
string output = context.ParseResult.GetValueForOption(OutputOption) ?? string.Empty;
List<PluginType> 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;
Expand All @@ -56,6 +69,7 @@ public override async Task<int> 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)
Expand Down
17 changes: 16 additions & 1 deletion src/kiota/Handlers/Plugin/EditHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Kiota.Builder.Extensions;
using Kiota.Builder.WorkspaceManagement;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Models;

namespace kiota.Handlers.Plugin;

Expand Down Expand Up @@ -40,10 +41,24 @@ public required Option<List<PluginType>> PluginTypesOption
get; init;
}

public required Option<SecuritySchemeType> PluginAuthTypeOption
{
get; init;
}

public required Option<string> PluginAuthRefIdOption
{
get; init;
}



public override async Task<int> InvokeAsync(InvocationContext context)
{
string output = context.ParseResult.GetValueForOption(OutputOption) ?? string.Empty;
List<PluginType>? 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;
Expand All @@ -53,7 +68,7 @@ public override async Task<int> InvokeAsync(InvocationContext context)

Configuration.Generation.SkipGeneration = skipGeneration;
Configuration.Generation.Operation = ConsumerOperation.Edit;

Configuration.Generation.PluginAuthInformation = PluginAuthConfiguration.FromParameters(pluginAuthType, pluginAuthRefId);
var (loggerFactory, logger) = GetLoggerAndFactory<KiotaBuilder>(context, Configuration.Generation.OutputPath);
using (loggerFactory)
{
Expand Down
12 changes: 12 additions & 0 deletions src/kiota/KiotaHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,18 @@ private static void AddStringRegexValidator(Option<string> 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<string> knownValues)
{
var knownValuesHash = new HashSet<string>(knownValues, StringComparer.OrdinalIgnoreCase);
Expand Down
43 changes: 43 additions & 0 deletions src/kiota/KiotaPluginCommands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -37,6 +38,28 @@ internal static Option<List<PluginType>> GetPluginTypeOption(bool isRequired = t
typeOption.AddValidator(x => KiotaHost.ValidateKnownValues(x, "type", Enum.GetNames<PluginType>()));
return typeOption;
}

internal static Option<SecuritySchemeType> GetPluginAuthenticationTypeOption(bool isRequired = false)
{
var authTypeOption = new Option<SecuritySchemeType>("--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<SecuritySchemeType>()));
return authTypeOption;
}

internal static Option<string> GetPluginAuthenticationReferenceIdOption(bool required = false)
{
var authRefIdOption = new Option<string>("--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();
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down
39 changes: 39 additions & 0 deletions tests/Kiota.Builder.Tests/Plugins/PluginAuthConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<ArgumentNullException>(() =>
{
_ = PluginAuthConfiguration.FromParameters(null, "reference");
});
}

[Fact]
public void FromParametersThrowsExceptionIfSecuritySchemeTypeIsUnknown()
{
Assert.Throws<ArgumentOutOfRangeException>(() =>
{
_ = 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);
}
}

0 comments on commit 81ce751

Please sign in to comment.