Skip to content

Commit 20510da

Browse files
committed
Implement new authorization framework and refactor code
1 parent 7c90dfb commit 20510da

File tree

18 files changed

+299
-176
lines changed

18 files changed

+299
-176
lines changed

TodoSample/Core/Application.Contracts/TodoItems/Commands/CreateTodoItemCommand.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11

2+
using Honamic.Framework.Applications.Authorizes;
23
using Honamic.Framework.Applications.Results;
34
using Honamic.Framework.Commands;
45

@@ -8,6 +9,8 @@ public record CreateTodoItemCommand(string title, string content, List<string> t
89

910

1011

12+
//[DynamicAuthorize]
13+
[Authorize("admin")]
1114
public record CreateTodoItem2Command(string title, string content, List<string> tags)
1215
: ICommand<Result<CreateTodoItem2ResultCommand>>;
1316

TodoSample/Facade/TodoItems/TodoItemFacade.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using Honamic.Framework.Applications.Results;
1+
using Honamic.Framework.Applications.Authorizes;
2+
using Honamic.Framework.Applications.Results;
23
using Honamic.Framework.Commands;
34
using Honamic.Framework.Events;
45
using Honamic.Framework.Facade;
@@ -8,7 +9,7 @@
89

910
namespace Honamic.Todo.Facade.TodoItems;
1011

11-
[FacadeDynamicAuthorize]
12+
[DynamicAuthorize]
1213
internal class TodoItemFacade : BaseFacade, ITodoItemFacade
1314
{
1415
private readonly ICommandBus _commandBus;

TodoSample/Facade/TodoItems/TodoItemQueryFacade.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
using Honamic.Todo.Query.Domain.TodoItems.Queries;
77
using Honamic.Todo.Query.Domain.TodoItems;
88
using Honamic.Framework.Applications.Results;
9+
using Honamic.Framework.Applications.Authorizes;
910

1011
namespace Honamic.Todo.Facade.TodoItems;
1112

12-
[FacadeDynamicAuthorize]
13+
[DynamicAuthorize]
1314
[DisplayName("نمایش انجام دادنی ها")]
1415
public class TodoItemQueryFacade : BaseFacade, ITodoItemQueryFacade
1516
{
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Honamic.Framework.Applications.Authorizes;
2+
3+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
4+
public class AllowAnonymousAttribute : Attribute
5+
{
6+
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
namespace Honamic.Framework.Applications.Authorizes;
2+
3+
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
4+
public class AuthorizeAttribute : Attribute
5+
{
6+
public AuthorizeAttribute()
7+
{
8+
Permissions = null;
9+
}
10+
11+
public AuthorizeAttribute(params string[] permissions)
12+
{
13+
Permissions = permissions;
14+
}
15+
16+
public string[]? Permissions { get; }
17+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Honamic.Framework.Applications.Authorizes;
2+
3+
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
4+
public class DynamicAuthorizeAttribute : Attribute
5+
{
6+
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
namespace Honamic.Framework.Applications.Authorizes;
2+
3+
public interface IAuthorization
4+
{
5+
[Obsolete("Use HaveAccessAsync instead. This method will be removed in future versions.")]
6+
bool HaveAccess(string permission);
7+
8+
Task<bool> HaveAccessAsync(string permission);
9+
10+
bool IsAuthenticated();
11+
}

src/Core/Applications.Abstractions/Exceptions/UnauthorizedException.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ public UnauthorizedException(string permissionName)
66
{
77
PermissionName = permissionName;
88
}
9-
public override string Message => "the request does not have valid authentication credentials for the operation";
9+
public override string Message => "You do not have permission to perform this operation";
1010

1111
public string PermissionName { get; }
1212
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using Honamic.Framework.Applications.Authorizes;
2+
using Honamic.Framework.Applications.Exceptions;
3+
using Honamic.Framework.Commands;
4+
using System.Reflection;
5+
6+
namespace Honamic.Framework.Applications.CommandHandlerDecorators;
7+
8+
public class AuthorizeCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
9+
where TCommand : ICommand
10+
{
11+
private readonly ICommandHandler<TCommand> _commandHandler;
12+
private readonly IAuthorization _authorization;
13+
14+
public AuthorizeCommandHandlerDecorator(ICommandHandler<TCommand> commandHandler, IAuthorization authorization)
15+
{
16+
_commandHandler = commandHandler;
17+
_authorization = authorization;
18+
}
19+
20+
public async Task HandleAsync(TCommand command, CancellationToken cancellationToken)
21+
{
22+
await _authorization.AuthorizeCommandAttributes(typeof(TCommand));
23+
24+
await _commandHandler.HandleAsync(command, cancellationToken);
25+
}
26+
}
27+
28+
public class AuthorizeCommandHandlerDecorator<TCommand, TResponse> : ICommandHandler<TCommand, TResponse>
29+
where TCommand : ICommand<TResponse>
30+
{
31+
private readonly ICommandHandler<TCommand, TResponse> _commandHandler;
32+
private readonly IAuthorization _authorization;
33+
34+
public AuthorizeCommandHandlerDecorator(ICommandHandler<TCommand, TResponse> commandHandler, IAuthorization authorization)
35+
{
36+
_commandHandler = commandHandler;
37+
_authorization = authorization;
38+
}
39+
40+
public async Task<TResponse> HandleAsync(TCommand command, CancellationToken cancellationToken)
41+
{
42+
await _authorization.AuthorizeCommandAttributes(typeof(TCommand));
43+
44+
return await _commandHandler.HandleAsync(command, cancellationToken);
45+
}
46+
}
47+
48+
internal static class AuthorizeCommandHandlerDecoratorHelper
49+
{
50+
51+
public static async Task AuthorizeCommandAttributes(this IAuthorization authorization, Type type)
52+
{
53+
await authorization.AuthorizeWithAttributes(type);
54+
55+
await authorization.AuthorizeWithDynamicPermissions(type);
56+
}
57+
58+
public static async Task AuthorizeWithDynamicPermissions(this IAuthorization authorization, Type type)
59+
{
60+
var dynamicAuthorizeAttribute = type.GetCustomAttribute<DynamicAuthorizeAttribute>();
61+
62+
if (dynamicAuthorizeAttribute is not null)
63+
{
64+
if (!authorization.IsAuthenticated())
65+
{
66+
throw new UnauthenticatedException();
67+
}
68+
69+
string dynamicPermission = CalculatePermissionName(type);
70+
71+
if (!await authorization.HaveAccessAsync(dynamicPermission))
72+
{
73+
throw new UnauthorizedException(dynamicPermission);
74+
}
75+
}
76+
}
77+
78+
public static async Task AuthorizeWithAttributes(this IAuthorization authorization, Type type)
79+
{
80+
var authorizeAttribute = type.GetCustomAttribute<AuthorizeAttribute>();
81+
82+
if (authorizeAttribute is not null)
83+
{
84+
if (!authorization.IsAuthenticated())
85+
{
86+
throw new UnauthenticatedException();
87+
}
88+
89+
if (authorizeAttribute.Permissions?.Length > 0)
90+
{
91+
foreach (var permission in authorizeAttribute.Permissions)
92+
{
93+
if (!await authorization.HaveAccessAsync(permission))
94+
{
95+
throw new UnauthorizedException(permission);
96+
}
97+
}
98+
}
99+
}
100+
}
101+
102+
private static string CalculatePermissionName(Type type)
103+
{
104+
return type.Name;
105+
}
106+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using Honamic.Framework.Applications.Exceptions;
2+
using Honamic.Framework.Applications.Results;
3+
using Honamic.Framework.Commands;
4+
using Honamic.Framework.Domain;
5+
6+
namespace Honamic.Framework.Applications.CommandHandlerDecorators;
7+
8+
public class ExceptionCommandHandlerDecorator<TCommand, TResponse> : ICommandHandler<TCommand, TResponse>
9+
where TCommand : ICommand<TResponse>
10+
{
11+
private readonly ICommandHandler<TCommand, TResponse> _commandHandler;
12+
13+
public ExceptionCommandHandlerDecorator(ICommandHandler<TCommand, TResponse> commandHandler)
14+
{
15+
_commandHandler = commandHandler;
16+
}
17+
18+
public async Task<TResponse> HandleAsync(TCommand command, CancellationToken cancellationToken)
19+
{
20+
TResponse result;
21+
try
22+
{
23+
result = await _commandHandler.HandleAsync(command, cancellationToken);
24+
}
25+
catch (Exception ex)
26+
{
27+
if (IsResultOriented(typeof(TResponse)))
28+
{
29+
result = CreateResultWithError(typeof(TResponse), ex);
30+
return result;
31+
}
32+
33+
throw;
34+
}
35+
36+
return result;
37+
}
38+
39+
private TResponse CreateResultWithError(Type type, Exception ex)
40+
{
41+
var resultObject = CreateResult(type);
42+
43+
if (resultObject is Result result)
44+
{
45+
switch (ex)
46+
{
47+
case UnauthenticatedException:
48+
result.SetStatusAsUnauthenticated();
49+
result.AppendError(ex.Message);
50+
break;
51+
case UnauthorizedException:
52+
result.SetStatusAsUnauthorized();
53+
result.AppendError(ex.Message);
54+
break;
55+
case BusinessException businessException:
56+
result.Status = ResultStatus.ValidationError;
57+
var code = businessException.GetCode();
58+
var message = businessException.GetMessage();
59+
result.AppendError(message, null, code);
60+
break;
61+
default:
62+
result.SetStatusAsUnhandledExceptionWithSorryError();
63+
result.AppendError(ex.ToString(), "Exception");
64+
break;
65+
}
66+
return resultObject;
67+
}
68+
69+
// If we can't cast to Result, we have a serious error
70+
throw new ArgumentException($"Expected a Result type but got {type.FullName}");
71+
}
72+
73+
private TResponse CreateResult(Type type)
74+
{
75+
// For non-generic Result
76+
if (type == typeof(Result))
77+
{
78+
return (TResponse)(object)new Result();
79+
}
80+
81+
// For Result<T>
82+
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Result<>))
83+
{
84+
var genericArgType = type.GenericTypeArguments[0];
85+
var resultType = typeof(Result<>).MakeGenericType(genericArgType);
86+
return (TResponse)Activator.CreateInstance(resultType);
87+
}
88+
89+
return default;
90+
}
91+
92+
private bool IsResultOriented(Type type)
93+
{
94+
if (type == typeof(Result))
95+
{
96+
return true;
97+
}
98+
99+
if (type.IsGenericType
100+
&& type.GetGenericTypeDefinition() == typeof(Result<>))
101+
{
102+
return true;
103+
}
104+
105+
return false;
106+
}
107+
}

0 commit comments

Comments
 (0)