Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Commit

Permalink
Add support for dynamic permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
oliverbooth committed Mar 23, 2022
1 parent c3c06cf commit aad60f0
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 1 deletion.
76 changes: 75 additions & 1 deletion BrackeysBot.API/Extensions/CommandContextExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
using System.Threading.Tasks;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BrackeysBot.API.Permissions;
using BrackeysBot.API.Plugins;
using DisCatSharp.CommandsNext;
using DisCatSharp.CommandsNext.Attributes;
using Microsoft.Extensions.DependencyInjection;

namespace BrackeysBot.API.Extensions;

Expand All @@ -16,4 +23,71 @@ public static Task AcknowledgeAsync(this CommandContext context)
{
return context.Message.AcknowledgeAsync();
}

/// <summary>
/// Returns a value indicating whether the user which invoked the command has a specified permission.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="permission">The permission to validate.</param>
/// <returns>
/// <see langword="true" /> if the invoking user has the specified permission; otherwise, <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="permission" /> is <see langword="null" />.</exception>
public static bool HasPermission(this CommandContext context, Permission permission)
{
if (permission is null) throw new ArgumentNullException(nameof(permission));

PermissionType permissionType = permission.Type;

if (context.Member is not { } member)
{
if (context.Command.ExecutionChecks.Any(check => check is RequireGuildAttribute))
return false;

return permissionType == PermissionType.Everyone ||
(permissionType == PermissionType.User && FilterPermissionIds(permission.Ids).Contains(context.User.Id));
}

switch (permissionType)
{
case PermissionType.Everyone:
return true;

case PermissionType.Role:
IEnumerable<ulong> roleIds = member.Roles.Select(r => r.Id);
return FilterPermissionIds(permission.Ids).Any(id => roleIds.Contains(id));

case PermissionType.User:
return FilterPermissionIds(permission.Ids).Contains(member.Id);

default:
return false;
}
}

/// <summary>
/// Returns a value indicating whether the user which invoked the command has a specified permission.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="permissionName">The permission to validate.</param>
/// <returns>
/// <see langword="true" /> if the invoking user has the specified permission; otherwise, <see langword="false" />.
/// </returns>
/// <exception cref="ArgumentNullException"><paramref name="permissionName" /> is <see langword="null" />.</exception>
public static bool HasPermission(this CommandContext context, string permissionName)
{
var plugin = context.Services.GetRequiredService<IPlugin>();
Permission? permission = plugin.GetPermission(permissionName);
return permission is not null && context.HasPermission(permission);
}

private static IEnumerable<ulong> FilterPermissionIds(IEnumerable<string> source)
{
return source.Select(value =>
{
if (value.StartsWith('-') && ulong.TryParse(value[1..], out ulong id))
return id;
return ulong.TryParse(value, out id) ? id : 0;
}).Where(id => id > 0);
}
}
91 changes: 91 additions & 0 deletions BrackeysBot.API/Permissions/Permission.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.Json.Serialization;

namespace BrackeysBot.API.Permissions;

