Skip to content

Commit 773bb4a

Browse files
Merge pull request #13 from FiniteReality/feature/user-info
Expose APIs for specifying user information in a platform agnostic way
2 parents 20f6308 + 3151a23 commit 773bb4a

File tree

9 files changed

+172
-6
lines changed

9 files changed

+172
-6
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
<PackageOutputPath Condition="'$(PlatformName)' == 'AnyCPU'">$(BaseArtifactsPath)pkg/$(Configuration)</PackageOutputPath>
4949
<PackageOutputPath Condition="'$(PlatformName)' != 'AnyCPU'">$(BaseArtifactsPath)pkg/$(Configuration)</PackageOutputPath>
5050
<Product>Finite.Commands</Product>
51-
<VersionPrefix>0.3.0</VersionPrefix>
51+
<VersionPrefix>0.3.1</VersionPrefix>
5252
<VersionSuffix>alpha</VersionSuffix>
5353
<VersionSuffix Condition="'$(PullRequestNumber)' != ''">pr$(PullRequestNumber)</VersionSuffix>
5454
</PropertyGroup>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System;
2+
using System.Linq;
3+
using System.Security.Claims;
4+
using System.Security.Principal;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Finite.Commands;
8+
using Microsoft.Extensions.Logging;
9+
10+
namespace ConsoleCommands.Authentication
11+
{
12+
internal class PlatformUserMiddleware : ICommandMiddleware
13+
{
14+
private static readonly Random Rng = new Random();
15+
private readonly ILogger _logger;
16+
17+
public PlatformUserMiddleware(ILogger<PlatformUserMiddleware> logger)
18+
{
19+
_logger = logger;
20+
}
21+
22+
public ValueTask<ICommandResult> ExecuteAsync(CommandMiddleware next,
23+
CommandContext context, CancellationToken cancellationToken)
24+
{
25+
cancellationToken.ThrowIfCancellationRequested();
26+
27+
if (OperatingSystem.IsWindows())
28+
{
29+
var identity = WindowsIdentity.GetCurrent();
30+
31+
context.User.AddIdentity(identity);
32+
}
33+
else
34+
{
35+
context.User.AddIdentity(
36+
new ClaimsIdentity(
37+
new[]
38+
{
39+
new Claim(ClaimTypes.Name, Environment.UserName),
40+
}
41+
));
42+
}
43+
44+
if (Rng.NextDouble() > 0.5)
45+
{
46+
_logger.LogDebug("Adding cool role");
47+
context.User.AddIdentity(
48+
new ClaimsIdentity(
49+
new[]
50+
{
51+
new Claim(ClaimTypes.Role, "Cool")
52+
}
53+
));
54+
}
55+
56+
var nameClaim = context.User
57+
.FindFirst(x => x.Type == ClaimTypes.Name);
58+
59+
if (nameClaim != null)
60+
_logger.LogInformation("Loaded user identity as {name}",
61+
nameClaim.Value);
62+
else
63+
_logger.LogWarning("Unable to load user identity");
64+
65+
return next();
66+
}
67+
}
68+
}

samples/Console/Program.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using System.Threading;
33
using System.Threading.Tasks;
4+
using ConsoleCommands.Authentication;
45
using Finite.Commands;
56
using Finite.Commands.Parsing;
67
using Microsoft.Extensions.DependencyInjection;
7-
using Microsoft.Extensions.DependencyInjection.Extensions;
88
using Microsoft.Extensions.Hosting;
99
using Microsoft.Extensions.Logging;
1010

@@ -25,11 +25,14 @@ private static void ConfigureServices(HostBuilderContext context,
2525
// (PR was not merged for .NET 5.0)
2626
_ = services.Configure<HostOptions>(x => x.ShutdownTimeout = TimeSpan.Zero);
2727

28+
_ = services.AddSingleton<PlatformUserMiddleware>();
29+
2830
_ = services.AddCommands()
2931
.AddPositionalCommandParser()
3032
.AddAttributedCommands(x => x.Assemblies.Add(
3133
typeof(Program).Assembly.Location))
32-
.Use(TestMiddlewareAsync);
34+
.Use(TestMiddlewareAsync)
35+
.Use<PlatformUserMiddleware>();
3336

3437
_ = services.AddHostedService<LineReaderService>();
3538
}

samples/Console/TestCommands/HelloWorldCommand.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@ public ValueTask<ICommandResult> HelloWorldCommand(
3131
coolParameter,
3232
coolerParameter);
3333

