-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
60 changed files
with
2,153 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?xml version="1.0" encoding="UTF-8" ?> | ||
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" | ||
xmlns:local="clr-namespace:Headway.App.Blazor.Maui" | ||
x:Class="Headway.App.Blazor.Maui.App"> | ||
<Application.Resources> | ||
<ResourceDictionary> | ||
|
||
<Color x:Key="PageBackgroundColor">#512bdf</Color> | ||
<Color x:Key="PrimaryTextColor">White</Color> | ||
|
||
<Style TargetType="Label"> | ||
<Setter Property="TextColor" Value="{DynamicResource PrimaryTextColor}" /> | ||
<Setter Property="FontFamily" Value="OpenSansRegular" /> | ||
</Style> | ||
|
||
<Style TargetType="Button"> | ||
<Setter Property="TextColor" Value="{DynamicResource PrimaryTextColor}" /> | ||
<Setter Property="FontFamily" Value="OpenSansRegular" /> | ||
<Setter Property="BackgroundColor" Value="#2b0b98" /> | ||
<Setter Property="Padding" Value="14,10" /> | ||
</Style> | ||
|
||
</ResourceDictionary> | ||
</Application.Resources> | ||
</Application> |
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,14 @@ | ||
using Microsoft.Maui.Controls; | ||
|
||
namespace Headway.App.Blazor.Maui | ||
{ | ||
public partial class App : Application | ||
{ | ||
public App() | ||
{ | ||
InitializeComponent(); | ||
|
||
MainPage = new MainPage(); | ||
} | ||
} | ||
} |
114 changes: 114 additions & 0 deletions
114
src/Headway.App.Blazor.Maui/Authentication/CustomAuthenticationStateProvider.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,114 @@ | ||
using Headway.Core.Model; | ||
using IdentityModel.Client; | ||
using IdentityModel.OidcClient; | ||
using IdentityModel.OidcClient.Browser; | ||
using Microsoft.AspNetCore.Components.Authorization; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Security.Claims; | ||
using System.Threading.Tasks; | ||
|
||
namespace Headway.App.Blazor.Maui.Authentication | ||
{ | ||
public class CustomAuthenticationStateProvider : AuthenticationStateProvider | ||
{ | ||
private readonly OidcClient oidcClient; | ||
private readonly TokenProvider tokenProvider; | ||
private readonly CustomAuthenticationStateProviderOptions options; | ||
|
||
private ClaimsPrincipal currentUser = new ClaimsPrincipal(new ClaimsIdentity()); | ||
|
||
public CustomAuthenticationStateProvider(CustomAuthenticationStateProviderOptions options, TokenProvider tokenProvider) | ||
{ | ||
oidcClient = new OidcClient(new OidcClientOptions | ||
{ | ||
Authority = $"https://{options.Authority}", | ||
ClientId = options.ClientId, | ||
Scope = options.Scope, | ||
RedirectUri = options.RedirectUri, | ||
PostLogoutRedirectUri = options.PostLogoutRedirectUris, | ||
Browser = options.Browser | ||
}); | ||
|
||
this.options = options; | ||
this.tokenProvider = tokenProvider; | ||
} | ||
|
||
public override Task<AuthenticationState> GetAuthenticationStateAsync() => | ||
Task.FromResult(new AuthenticationState(currentUser)); | ||
|
||
public IdentityModel.OidcClient.Browser.IBrowser Browser | ||
{ | ||
get | ||
{ | ||
return oidcClient.Options.Browser; | ||
} | ||
set | ||
{ | ||
oidcClient.Options.Browser = value; | ||
} | ||
} | ||
|
||
public async Task LogInAsync() | ||
{ | ||
var loginRequest = new LoginRequest { FrontChannelExtraParameters = new Parameters(options.AdditionalProviderParameters) }; | ||
var loginResult = await oidcClient.LoginAsync(loginRequest); | ||
tokenProvider.RefreshToken = loginResult.RefreshToken; | ||
tokenProvider.AccessToken = loginResult.AccessToken; | ||
tokenProvider.IdToken = loginResult.IdentityToken; | ||
currentUser = loginResult.User; | ||
|
||
if (currentUser.Identity.IsAuthenticated) | ||
{ | ||
var identity = (ClaimsIdentity)currentUser.Identity; | ||
|
||
if (identity.RoleClaimType != options.RoleClaim) | ||
{ | ||
var roleClaims = identity.FindAll(options.RoleClaim).ToArray(); | ||
|
||
if (roleClaims != null && roleClaims.Any()) | ||
{ | ||
foreach (var roleClaim in roleClaims) | ||
{ | ||
identity.RemoveClaim(roleClaim); | ||
} | ||
|
||
foreach (var roleClaim in roleClaims) | ||
{ | ||
identity.AddClaim(new Claim(identity.RoleClaimType, roleClaim.Value)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
NotifyAuthenticationStateChanged( | ||
Task.FromResult(new AuthenticationState(currentUser))); | ||
} | ||
|
||
public async Task LogoutAsync() | ||
{ | ||
var logoutParameters = new Dictionary<string, string> | ||
{ | ||
{"client_id", oidcClient.Options.ClientId }, | ||
{"returnTo", oidcClient.Options.RedirectUri } | ||
}; | ||
|
||
var logoutRequest = new LogoutRequest(); | ||
var endSessionUrl = new RequestUrl($"{oidcClient.Options.Authority}/v2/logout") | ||
.Create(new Parameters(logoutParameters)); | ||
var browserOptions = new BrowserOptions(endSessionUrl, oidcClient.Options.RedirectUri) | ||
{ | ||
Timeout = TimeSpan.FromSeconds(logoutRequest.BrowserTimeout), | ||
DisplayMode = logoutRequest.BrowserDisplayMode | ||
}; | ||
|
||
await oidcClient.Options.Browser.InvokeAsync(browserOptions); | ||
|
||
currentUser = new ClaimsPrincipal(new ClaimsIdentity()); | ||
|
||
NotifyAuthenticationStateChanged( | ||
Task.FromResult(new AuthenticationState(currentUser))); | ||
} | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/Headway.App.Blazor.Maui/Authentication/CustomAuthenticationStateProviderOptions.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,29 @@ | ||
using System.Collections.Generic; | ||
|
||
namespace Headway.App.Blazor.Maui.Authentication | ||
{ | ||
public class CustomAuthenticationStateProviderOptions | ||
{ | ||
public CustomAuthenticationStateProviderOptions() | ||
{ | ||
Browser = new WebBrowserAuthenticator(); | ||
AdditionalProviderParameters = new Dictionary<string, string>(); | ||
} | ||
|
||
public string Authority { get; set; } | ||
|
||
public string ClientId { get; set; } | ||
|
||
public string RedirectUri { get; set; } | ||
|
||
public string PostLogoutRedirectUris { get; set; } | ||
|
||
public string Scope { get; set; } | ||
|
||
public string RoleClaim { get; set; } | ||
|
||
public Dictionary<string, string> AdditionalProviderParameters { get; set; } | ||
|
||
public IdentityModel.OidcClient.Browser.IBrowser Browser { get; set; } | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
src/Headway.App.Blazor.Maui/Authentication/WebBrowserAuthenticator.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,39 @@ | ||
using IdentityModel.Client; | ||
using IdentityModel.OidcClient.Browser; | ||
using Microsoft.Maui.Authentication; | ||
using System; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Headway.App.Blazor.Maui.Authentication | ||
{ | ||
public class WebBrowserAuthenticator : IdentityModel.OidcClient.Browser.IBrowser | ||
{ | ||
public async Task<BrowserResult> InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default) | ||
{ | ||
try | ||
{ | ||
WebAuthenticatorResult result = await WebAuthenticator.Default.AuthenticateAsync( | ||
new Uri(options.StartUrl), | ||
new Uri(options.EndUrl)); | ||
|
||
var url = new RequestUrl(options.EndUrl) | ||
.Create(new Parameters(result.Properties)); | ||
|
||
return new BrowserResult | ||
{ | ||
Response = url, | ||
ResultType = BrowserResultType.Success | ||
}; | ||
} | ||
catch (TaskCanceledException) | ||
{ | ||
return new BrowserResult | ||
{ | ||
ResultType = BrowserResultType.UserCancel, | ||
ErrorDescription = "Login canceled by the user." | ||
}; | ||
} | ||
} | ||
} | ||
} |
94 changes: 94 additions & 0 deletions
94
src/Headway.App.Blazor.Maui/DevHttp/DevHttpClientHelperExtensions.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,94 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using System; | ||
using System.Net.Http; | ||
using System.Net.Security; | ||
|
||
namespace Headway.App.Blazor.Maui.DevHttp | ||
{ | ||
public static class DevHttpClientHelperExtensions | ||
{ | ||
/// <summary> | ||
/// Adds the <see cref="IHttpClientFactory"/> and related services to the <see cref="IServiceCollection"/> and configures | ||
/// a named <see cref="HttpClient"/> to use localhost or 10.0.2.2 and bypass certificate checking on Android. | ||
/// </summary> | ||
/// <param name="name">name</param> | ||
/// <param name="sslPort">Development server port</param> | ||
/// <returns>The IServiceCollection</returns> | ||
/// <remarks> | ||
/// <para> | ||
/// https://github.com/dotnet/maui/discussions/8131 | ||
/// </para> | ||
/// <para> | ||
/// https://gist.github.com/Eilon/49e3c5216abfa3eba81e453d45cba2d4 | ||
/// by https://gist.github.com/Eilon | ||
/// </para> | ||
/// <para> | ||
/// https://gist.github.com/EdCharbeneau/ed3d44d8298319c201f276de7a0580f1 | ||
/// by https://gist.github.com/EdCharbeneau | ||
/// </para> | ||
/// </remarks> | ||
public static IServiceCollection AddDevHttpClient(this IServiceCollection services, string name, int sslPort) | ||
{ | ||
var devServerRootUrl = new UriBuilder("https", DevServerName, sslPort).Uri.ToString(); | ||
|
||
#if WINDOWS | ||
services.AddHttpClient(name, client => | ||
{ | ||
client.BaseAddress = new UriBuilder("https", DevServerName, sslPort).Uri; | ||
}); | ||
|
||
return services; | ||
#endif | ||
|
||
#if ANDROID | ||
services.AddHttpClient(name, client => | ||
{ | ||
client.BaseAddress = new UriBuilder("https", DevServerName, sslPort).Uri; | ||
}) | ||
.ConfigurePrimaryHttpMessageHandler(() => | ||
{ | ||
var handler = new CustomAndroidMessageHandler(); | ||
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => | ||
{ | ||
if (cert != null && cert.Issuer.Equals("CN=localhost")) | ||
return true; | ||
return errors == SslPolicyErrors.None; | ||
}; | ||
return handler; | ||
}); | ||
|
||
return services; | ||
|
||
#else | ||
throw new PlatformNotSupportedException("Only Windows and Android currently supported."); | ||
#endif | ||
} | ||
|
||
public static string DevServerName => | ||
#if WINDOWS | ||
"localhost"; | ||
#elif ANDROID | ||
"10.0.2.2"; | ||
#else | ||
throw new PlatformNotSupportedException("Only Windows and Android currently supported."); | ||
#endif | ||
|
||
#if ANDROID | ||
internal sealed class CustomAndroidMessageHandler : Xamarin.Android.Net.AndroidMessageHandler | ||
{ | ||
protected override Javax.Net.Ssl.IHostnameVerifier GetSSLHostnameVerifier(Javax.Net.Ssl.HttpsURLConnection connection) | ||
=> new CustomHostnameVerifier(); | ||
|
||
private sealed class CustomHostnameVerifier : Java.Lang.Object, Javax.Net.Ssl.IHostnameVerifier | ||
{ | ||
public bool Verify(string hostname, Javax.Net.Ssl.ISSLSession session) | ||
{ | ||
return | ||
Javax.Net.Ssl.HttpsURLConnection.DefaultHostnameVerifier.Verify(hostname, session) | ||
|| hostname == "10.0.2.2" && session.PeerPrincipal?.Name == "CN=localhost"; | ||
} | ||
} | ||
} | ||
#endif | ||
} | ||
} |
22 changes: 22 additions & 0 deletions
22
src/Headway.App.Blazor.Maui/Extensions/ServiceCollectionExtensions.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,22 @@ | ||
using Microsoft.Extensions.DependencyInjection; | ||
using System.Collections.Generic; | ||
using System.Reflection; | ||
|
||
namespace Headway.App.Blazor.Maui.Extensions | ||
{ | ||
public static class ServiceCollectionExtensions | ||
{ | ||
/// <summary> | ||
/// A collection of additional assemblies that should be eager loaded at startup so they can be | ||
/// searched for classes with Headway attributes such as [DynamicModel] etc. | ||
/// </summary> | ||
/// <param name="services">The services collection.</param> | ||
/// <param name="assemblies">A collection of assemblies to be eager loaded at startup.</param> | ||
/// <returns>The services collection.</returns> | ||
public static IServiceCollection UseAdditionalAssemblies(this IServiceCollection services, IEnumerable<Assembly> assemblies) | ||
{ | ||
// Intentionally returns services without actually doing anything. | ||
return services; | ||
} | ||
} | ||
} |
Oops, something went wrong.