Skip to content

Commit ae23c2a

Browse files
Start to support fastfed SAML
1 parent fef2195 commit ae23c2a

35 files changed

Lines changed: 588 additions & 63 deletions

File tree

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using SimpleIdServer.FastFed.Apis.FastFedMetadata;
7+
using SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml.Infrastructures;
8+
using System;
9+
10+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml;
11+
12+
public static class FastFedServicesBuilderExtensions
13+
{
14+
public static FastFedServicesBuilder AddSamlAppProviderAuthenticationProfile(this FastFedServicesBuilder builder, Action<SamlAuthenticationOptions> cb)
15+
{
16+
builder.Services.Configure(cb);
17+
builder.Services.AddTransient<IProviderMetadataEnricher, SamlAuthenticationProviderMetadataEnricher>();
18+
builder.Services.AddTransient<IAppProviderProvisioningService, SamlAuthenticationProvisioningService>();
19+
builder.Services.AddScoped<IAuthenticationHandlerProvider, DynamicSamlAuthenticationHandlerProvider>();
20+
builder.Services.AddSingleton<IAuthenticationSchemeProvider, DynamicSamlAuthenticationSchemeProvider>();
21+
builder.Services.AddSingleton<ISamlAuthenticationSchemeProvider>(x => x.GetService<IAuthenticationSchemeProvider>() as ISamlAuthenticationSchemeProvider);
22+
return builder;
23+
}
24+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml.Infrastructures;
4+
5+
public class AuthSchemeProvider
6+
{
7+
public string Name { get; set; }
8+
public string DisplayName { get; set; }
9+
public string SamlMetadataUri { get; set; }
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using Microsoft.AspNetCore.Authentication;
4+
using Microsoft.AspNetCore.Http;
5+
using Microsoft.Extensions.Options;
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Linq;
9+
using System.Threading.Tasks;
10+
11+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml.Infrastructures;
12+
13+
public class DynamicSamlAuthenticationHandlerProvider : IAuthenticationHandlerProvider
14+
{
15+
private readonly Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
16+
private readonly ISamlAuthenticationSchemeProvider _authenticationSchemeProvider;
17+
18+
public DynamicSamlAuthenticationHandlerProvider(ISamlAuthenticationSchemeProvider authenticationSchemeProvider)
19+
{
20+
_authenticationSchemeProvider = authenticationSchemeProvider;
21+
}
22+
23+
public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
24+
{
25+
if (_handlerMap.TryGetValue(authenticationScheme, out var value))
26+
return value;
27+
28+
var scheme = await _authenticationSchemeProvider.GetSamlSchemeAsync(authenticationScheme);
29+
if (scheme == null) return null;
30+
var handlerType = scheme.AuthScheme.HandlerType;
31+
var handler = context.RequestServices.GetService(handlerType) as IAuthenticationHandler;
32+
if (handler == null)
33+
{
34+
var ctr = handlerType.GetConstructors().First();
35+
var args = new List<object>();
36+
foreach (var par in ctr.GetParameters())
37+
{
38+
if (par.ParameterType.IsGenericType && par.ParameterType.GetGenericTypeDefinition() == typeof(IOptionsMonitor<>).GetGenericTypeDefinition())
39+
args.Add(scheme.SamlSpOptions);
40+
else
41+
args.Add(context.RequestServices.GetService(par.ParameterType));
42+
}
43+
44+
handler = Activator.CreateInstance(handlerType, args.ToArray()) as IAuthenticationHandler;
45+
}
46+
47+
if (handler != null)
48+
{
49+
await handler.InitializeAsync(scheme.AuthScheme, context);
50+
_handlerMap[authenticationScheme] = handler;
51+
}
52+
53+
return handler;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using MassTransit;
4+
using Microsoft.AspNetCore.Authentication;
5+
using Microsoft.Extensions.DependencyInjection;
6+
using Microsoft.Extensions.Options;
7+
using SimpleIdServer.FastFed.Stores;
8+
using SimpleIdServer.IdServer.Saml.Sp;
9+
using System;
10+
using System.Collections.Generic;
11+
using System.Linq;
12+
using System.Net.Http;
13+
using System.Text.Json.Nodes;
14+
using System.Threading;
15+
using System.Threading.Tasks;
16+
17+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml.Infrastructures;
18+
19+
public interface ISamlAuthenticationSchemeProvider
20+
{
21+
Task<SamlAuthenticationScheme> GetSamlSchemeAsync(string name);
22+
}
23+
24+
public class DynamicSamlAuthenticationSchemeProvider : AuthenticationSchemeProvider, ISamlAuthenticationSchemeProvider
25+
{
26+
private readonly IBusControl _busControl;
27+
private readonly IServiceProvider _serviceProvider;
28+
private readonly SamlAuthenticationOptions _samlAuthOptions;
29+
private readonly SamlSpOptions _samlSpOptions;
30+
private DateTime? _nextExpirationTime;
31+
private IEnumerable<AuthSchemeProvider> _cachedAuthSchemeProviders;
32+
private object _lck = new object();
33+
34+
public DynamicSamlAuthenticationSchemeProvider(
35+
IBusControl busControl,
36+
IServiceProvider serviceProvider,
37+
IOptions<SamlAuthenticationOptions> samlAuthOptions,
38+
IOptions<SamlSpOptions> samlSpOptions,
39+
IOptions<AuthenticationOptions> options) : base(options)
40+
{
41+
_busControl = busControl;
42+
_serviceProvider = serviceProvider;
43+
_samlAuthOptions = samlAuthOptions.Value;
44+
_samlSpOptions = samlSpOptions.Value;
45+
}
46+
47+
public async override Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
48+
{
49+
var rules = (await base.GetAllSchemesAsync()).ToList();
50+
var authenticationSchemeProviders = await GetAuthenticationSchemeProviders();
51+
foreach (var scheme in authenticationSchemeProviders)
52+
{
53+
var newRule = Convert(scheme);
54+
if (newRule == null)
55+
continue;
56+
57+
rules.Add(newRule.AuthScheme);
58+
}
59+
60+
return rules;
61+
}
62+
63+
public override async Task<AuthenticationScheme> GetSchemeAsync(string name) => (await GetSamlSchemeAsync(name)).AuthScheme;
64+
65+
public async Task<SamlAuthenticationScheme> GetSamlSchemeAsync(string name)
66+
{
67+
var result = await base.GetSchemeAsync(name);
68+
if (result != null)
69+
return new SamlAuthenticationScheme(result);
70+
71+
var providers = await GetAuthenticationSchemeProviders();
72+
var provider = providers.FirstOrDefault(p => p.Name == name);
73+
return provider == null ? null : Convert(provider);
74+
}
75+
76+
private SamlAuthenticationScheme Convert(AuthSchemeProvider provider)
77+
{
78+
lock (_lck)
79+
{
80+
var handlerType = typeof(SamlSpHandler);
81+
var options = new SamlSpOptions
82+
{
83+
SPId = _samlSpOptions.SPId,
84+
IdpMetadataUrl = provider.SamlMetadataUri,
85+
SigningCertificate = _samlSpOptions.SigningCertificate
86+
};
87+
if (options.Backchannel == null)
88+
options.Backchannel = new HttpClient(_samlSpOptions.BackchannelHttpHandler ?? new HttpClientHandler());
89+
return new SamlAuthenticationScheme(new AuthenticationScheme(provider.Name, provider.DisplayName, handlerType), options);
90+
}
91+
}
92+
93+
private async Task<IEnumerable<AuthSchemeProvider>> GetAuthenticationSchemeProviders()
94+
{
95+
using (var scope = _serviceProvider.CreateScope())
96+
{
97+
var providerFederationStore = scope.ServiceProvider.GetRequiredService<IProviderFederationStore>();
98+
var currentDateTime = DateTime.UtcNow;
99+
var authenticationSchemeProviders = _cachedAuthSchemeProviders;
100+
if (_nextExpirationTime == null ||
101+
_nextExpirationTime.Value <= currentDateTime ||
102+
_samlAuthOptions.CacheSamlAuthProvidersInSeconds == null)
103+
{
104+
var result = new List<AuthSchemeProvider>();
105+
var providerFederations = await providerFederationStore.GetAll(CancellationToken.None);
106+
foreach (var providerFederation in providerFederations.Where(f => f.LastCapabilities != null && f.LastCapabilities.Status == Models.IdentityProviderStatus.CONFIRMED))
107+
{
108+
var configuration = providerFederation.LastCapabilities.Configurations.SingleOrDefault(c => c.ProfileName == SimpleIdServer.FastFed.Authentication.Saml.Constants.ProvisioningProfileName);
109+
if (configuration == null || string.IsNullOrWhiteSpace(configuration.IdProviderConfiguration)) continue;
110+
var jObj = JsonObject.Parse(configuration.IdProviderConfiguration).AsObject();
111+
if (jObj == null) continue;
112+
if (jObj.ContainsKey(FastFed.Authentication.Saml.Constants.SamlMetadataUri))
113+
{
114+
result.Add(new AuthSchemeProvider
115+
{
116+
Name = providerFederation.EntityId,
117+
DisplayName = providerFederation.DisplayName,
118+
SamlMetadataUri = jObj[FastFed.Authentication.Saml.Constants.SamlMetadataUri].ToString()
119+
});
120+
}
121+
}
122+
123+
authenticationSchemeProviders = result;
124+
if (_samlAuthOptions.CacheSamlAuthProvidersInSeconds != null)
125+
{
126+
_nextExpirationTime = currentDateTime.AddSeconds(_samlAuthOptions.CacheSamlAuthProvidersInSeconds.Value);
127+
_cachedAuthSchemeProviders = authenticationSchemeProviders;
128+
}
129+
}
130+
131+
return authenticationSchemeProviders;
132+
}
133+
}
134+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
using Microsoft.AspNetCore.Authentication;
4+
using SimpleIdServer.IdServer.Saml.Sp;
5+
6+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml.Infrastructures;
7+
8+
public class SamlAuthenticationScheme
9+
{
10+
public SamlAuthenticationScheme(AuthenticationScheme authScheme)
11+
{
12+
AuthScheme = authScheme;
13+
}
14+
15+
public SamlAuthenticationScheme(AuthenticationScheme authScheme, SamlSpOptions samlSpOptions) : this(authScheme)
16+
{
17+
SamlSpOptions = samlSpOptions;
18+
}
19+
20+
21+
public AuthenticationScheme AuthScheme { get; set; }
22+
public SamlSpOptions SamlSpOptions { get; set; }
23+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using SimpleIdServer.FastFed.Authentication.Saml;
5+
6+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml;
7+
8+
public class SamlAuthenticationOptions
9+
{
10+
public string SpId { get; set; } = "samlApplicationProvider";
11+
public string SamlMetadataUri { get; set; }
12+
public SamlEntrepriseMappingsResult Mappings { get; set; }
13+
public int? CacheSamlAuthProvidersInSeconds { get; set; }
14+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using Microsoft.Extensions.Options;
5+
using SimpleIdServer.FastFed.Apis.FastFedMetadata;
6+
using System.Text.Json;
7+
using System.Text.Json.Nodes;
8+
9+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml;
10+
11+
public class SamlAuthenticationProviderMetadataEnricher : IProviderMetadataEnricher
12+
{
13+
private readonly SamlAuthenticationOptions _options;
14+
15+
public SamlAuthenticationProviderMetadataEnricher(IOptions<SamlAuthenticationOptions> options)
16+
{
17+
_options = options.Value;
18+
}
19+
20+
public void EnrichApplicationProvider(JsonObject otherParameters)
21+
{
22+
if (_options.Mappings != null)
23+
otherParameters.Add(SimpleIdServer.FastFed.Authentication.Saml.Constants.ProvisioningProfileName, JsonObject.Parse(JsonSerializer.Serialize(_options.Mappings)).AsObject());
24+
}
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright (c) SimpleIdServer. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
using Microsoft.Extensions.Options;
5+
using Microsoft.IdentityModel.JsonWebTokens;
6+
using SimpleIdServer.FastFed.Models;
7+
using System.Text.Json.Nodes;
8+
using System.Threading;
9+
using System.Threading.Tasks;
10+
11+
namespace SimpleIdServer.FastFed.ApplicationProvider.Authentication.Saml;
12+
13+
public class SamlAuthenticationProvisioningService : IAppProviderProvisioningService
14+
{
15+
private readonly SamlAuthenticationOptions _options;
16+
17+
public SamlAuthenticationProvisioningService(IOptions<SamlAuthenticationOptions> options)
18+
{
19+
_options = options.Value;
20+
}
21+
22+
public string Name => FastFed.Authentication.Saml.Constants.ProvisioningProfileName;
23+
24+
public string RegisterConfigurationName => FastFed.Authentication.Saml.Constants.SamlAuthentication;
25+
26+
public Task<JsonObject> EnableCapability(IdentityProviderFederation identityProviderFederation, JsonWebToken jwt, CancellationToken cancellationToken)
27+
{
28+
var result = new JsonObject
29+
{
30+
{ FastFed.Authentication.Saml.Constants.SamlMetadataUri, _options.SamlMetadataUri }
31+
};
32+
return Task.FromResult(result);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>net8.0</TargetFramework>
4+
<Description>Add SAML authentication support to the application provider.</Description>
5+
</PropertyGroup>
6+
<ItemGroup>
7+
<ProjectReference Include="..\..\IdServer\SimpleIdServer.IdServer.Saml.Sp\SimpleIdServer.IdServer.Saml.Sp.csproj" />
8+
<ProjectReference Include="..\SimpleIdServer.FastFed.Authentication.Saml\SimpleIdServer.FastFed.Authentication.Saml.csproj" />
9+
<ProjectReference Include="..\SimpleIdServer.FastFed\SimpleIdServer.FastFed.csproj" />
10+
</ItemGroup>
11+
</Project>

src/FastFed/SimpleIdServer.FastFed.ApplicationProvider.Provisioning.Scim/FastFedServicesBuilderExtensions.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@ public static class FastFedServicesBuilderExtensions
1313
public static FastFedServicesBuilder AddAppProviderScimProvisioning(this FastFedServicesBuilder builder, Action<ScimProvisioningOptions> cb)
1414
{
1515
builder.Services.Configure(cb);
16-
builder.Services.RemoveAll<IGetProviderMetadataQuery>();
1716
builder.Services.AddTransient<IAppProviderProvisioningService, ScimProvisioningService>();
18-
builder.Services.AddTransient<IGetProviderMetadataQuery, ScimGetProviderMetadataQuery>();
17+
builder.Services.AddTransient<IProviderMetadataEnricher, ScimProviderMetadataEnricher>();
1918
return builder;
2019
}
2120
}

0 commit comments

Comments
 (0)