34+
if (Context.User.IsInRole("Cool"))
35+
{
36+
_logger.LogInformation("The user is very cool!");
37+
}
38+
else
39+
{
40+
_logger.LogInformation("The user is not cool.");
41+
}
42+
3443
return new ValueTask<ICommandResult>(new NoContentCommandResult());
3544
}
3645
}

src/Abstractions/CommandContext.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Threading;
3+
using System.Security.Claims;
44

55
namespace Finite.Commands
66
{
@@ -26,6 +26,11 @@ public abstract class CommandContext
2626
/// </summary>
2727
public abstract IDictionary<string, object?> Parameters { get; set; }
2828

29+
/// <summary>
30+
/// Gets or sets the user for this cmomand.
31+
/// </summary>
32+
public abstract ClaimsPrincipal User { get; set; }
33+
2934
/// <summary>
3035
/// Gets or sets the command to execute.
3136
/// </summary>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
using Microsoft.Extensions.DependencyInjection;
4+
using Microsoft.Extensions.DependencyInjection.Extensions;
5+
6+
namespace Finite.Commands
7+
{
8+
/// <summary>
9+
/// Extension methods for adding middleware to
10+
/// an <see cref="ICommandsBuilder"/>.
11+
/// </summary>
12+
public static class CommandsBuilderMiddlewareExtensions
13+
{
14+
/// <summary>
15+
/// Adds a middleware object to the command pipeline.
16+
/// </summary>
17+
/// <param name="builder">
18+
/// The commands builder to add middleware to.
19+
/// </param>
20+
/// <typeparam name="TMiddleware">
21+
/// The type of middleware to add to the command pipeline.
22+
/// </typeparam>
23+
/// <returns>
24+
/// The builder.
25+
/// </returns>
26+
public static ICommandsBuilder Use<TMiddleware>(
27+
this ICommandsBuilder builder)
28+
where TMiddleware : ICommandMiddleware
29+
{
30+
31+
return builder.Use(
32+
static (n, c, ct) => ExecuteMiddleware(n, c, ct));
33+
34+
static ValueTask<ICommandResult> ExecuteMiddleware(
35+
CommandMiddleware next, CommandContext context,
36+
CancellationToken cancellationToken)
37+
{
38+
var middleware = context.Services
39+
.GetRequiredService<TMiddleware>();
40+
41+
return middleware.ExecuteAsync(next, context, cancellationToken);
42+
}
43+
}
44+
}
45+
}

src/Core/ICommandMiddleware.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System.Threading;
2+
using System.Threading.Tasks;
3+
4+
namespace Finite.Commands
5+
{
6+
/// <summary>
7+
/// Defines an interface which can be used to define middleware using a
8+
/// type.
9+
/// </summary>
10+
public interface ICommandMiddleware
11+
{
12+
/// <summary>
13+
/// Executes the middleware.
14+
/// </summary>
15+
/// <param name="next">
16+
/// A callback used to transfer control in the middleware pipeline.
17+
/// </param>
18+
/// <param name="context">
19+
/// The command context to execute.
20+
/// </param>
21+
/// <param name="cancellationToken">
22+
/// A cancellation token, indicating cancellation of processing.
23+
/// </param>
24+
/// <returns>
25+
/// A task that represents the completion of command processing.
26+
/// </returns>
27+
public ValueTask<ICommandResult> ExecuteAsync(
28+
CommandMiddleware next, CommandContext context,
29+
CancellationToken cancellationToken);
30+
}
31+
}

src/Core/ICommandsBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace Finite.Commands
77
{
88
/// <summary>
9-
/// An interface for configuring commands services.
9+
/// Defines an interface for configuring commands services.
1010
/// </summary>
1111
public interface ICommandsBuilder
1212
{
@@ -17,7 +17,7 @@ public interface ICommandsBuilder
1717
IServiceCollection Services { get; }
1818

1919
/// <summary>
20-
/// Adds the middleware to the command pipeline.
20+
/// Adds middleware to the command pipeline.
2121
/// </summary>
2222
/// <param name="middleware">
2323
/// The middleware.

src/Core/Internal/DefaultCommandContext.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Security.Claims;
34
using Microsoft.Extensions.DependencyInjection;
45

56
namespace Finite.Commands
@@ -12,6 +13,10 @@ internal sealed class DefaultCommandContext : CommandContext
1213
= new Dictionary<object, object?>();
1314
public override IDictionary<string, object?> Parameters { get; set; }
1415
= new Dictionary<string, object?>();
16+
17+
public override ClaimsPrincipal User { get; set; }
18+
= new ClaimsPrincipal(new ClaimsIdentity());
19+
1520
public override ICommand Command { get; set; } = null!;
1621

1722
public override IServiceProvider Services

0 commit comments

Comments
 (0)