feat: add permission system to User
This commit is contained in:
parent
9a447db891
commit
3917dda658
@ -1,6 +1,5 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OliverBooth.Data.Blog.Configuration;
|
||||
using OliverBooth.Data.Web;
|
||||
|
||||
namespace OliverBooth.Data.Blog;
|
||||
|
||||
|
37
OliverBooth/Data/Permission.cs
Normal file
37
OliverBooth/Data/Permission.cs
Normal file
@ -0,0 +1,37 @@
|
||||
namespace OliverBooth.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a permission.
|
||||
/// </summary>
|
||||
public struct Permission
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a permission that grants all scopes.
|
||||
/// </summary>
|
||||
public static readonly Permission Administrator = new("*");
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="Permission" /> struct.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the permission.</param>
|
||||
/// <param name="isAllowed">
|
||||
/// <see langword="true" /> if the permission is allowed; otherwise, <see langword="false" />.
|
||||
/// </param>
|
||||
public Permission(string name, bool isAllowed = true)
|
||||
{
|
||||
Name = name;
|
||||
IsAllowed = isAllowed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of this permission.
|
||||
/// </summary>
|
||||
/// <value>The name.</value>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this permission is allowed.
|
||||
/// </summary>
|
||||
/// <value><see langword="true" /> if the permission is allowed; otherwise, <see langword="false" />.</value>
|
||||
public bool IsAllowed { get; }
|
||||
}
|
42
OliverBooth/Data/PermissionListConverter.cs
Normal file
42
OliverBooth/Data/PermissionListConverter.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
namespace OliverBooth.Data;
|
||||
|
||||
internal sealed class PermissionListConverter : ValueConverter<IReadOnlyList<Permission>, string>
|
||||
{
|
||||
public PermissionListConverter() : this(';')
|
||||
{
|
||||
}
|
||||
|
||||
public PermissionListConverter(char separator) :
|
||||
base(v => ToProvider(v, separator),
|
||||
s => FromProvider(s, separator))
|
||||
{
|
||||
}
|
||||
|
||||
private static IReadOnlyList<Permission> FromProvider(string source, char separator = ';')
|
||||
{
|
||||
var permissions = new List<Permission>();
|
||||
|
||||
foreach (string permission in source.Split(separator))
|
||||
{
|
||||
string name = permission;
|
||||
var allowed = true;
|
||||
|
||||
if (name.Length > 1 && name[0] == '-')
|
||||
{
|
||||
name = name[1..];
|
||||
allowed = false;
|
||||
}
|
||||
|
||||
permissions.Add(new Permission(name, allowed));
|
||||
}
|
||||
|
||||
return permissions.AsReadOnly();
|
||||
}
|
||||
|
||||
private static string ToProvider(IEnumerable<Permission> permissions, char separator = ';')
|
||||
{
|
||||
return string.Join(separator, permissions.Select(p => $"{(p.IsAllowed ? "-" : "")}{p.Name}"));
|
||||
}
|
||||
}
|
@ -18,5 +18,6 @@ internal sealed class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
builder.Property(e => e.Salt).HasMaxLength(255).IsRequired();
|
||||
builder.Property(e => e.Registered).IsRequired();
|
||||
builder.Property(e => e.Totp);
|
||||
builder.Property(e => e.Permissions).HasConversion<PermissionListConverter>();
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,12 @@ public interface IUser
|
||||
/// <value>The registration date and time.</value>
|
||||
DateTimeOffset Registered { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the permissions this user is granted.
|
||||
/// </summary>
|
||||
/// <value>A read-only view of the permissions this user is granted.</value>
|
||||
IReadOnlyList<Permission> Permissions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the user's TOTP token.
|
||||
/// </summary>
|
||||
@ -48,6 +54,24 @@ public interface IUser
|
||||
/// <returns>The URL of the user's avatar.</returns>
|
||||
Uri GetAvatarUrl(int size = 28);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user has the specified permission.
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission to test.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the user has the specified permission; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
bool HasPermission(Permission permission);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the user has the specified permission.
|
||||
/// </summary>
|
||||
/// <param name="permission">The permission to test.</param>
|
||||
/// <returns>
|
||||
/// <see langword="true" /> if the user has the specified permission; otherwise, <see langword="false" />.
|
||||
/// </returns>
|
||||
bool HasPermission(string permission);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the specified password is valid for the user.
|
||||
/// </summary>
|
||||
|
@ -24,6 +24,9 @@ internal sealed class User : IUser, IBlogAuthor
|
||||
/// <inheritdoc cref="IUser.Id" />
|
||||
public Guid Id { get; private set; } = Guid.NewGuid();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<Permission> Permissions { get; private set; } = ArraySegment<Permission>.Empty;
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset Registered { get; private set; } = DateTimeOffset.UtcNow;
|
||||
|
||||
@ -75,6 +78,20 @@ internal sealed class User : IUser, IBlogAuthor
|
||||
return new Uri($"https://www.gravatar.com/avatar/{builder}?size={size}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPermission(Permission permission)
|
||||
{
|
||||
return HasPermission(permission.Name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool HasPermission(string permission)
|
||||
{
|
||||
return (Permissions.Any(p => p.IsAllowed && p.Name == permission) ||
|
||||
Permissions.Any(p => p is { IsAllowed: true, Name: "*" })) &&
|
||||
!Permissions.Any(p => !p.IsAllowed && p.Name == permission);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TestCredentials(string password)
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using OliverBooth.Data.Blog.Configuration;
|
||||
using OliverBooth.Data.Web.Configuration;
|
||||
|
||||
namespace OliverBooth.Data.Web;
|
||||
|
Loading…
Reference in New Issue
Block a user