58 changed files with 4063 additions and 86 deletions
@ -0,0 +1,82 @@ |
|||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using OpenIddict.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Abstractions.OpenIddictExceptions; |
||||
|
using static System.Console; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.Console.Client; |
||||
|
|
||||
|
public class InteractiveService : BackgroundService |
||||
|
{ |
||||
|
private readonly IHostApplicationLifetime _lifetime; |
||||
|
private readonly OpenIddictClientService _service; |
||||
|
|
||||
|
public InteractiveService( |
||||
|
IHostApplicationLifetime lifetime, |
||||
|
OpenIddictClientService service) |
||||
|
{ |
||||
|
_lifetime = lifetime; |
||||
|
_service = service; |
||||
|
} |
||||
|
|
||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
// Wait for the host to confirm that the application has started.
|
||||
|
var source = new TaskCompletionSource(); |
||||
|
using (_lifetime.ApplicationStarted.Register(static state => ((TaskCompletionSource) state!).SetResult(), source)) |
||||
|
{ |
||||
|
await source.Task; |
||||
|
} |
||||
|
|
||||
|
string? provider; |
||||
|
|
||||
|
while (!stoppingToken.IsCancellationRequested) |
||||
|
{ |
||||
|
do |
||||
|
{ |
||||
|
await Out.WriteLineAsync("Type '1' + ENTER to log in using the local server or '2' + ENTER to log in using Twitter"); |
||||
|
|
||||
|
provider = await In.ReadLineAsync(stoppingToken) switch |
||||
|
{ |
||||
|
"1" => "Local", |
||||
|
"2" => "Twitter", |
||||
|
_ => null |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
while (string.IsNullOrEmpty(provider)); |
||||
|
|
||||
|
await Out.WriteLineAsync("Launching the system browser."); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// Ask OpenIddict to initiate the challenge and launch the system browser
|
||||
|
// to allow the user to complete the interactive authentication dance.
|
||||
|
var nonce = await _service.ChallengeWithBrowserAsync( |
||||
|
provider, cancellationToken: stoppingToken); |
||||
|
|
||||
|
// Wait until the user approved or rejected the authorization
|
||||
|
// demand and retrieve the resulting claims-based principal.
|
||||
|
var (_, _, principal) = await _service.AuthenticateWithBrowserAsync( |
||||
|
nonce, cancellationToken: stoppingToken); |
||||
|
|
||||
|
await Out.WriteLineAsync($"Welcome, {principal.FindFirst(Claims.Name)!.Value}."); |
||||
|
} |
||||
|
|
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
await Error.WriteLineAsync("The authentication process was aborted."); |
||||
|
} |
||||
|
|
||||
|
catch (ProtocolException exception) when (exception.Error is Errors.AccessDenied) |
||||
|
{ |
||||
|
await Error.WriteLineAsync("The authorization was denied by the end user."); |
||||
|
} |
||||
|
|
||||
|
catch |
||||
|
{ |
||||
|
await Error.WriteLineAsync("An error occurred while trying to authenticate the user."); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>net7.0-windows</TargetFramework> |
||||
|
<EnablePreviewFeatures>true</EnablePreviewFeatures> |
||||
|
<IsShipping>false</IsShipping> |
||||
|
<SignAssembly>false</SignAssembly> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemNetHttp\OpenIddict.Client.SystemNetHttp.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.WebIntegration\OpenIddict.Client.WebIntegration.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.Windows\OpenIddict.Client.Windows.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.EntityFrameworkCore\OpenIddict.EntityFrameworkCore.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.AppServices" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,99 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using OpenIddict.Client; |
||||
|
using OpenIddict.Sandbox.Console.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
|
||||
|
var host = Host.CreateDefaultBuilder(args) |
||||
|
// Note: applications for which a single instance is preferred can reference
|
||||
|
// the Dapplo.Microsoft.Extensions.Hosting.AppServices package and call this
|
||||
|
// method to automatically close extra instances based on the specified identifier:
|
||||
|
//
|
||||
|
// .ConfigureSingleInstance(options => options.MutexId = "{802A478D-00E8-4DAE-9A27-27B31A47CB39}")
|
||||
|
//
|
||||
|
.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddDbContext<DbContext>(options => |
||||
|
{ |
||||
|
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-console-client.sqlite3")}"); |
||||
|
options.UseOpenIddict(); |
||||
|
}); |
||||
|
|
||||
|
services.AddOpenIddict() |
||||
|
|
||||
|
// Register the OpenIddict core components.
|
||||
|
.AddCore(options => |
||||
|
{ |
||||
|
// Configure OpenIddict to use the Entity Framework Core stores and models.
|
||||
|
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
|
||||
|
options.UseEntityFrameworkCore() |
||||
|
.UseDbContext<DbContext>(); |
||||
|
}) |
||||
|
|
||||
|
// Register the OpenIddict client components.
|
||||
|
.AddClient(options => |
||||
|
{ |
||||
|
// Note: this sample uses the authorization code and refresh token
|
||||
|
// flows, but you can enable the other flows if necessary.
|
||||
|
options.AllowAuthorizationCodeFlow() |
||||
|
.AllowRefreshTokenFlow(); |
||||
|
|
||||
|
// Register the signing and encryption credentials used to protect
|
||||
|
// sensitive data like the state tokens produced by OpenIddict.
|
||||
|
options.AddDevelopmentEncryptionCertificate() |
||||
|
.AddDevelopmentSigningCertificate(); |
||||
|
|
||||
|
// Register the Windows host.
|
||||
|
options.UseWindows(); |
||||
|
|
||||
|
// Set the client URI that will uniquely identify this application.
|
||||
|
options.SetClientUri(new Uri("openiddict-sandbox-console-client://localhost/", UriKind.Absolute)); |
||||
|
|
||||
|
// Register the System.Net.Http integration and use the identity of the current
|
||||
|
// assembly as a more specific user agent, which can be useful when dealing with
|
||||
|
// providers that use the user agent as a way to throttle requests (e.g Reddit).
|
||||
|
options.UseSystemNetHttp() |
||||
|
.SetProductInformation(typeof(Program).Assembly); |
||||
|
|
||||
|
// Add a client registration matching the client application definition in the server project.
|
||||
|
options.AddRegistration(new OpenIddictClientRegistration |
||||
|
{ |
||||
|
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute), |
||||
|
ProviderName = "Local", |
||||
|
|
||||
|
ClientId = "console", |
||||
|
RedirectUri = new Uri("callback/login/local", UriKind.Relative), |
||||
|
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" } |
||||
|
}); |
||||
|
|
||||
|
// Register the Web providers integrations.
|
||||
|
//
|
||||
|
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
|
||||
|
// address per provider, unless all the registered providers support returning an "iss"
|
||||
|
// parameter containing their URL as part of authorization responses. For more information,
|
||||
|
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
|
||||
|
options.UseWebProviders() |
||||
|
.UseTwitter() |
||||
|
.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ") |
||||
|
.SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS") |
||||
|
.SetRedirectUri(new Uri("callback/login/twitter", UriKind.Relative)); |
||||
|
}); |
||||
|
|
||||
|
// Register the worker responsible for creating the database used to store tokens
|
||||
|
// and adding the registry entries required to register the custom URI scheme.
|
||||
|
//
|
||||
|
// Note: in a real world application, this step should be part of a setup script.
|
||||
|
services.AddHostedService<Worker>(); |
||||
|
|
||||
|
// Register the background service responsible for handling the console interactions.
|
||||
|
services.AddHostedService<InteractiveService>(); |
||||
|
|
||||
|
services.RemoveAll<ILoggerProvider>(); |
||||
|
}) |
||||
|
.UseConsoleLifetime() |
||||
|
.Build(); |
||||
|
|
||||
|
await host.RunAsync(); |
||||
@ -0,0 +1,54 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Win32; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.Console.Client; |
||||
|
|
||||
|
public class Worker : IHostedService |
||||
|
{ |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
public Worker(IServiceProvider provider) |
||||
|
=> _provider = provider; |
||||
|
|
||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
using var scope = _provider.CreateScope(); |
||||
|
|
||||
|
var context = scope.ServiceProvider.GetRequiredService<DbContext>(); |
||||
|
await context.Database.EnsureCreatedAsync(); |
||||
|
|
||||
|
RegistryKey? root = null; |
||||
|
|
||||
|
// Create the registry entries necessary to handle URI protocol activations.
|
||||
|
// Note: the application MUST be run once as an administrator for this to work.
|
||||
|
try |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.OpenSubKey("openiddict-sandbox-console-client"); |
||||
|
|
||||
|
if (root is null) |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.CreateSubKey("openiddict-sandbox-console-client"); |
||||
|
root.SetValue(string.Empty, "URL:openiddict-sandbox-console-client"); |
||||
|
root.SetValue("URL Protocol", string.Empty); |
||||
|
|
||||
|
using var command = root.CreateSubKey("shell\\open\\command"); |
||||
|
command.SetValue(string.Empty, string.Format("\"{0}\" \"%1\"", |
||||
|
#if SUPPORTS_ENVIRONMENT_PROCESS_PATH
|
||||
|
Environment.ProcessPath |
||||
|
#else
|
||||
|
Process.GetCurrentProcess().MainModule.FileName |
||||
|
#endif
|
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
root?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
namespace OpenIddict.Sandbox.WinForms.Client |
||||
|
{ |
||||
|
partial class MainForm |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Required designer variable.
|
||||
|
/// </summary>
|
||||
|
private System.ComponentModel.IContainer components = null; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Clean up any resources being used.
|
||||
|
/// </summary>
|
||||
|
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
|
protected override void Dispose(bool disposing) |
||||
|
{ |
||||
|
if (disposing && (components != null)) |
||||
|
{ |
||||
|
components.Dispose(); |
||||
|
} |
||||
|
base.Dispose(disposing); |
||||
|
} |
||||
|
|
||||
|
#region Windows Form Designer generated code
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Required method for Designer support - do not modify
|
||||
|
/// the contents of this method with the code editor.
|
||||
|
/// </summary>
|
||||
|
private void InitializeComponent() |
||||
|
{ |
||||
|
this.LocalLogin = new System.Windows.Forms.Button(); |
||||
|
this.TwitterLogin = new System.Windows.Forms.Button(); |
||||
|
this.SuspendLayout(); |
||||
|
//
|
||||
|
// LocalLogin
|
||||
|
//
|
||||
|
this.LocalLogin.Location = new System.Drawing.Point(258, 93); |
||||
|
this.LocalLogin.Name = "LocalLogin"; |
||||
|
this.LocalLogin.Size = new System.Drawing.Size(283, 83); |
||||
|
this.LocalLogin.TabIndex = 0; |
||||
|
this.LocalLogin.Text = "Log in using the local server"; |
||||
|
this.LocalLogin.UseVisualStyleBackColor = true; |
||||
|
this.LocalLogin.Click += new System.EventHandler(this.LocalLoginButton_Click); |
||||
|
//
|
||||
|
// TwitterLogin
|
||||
|
//
|
||||
|
this.TwitterLogin.Location = new System.Drawing.Point(258, 258); |
||||
|
this.TwitterLogin.Name = "TwitterLogin"; |
||||
|
this.TwitterLogin.Size = new System.Drawing.Size(283, 83); |
||||
|
this.TwitterLogin.TabIndex = 1; |
||||
|
this.TwitterLogin.Text = "Log in using Twitter"; |
||||
|
this.TwitterLogin.UseVisualStyleBackColor = true; |
||||
|
this.TwitterLogin.Click += new System.EventHandler(this.TwitterLoginButton_Click); |
||||
|
//
|
||||
|
// MainForm
|
||||
|
//
|
||||
|
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); |
||||
|
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; |
||||
|
this.ClientSize = new System.Drawing.Size(800, 450); |
||||
|
this.Controls.Add(this.TwitterLogin); |
||||
|
this.Controls.Add(this.LocalLogin); |
||||
|
this.Name = "MainForm"; |
||||
|
this.Text = "OpenIddict WinForms client"; |
||||
|
this.ResumeLayout(false); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#endregion
|
||||
|
|
||||
|
private Button LocalLogin; |
||||
|
private Button TwitterLogin; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
using Dapplo.Microsoft.Extensions.Hosting.WinForms; |
||||
|
using OpenIddict.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Abstractions.OpenIddictExceptions; |
||||
|
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.WinForms.Client |
||||
|
{ |
||||
|
public partial class MainForm : Form, IWinFormsShell |
||||
|
{ |
||||
|
private readonly OpenIddictClientService _service; |
||||
|
|
||||
|
public MainForm(OpenIddictClientService service) |
||||
|
{ |
||||
|
_service = service ?? throw new ArgumentNullException(nameof(service)); |
||||
|
|
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private async void LocalLoginButton_Click(object sender, EventArgs e) |
||||
|
=> await AuthenticateAsync("Local"); |
||||
|
|
||||
|
private async void TwitterLoginButton_Click(object sender, EventArgs e) |
||||
|
=> await AuthenticateAsync(Providers.Twitter); |
||||
|
|
||||
|
private async Task AuthenticateAsync(string provider) |
||||
|
{ |
||||
|
using var source = new CancellationTokenSource(delay: TimeSpan.FromSeconds(90)); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// Ask OpenIddict to initiate the challenge and launch the system browser
|
||||
|
// to allow the user to complete the interactive authentication dance.
|
||||
|
var nonce = await _service.ChallengeWithBrowserAsync( |
||||
|
provider, cancellationToken: source.Token); |
||||
|
|
||||
|
// Wait until the user approved or rejected the authorization
|
||||
|
// demand and retrieve the resulting claims-based principal.
|
||||
|
var (_, _, principal) = await _service.AuthenticateWithBrowserAsync( |
||||
|
nonce, cancellationToken: source.Token); |
||||
|
|
||||
|
#if SUPPORTS_WINFORMS_TASK_DIALOG
|
||||
|
TaskDialog.ShowDialog(new TaskDialogPage |
||||
|
{ |
||||
|
Caption = "Authentication successful", |
||||
|
Heading = "Authentication successful", |
||||
|
Icon = TaskDialogIcon.ShieldSuccessGreenBar, |
||||
|
Text = $"Welcome, {principal.FindFirst(Claims.Name)!.Value}." |
||||
|
}); |
||||
|
#else
|
||||
|
MessageBox.Show($"Welcome, {principal.FindFirst(Claims.Name)!.Value}.", |
||||
|
"Authentication successful", MessageBoxButtons.OK, MessageBoxIcon.Information); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
#if SUPPORTS_WINFORMS_TASK_DIALOG
|
||||
|
TaskDialog.ShowDialog(new TaskDialogPage |
||||
|
{ |
||||
|
Caption = "Authentication timed out", |
||||
|
Heading = "Authentication timed out", |
||||
|
Icon = TaskDialogIcon.Warning, |
||||
|
Text = "The authentication process was aborted." |
||||
|
}); |
||||
|
#else
|
||||
|
MessageBox.Show("The authentication process was aborted.", |
||||
|
"Authentication timed out", MessageBoxButtons.OK, MessageBoxIcon.Warning); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
catch (ProtocolException exception) when (exception.Error is Errors.AccessDenied) |
||||
|
{ |
||||
|
#if SUPPORTS_WINFORMS_TASK_DIALOG
|
||||
|
TaskDialog.ShowDialog(new TaskDialogPage |
||||
|
{ |
||||
|
Caption = "Authorization denied", |
||||
|
Heading = "Authorization denied", |
||||
|
Icon = TaskDialogIcon.Warning, |
||||
|
Text = "The authorization was denied by the end user." |
||||
|
}); |
||||
|
#else
|
||||
|
MessageBox.Show("The authorization was denied by the end user.", |
||||
|
"Authorization denied", MessageBoxButtons.OK, MessageBoxIcon.Warning); |
||||
|
#endif
|
||||
|
} |
||||
|
|
||||
|
catch |
||||
|
{ |
||||
|
#if SUPPORTS_WINFORMS_TASK_DIALOG
|
||||
|
TaskDialog.ShowDialog(new TaskDialogPage |
||||
|
{ |
||||
|
Caption = "Authentication failed", |
||||
|
Heading = "Authentication failed", |
||||
|
Icon = TaskDialogIcon.Error, |
||||
|
Text = "An error occurred while trying to authenticate the user." |
||||
|
}); |
||||
|
#else
|
||||
|
MessageBox.Show("An error occurred while trying to authenticate the user.", |
||||
|
"Authentication failed", MessageBoxButtons.OK, MessageBoxIcon.Error); |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<TargetFrameworks>net48;net7.0-windows</TargetFrameworks> |
||||
|
<EnablePreviewFeatures>true</EnablePreviewFeatures> |
||||
|
<EnableWindowsTargeting>true</EnableWindowsTargeting> |
||||
|
<UseWindowsForms>true</UseWindowsForms> |
||||
|
<IsShipping>false</IsShipping> |
||||
|
<SignAssembly>false</SignAssembly> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemNetHttp\OpenIddict.Client.SystemNetHttp.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.WebIntegration\OpenIddict.Client.WebIntegration.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.Windows\OpenIddict.Client.Windows.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.EntityFrameworkCore\OpenIddict.EntityFrameworkCore.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.AppServices" /> |
||||
|
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.WinForms" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Using Remove="System.Net.Http" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,94 @@ |
|||||
|
using Dapplo.Microsoft.Extensions.Hosting.WinForms; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using OpenIddict.Client; |
||||
|
using OpenIddict.Sandbox.WinForms.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
|
||||
|
var host = Host.CreateDefaultBuilder(args) |
||||
|
// Note: applications for which a single instance is preferred can reference
|
||||
|
// the Dapplo.Microsoft.Extensions.Hosting.AppServices package and call this
|
||||
|
// method to automatically close extra instances based on the specified identifier:
|
||||
|
//
|
||||
|
// .ConfigureSingleInstance(options => options.MutexId = "{D6FEAFC8-3079-4881-B9F2-0B78EAF38B85}")
|
||||
|
//
|
||||
|
.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddDbContext<DbContext>(options => |
||||
|
{ |
||||
|
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-winforms-client.sqlite3")}"); |
||||
|
options.UseOpenIddict(); |
||||
|
}); |
||||
|
|
||||
|
services.AddOpenIddict() |
||||
|
|
||||
|
// Register the OpenIddict core components.
|
||||
|
.AddCore(options => |
||||
|
{ |
||||
|
// Configure OpenIddict to use the Entity Framework Core stores and models.
|
||||
|
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
|
||||
|
options.UseEntityFrameworkCore() |
||||
|
.UseDbContext<DbContext>(); |
||||
|
}) |
||||
|
|
||||
|
// Register the OpenIddict client components.
|
||||
|
.AddClient(options => |
||||
|
{ |
||||
|
// Note: this sample uses the authorization code and refresh token
|
||||
|
// flows, but you can enable the other flows if necessary.
|
||||
|
options.AllowAuthorizationCodeFlow() |
||||
|
.AllowRefreshTokenFlow(); |
||||
|
|
||||
|
// Register the signing and encryption credentials used to protect
|
||||
|
// sensitive data like the state tokens produced by OpenIddict.
|
||||
|
options.AddDevelopmentEncryptionCertificate() |
||||
|
.AddDevelopmentSigningCertificate(); |
||||
|
|
||||
|
// Register the Windows host.
|
||||
|
options.UseWindows(); |
||||
|
|
||||
|
// Set the client URI that will uniquely identify this application.
|
||||
|
options.SetClientUri(new Uri("openiddict-sandbox-winforms-client://localhost/", UriKind.Absolute)); |
||||
|
|
||||
|
// Register the System.Net.Http integration and use the identity of the current
|
||||
|
// assembly as a more specific user agent, which can be useful when dealing with
|
||||
|
// providers that use the user agent as a way to throttle requests (e.g Reddit).
|
||||
|
options.UseSystemNetHttp() |
||||
|
.SetProductInformation(typeof(Program).Assembly); |
||||
|
|
||||
|
// Add a client registration matching the client application definition in the server project.
|
||||
|
options.AddRegistration(new OpenIddictClientRegistration |
||||
|
{ |
||||
|
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute), |
||||
|
ProviderName = "Local", |
||||
|
|
||||
|
ClientId = "winforms", |
||||
|
RedirectUri = new Uri("callback/login/local", UriKind.Relative), |
||||
|
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" } |
||||
|
}); |
||||
|
|
||||
|
// Register the Web providers integrations.
|
||||
|
//
|
||||
|
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
|
||||
|
// address per provider, unless all the registered providers support returning an "iss"
|
||||
|
// parameter containing their URL as part of authorization responses. For more information,
|
||||
|
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
|
||||
|
options.UseWebProviders() |
||||
|
.UseTwitter() |
||||
|
.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ") |
||||
|
.SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS") |
||||
|
.SetRedirectUri(new Uri("callback/login/twitter", UriKind.Relative)); |
||||
|
}); |
||||
|
|
||||
|
// Register the worker responsible for creating the database used to store tokens
|
||||
|
// and adding the registry entries required to register the custom URI scheme.
|
||||
|
//
|
||||
|
// Note: in a real world application, this step should be part of a setup script.
|
||||
|
services.AddHostedService<Worker>(); |
||||
|
}) |
||||
|
.ConfigureWinForms<MainForm>() |
||||
|
.UseWinFormsLifetime() |
||||
|
.Build(); |
||||
|
|
||||
|
await host.RunAsync(); |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Win32; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.WinForms.Client; |
||||
|
|
||||
|
public class Worker : IHostedService |
||||
|
{ |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
public Worker(IServiceProvider provider) |
||||
|
=> _provider = provider; |
||||
|
|
||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
using var scope = _provider.CreateScope(); |
||||
|
|
||||
|
var context = scope.ServiceProvider.GetRequiredService<DbContext>(); |
||||
|
await context.Database.EnsureCreatedAsync(); |
||||
|
|
||||
|
RegistryKey? root = null; |
||||
|
|
||||
|
// Create the registry entries necessary to handle URI protocol activations.
|
||||
|
// Note: the application MUST be run once as an administrator for this to work.
|
||||
|
try |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.OpenSubKey("openiddict-sandbox-winforms-client"); |
||||
|
|
||||
|
if (root is null) |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.CreateSubKey("openiddict-sandbox-winforms-client"); |
||||
|
root.SetValue(string.Empty, "URL:openiddict-sandbox-winforms-client"); |
||||
|
root.SetValue("URL Protocol", string.Empty); |
||||
|
|
||||
|
using var command = root.CreateSubKey("shell\\open\\command"); |
||||
|
command.SetValue(string.Empty, string.Format("\"{0}\" \"%1\"", |
||||
|
#if SUPPORTS_ENVIRONMENT_PROCESS_PATH
|
||||
|
Environment.ProcessPath |
||||
|
#else
|
||||
|
Process.GetCurrentProcess().MainModule.FileName |
||||
|
#endif
|
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
root?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
||||
|
} |
||||
@ -0,0 +1,7 @@ |
|||||
|
<Application x:Class="OpenIddict.Sandbox.Wpf.Client.App" |
||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
||||
|
<Application.Resources> |
||||
|
|
||||
|
</Application.Resources> |
||||
|
</Application> |
||||
@ -0,0 +1,8 @@ |
|||||
|
using System.Windows; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.Wpf.Client |
||||
|
{ |
||||
|
public partial class App : Application |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<Window x:Class="OpenIddict.Sandbox.Wpf.Client.MainWindow" |
||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" |
||||
|
Title="OpenIddict WPF client" Height="450" Width="800"> |
||||
|
<Grid> |
||||
|
<Button Content="Log in using the local server" HorizontalAlignment="Center" VerticalAlignment="Top" Click="LocalLoginButton_Click" Height="64" Width="252" FontSize="20" Margin="0,130,0,0" /> |
||||
|
<Button Content="Log in using Twitter" HorizontalAlignment="Center" VerticalAlignment="Top" Click="TwitterLoginButton_Click" Height="64" Width="252" FontSize="20" Margin="0,234,0,0" /> |
||||
|
</Grid> |
||||
|
</Window> |
||||
@ -0,0 +1,66 @@ |
|||||
|
using System.Windows; |
||||
|
using Dapplo.Microsoft.Extensions.Hosting.Wpf; |
||||
|
using OpenIddict.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
using static OpenIddict.Abstractions.OpenIddictExceptions; |
||||
|
using static OpenIddict.Client.WebIntegration.OpenIddictClientWebIntegrationConstants; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.Wpf.Client |
||||
|
{ |
||||
|
public partial class MainWindow : Window, IWpfShell |
||||
|
{ |
||||
|
private readonly OpenIddictClientService _service; |
||||
|
|
||||
|
public MainWindow(OpenIddictClientService service) |
||||
|
{ |
||||
|
_service = service ?? throw new ArgumentNullException(nameof(service)); |
||||
|
|
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
private async void LocalLoginButton_Click(object sender, RoutedEventArgs e) |
||||
|
=> await AuthenticateAsync("Local"); |
||||
|
|
||||
|
private async void TwitterLoginButton_Click(object sender, RoutedEventArgs e) |
||||
|
=> await AuthenticateAsync(Providers.Twitter); |
||||
|
|
||||
|
private async Task AuthenticateAsync(string provider) |
||||
|
{ |
||||
|
using var source = new CancellationTokenSource(delay: TimeSpan.FromSeconds(90)); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
// Ask OpenIddict to initiate the challenge and launch the system browser
|
||||
|
// to allow the user to complete the interactive authentication dance.
|
||||
|
var nonce = await _service.ChallengeWithBrowserAsync( |
||||
|
provider, cancellationToken: source.Token); |
||||
|
|
||||
|
// Wait until the user approved or rejected the authorization
|
||||
|
// demand and retrieve the resulting claims-based principal.
|
||||
|
var (_, _, principal) = await _service.AuthenticateWithBrowserAsync( |
||||
|
nonce, cancellationToken: source.Token); |
||||
|
|
||||
|
MessageBox.Show($"Welcome, {principal.FindFirst(Claims.Name)!.Value}.", |
||||
|
"Authentication successful", MessageBoxButton.OK, MessageBoxImage.Information); |
||||
|
} |
||||
|
|
||||
|
catch (OperationCanceledException) |
||||
|
{ |
||||
|
MessageBox.Show("The authentication process was aborted.", |
||||
|
"Authentication timed out", MessageBoxButton.OK, MessageBoxImage.Warning); |
||||
|
} |
||||
|
|
||||
|
catch (ProtocolException exception) when (exception.Error is Errors.AccessDenied) |
||||
|
{ |
||||
|
MessageBox.Show("The authorization was denied by the end user.", |
||||
|
"Authorization denied", MessageBoxButton.OK, MessageBoxImage.Warning); |
||||
|
} |
||||
|
|
||||
|
catch |
||||
|
{ |
||||
|
MessageBox.Show("An error occurred while trying to authenticate the user.", |
||||
|
"Authentication failed", MessageBoxButton.OK, MessageBoxImage.Error); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<TargetFrameworks>net48;net7.0-windows</TargetFrameworks> |
||||
|
<EnablePreviewFeatures>true</EnablePreviewFeatures> |
||||
|
<EnableWindowsTargeting>true</EnableWindowsTargeting> |
||||
|
<UseWPF>true</UseWPF> |
||||
|
<IsShipping>false</IsShipping> |
||||
|
<SignAssembly>false</SignAssembly> |
||||
|
<EnableDefaultApplicationDefinition>false</EnableDefaultApplicationDefinition> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.SystemNetHttp\OpenIddict.Client.SystemNetHttp.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.WebIntegration\OpenIddict.Client.WebIntegration.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.Client.Windows\OpenIddict.Client.Windows.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\OpenIddict.EntityFrameworkCore\OpenIddict.EntityFrameworkCore.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.AppServices" /> |
||||
|
<PackageReference Include="Dapplo.Microsoft.Extensions.Hosting.Wpf" /> |
||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" /> |
||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Using Remove="System.Net.Http" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,99 @@ |
|||||
|
using System.IO; |
||||
|
using Dapplo.Microsoft.Extensions.Hosting.Wpf; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using OpenIddict.Client; |
||||
|
using OpenIddict.Sandbox.Wpf.Client; |
||||
|
using static OpenIddict.Abstractions.OpenIddictConstants; |
||||
|
|
||||
|
var host = Host.CreateDefaultBuilder(args) |
||||
|
// Note: applications for which a single instance is preferred can reference
|
||||
|
// the Dapplo.Microsoft.Extensions.Hosting.AppServices package and call this
|
||||
|
// method to automatically close extra instances based on the specified identifier:
|
||||
|
//
|
||||
|
// .ConfigureSingleInstance(options => options.MutexId = "{C587B9EA-A870-4CF3-8B00-33DF67FCA143}")
|
||||
|
//
|
||||
|
.ConfigureServices(services => |
||||
|
{ |
||||
|
services.AddDbContext<DbContext>(options => |
||||
|
{ |
||||
|
options.UseSqlite($"Filename={Path.Combine(Path.GetTempPath(), "openiddict-sandbox-wpf-client.sqlite3")}"); |
||||
|
options.UseOpenIddict(); |
||||
|
}); |
||||
|
|
||||
|
services.AddOpenIddict() |
||||
|
|
||||
|
// Register the OpenIddict core components.
|
||||
|
.AddCore(options => |
||||
|
{ |
||||
|
// Configure OpenIddict to use the Entity Framework Core stores and models.
|
||||
|
// Note: call ReplaceDefaultEntities() to replace the default OpenIddict entities.
|
||||
|
options.UseEntityFrameworkCore() |
||||
|
.UseDbContext<DbContext>(); |
||||
|
}) |
||||
|
|
||||
|
// Register the OpenIddict client components.
|
||||
|
.AddClient(options => |
||||
|
{ |
||||
|
// Note: this sample uses the authorization code and refresh token
|
||||
|
// flows, but you can enable the other flows if necessary.
|
||||
|
options.AllowAuthorizationCodeFlow() |
||||
|
.AllowRefreshTokenFlow(); |
||||
|
|
||||
|
// Register the signing and encryption credentials used to protect
|
||||
|
// sensitive data like the state tokens produced by OpenIddict.
|
||||
|
options.AddDevelopmentEncryptionCertificate() |
||||
|
.AddDevelopmentSigningCertificate(); |
||||
|
|
||||
|
// Register the Windows host.
|
||||
|
options.UseWindows(); |
||||
|
|
||||
|
// Set the client URI that will uniquely identify this application.
|
||||
|
options.SetClientUri(new Uri("openiddict-sandbox-wpf-client://localhost/", UriKind.Absolute)); |
||||
|
|
||||
|
// Register the System.Net.Http integration and use the identity of the current
|
||||
|
// assembly as a more specific user agent, which can be useful when dealing with
|
||||
|
// providers that use the user agent as a way to throttle requests (e.g Reddit).
|
||||
|
options.UseSystemNetHttp() |
||||
|
.SetProductInformation(typeof(Program).Assembly); |
||||
|
|
||||
|
// Add a client registration matching the client application definition in the server project.
|
||||
|
options.AddRegistration(new OpenIddictClientRegistration |
||||
|
{ |
||||
|
Issuer = new Uri("https://localhost:44395/", UriKind.Absolute), |
||||
|
ProviderName = "Local", |
||||
|
|
||||
|
ClientId = "wpf", |
||||
|
RedirectUri = new Uri("callback/login/local", UriKind.Relative), |
||||
|
Scopes = { Scopes.Email, Scopes.Profile, Scopes.OfflineAccess, "demo_api" } |
||||
|
}); |
||||
|
|
||||
|
// Register the Web providers integrations.
|
||||
|
//
|
||||
|
// Note: to mitigate mix-up attacks, it's recommended to use a unique redirection endpoint
|
||||
|
// address per provider, unless all the registered providers support returning an "iss"
|
||||
|
// parameter containing their URL as part of authorization responses. For more information,
|
||||
|
// see https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.4.
|
||||
|
options.UseWebProviders() |
||||
|
.UseTwitter() |
||||
|
.SetClientId("bXgwc0U3N3A3YWNuaWVsdlRmRWE6MTpjaQ") |
||||
|
.SetClientSecret("VcohOgBp-6yQCurngo4GAyKeZh0D6SUCCSjJgEo1uRzJarjIUS") |
||||
|
.SetRedirectUri(new Uri("callback/login/twitter", UriKind.Relative)); |
||||
|
}); |
||||
|
|
||||
|
// Register the worker responsible for creating the database used to store tokens
|
||||
|
// and adding the registry entries required to register the custom URI scheme.
|
||||
|
//
|
||||
|
// Note: in a real world application, this step should be part of a setup script.
|
||||
|
services.AddHostedService<Worker>(); |
||||
|
}) |
||||
|
.ConfigureWpf(options => |
||||
|
{ |
||||
|
options.UseApplication<App>(); |
||||
|
options.UseWindow<MainWindow>(); |
||||
|
}) |
||||
|
.UseWpfLifetime() |
||||
|
.Build(); |
||||
|
|
||||
|
await host.RunAsync(); |
||||
@ -0,0 +1,55 @@ |
|||||
|
using System.Diagnostics; |
||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Win32; |
||||
|
|
||||
|
namespace OpenIddict.Sandbox.Wpf.Client; |
||||
|
|
||||
|
public class Worker : IHostedService |
||||
|
{ |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
public Worker(IServiceProvider provider) |
||||
|
=> _provider = provider; |
||||
|
|
||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
using var scope = _provider.CreateScope(); |
||||
|
|
||||
|
var context = scope.ServiceProvider.GetRequiredService<DbContext>(); |
||||
|
await context.Database.EnsureCreatedAsync(); |
||||
|
|
||||
|
RegistryKey? root = null; |
||||
|
|
||||
|
// Create the registry entries necessary to handle URI protocol activations.
|
||||
|
// Note: the application MUST be run once as an administrator for this to work.
|
||||
|
try |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.OpenSubKey("openiddict-sandbox-wpf-client"); |
||||
|
|
||||
|
if (root is null) |
||||
|
{ |
||||
|
root = Registry.ClassesRoot.CreateSubKey("openiddict-sandbox-wpf-client"); |
||||
|
root.SetValue(string.Empty, "URL:openiddict-sandbox-wpf-client"); |
||||
|
root.SetValue("URL Protocol", string.Empty); |
||||
|
|
||||
|
using var command = root.CreateSubKey("shell\\open\\command"); |
||||
|
command.SetValue(string.Empty, string.Format("\"{0}\" \"%1\"", |
||||
|
#if SUPPORTS_ENVIRONMENT_PROCESS_PATH
|
||||
|
Environment.ProcessPath |
||||
|
#else
|
||||
|
Process.GetCurrentProcess().MainModule.FileName |
||||
|
#endif
|
||||
|
)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
root?.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>net461;netcoreapp3.1;net6.0-windows;net7.0-windows</TargetFrameworks> |
||||
|
<EnablePreviewFeatures>true</EnablePreviewFeatures> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<Description>Windows integration package for the OpenIddict client services.</Description> |
||||
|
<PackageTags>$(PackageTags);client;windows</PackageTags> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\OpenIddict.Client\OpenIddict.Client.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup |
||||
|
Condition=" ('$(TargetFrameworkIdentifier)' == '.NETCoreApp' And $([MSBuild]::VersionLessThan($(TargetFrameworkVersion), '5.0'))) "> |
||||
|
<PackageReference Include="NamedPipeServerStream.NetFrameworkVersion" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Compile Include="..\..\shared\OpenIddict.Extensions\*\*.cs" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Using Include="OpenIddict.Abstractions" /> |
||||
|
<Using Include="OpenIddict.Abstractions.OpenIddictConstants" Static="true" /> |
||||
|
<Using Include="OpenIddict.Abstractions.OpenIddictResources" Alias="SR" /> |
||||
|
<Using Include="OpenIddict.Client.OpenIddictClientEvents" Static="true" /> |
||||
|
<Using Include="OpenIddict.Client.OpenIddictClientHandlers" Static="true" /> |
||||
|
<Using Include="OpenIddict.Client.OpenIddictClientHandlerFilters" Static="true" /> |
||||
|
<Using Include="OpenIddict.Client.Windows.OpenIddictClientWindowsHandlers" Static="true" /> |
||||
|
<Using Include="OpenIddict.Client.Windows.OpenIddictClientWindowsHandlerFilters" Static="true" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,29 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.Collections.Immutable; |
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents a Windows application activation.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public sealed class OpenIddictClientWindowsActivation |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the command line arguments used to
|
||||
|
/// launch the current instance of the application.
|
||||
|
/// </summary>
|
||||
|
public ImmutableArray<string> CommandLineArguments { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets a boolean indicating whether the activation
|
||||
|
/// was redirected from another instance of the application.
|
||||
|
/// </summary>
|
||||
|
public bool IsActivationRedirected { get; set; } |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.ComponentModel; |
||||
|
using System.IO.Pipes; |
||||
|
using OpenIddict.Client.Windows; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes the necessary methods required to configure
|
||||
|
/// the OpenIddict client Windows integration.
|
||||
|
/// </summary>
|
||||
|
public sealed class OpenIddictClientWindowsBuilder |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of <see cref="OpenIddictClientWindowsBuilder"/>.
|
||||
|
/// </summary>
|
||||
|
/// <param name="services">The services collection.</param>
|
||||
|
public OpenIddictClientWindowsBuilder(IServiceCollection services) |
||||
|
=> Services = services ?? throw new ArgumentNullException(nameof(services)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the services collection.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public IServiceCollection Services { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Amends the default OpenIddict client Windows configuration.
|
||||
|
/// </summary>
|
||||
|
/// <param name="configuration">The delegate used to configure the OpenIddict options.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
public OpenIddictClientWindowsBuilder Configure(Action<OpenIddictClientWindowsOptions> configuration) |
||||
|
{ |
||||
|
if (configuration is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(configuration)); |
||||
|
} |
||||
|
|
||||
|
Services.Configure(configuration); |
||||
|
|
||||
|
return this; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the timeout after which authentication demands that
|
||||
|
/// are not completed are automatically aborted by OpenIddict.
|
||||
|
/// </summary>
|
||||
|
/// <param name="timeout">The authentication timeout.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
public OpenIddictClientWindowsBuilder SetAuthenticationTimeout(TimeSpan timeout) |
||||
|
=> Configure(options => options.AuthenticationTimeout = timeout); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the identifier used to represent the current application
|
||||
|
/// instance and redirect protocol activations when necessary.
|
||||
|
/// </summary>
|
||||
|
/// <param name="identifier">The identifier of the current instance.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public OpenIddictClientWindowsBuilder SetInstanceIdentifier(string identifier) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(identifier)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.FormatID0366(nameof(identifier)), nameof(identifier)); |
||||
|
} |
||||
|
|
||||
|
return Configure(options => options.InstanceIdentifier = identifier); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the base name of the pipe created by OpenIddict to enable
|
||||
|
/// inter-process communication and handle protocol activation redirections.
|
||||
|
/// </summary>
|
||||
|
/// <param name="name">The name of the pipe.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public OpenIddictClientWindowsBuilder SetPipeName(string name) |
||||
|
{ |
||||
|
if (string.IsNullOrEmpty(name)) |
||||
|
{ |
||||
|
throw new ArgumentException(SR.FormatID0366(nameof(name)), nameof(name)); |
||||
|
} |
||||
|
|
||||
|
return Configure(options => options.PipeName = name); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the security policy applied to the pipe created by OpenIddict to enable
|
||||
|
/// inter-process communication and handle protocol activation redirections.
|
||||
|
/// </summary>
|
||||
|
/// <param name="security">The security policy applied to the pipe.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public OpenIddictClientWindowsBuilder SetPipeSecurity(PipeSecurity security) |
||||
|
{ |
||||
|
if (security is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(security)); |
||||
|
} |
||||
|
|
||||
|
return Configure(options => options.PipeSecurity = security); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether the specified object is equal to the current object.
|
||||
|
/// </summary>
|
||||
|
/// <param name="obj">The object to compare with the current object.</param>
|
||||
|
/// <returns><see langword="true"/> if the specified object is equal to the current object; otherwise, false.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override bool Equals(object? obj) => base.Equals(obj); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Serves as the default hash function.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A hash code for the current object.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override int GetHashCode() => base.GetHashCode(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns a string that represents the current object.
|
||||
|
/// </summary>
|
||||
|
/// <returns>A string that represents the current object.</returns>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public override string? ToString() => base.ToString(); |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.ComponentModel; |
||||
|
using System.IO.Pipes; |
||||
|
using System.Security.AccessControl; |
||||
|
using System.Security.Principal; |
||||
|
using System.Text; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Microsoft.IdentityModel.Tokens; |
||||
|
using OpenIddict.Extensions; |
||||
|
|
||||
|
#if !SUPPORTS_HOST_ENVIRONMENT
|
||||
|
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment; |
||||
|
#endif
|
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the methods required to ensure that the OpenIddict client configuration is valid.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public sealed class OpenIddictClientWindowsConfiguration : IConfigureOptions<OpenIddictClientOptions>, |
||||
|
IPostConfigureOptions<OpenIddictClientOptions>, |
||||
|
IPostConfigureOptions<OpenIddictClientWindowsOptions> |
||||
|
{ |
||||
|
private readonly IHostEnvironment _environment; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="OpenIddictClientWindowsConfiguration"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="environment">The host environment.</param>
|
||||
|
public OpenIddictClientWindowsConfiguration(IHostEnvironment environment) |
||||
|
=> _environment = environment ?? throw new ArgumentNullException(nameof(environment)); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void Configure(OpenIddictClientOptions options) |
||||
|
{ |
||||
|
if (options is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(options)); |
||||
|
} |
||||
|
|
||||
|
// Register the built-in event handlers used by the OpenIddict Windows client components.
|
||||
|
options.Handlers.AddRange(OpenIddictClientWindowsHandlers.DefaultHandlers); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PostConfigure(string? name, OpenIddictClientOptions options) |
||||
|
{ |
||||
|
if (options is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(options)); |
||||
|
} |
||||
|
|
||||
|
// Ensure an explicit client URI was set when using the Windows integration.
|
||||
|
if (options.ClientUri is not { IsAbsoluteUri: true }) |
||||
|
{ |
||||
|
throw new InvalidOperationException(SR.GetResourceString(SR.ID0384)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public void PostConfigure(string? name, OpenIddictClientWindowsOptions options) |
||||
|
{ |
||||
|
if (options is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(options)); |
||||
|
} |
||||
|
|
||||
|
// If no explicit instance identifier was specified, use a random GUID.
|
||||
|
if (string.IsNullOrEmpty(options.InstanceIdentifier)) |
||||
|
{ |
||||
|
options.InstanceIdentifier = Guid.NewGuid().ToString(); |
||||
|
} |
||||
|
|
||||
|
// If no explicit pipe name was specified, compute the SHA-256 hash of the
|
||||
|
// application name resolved from the host and use it as a unique identifier.
|
||||
|
//
|
||||
|
// Note: the pipe name is deliberately prefixed with "LOCAL\" to support
|
||||
|
// partial trust/sandboxed applications that are executed in an AppContainer
|
||||
|
// and cannot communicate with applications outside the sandbox container.
|
||||
|
if (string.IsNullOrEmpty(options.PipeName)) |
||||
|
{ |
||||
|
options.PipeName = $@"LOCAL\OpenIddict.Client.Windows\{
|
||||
|
Base64UrlEncoder.Encode(OpenIddictHelpers.ComputeSha256Hash( |
||||
|
Encoding.UTF8.GetBytes(_environment.ApplicationName))) |
||||
|
}";
|
||||
|
} |
||||
|
|
||||
|
// If no explicit pipe security policy was specified, grant the current user
|
||||
|
// full control over the created pipe and allow cross-process communication
|
||||
|
// between elevated and non-elevated processes.
|
||||
|
if (options.PipeSecurity is null) |
||||
|
{ |
||||
|
using var identity = WindowsIdentity.GetCurrent(); |
||||
|
|
||||
|
options.PipeSecurity = new PipeSecurity(); |
||||
|
options.PipeSecurity.SetOwner(identity.User!); |
||||
|
options.PipeSecurity.AddAccessRule(new PipeAccessRule(identity.User!, |
||||
|
PipeAccessRights.FullControl, AccessControlType.Allow)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes common constants used by the OpenIddict Windows host.
|
||||
|
/// </summary>
|
||||
|
public static class OpenIddictClientWindowsConstants |
||||
|
{ |
||||
|
public static class Tokens |
||||
|
{ |
||||
|
public const string AuthorizationCode = "authorization_code"; |
||||
|
public const string BackchannelAccessToken = "backchannel_access_token"; |
||||
|
public const string BackchannelIdentityToken = "backchannel_id_token"; |
||||
|
public const string FrontchannelAccessToken = "frontchannel_access_token"; |
||||
|
public const string FrontchannelIdentityToken = "frontchannel_id_token"; |
||||
|
public const string RefreshToken = "refresh_token"; |
||||
|
public const string StateToken = "state_token"; |
||||
|
public const string UserinfoToken = "userinfo_token"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,95 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using Microsoft.Extensions.DependencyInjection.Extensions; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using OpenIddict.Client; |
||||
|
using OpenIddict.Client.Windows; |
||||
|
|
||||
|
namespace Microsoft.Extensions.DependencyInjection; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes extensions allowing to register the OpenIddict client services.
|
||||
|
/// </summary>
|
||||
|
public static class OpenIddictClientWindowsExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Registers the OpenIddict client services for Windows in the DI container.
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsBuilder"/>.</returns>
|
||||
|
public static OpenIddictClientWindowsBuilder UseWindows(this OpenIddictClientBuilder builder) |
||||
|
{ |
||||
|
if (builder is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(builder)); |
||||
|
} |
||||
|
|
||||
|
// Note: the OpenIddict IHostedService implementation is deliberately registered as early as possible to
|
||||
|
// ensure protocol activations can be handled before another service can stop the initialization of the
|
||||
|
// application (e.g Dapplo.Microsoft.Extensions.Hosting.AppServices relies on an IHostedService to implement
|
||||
|
// single instantiation, which would prevent the OpenIddict service from handling the protocol activation
|
||||
|
// if the OpenIddict IHostedService implementation was not registered before the Dapplo IHostedService).
|
||||
|
if (!builder.Services.Any(static descriptor => descriptor.ServiceType == typeof(IHostedService) && |
||||
|
descriptor.ImplementationType == typeof(OpenIddictClientWindowsService))) |
||||
|
{ |
||||
|
builder.Services.Insert(0, ServiceDescriptor.Singleton<IHostedService, OpenIddictClientWindowsService>()); |
||||
|
} |
||||
|
|
||||
|
// Register the marshaller responsible for managing authentication operations.
|
||||
|
builder.Services.TryAddSingleton<OpenIddictClientWindowsMarshaller>(); |
||||
|
|
||||
|
// Register the built-in filters used by the default OpenIddict Windows client event handlers.
|
||||
|
builder.Services.TryAddSingleton<RequireAuthenticationNonce>(); |
||||
|
builder.Services.TryAddSingleton<RequireInteractiveSession>(); |
||||
|
builder.Services.TryAddSingleton<RequireWindowsActivation>(); |
||||
|
|
||||
|
// Register the built-in event handlers used by the OpenIddict Windows client components.
|
||||
|
// Note: the order used here is not important, as the actual order is set in the options.
|
||||
|
builder.Services.TryAdd(OpenIddictClientWindowsHandlers.DefaultHandlers.Select(descriptor => descriptor.ServiceDescriptor)); |
||||
|
|
||||
|
// Register the option initializer and the background service used by the OpenIddict Windows client integration services.
|
||||
|
// Note: TryAddEnumerable() is used here to ensure the initializers and the background service are only registered once.
|
||||
|
builder.Services.TryAddEnumerable(new[] |
||||
|
{ |
||||
|
ServiceDescriptor.Singleton<IHostedService, OpenIddictClientWindowsListener>(), |
||||
|
|
||||
|
ServiceDescriptor.Singleton<IConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWindowsConfiguration>(), |
||||
|
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictClientOptions>, OpenIddictClientWindowsConfiguration>(), |
||||
|
|
||||
|
ServiceDescriptor.Singleton<IPostConfigureOptions<OpenIddictClientWindowsOptions>, OpenIddictClientWindowsConfiguration>() |
||||
|
}); |
||||
|
|
||||
|
return new OpenIddictClientWindowsBuilder(builder.Services); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Registers the OpenIddict client services for Windows in the DI container.
|
||||
|
/// </summary>
|
||||
|
/// <param name="builder">The services builder used by OpenIddict to register new services.</param>
|
||||
|
/// <param name="configuration">The configuration delegate used to configure the client services.</param>
|
||||
|
/// <remarks>This extension can be safely called multiple times.</remarks>
|
||||
|
/// <returns>The <see cref="OpenIddictClientBuilder"/>.</returns>
|
||||
|
public static OpenIddictClientBuilder UseWindows( |
||||
|
this OpenIddictClientBuilder builder, Action<OpenIddictClientWindowsBuilder> configuration) |
||||
|
{ |
||||
|
if (builder is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(builder)); |
||||
|
} |
||||
|
|
||||
|
if (configuration is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(configuration)); |
||||
|
} |
||||
|
|
||||
|
configuration(builder.UseWindows()); |
||||
|
|
||||
|
return builder; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,68 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.ComponentModel; |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains a collection of event handler filters commonly used by the Windows handlers.
|
||||
|
/// </summary>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Advanced)] |
||||
|
public static class OpenIddictClientWindowsHandlerFilters |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents a filter that excludes the associated handlers
|
||||
|
/// if no explicit nonce was attached to the authentication context.
|
||||
|
/// </summary>
|
||||
|
public sealed class RequireAuthenticationNonce : IOpenIddictClientHandlerFilter<ProcessAuthenticationContext> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask<bool> IsActiveAsync(ProcessAuthenticationContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
return new(!string.IsNullOrEmpty(context.Nonce)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents a filter that excludes the associated handlers if no interactive user session was detected.
|
||||
|
/// </summary>
|
||||
|
public sealed class RequireInteractiveSession : IOpenIddictClientHandlerFilter<BaseContext> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask<bool> IsActiveAsync(BaseContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
return new(Environment.UserInteractive); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Represents a filter that excludes the associated handlers if no Windows activation was found.
|
||||
|
/// </summary>
|
||||
|
public sealed class RequireWindowsActivation : IOpenIddictClientHandlerFilter<BaseContext> |
||||
|
{ |
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask<bool> IsActiveAsync(BaseContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
return new(context.Transaction.GetWindowsActivation() is not null); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.Collections.Immutable; |
||||
|
using System.Diagnostics; |
||||
|
using Microsoft.Extensions.Primitives; |
||||
|
using OpenIddict.Extensions; |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
public static partial class OpenIddictClientWindowsHandlers |
||||
|
{ |
||||
|
public static class Authentication |
||||
|
{ |
||||
|
public static ImmutableArray<OpenIddictClientHandlerDescriptor> DefaultHandlers { get; } = ImmutableArray.Create( |
||||
|
/* |
||||
|
* Authorization request processing: |
||||
|
*/ |
||||
|
LaunchSystemBrowser.Descriptor, |
||||
|
|
||||
|
/* |
||||
|
* Redirection request extraction: |
||||
|
*/ |
||||
|
ExtractRequestUriParameters<ExtractRedirectionRequestContext>.Descriptor, |
||||
|
|
||||
|
/* |
||||
|
* Redirection request handling: |
||||
|
*/ |
||||
|
ProcessResponse<HandleRedirectionRequestContext>.Descriptor, |
||||
|
|
||||
|
/* |
||||
|
* Redirection response handling: |
||||
|
*/ |
||||
|
ProcessResponse<ApplyRedirectionResponseContext>.Descriptor); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic responsible for initiating authorization requests using the system browser.
|
||||
|
/// Note: this handler is not used when the OpenID Connect request is not initially handled by Windows.
|
||||
|
/// </summary>
|
||||
|
public class LaunchSystemBrowser : IOpenIddictClientHandler<ApplyAuthorizationRequestContext> |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the default descriptor definition assigned to this handler.
|
||||
|
/// </summary>
|
||||
|
public static OpenIddictClientHandlerDescriptor Descriptor { get; } |
||||
|
= OpenIddictClientHandlerDescriptor.CreateBuilder<ApplyAuthorizationRequestContext>() |
||||
|
.AddFilter<RequireInteractiveSession>() |
||||
|
.UseSingletonHandler<LaunchSystemBrowser>() |
||||
|
.SetOrder(50_000) |
||||
|
.SetType(OpenIddictClientHandlerType.BuiltIn) |
||||
|
.Build(); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public ValueTask HandleAsync(ApplyAuthorizationRequestContext context) |
||||
|
{ |
||||
|
if (context is null) |
||||
|
{ |
||||
|
throw new ArgumentNullException(nameof(context)); |
||||
|
} |
||||
|
|
||||
|
Debug.Assert(context.Transaction.Request is not null, SR.GetResourceString(SR.ID4008)); |
||||
|
|
||||
|
var uri = OpenIddictHelpers.AddQueryStringParameters( |
||||
|
new Uri(context.AuthorizationEndpoint, UriKind.Absolute), |
||||
|
context.Transaction.Request.GetParameters().ToDictionary( |
||||
|
parameter => parameter.Key, |
||||
|
parameter => new StringValues((string?[]?) parameter.Value))); |
||||
|
|
||||
|
Process.Start(new ProcessStartInfo |
||||
|
{ |
||||
|
FileName = uri.AbsoluteUri, |
||||
|
UseShellExecute = true |
||||
|
}); |
||||
|
|
||||
|
return default; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,21 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Exposes companion extensions for the OpenIddict/Windows integration.
|
||||
|
/// </summary>
|
||||
|
public static class OpenIddictClientWindowsHelpers |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="OpenIddictClientWindowsActivation"/> associated with the current context.
|
||||
|
/// </summary>
|
||||
|
/// <param name="transaction">The transaction instance.</param>
|
||||
|
/// <returns>The <see cref="OpenIddictClientWindowsActivation"/> instance or <see langword="null"/> if it couldn't be found.</returns>
|
||||
|
public static OpenIddictClientWindowsActivation? GetWindowsActivation(this OpenIddictClientTransaction transaction) |
||||
|
=> transaction.GetProperty<OpenIddictClientWindowsActivation>(typeof(OpenIddictClientWindowsActivation).FullName!); |
||||
|
} |
||||
@ -0,0 +1,161 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.Collections.Immutable; |
||||
|
using System.ComponentModel; |
||||
|
using System.IO.Pipes; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
|
||||
|
#if !SUPPORTS_HOST_APPLICATION_LIFETIME
|
||||
|
using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime; |
||||
|
#endif
|
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic necessary to handle URI protocol activations that
|
||||
|
/// are redirected by other instances using inter-process communication.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Note: initial URI protocol activations are handled by <see cref="OpenIddictClientWindowsService"/>.
|
||||
|
/// </remarks>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public sealed class OpenIddictClientWindowsListener : BackgroundService |
||||
|
{ |
||||
|
private readonly ILogger<OpenIddictClientWindowsListener> _logger; |
||||
|
private readonly IOptionsMonitor<OpenIddictClientWindowsOptions> _options; |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
public OpenIddictClientWindowsListener( |
||||
|
ILogger<OpenIddictClientWindowsListener> logger, |
||||
|
IOptionsMonitor<OpenIddictClientWindowsOptions> options, |
||||
|
IServiceProvider provider) |
||||
|
{ |
||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger)); |
||||
|
_options = options ?? throw new ArgumentNullException(nameof(options)); |
||||
|
_provider = provider ?? throw new ArgumentNullException(nameof(provider)); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken) |
||||
|
{ |
||||
|
while (!stoppingToken.IsCancellationRequested) |
||||
|
{ |
||||
|
using var buffer = new MemoryStream(); |
||||
|
using var reader = new BinaryReader(buffer); |
||||
|
|
||||
|
#if SUPPORTS_NAMED_PIPE_CONSTRUCTOR_WITH_ACL
|
||||
|
using var stream = new NamedPipeServerStream( |
||||
|
#elif SUPPORTS_NAMED_PIPE_STATIC_FACTORY_WITH_ACL
|
||||
|
using var stream = NamedPipeServerStreamAcl.Create( |
||||
|
#else
|
||||
|
using var stream = NamedPipeServerStreamConstructors.New( |
||||
|
#endif
|
||||
|
pipeName : $@"{_options.CurrentValue.PipeName}\{_options.CurrentValue.InstanceIdentifier}", |
||||
|
direction : PipeDirection.In, |
||||
|
maxNumberOfServerInstances: 1, |
||||
|
transmissionMode : PipeTransmissionMode.Message, |
||||
|
options : PipeOptions.Asynchronous, |
||||
|
inBufferSize : 0, |
||||
|
outBufferSize : 0, |
||||
|
pipeSecurity : _options.CurrentValue.PipeSecurity, |
||||
|
inheritability : HandleInheritability.None, |
||||
|
additionalAccessRights : 0); |
||||
|
|
||||
|
// Wait for a writer to connect to the named pipe,
|
||||
|
// copy its content to the memory stream and rewind it.
|
||||
|
await stream.WaitForConnectionAsync(stoppingToken); |
||||
|
await stream.CopyToAsync(buffer, bufferSize: 81_920, stoppingToken); |
||||
|
buffer.Seek(0L, SeekOrigin.Begin); |
||||
|
|
||||
|
var scope = _provider.CreateScope(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); |
||||
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); |
||||
|
|
||||
|
switch (reader.ReadInt32()) |
||||
|
{ |
||||
|
case 0x01: // Protocol activations
|
||||
|
{ |
||||
|
// Ensure the binary serialization format is supported.
|
||||
|
var version = reader.ReadInt32(); |
||||
|
if (version is not 0x01) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var length = reader.ReadInt32(); |
||||
|
if (length is not > 0) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var builder = ImmutableArray.CreateBuilder<string>(length); |
||||
|
for (var index = 0; index < length; index++) |
||||
|
{ |
||||
|
builder.Add(reader.ReadString()); |
||||
|
} |
||||
|
|
||||
|
// Create a client transaction and store the command line arguments so they can be
|
||||
|
// retrieved by the Windows-specific client event handlers that need to access them.
|
||||
|
var transaction = await factory.CreateTransactionAsync(); |
||||
|
transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, |
||||
|
new OpenIddictClientWindowsActivation |
||||
|
{ |
||||
|
CommandLineArguments = builder.MoveToImmutable(), |
||||
|
IsActivationRedirected = true |
||||
|
}); |
||||
|
|
||||
|
var context = new ProcessRequestContext(transaction) |
||||
|
{ |
||||
|
CancellationToken = stoppingToken |
||||
|
}; |
||||
|
|
||||
|
await dispatcher.DispatchAsync(context); |
||||
|
|
||||
|
if (context.IsRejected) |
||||
|
{ |
||||
|
await dispatcher.DispatchAsync(new ProcessErrorContext(transaction) |
||||
|
{ |
||||
|
CancellationToken = stoppingToken, |
||||
|
Error = context.Error ?? Errors.InvalidRequest, |
||||
|
ErrorDescription = context.ErrorDescription, |
||||
|
ErrorUri = context.ErrorUri, |
||||
|
Response = new OpenIddictResponse() |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Swallow all exceptions to ensure the service doesn't exit when encountering an exception.
|
||||
|
catch (Exception exception) |
||||
|
{ |
||||
|
_logger.LogWarning(exception, SR.GetResourceString(SR.ID6213)); |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
if (scope is IAsyncDisposable disposable) |
||||
|
{ |
||||
|
await disposable.DisposeAsync(); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
scope.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,130 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the APIs needed to coordinate authentication operations that happen in a different context.
|
||||
|
/// </summary>
|
||||
|
public sealed class OpenIddictClientWindowsMarshaller |
||||
|
{ |
||||
|
private readonly ConcurrentDictionary<string, Lazy<( |
||||
|
string RequestForgeryProtection, |
||||
|
SemaphoreSlim Semaphore, |
||||
|
TaskCompletionSource<ProcessAuthenticationContext> TaskCompletionSource)>> _operations = new(); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Determines whether the authentication demand corresponding to the specified nonce is tracked.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <returns><see langword="true"/> if the operation is tracked, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool IsTracked(string nonce) => _operations.ContainsKey(nonce); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to add the specified authentication demand to the list of tracked operations.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <param name="protection">The request forgery protection associated with the specified authentication demand.</param>
|
||||
|
/// <returns><see langword="true"/> if the operation could be added, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryAdd(string nonce, string protection) |
||||
|
=> _operations.TryAdd(nonce, new(() => (protection, new SemaphoreSlim(initialCount: 1, maxCount: 1), new()))); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to acquire a lock on the authentication demand corresponding to the specified nonce.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <returns><see langword="true"/> if the lock could be taken, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryAcquireLock(string nonce) |
||||
|
=> _operations.TryGetValue(nonce, out var operation) && operation.Value.Semaphore.Wait(TimeSpan.Zero); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to resolve the authentication context associated with the specified nonce.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <param name="context">The authentication context associated with the tracked operation.</param>
|
||||
|
/// <returns><see langword="true"/> if the context could be resolved, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryGetResult(string nonce, [NotNullWhen(true)] out ProcessAuthenticationContext? context) |
||||
|
{ |
||||
|
if (!_operations.TryGetValue(nonce, out var operation)) |
||||
|
{ |
||||
|
context = null; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
if (!operation.IsValueCreated || !operation.Value.TaskCompletionSource.Task.IsCompleted) |
||||
|
{ |
||||
|
context = null; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
context = operation.Value.TaskCompletionSource.Task.Result; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to wait for the authentication demand associated with the specified nonce to complete.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> that can be used to abort the operation.</param>
|
||||
|
/// <returns><see langword="true"/> if the authentication demand is tracked, <see langword="false"/> otherwise.</returns>
|
||||
|
/// <exception cref="OperationCanceledException">The operation was canceled by the user.</exception>
|
||||
|
internal async Task<bool> TryWaitForCompletionAsync(string nonce, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
if (!_operations.TryGetValue(nonce, out var operation)) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
var source = new TaskCompletionSource<bool>(TaskCreationOptions.None); |
||||
|
using (cancellationToken.Register(static state => ((TaskCompletionSource<bool>) state!).SetResult(true), source)) |
||||
|
{ |
||||
|
if (await Task.WhenAny(operation.Value.TaskCompletionSource.Task, source.Task) == source.Task) |
||||
|
{ |
||||
|
throw new OperationCanceledException(cancellationToken); |
||||
|
} |
||||
|
|
||||
|
await operation.Value.TaskCompletionSource.Task; |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to resolve the request forgery protection associated with the specified authentication demand.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <param name="protection">The request forgery protection associated with the specified authentication demand.</param>
|
||||
|
/// <returns><see langword="true"/> if the operation could be validated, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryGetRequestForgeryProtection(string nonce, [NotNullWhen(true)] out string? protection) |
||||
|
{ |
||||
|
if (_operations.TryGetValue(nonce, out var operation)) |
||||
|
{ |
||||
|
protection = operation.Value.RequestForgeryProtection; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
protection = null; |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to complete the specified authentication demand.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <param name="context">The authentication context that will be returned to the caller.</param>
|
||||
|
/// <returns><see langword="true"/> if the operation could be completed, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryComplete(string nonce, ProcessAuthenticationContext context) |
||||
|
=> _operations.TryGetValue(nonce, out var operation) && operation.Value.TaskCompletionSource.TrySetResult(context); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Tries to remove the specified authentication operation from the list of tracked operations.
|
||||
|
/// </summary>
|
||||
|
/// <param name="nonce">The nonce, used as a unique identifier.</param>
|
||||
|
/// <returns><see langword="true"/> if the operation could be removed, <see langword="false"/> otherwise.</returns>
|
||||
|
internal bool TryRemove(string nonce) => _operations.TryRemove(nonce, out _); |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.IO.Pipes; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
|
||||
|
#if !SUPPORTS_HOST_ENVIRONMENT
|
||||
|
using IHostEnvironment = Microsoft.Extensions.Hosting.IHostingEnvironment; |
||||
|
#endif
|
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides various settings needed to configure the OpenIddict Windows client integration.
|
||||
|
/// </summary>
|
||||
|
public sealed class OpenIddictClientWindowsOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the timeout after which authentication demands
|
||||
|
/// that are not completed are automatically aborted by OpenIddict.
|
||||
|
/// </summary>
|
||||
|
public TimeSpan AuthenticationTimeout { get; set; } = TimeSpan.FromMinutes(10); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the identifier used to represent the current application
|
||||
|
/// instance and redirect protocol activations when necessary.
|
||||
|
/// </summary>
|
||||
|
public string? InstanceIdentifier { get; set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the base name of the pipe created by OpenIddict to enable
|
||||
|
/// inter-process communication and handle protocol activation redirections.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// If no value is explicitly set, a default name is automatically computed.
|
||||
|
/// </remarks>
|
||||
|
public string PipeName { get; set; } = default!; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the security policy applied to the pipe created by OpenIddict
|
||||
|
/// to enable inter-process communication and handle protocol activation redirections.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// If no value is explicitly set, a default policy is automatically created.
|
||||
|
/// </remarks>
|
||||
|
public PipeSecurity PipeSecurity { get; set; } = default!; |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
/* |
||||
|
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
|
||||
|
* See https://github.com/openiddict/openiddict-core for more information concerning
|
||||
|
* the license and the contributors participating to this project. |
||||
|
*/ |
||||
|
|
||||
|
using System.Collections.Immutable; |
||||
|
using System.ComponentModel; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
|
||||
|
#if !SUPPORTS_HOST_APPLICATION_LIFETIME
|
||||
|
using IHostApplicationLifetime = Microsoft.Extensions.Hosting.IApplicationLifetime; |
||||
|
#endif
|
||||
|
|
||||
|
namespace OpenIddict.Client.Windows; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Contains the logic necessary to handle initial URI protocol activations.
|
||||
|
/// </summary>
|
||||
|
/// <remarks>
|
||||
|
/// Note: redirected URI protocol activations are handled by <see cref="OpenIddictClientWindowsListener"/>.
|
||||
|
/// </remarks>
|
||||
|
[EditorBrowsable(EditorBrowsableState.Never)] |
||||
|
public sealed class OpenIddictClientWindowsService : IHostedService |
||||
|
{ |
||||
|
private readonly IServiceProvider _provider; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of the <see cref="OpenIddictClientWindowsService"/> class.
|
||||
|
/// </summary>
|
||||
|
/// <param name="provider">The service provider.</param>
|
||||
|
/// <exception cref="ArgumentNullException"><paramref name="provider"/> is <see langword="null"/>.</exception>
|
||||
|
public OpenIddictClientWindowsService(IServiceProvider provider) |
||||
|
=> _provider = provider ?? throw new ArgumentNullException(nameof(provider)); |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public async Task StartAsync(CancellationToken cancellationToken) |
||||
|
{ |
||||
|
cancellationToken.ThrowIfCancellationRequested(); |
||||
|
|
||||
|
// Note: initial URI protocol activation handling is implemented as a regular IHostedService
|
||||
|
// rather than as a BackgroundService to allow blocking the initialization of the host until
|
||||
|
// the activation is fully processed by the OpenIddict pipeline. By doing that, the UI thread
|
||||
|
// is not started until redirection requests (like authorization responses) are fully processed,
|
||||
|
// which allows handling these requests transparently and helps avoid the "flashing window effect":
|
||||
|
// once a request has been handled by the OpenIddict pipeline, a dedicated handler is responsible
|
||||
|
// for stopping the application gracefully using the IHostApplicationLifetime.StopApplication() API.
|
||||
|
|
||||
|
var scope = _provider.CreateScope(); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
var dispatcher = scope.ServiceProvider.GetRequiredService<IOpenIddictClientDispatcher>(); |
||||
|
var factory = scope.ServiceProvider.GetRequiredService<IOpenIddictClientFactory>(); |
||||
|
|
||||
|
// Create a client transaction and store the command line arguments so they can be
|
||||
|
// retrieved by the Windows-specific client event handlers that need to access them.
|
||||
|
var transaction = await factory.CreateTransactionAsync(); |
||||
|
transaction.SetProperty(typeof(OpenIddictClientWindowsActivation).FullName!, |
||||
|
new OpenIddictClientWindowsActivation |
||||
|
{ |
||||
|
CommandLineArguments = ImmutableArray.CreateRange(Environment.GetCommandLineArgs()), |
||||
|
IsActivationRedirected = false |
||||
|
}); |
||||
|
|
||||
|
var context = new ProcessRequestContext(transaction) |
||||
|
{ |
||||
|
CancellationToken = cancellationToken |
||||
|
}; |
||||
|
|
||||
|
await dispatcher.DispatchAsync(context); |
||||
|
|
||||
|
if (context.IsRejected) |
||||
|
{ |
||||
|
await dispatcher.DispatchAsync(new ProcessErrorContext(transaction) |
||||
|
{ |
||||
|
CancellationToken = cancellationToken, |
||||
|
Error = context.Error ?? Errors.InvalidRequest, |
||||
|
ErrorDescription = context.ErrorDescription, |
||||
|
ErrorUri = context.ErrorUri, |
||||
|
Response = new OpenIddictResponse() |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
finally |
||||
|
{ |
||||
|
if (scope is IAsyncDisposable disposable) |
||||
|
{ |
||||
|
await disposable.DisposeAsync(); |
||||
|
} |
||||
|
|
||||
|
else |
||||
|
{ |
||||
|
scope.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
||||
|
} |
||||
Loading…
Reference in new issue