Skip to content

Commit

Permalink
fix(Solution): Updated solution packages
Browse files Browse the repository at this point in the history
fix(Infrastructure): Fixed the OAuth2TokenManager to implement all request encodings and client authentication methods
  • Loading branch information
cdavernas committed Sep 2, 2024
1 parent 2b50bef commit 715c44f
Show file tree
Hide file tree
Showing 25 changed files with 161 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="8.0.8" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.11" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.12" />
<PackageReference Include="System.Reactive" Version="6.0.1" />
</ItemGroup>

Expand Down
4 changes: 2 additions & 2 deletions src/api/Synapse.Api.Http/Synapse.Api.Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.15.0" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.15.0" />
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.15.2" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.15.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.3" />
</ItemGroup>

Expand Down
2 changes: 1 addition & 1 deletion src/cli/Synapse.Cli/Synapse.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.1" />
<PackageReference Include="moment.net" Version="1.3.4" />
<PackageReference Include="NetEscapades.Configuration.Yaml" Version="3.1.0" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.11" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.12" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ public interface IOAuth2TokenManager
/// <param name="configuration">The configuration that defines how to generate the <see cref="OAuth2Token"/> to get</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
/// <returns>An <see cref="OAuth2Token"/></returns>
Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeDefinition configuration, CancellationToken cancellationToken = default);
Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeDefinitionBase configuration, CancellationToken cancellationToken = default);

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@

using IdentityModel.Client;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Neuroglia.Serialization;
using ServerlessWorkflow.Sdk;
using ServerlessWorkflow.Sdk.Models.Authentication;
using System.Collections.Concurrent;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Mime;
using System.Security.Claims;
using System.Text;
using YamlDotNet.Core.Tokens;

namespace Synapse.Core.Infrastructure.Services;