/// <summary>
/// Represents a permission.
/// </summary>
public sealed class Permission : IEquatable<Permission>
{
[JsonPropertyName("ids")] [JsonInclude]
private string[] _ids;

[JsonConstructor]
private Permission()
{
_ids = Array.Empty<string>();
Name = string.Empty;
Type = PermissionType.Everyone;
}

/// <summary>
/// Initializes a new instance of the <see cref="Permission" /> class.
/// </summary>
/// <param name="name">The name of the permission.</param>
/// <param name="type">The type of the permission.</param>
/// <param name="ids">The set of applicable IDs.</param>
public Permission(string name, PermissionType type, params string[] ids)
{
Name = name;
Type = type;
_ids = ids.ToArray(); // defensive copy
}

/// <summary>
/// Gets the set of IDs applicable to this permission.
/// </summary>
/// <value>The set of applicable IDs.</value>
public IReadOnlyList<string> Ids => _ids.ToArray(); // defensive copy

/// <summary>
/// Gets the name of the permission.
/// </summary>
/// <value>The name of the permission.</value>
[JsonPropertyName("name")]
[JsonInclude]
public string Name { get; private set; }

/// <summary>
/// Gets the type of the permission.
/// </summary>
/// <value>The type of the permission.</value>
[JsonPropertyName("type")]
[JsonInclude]
public PermissionType Type { get; private set; }

/// <inheritdoc />
public bool Equals(Permission? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Name == other.Name;
}

public static bool operator ==(Permission? left, Permission? right)
{
if (left is null) return right is null;
return left.Equals(right);
}

public static bool operator !=(Permission? left, Permission? right)
{
return !(left == right);
}

/// <inheritdoc />
public override bool Equals(object? obj)
{
return ReferenceEquals(this, obj) || (obj is Permission other && Equals(other));
}

/// <inheritdoc />
[SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
22 changes: 22 additions & 0 deletions BrackeysBot.API/Permissions/PermissionType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace BrackeysBot.API.Permissions;

/// <summary>
/// An enumeration of permission types.
/// </summary>
public enum PermissionType
{
/// <summary>
/// Defines that the permission is granted to everybody.
/// </summary>
Everyone,

/// <summary>
/// Defines that the permission is granted to a set of roles.
/// </summary>
Role,

/// <summary>
/// Defines that the permission is granted to a set of users.
/// </summary>
User
}
36 changes: 36 additions & 0 deletions BrackeysBot.API/Permissions/RequirePermissionAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using BrackeysBot.API.Extensions;
using DisCatSharp.CommandsNext;
using DisCatSharp.CommandsNext.Attributes;

namespace BrackeysBot.API.Permissions;

/// <summary>
/// Defines valid permissions for this command or group.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public sealed class RequirePermissionAttribute : CheckBaseAttribute
{
/// <summary>
/// Initializes a new instance of the <see cref="RequirePermissionAttribute" /> class.
/// </summary>
/// <param name="permissionName">The permission node name.</param>
public RequirePermissionAttribute(string permissionName)
{
if (string.IsNullOrWhiteSpace(permissionName)) throw new ArgumentNullException(nameof(permissionName));
PermissionName = permissionName;
}

/// <summary>
/// Gets or sets the name of the permission node.
/// </summary>
/// <value>The permission node name.</value>
public string PermissionName { get; }

/// <inheritdoc />
public override Task<bool> ExecuteCheckAsync(CommandContext ctx, bool help)
{
return Task.FromResult(ctx.HasPermission(PermissionName));
}
}
24 changes: 24 additions & 0 deletions BrackeysBot.API/Plugins/IPlugin.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.IO;
using BrackeysBot.API.Configuration;
using BrackeysBot.API.Permissions;
using NLog;

namespace BrackeysBot.API.Plugins;
Expand Down Expand Up @@ -31,6 +33,18 @@ public interface IPlugin : IDisposable, IConfigurationHolder
/// <value>The plugin's logger.</value>
ILogger Logger { get; }

/// <summary>
/// Gets a list of the default permissions for this plugin.
/// </summary>
/// <value>The default permissions.</value>
IReadOnlyList<Permission> PermissionDefaults { get; }

/// <summary>
/// Gets a list of the current permissions for this plugin.
/// </summary>
/// <value>The current permissions.</value>
IReadOnlyList<Permission> Permissions { get; }

/// <summary>
/// Gets the information about this plugin.
/// </summary>
Expand All @@ -48,4 +62,14 @@ public interface IPlugin : IDisposable, IConfigurationHolder
/// </summary>
/// <value>The service provider.</value>
public IServiceProvider ServiceProvider { get; }

/// <summary>
/// Gets a permission by its name.
/// </summary>
/// <param name="name">The name of the permission.</param>
/// <returns>The permission with the specified name, or <see langword="null" /> if no matching permission was found.</returns>
/// <exception cref="ArgumentNullException">
/// <paramref name="name" /> is <see langword="null" />, empty, or consists of only whitespace.
/// </exception>
Permission? GetPermission(string name);
}
20 changes: 20 additions & 0 deletions BrackeysBot.API/Plugins/MonoPlugin.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Loader;
using System.Threading.Tasks;
using BrackeysBot.API.Configuration;
using BrackeysBot.API.Permissions;
using DisCatSharp;
using Microsoft.Extensions.DependencyInjection;
using NLog;
Expand Down Expand Up @@ -34,6 +36,12 @@ public abstract class MonoPlugin : IPlugin
/// <inheritdoc />
public ILogger Logger { get; internal set; } = null!;

/// <inheritdoc />
public IReadOnlyList<Permission> PermissionDefaults { get; internal set; } = Array.Empty<Permission>();

/// <inheritdoc />
public IReadOnlyList<Permission> Permissions { get; internal set; } = Array.Empty<Permission>();

/// <inheritdoc />
public PluginInfo PluginInfo { get; internal set; } = null!;

Expand All @@ -48,6 +56,18 @@ public virtual void Dispose()
{
}

/// <inheritdoc />
public Permission? GetPermission(string name)
{
for (var index = 0; index < Permissions.Count; index++)
{
if (string.Equals(name, Permissions[index].Name, StringComparison.Ordinal))
return Permissions[index];
}

return null;
}

/// <inheritdoc />
~MonoPlugin()
{
Expand Down

0 comments on commit aad60f0

Please sign in to comment.