Skip to content

Commit

Permalink
Merge pull request #13 from FiniteReality/feature/user-info
Browse files Browse the repository at this point in the history
Expose APIs for specifying user information in a platform agnostic way
  • Loading branch information
FiniteReality authored May 20, 2021
2 parents 20f6308 + 3151a23 commit 773bb4a
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 6 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<PackageOutputPath Condition="'$(PlatformName)' == 'AnyCPU'">$(BaseArtifactsPath)pkg/$(Configuration)</PackageOutputPath>
<PackageOutputPath Condition="'$(PlatformName)' != 'AnyCPU'">$(BaseArtifactsPath)pkg/$(Configuration)</PackageOutputPath>
<Product>Finite.Commands</Product>
<VersionPrefix>0.3.0</VersionPrefix>
<VersionPrefix>0.3.1</VersionPrefix>
<VersionSuffix>alpha</VersionSuffix>
<VersionSuffix Condition="'$(PullRequestNumber)' != ''">pr$(PullRequestNumber)</VersionSuffix>
</PropertyGroup>
Expand Down
68 changes: 68 additions & 0 deletions samples/Console/Authentication/PlatformUserMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System;
using System.Linq;
using System.Security.Claims;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Finite.Commands;
using Microsoft.Extensions.Logging;

namespace ConsoleCommands.Authentication
{
internal class PlatformUserMiddleware : ICommandMiddleware
{
private static readonly Random Rng = new Random();
private readonly ILogger _logger;

public PlatformUserMiddleware(ILogger<PlatformUserMiddleware> logger)
{
_logger = logger;
}

public ValueTask<ICommandResult> ExecuteAsync(CommandMiddleware next,
CommandContext context, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

if (OperatingSystem.IsWindows())
{
var identity = WindowsIdentity.GetCurrent();

context.User.AddIdentity(identity);
}
else
{
context.User.AddIdentity(
new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Name, Environment.UserName),
}
));
}

if (Rng.NextDouble() > 0.5)
{
_logger.LogDebug("Adding cool role");
context.User.AddIdentity(
new ClaimsIdentity(
new[]
{
new Claim(ClaimTypes.Role, "Cool")
}
));
}

var nameClaim = context.User
.FindFirst(x => x.Type == ClaimTypes.Name);

if (nameClaim != null)
_logger.LogInformation("Loaded user identity as {name}",
nameClaim.Value);
else
_logger.LogWarning("Unable to load user identity");

return next();
}
}
}
7 changes: 5 additions & 2 deletions samples/Console/Program.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using ConsoleCommands.Authentication;
using Finite.Commands;
using Finite.Commands.Parsing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

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

_ = services.AddSingleton<PlatformUserMiddleware>();

_ = services.AddCommands()
.AddPositionalCommandParser()
.AddAttributedCommands(x => x.Assemblies.Add(
typeof(Program).Assembly.Location))
.Use(TestMiddlewareAsync);
.Use(TestMiddlewareAsync)
.Use<PlatformUserMiddleware>();

_ = services.AddHostedService<LineReaderService>();
}
Expand Down
9 changes: 9 additions & 0 deletions samples/Console/TestCommands/HelloWorldCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,15 @@ public ValueTask<ICommandResult> HelloWorldCommand(
coolParameter,
coolerParameter);

if (Context.User.IsInRole("Cool"))
{
_logger.LogInformation("The user is very cool!");
}
else
{
_logger.LogInformation("The user is not cool.");
}

return new ValueTask<ICommandResult>(new NoContentCommandResult());
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/Abstractions/CommandContext.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Security.Claims;

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

/// <summary>
/// Gets or sets the user for this cmomand.
/// </summary>
public abstract ClaimsPrincipal User { get; set; }

/// <summary>
/// Gets or sets the command to execute.
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions src/Core/CommandsBuilderMiddlewareExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Finite.Commands
{
/// <summary>
/// Extension methods for adding middleware to
/// an <see cref="ICommandsBuilder"/>.
/// </summary>
public static class CommandsBuilderMiddlewareExtensions
{
/// <summary>
/// Adds a middleware object to the command pipeline.
/// </summary>
/// <param name="builder">
/// The commands builder to add middleware to.
/// </param>
/// <typeparam name="TMiddleware">
/// The type of middleware to add to the command pipeline.
/// </typeparam>
/// <returns>
/// The builder.
/// </returns>
public static ICommandsBuilder Use<TMiddleware>(
this ICommandsBuilder builder)
where TMiddleware : ICommandMiddleware
{

return builder.Use(
static (n, c, ct) => ExecuteMiddleware(n, c, ct));

static ValueTask<ICommandResult> ExecuteMiddleware(
CommandMiddleware next, CommandContext context,
CancellationToken cancellationToken)
{
var middleware = context.Services
.GetRequiredService<TMiddleware>();

return middleware.ExecuteAsync(next, context, cancellationToken);
}
}
}
}
31 changes: 31 additions & 0 deletions src/Core/ICommandMiddleware.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Threading;
using System.Threading.Tasks;

namespace Finite.Commands
{
/// <summary>
/// Defines an interface which can be used to define middleware using a
/// type.
/// </summary>
public interface ICommandMiddleware
{
/// <summary>
/// Executes the middleware.
/// </summary>
/// <param name="next">
/// A callback used to transfer control in the middleware pipeline.
/// </param>
/// <param name="context">
/// The command context to execute.
/// </param>
/// <param name="cancellationToken">
/// A cancellation token, indicating cancellation of processing.
/// </param>
/// <returns>
/// A task that represents the completion of command processing.
/// </returns>
public ValueTask<ICommandResult> ExecuteAsync(
CommandMiddleware next, CommandContext context,
CancellationToken cancellationToken);
}
}
4 changes: 2 additions & 2 deletions src/Core/ICommandsBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
namespace Finite.Commands
{
/// <summary>
/// An interface for configuring commands services.
/// Defines an interface for configuring commands services.
/// </summary>
public interface ICommandsBuilder
{
Expand All @@ -17,7 +17,7 @@ public interface ICommandsBuilder
IServiceCollection Services { get; }

/// <summary>
/// Adds the middleware to the command pipeline.
/// Adds middleware to the command pipeline.
/// </summary>
/// <param name="middleware">
/// The middleware.
Expand Down
5 changes: 5 additions & 0 deletions src/Core/Internal/DefaultCommandContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Security.Claims;
using Microsoft.Extensions.DependencyInjection;

namespace Finite.Commands
Expand All @@ -12,6 +13,10 @@ internal sealed class DefaultCommandContext : CommandContext
= new Dictionary<object, object?>();
public override IDictionary<string, object?> Parameters { get; set; }
= new Dictionary<string, object?>();

public override ClaimsPrincipal User { get; set; }
= new ClaimsPrincipal(new ClaimsIdentity());

public override ICommand Command { get; set; } = null!;

public override IServiceProvider Services
Expand Down

0 comments on commit 773bb4a

Please sign in to comment.