Expand Down Expand Up @@ -50,16 +57,49 @@ public class OAuth2TokenManager(ILogger<OAuth2TokenManager> logger, IJsonSeriali
protected ConcurrentDictionary<string, OAuth2Token> Tokens { get; } = [];

/// <inheritdoc/>
public virtual async Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeDefinition configuration, CancellationToken cancellationToken = default)
public virtual async Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeDefinitionBase configuration, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(configuration);
var tokenKey = $"{configuration.Client.Id}@{configuration.Authority}";
Uri tokenEndpoint;
if (configuration is OpenIDConnectSchemeDefinition)
{
var discoveryDocument = await this.HttpClient.GetDiscoveryDocumentAsync(configuration.Authority.OriginalString, cancellationToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(discoveryDocument.TokenEndpoint)) throw new NullReferenceException("The token endpoint is not documented by the OIDC discovery document");
tokenEndpoint = new(discoveryDocument.TokenEndpoint!);
}
else if (configuration is OAuth2AuthenticationSchemeDefinition oauth2) tokenEndpoint = oauth2.Endpoints.Token;
else throw new NotSupportedException($"The specified scheme type '{configuration.GetType().FullName}' is not supported in this context");
var tokenKey = $"{configuration.Client?.Id}@{configuration.Authority}";
var properties = new Dictionary<string, string>()
{
{ "grant_type", configuration.Grant },
{ "client_id", configuration.Client.Id }
{ "grant_type", configuration.Grant }
};
if (!string.IsNullOrWhiteSpace(configuration.Client.Secret)) properties["client_secret"] = configuration.Client.Secret;
switch (configuration.Client?.Authentication)
{
case null:
if(!string.IsNullOrWhiteSpace(configuration.Client?.Id) && !string.IsNullOrWhiteSpace(configuration.Client?.Secret))
{
properties["client_id"] = configuration.Client.Id!;
properties["client_secret"] = configuration.Client.Secret!;
}
break;
case OAuth2ClientAuthenticationMethod.Post:
this.ThrowIfInvalidClientCredentials(configuration.Client);
properties["client_id"] = configuration.Client.Id!;
properties["client_secret"] = configuration.Client.Secret!;
break;
case OAuth2ClientAuthenticationMethod.JwT:
this.ThrowIfInvalidClientCredentials(configuration.Client);
properties["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
properties["client_assertion"] = this.CreateClientAssertionJwt(configuration.Client.Id!, tokenEndpoint.OriginalString, new(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.Client.Secret!)), SecurityAlgorithms.HmacSha256));
break;
case OAuth2ClientAuthenticationMethod.PrivateKey:
this.ThrowIfInvalidClientCredentials(configuration.Client);
throw new NotImplementedException(); //todo
case OAuth2ClientAuthenticationMethod.Basic:
break;
default: throw new NotSupportedException($"The specified OAUTH2 client authentication method '{configuration.Client?.Authentication}' is not supported");
}
if (configuration.Scopes?.Count > 0) properties["scope"] = string.Join(" ", configuration.Scopes);
if (configuration.Audiences?.Count > 0) properties["audience"] = string.Join(" ", configuration.Audiences);
if (!string.IsNullOrWhiteSpace(configuration.Username)) properties["username"] = configuration.Username;
Expand All @@ -84,11 +124,18 @@ public virtual async Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeD
}
else return token;
}
var discoveryDocument = await this.HttpClient.GetDiscoveryDocumentAsync(configuration.Authority.OriginalString, cancellationToken).ConfigureAwait(false);
using var request = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint)
using var content = configuration.Request.Encoding switch
{
Content = new FormUrlEncodedContent(properties)
OAuth2RequestEncoding.FormUrl => (HttpContent)new FormUrlEncodedContent(properties),
OAuth2RequestEncoding.Json => new StringContent(this.JsonSerializer.SerializeToText(properties), Encoding.UTF8, MediaTypeNames.Application.Json),
_ => throw new NotSupportedException($"The specified OAUTH2 request encoding '{configuration.Request.Encoding}' is not supported")
};
using var request = new HttpRequestMessage(HttpMethod.Post, tokenEndpoint) { Content = content };
if (configuration.Client?.Authentication == OAuth2ClientAuthenticationMethod.Basic)
{
this.ThrowIfInvalidClientCredentials(configuration.Client);
request.Headers.Authorization = new("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{configuration.Client.Id}:{configuration.Client.Secret}")));
}
using var response = await this.HttpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
var json = await response.Content?.ReadAsStringAsync(cancellationToken)!;
if (!response.IsSuccessStatusCode)
Expand All @@ -101,4 +148,36 @@ public virtual async Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationSchemeD
return token;
}

/// <summary>
/// Throws a new <see cref="Exception"/> if the specified client credentials have not been properly configured, as required by the configured authentication method
/// </summary>
/// <param name="client">The client credentials to validate</param>
protected virtual void ThrowIfInvalidClientCredentials(OAuth2AuthenticationClientDefinition? client)
{
if(string.IsNullOrWhiteSpace(client?.Id) || string.IsNullOrWhiteSpace(client?.Secret)) throw new NullReferenceException($"The client id and client secret must be configured when using the '{client?.Authentication}' OAUTH2 authentication method");
}

