-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
726 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
# Common Libs | ||
# Common Libs | ||
|
||
## SwaggerExtensions | ||
|
||
## Saml2 Authentication |
89 changes: 89 additions & 0 deletions
89
src/Passingwind.Authentication.Saml2/Configuration/ConfigurationManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
using System; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using ITfoxtec.Identity.Saml2; | ||
using ITfoxtec.Identity.Saml2.Schemas.Metadata; | ||
|
||
namespace Passingwind.Authentication.Saml2.Configuration; | ||
|
||
public class ConfigurationManager : IConfigurationManager | ||
{ | ||
private Saml2Configuration? _saml2Configuration; | ||
|
||
private readonly Saml2Options _options; | ||
private readonly Uri _idpMetadataUri; | ||
private readonly HttpClient _httpClient; | ||
|
||
public ConfigurationManager(Saml2Options options, Uri idpMetadataUrl, HttpClient httpClient) | ||
{ | ||
_options = options; | ||
_idpMetadataUri = idpMetadataUrl; | ||
_httpClient = httpClient; | ||
} | ||
|
||
public async Task<Saml2Configuration> GetConfigurationAsync(CancellationToken cancellationToken = default) | ||
{ | ||
if (_saml2Configuration != null) | ||
return _saml2Configuration; | ||
|
||
var configuration = new Saml2Configuration() | ||
{ | ||
Issuer = _options.Issuer, | ||
CertificateValidationMode = _options.CertificateValidationMode, | ||
SigningCertificate = _options.SigningCertificate, | ||
}; | ||
|
||
_options.SignatureValidationCertificates?.ForEach(configuration.SignatureValidationCertificates.Add); | ||
|
||
configuration.AllowedAudienceUris.Add(configuration.Issuer); | ||
|
||
var entityDescriptor = new EntityDescriptor(); | ||
|
||
if (_idpMetadataUri.IsFile) | ||
{ | ||
entityDescriptor.ReadIdPSsoDescriptorFromFile(_idpMetadataUri.ToString()); | ||
} | ||
else | ||
{ | ||
// await entityDescriptor.ReadIdPSsoDescriptorFromUrlAsync(_httpClientFactory, _idpMetadata, cancellationToken); | ||
var metadataGetResponse = await _httpClient.GetAsync(_idpMetadataUri, cancellationToken); | ||
metadataGetResponse.EnsureSuccessStatusCode(); | ||
|
||
var metadataString = await metadataGetResponse.Content.ReadAsStringAsync(); | ||
entityDescriptor.ReadIdPSsoDescriptor(metadataString); | ||
} | ||
|
||
if (entityDescriptor.IdPSsoDescriptor != null) | ||
{ | ||
configuration.AllowedIssuer = entityDescriptor.EntityId; | ||
|
||
configuration.SingleSignOnDestination = entityDescriptor.IdPSsoDescriptor.SingleSignOnServices.First().Location; | ||
configuration.SingleLogoutDestination = entityDescriptor.IdPSsoDescriptor.SingleLogoutServices.First().Location; | ||
|
||
foreach (var signingCertificate in entityDescriptor.IdPSsoDescriptor.SigningCertificates) | ||
{ | ||
if (signingCertificate.IsValidLocalTime()) | ||
{ | ||
configuration.SignatureValidationCertificates.Add(signingCertificate); | ||
} | ||
} | ||
if (configuration.SignatureValidationCertificates.Count == 0) | ||
{ | ||
throw new Exception("The saml2 idp signing certificates has expired."); | ||
} | ||
if (entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.HasValue) | ||
{ | ||
configuration.SignAuthnRequest = entityDescriptor.IdPSsoDescriptor.WantAuthnRequestsSigned.Value; | ||
} | ||
} | ||
else | ||
{ | ||
throw new Exception("The saml2 idp entity descriptor not loaded from metadata."); | ||
} | ||
|
||
_saml2Configuration = configuration; | ||
return configuration; | ||
} | ||
} |
10 changes: 10 additions & 0 deletions
10
src/Passingwind.Authentication.Saml2/Configuration/IConfigurationManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using ITfoxtec.Identity.Saml2; | ||
|
||
namespace Passingwind.Authentication.Saml2.Configuration; | ||
|
||
public interface IConfigurationManager | ||
{ | ||
Task<Saml2Configuration> GetConfigurationAsync(CancellationToken cancellationToken = default); | ||
} |
20 changes: 20 additions & 0 deletions
20
src/Passingwind.Authentication.Saml2/Configuration/StaticConfigurationManager.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using ITfoxtec.Identity.Saml2; | ||
|
||
namespace Passingwind.Authentication.Saml2.Configuration; | ||
|
||
public class StaticConfigurationManager : IConfigurationManager | ||
{ | ||
private readonly Saml2Configuration _saml2Configuration; | ||
|
||
public StaticConfigurationManager(Saml2Configuration saml2Configuration) | ||
{ | ||
_saml2Configuration = saml2Configuration; | ||
} | ||
|
||
public Task<Saml2Configuration> GetConfigurationAsync(CancellationToken cancellationToken = default) | ||
{ | ||
return Task.FromResult(_saml2Configuration); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Specialized; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.Extensions.Primitives; | ||
|
||
namespace Passingwind.Authentication.Saml2; | ||
|
||
static class Extensions | ||
{ | ||
public static ITfoxtec.Identity.Saml2.Http.HttpRequest ToGenericHttpRequest(this HttpRequest request, bool readBodyAsString = false) | ||
{ | ||
return new ITfoxtec.Identity.Saml2.Http.HttpRequest | ||
{ | ||
Method = request.Method, | ||
QueryString = request.QueryString.Value, | ||
Query = ToNameValueCollection(request.Query), | ||
Form = "POST".Equals(request.Method, StringComparison.InvariantCultureIgnoreCase) ? ToNameValueCollection(request.Form) : null | ||
}; | ||
} | ||
|
||
private static NameValueCollection ToNameValueCollection(IEnumerable<KeyValuePair<string, StringValues>> items) | ||
{ | ||
var nv = new NameValueCollection(); | ||
foreach (var item in items) | ||
{ | ||
nv.Add(item.Key, item.Value[0]); | ||
} | ||
return nv; | ||
} | ||
|
||
//private static async Task<string> ReadBodyStringAsync(HttpRequest request) | ||
//{ | ||
// using (var reader = new StreamReader(request.Body)) | ||
// { | ||
// return await reader.ReadToEndAsync(); | ||
// } | ||
//} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/Passingwind.Authentication.Saml2/Passingwind.Authentication.Saml2.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFrameworks>net6;net7</TargetFrameworks> | ||
<Nullable>enable</Nullable> | ||
<RootNamespace>Passingwind.Authentication.Saml2</RootNamespace> | ||
<PackageId>Passingwind.Authentication.Saml2</PackageId> | ||
<Authors>Passingwind</Authors> | ||
<PackageProjectUrl>https://github.com/jxnkwlp/Passingwind.CommonLibs</PackageProjectUrl> | ||
<RepositoryUrl>https://github.com/jxnkwlp/Passingwind.CommonLibs</RepositoryUrl> | ||
<RepositoryType>github</RepositoryType> | ||
<PackageTags>SAML2, authentication</PackageTags> | ||
<Description>ASP.NET Core authentication handler for the SAML2 protocol</Description> | ||
<PackageVersion>0.1.0</PackageVersion> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="ITfoxtec.Identity.Saml2" Version="4.8.8" /> | ||
<FrameworkReference Include="Microsoft.AspNetCore.App" /> | ||
</ItemGroup> | ||
|
||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Passingwind.Authentication.Saml2; | ||
|
||
public static class Saml2Defaults | ||
{ | ||
public const string AuthenticationScheme = "Saml2"; | ||
|
||
public const string DisplayName = "Saml2"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
using System; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
using ITfoxtec.Identity.Saml2; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.AspNetCore.Http; | ||
|
||
namespace Passingwind.Authentication.Saml2; | ||
|
||
public class Saml2Events : RemoteAuthenticationEvents | ||
{ | ||
/// <summary> | ||
/// Invoked when a protocol message is first received. | ||
/// </summary> | ||
public Func<MessageReceivedContext, Task> OnMessageReceived { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. | ||
/// </summary> | ||
public Func<AuthenticationFailedContext, Task> OnAuthenticationFailed { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. | ||
/// </summary> | ||
public Func<RedirectContext, Task> OnRedirectToIdentityProvider { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked when a wsignoutcleanup request is received at the RemoteSignOutPath endpoint. | ||
/// </summary> | ||
public Func<RemoteSignOutContext, Task> OnRemoteSignOut { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked with the security token that has been extracted from the protocol message. | ||
/// </summary> | ||
public Func<SecurityTokenReceivedContext, Task> OnSecurityTokenReceived { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. | ||
/// </summary> | ||
public Func<SecurityTokenValidatedContext, Task> OnSecurityTokenValidated { get; set; } = context => Task.CompletedTask; | ||
|
||
/// <summary> | ||
/// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); | ||
|
||
/// <summary> | ||
/// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task RedirectToIdentityProvider(RedirectContext context) => OnRedirectToIdentityProvider(context); | ||
|
||
/// <summary> | ||
/// Invoked when a protocol message is first received. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); | ||
|
||
/// <summary> | ||
/// Invoked when a wsignoutcleanup request is received at the RemoteSignOutPath endpoint. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task RemoteSignOut(RemoteSignOutContext context) => OnRemoteSignOut(context); | ||
|
||
/// <summary> | ||
/// Invoked with the security token that has been extracted from the protocol message. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task SecurityTokenReceived(SecurityTokenReceivedContext context) => OnSecurityTokenReceived(context); | ||
|
||
/// <summary> | ||
/// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. | ||
/// </summary> | ||
/// <param name="context"></param> | ||
public virtual Task SecurityTokenValidated(SecurityTokenValidatedContext context) => OnSecurityTokenValidated(context); | ||
} | ||
|
||
public class RedirectContext : PropertiesContext<Saml2Options> | ||
{ | ||
public RedirectContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options, AuthenticationProperties? properties) : base(context, scheme, options, properties) | ||
{ | ||
} | ||
|
||
public Saml2AuthnRequest Saml2AuthnRequest { get; set; } = default!; | ||
|
||
public Saml2RedirectBinding RedirectBinding { get; set; } = default!; | ||
|
||
/// <summary> | ||
/// If true, will skip any default logic for this redirect. | ||
/// </summary> | ||
public bool Handled { get; private set; } | ||
|
||
/// <summary> | ||
/// Skips any default logic for this redirect. | ||
/// </summary> | ||
public void HandleResponse() => Handled = true; | ||
} | ||
|
||
public class RemoteSignOutContext : RemoteAuthenticationContext<Saml2Options> | ||
{ | ||
public RemoteSignOutContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options, AuthenticationProperties? properties) : base(context, scheme, options, properties) | ||
{ | ||
} | ||
|
||
public Saml2AuthnResponse Saml2AuthnResponse { get; set; } = default!; | ||
} | ||
|
||
public class MessageReceivedContext : RemoteAuthenticationContext<Saml2Options> | ||
{ | ||
public MessageReceivedContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options, AuthenticationProperties? properties) : base(context, scheme, options, properties) | ||
{ | ||
} | ||
|
||
public Saml2AuthnResponse Saml2AuthnResponse { get; set; } = default!; | ||
} | ||
|
||
public class SecurityTokenReceivedContext : RemoteAuthenticationContext<Saml2Options> | ||
{ | ||
public SecurityTokenReceivedContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options, AuthenticationProperties? properties) : base(context, scheme, options, properties) | ||
{ | ||
} | ||
|
||
public Saml2AuthnResponse Saml2AuthnResponse { get; set; } = default!; | ||
} | ||
|
||
public class SecurityTokenValidatedContext : RemoteAuthenticationContext<Saml2Options> | ||
{ | ||
public SecurityTokenValidatedContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options, ClaimsPrincipal principal, AuthenticationProperties? properties) : base(context, scheme, options, properties) | ||
{ | ||
Principal = principal; | ||
} | ||
|
||
public Saml2AuthnResponse Saml2AuthnResponse { get; set; } = default!; | ||
} | ||
|
||
public class AuthenticationFailedContext : RemoteAuthenticationContext<Saml2Options> | ||
{ | ||
public AuthenticationFailedContext(HttpContext context, AuthenticationScheme scheme, Saml2Options options) : base(context, scheme, options, null) | ||
{ | ||
} | ||
|
||
public Saml2AuthnResponse Saml2AuthnResponse { get; set; } = default!; | ||
|
||
public Exception Exception { get; set; } = default!; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
using System; | ||
using Microsoft.AspNetCore.Authentication; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Microsoft.Extensions.DependencyInjection.Extensions; | ||
using Microsoft.Extensions.Options; | ||
|
||
namespace Passingwind.Authentication.Saml2; | ||
|
||
public static class Saml2Extensions | ||
{ | ||
public static AuthenticationBuilder AddSaml2(this AuthenticationBuilder builder, Action<Saml2Options>? configureOptions = null) | ||
{ | ||
return AddSaml2(builder, Saml2Defaults.AuthenticationScheme, Saml2Defaults.AuthenticationScheme, configureOptions); | ||
} | ||
|
||
public static AuthenticationBuilder AddSaml2(this AuthenticationBuilder builder, string scheme, string? displayName = null, Action<Saml2Options>? configureOptions = null) | ||
{ | ||
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<Saml2Options>, Saml2PostConfigureOptions>()); | ||
|
||
builder.AddScheme<Saml2Options, Saml2Handler>(scheme, displayName, configureOptions); | ||
|
||
builder.Services.AddTransient<Saml2Handler>(); | ||
|
||
return builder; | ||
} | ||
} |
Oops, something went wrong.