/// <summary>
/// Creates a JSON Web Token (JWT) for client authentication using the provided client ID, audience and signing credentials.
/// </summary>
/// <param name="clientId">The client ID used as the subject and issuer of the JWT</param>
/// <param name="audience">The audience for which the JWT is intended, typically the token endpoint URL</param>
/// <param name="signingCredentials">The credentials used to signed the JWT</param>
/// <returns>A signed JWT in string format, to be used as a client assertion in OAuth 2.0 requests</returns>
protected virtual string CreateClientAssertionJwt(string clientId, string audience, SigningCredentials signingCredentials)
{
ArgumentException.ThrowIfNullOrWhiteSpace(clientId);
ArgumentException.ThrowIfNullOrWhiteSpace(audience);
ArgumentNullException.ThrowIfNull(signingCredentials);
var tokenHandler = new JwtSecurityTokenHandler();
var claims = new List<Claim>
{
new(JwtRegisteredClaimNames.Sub, clientId),
new(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64)
};
var token = new JwtSecurityToken(clientId, audience, claims, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(5), signingCredentials);
return tokenHandler.WriteToken(token);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@

<ItemGroup>
<PackageReference Include="IdentityModel" Version="7.0.0" />
<PackageReference Include="Neuroglia.Data.Expressions.Abstractions" Version="4.15.0" />
<PackageReference Include="Neuroglia.Data.Infrastructure.Redis" Version="4.15.0" />
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented.Redis" Version="4.15.0" />
<PackageReference Include="Neuroglia.Mediation" Version="4.15.0" />
<PackageReference Include="Neuroglia.Plugins" Version="4.15.0" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.11" />
<PackageReference Include="Neuroglia.Data.Expressions.Abstractions" Version="4.15.2" />
<PackageReference Include="Neuroglia.Data.Infrastructure.Redis" Version="4.15.2" />
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented.Redis" Version="4.15.2" />
<PackageReference Include="Neuroglia.Mediation" Version="4.15.2" />
<PackageReference Include="Neuroglia.Plugins" Version="4.15.2" />
<PackageReference Include="ServerlessWorkflow.Sdk.IO" Version="1.0.0-alpha2.12" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="7.1.2" />
</ItemGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions src/core/Synapse.Core/Synapse.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented" Version="4.15.0" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents" Version="4.15.0" />
<PackageReference Include="Neuroglia.Data.Infrastructure.ResourceOriented" Version="4.15.2" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents" Version="4.15.2" />
<PackageReference Include="Semver" Version="2.3.0" />
<PackageReference Include="ServerlessWorkflow.Sdk" Version="1.0.0-alpha2.11" />
<PackageReference Include="ServerlessWorkflow.Sdk" Version="1.0.0-alpha2.12" />
</ItemGroup>

</Project>
12 changes: 6 additions & 6 deletions src/correlator/Synapse.Correlator/Synapse.Correlator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
<PackageReference Include="Microsoft.Extensions.Configuration.KeyPerFile" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Neuroglia.Data.Expressions.JavaScript" Version="4.15.0" />
<PackageReference Include="Neuroglia.Data.Expressions.JQ" Version="4.15.0" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents.AspNetCore" Version="4.15.0" />
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.15.0" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents.Infrastructure" Version="4.15.0" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.15.0" />
<PackageReference Include="Neuroglia.Data.Expressions.JavaScript" Version="4.15.2" />
<PackageReference Include="Neuroglia.Data.Expressions.JQ" Version="4.15.2" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents.AspNetCore" Version="4.15.2" />
<PackageReference Include="Neuroglia.Mediation.AspNetCore" Version="4.15.2" />
<PackageReference Include="Neuroglia.Eventing.CloudEvents.Infrastructure" Version="4.15.2" />
<PackageReference Include="Neuroglia.Security.AspNetCore" Version="4.15.2" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerGen" Version="6.7.3" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.7.3" />
</ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/dashboard/Synapse.Dashboard/Synapse.Dashboard.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.8" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" PrivateAssets="all" />
<PackageReference Include="moment.net" Version="1.3.4" />
<PackageReference Include="Neuroglia.Blazor.Dagre" Version="4.15.0" />
<PackageReference Include="Neuroglia.Blazor.Dagre" Version="4.15.2" />
</ItemGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/runner/Synapse.Runner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
services.AddPythonScriptExecutor();
services.AddSynapseHttpApiClient(http =>
{
var configuration = new ServerlessWorkflow.Sdk.Models.Authentication.OAuth2AuthenticationSchemeDefinition()
var configuration = new ServerlessWorkflow.Sdk.Models.Authentication.OpenIDConnectSchemeDefinition()
{
Authority = options.Api.BaseAddress,
Grant = OAuth2GrantType.ClientCredentials,
Expand Down
Loading

0 comments on commit 715c44f

Please sign in to comment.