Skip to content

Commit

Permalink
feat: Support generic request transformer
Browse files Browse the repository at this point in the history
Signed-off-by: sagilio <sagilio@outlook.com>
  • Loading branch information
sagilio committed Jul 31, 2022
1 parent b1bb3e7 commit e73d261
Show file tree
Hide file tree
Showing 17 changed files with 157 additions and 100 deletions.
2 changes: 1 addition & 1 deletion samples/WebApplicationSample/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public void ConfigureServices(IServiceCollection services)
// Comment line below to use the default BasicRequestTransformer
// Note: Commenting the line means that the action methods MUST have [CasbinAuthorize()] attribute which explicitly specifies obj and policy. Otherwise authorization will be denied
options.DefaultRequestTransformer = new KeyMatchRequestTransformer();
options.DefaultRequestTransformerType = typeof(KeyMatchRequestTransformer);
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
<PackageReference Include="Casbin.NET" Version="2.0.0-preview.4-build.262.preview.ce6e971" />
<PackageReference Include="Casbin.NET" Version="2.0.0-preview.4-build.263.preview.aa73b71" />
</ItemGroup>

<ItemGroup>
Expand Down
7 changes: 3 additions & 4 deletions src/Casbin.AspNetCore.Abstractions/IRequestTransformer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@

namespace Casbin.AspNetCore.Authorization
{
public interface IRequestTransformer
public interface IRequestTransformer<TRequest> where TRequest : IRequestValues
{
public string? Issuer { get; set; }
public string PreferSubClaimType { get; set; }

public ValueTask<TRequest> TransformAsync<TRequest>(ICasbinAuthorizationContext<TRequest> context,
ICasbinAuthorizationData<TRequest> data)
where TRequest : IRequestValues;
public ValueTask<TRequest> TransformAsync(ICasbinAuthorizationContext<TRequest> context,
ICasbinAuthorizationData<TRequest> data);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Casbin.Model;

namespace Casbin.AspNetCore.Authorization
{
public interface IRequestTransformersCache
public interface IRequestTransformersCache<TRequest> where TRequest : IRequestValues
{
public IEnumerable<IRequestTransformer>? Transformers { get; set; }
public IReadOnlyList<IRequestTransformer<TRequest>> Transformers { get; }
public bool TryGetTransformer(Type type, out IRequestTransformer<TRequest>? transformer);
}
}
53 changes: 44 additions & 9 deletions src/Casbin.AspNetCore.Core/Attributes/CasbinAuthorizeAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,73 @@ namespace Casbin.AspNetCore.Authorization;
/// Specifies that the class or method that this attribute is applied to requires the specified authorization.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CasbinAuthorizeAttribute : CasbinAuthorizeBaseAttribute, ICasbinAuthorizationData<RequestValues<string, string, string, string, string>>
public class CasbinAuthorizeAttribute : CasbinAuthorizeBaseAttribute, ICasbinAuthorizationData<StringRequestValues>
{
private RequestValues<string, string, string, string, string> _values;
private StringRequestValues _values;
public CasbinAuthorizeAttribute()
{
_values = new RequestValues<string, string, string, string, string>("", "", "", "", "");
_values = StringRequestValues.Empty;
}

public CasbinAuthorizeAttribute(string value1)
{
_values = new RequestValues<string, string, string, string, string>(value1, "", "", "", "");
_values = new StringRequestValues(value1);
}

public CasbinAuthorizeAttribute(string value1, string value2)
{
_values = new RequestValues<string, string, string, string, string>(value1, value2, "", "", "");
_values = new StringRequestValues(value1, value2);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3)
{
_values = new RequestValues<string, string, string, string, string>(value1, value2, value3, "", "");
_values = new StringRequestValues(value1, value2, value3);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4)
{
_values = new RequestValues<string, string, string, string, string>(value1, value2, value3, value4, "");
_values = new StringRequestValues(value1, value2, value3, value4);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5) : this(value1, value2, value3, value4)
{
_values = new RequestValues<string, string, string, string, string>(value1, value2, value3, value4, value5);
_values = new StringRequestValues(value1, value2, value3, value4, value5);
}

public ref RequestValues<string, string, string, string, string> Values => ref _values;
public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6) : this(value1, value2, value3, value4, value5)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7) : this(value1, value2, value3, value4, value5, value6)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7, string value8) : this(value1, value2, value3, value4, value5, value6, value7)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7, value8);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7, string value8, string value9) : this(value1, value2, value3, value4, value5, value6, value7, value8)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7, value8, value9);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7, string value8, string value9, string value10) : this(value1, value2, value3, value4, value5, value6, value7, value8, value9)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7, string value8, string value9, string value10, string value11) : this(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11);
}

public CasbinAuthorizeAttribute(string value1, string value2, string value3, string value4, string value5, string value6, string value7, string value8, string value9, string value10, string value11, string value12) : this(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11)
{
_values = new StringRequestValues(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10, value11, value12);
}

public ref StringRequestValues Values => ref _values;
}
3 changes: 2 additions & 1 deletion src/Casbin.AspNetCore.Core/CasbinAuthorizationOptions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Security.Claims;
using Casbin;
using Casbin.AspNetCore.Authorization.Transformers;
using Casbin.Model;

namespace Casbin.AspNetCore.Authorization
Expand All @@ -11,7 +12,7 @@ public class CasbinAuthorizationOptions
public string? DefaultPolicyPath { get; set; }
public Func<IServiceProvider, IModel?, Enforcer>? DefaultEnforcerFactory { get; set; }
public string? DefaultAuthenticationSchemes { get; set; }
public IRequestTransformer? DefaultRequestTransformer { get; set; }
public Type? DefaultRequestTransformerType { get; set; } = typeof(BasicRequestTransformer);
public string PreferSubClaimType { get; set; } = ClaimTypes.NameIdentifier;
public bool AllowAnyone { get; set; } = false;
}
Expand Down
17 changes: 10 additions & 7 deletions src/Casbin.AspNetCore.Core/CoreServiceCollectionExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static class CoreServiceCollectionExtension
ServiceLifetime defaultEnforcerProviderLifeTime = ServiceLifetime.Scoped,
ServiceLifetime defaultModelProviderLifeTime = ServiceLifetime.Scoped)
{
services.AddCasbinAuthorizationCore<RequestValues<string, string, string, string, string>>(configureOptions, defaultEnforcerProviderLifeTime, defaultModelProviderLifeTime);
services.AddCasbinAuthorizationCore<StringRequestValues>(configureOptions, defaultEnforcerProviderLifeTime, defaultModelProviderLifeTime);
return services;
}

Expand All @@ -67,15 +67,18 @@ public static class CoreServiceCollectionExtension
where TRequest : IRequestValues
{
services.AddCasbin(configureOptions, defaultEnforcerProviderLifeTime, defaultModelProviderLifeTime);
services.TryAdd(ServiceDescriptor.Singleton(typeof(ICasbinAuthorizationContextFactory<>), typeof(DefaultCasbinAuthorizationContextFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(
typeof(ICasbinAuthorizationContextFactory<>),
typeof(DefaultCasbinAuthorizationContextFactory<>)));
services.TryAddScoped<IEnforceService, DefaultEnforcerService>();
services.TryAddSingleton<IRequestTransformersCache, RequestTransformersCache>();

services.TryAdd(ServiceDescriptor.Singleton(
typeof(IRequestTransformersCache<>),
typeof(RequestTransformersCache<>)));
// Can not change to TryAdd, because there interface may need more than one implement.
services.AddScoped<IAuthorizationHandler, CasbinAuthorizationHandler<TRequest>>();
services.AddSingleton<IRequestTransformer, BasicRequestTransformer>();
services.AddSingleton<IRequestTransformer, RbacRequestTransformer>();
services.AddSingleton<IRequestTransformer, KeyMatchRequestTransformer>();
services.AddSingleton<IRequestTransformer<StringRequestValues>, BasicRequestTransformer>();
services.AddSingleton<IRequestTransformer<StringRequestValues>, RbacRequestTransformer>();
services.AddSingleton<IRequestTransformer<StringRequestValues>, KeyMatchRequestTransformer>();

services.AddAuthorizationCore();
return services;
Expand Down
57 changes: 33 additions & 24 deletions src/Casbin.AspNetCore.Core/DefaultEnforcerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Threading.Tasks;
using Casbin.AspNetCore.Authorization.Extensions;
using Casbin.Model;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

Expand All @@ -11,18 +12,18 @@ namespace Casbin.AspNetCore.Authorization
public class DefaultEnforcerService : IEnforceService
{
private readonly IOptions<CasbinAuthorizationOptions> _options;
private readonly IRequestTransformersCache _transformersCache;
private readonly IServiceProvider _serviceProvider;
private readonly IEnforcerProvider _enforcerProvider;
private readonly ILogger<DefaultEnforcerService> _logger;

public DefaultEnforcerService(
IOptions<CasbinAuthorizationOptions> options,
IRequestTransformersCache transformersCache,
IServiceProvider serviceProvider,
IEnforcerProvider enforcerProvider,
ILogger<DefaultEnforcerService> logger)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_transformersCache = transformersCache ?? throw new ArgumentNullException(nameof(transformersCache));
_serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
_enforcerProvider = enforcerProvider ?? throw new ArgumentNullException(nameof(enforcerProvider));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Expand All @@ -36,52 +37,60 @@ public virtual async Task<bool> EnforceAsync<TRequest>(ICasbinAuthorizationConte
throw new ArgumentException("Cannot find any enforcer.");
}

var transformersArray =
_transformersCache.Transformers as IRequestTransformer[] ??
_transformersCache.Transformers?.ToArray();

bool noDefault = _options.Value.DefaultRequestTransformer is null;
if (transformersArray is null || transformersArray.Length == 0 && noDefault)
var transformersCache = _serviceProvider.GetService<IRequestTransformersCache<TRequest>>();
if (transformersCache is null)
{
throw new ArgumentException("Cannot find the specific request values type transformers cache.");
}
var transformers = transformersCache.Transformers;
bool noDefault = _options.Value.DefaultRequestTransformerType is null;
if (transformers is null || transformers.Count is 0 && noDefault)
{
throw new ArgumentException("Cannot find any request transformer.");
throw new ArgumentException($"Cannot find the specific request values type request transformer.");
}

foreach (var data in context.AuthorizationData)
{
// The order of decide transformer is :
// 1. context.Data.RequestTransformerType >
// 2. _options.Value.DefaultRequestTransformer >
// 2. _options.Value.DefaultRequestTransformerType >
// 3. _transformers.FirstOrDefault()
IRequestTransformer? transformer = null;
IRequestTransformer<TRequest>? transformer = null;
if (data.RequestTransformerType is not null)
{
transformer = transformersArray.FirstOrDefault(t =>
t.GetType() == data.RequestTransformerType);

if (transformer is null)
if (transformersCache.TryGetTransformer(data.RequestTransformerType, out transformer) is false)
{
throw new ArgumentException("Cannot find any specified type request transformer.", nameof(data.RequestTransformerType));
throw new ArgumentException($"Cannot find request transformer {data.RequestTransformerType}.");
}
}
else if (!noDefault)
else if (_options.Value.DefaultRequestTransformerType is not null)
{
transformer = _options.Value.DefaultRequestTransformer;
if (transformersCache.TryGetTransformer(_options.Value.DefaultRequestTransformerType, out transformer) is false)
{
throw new ArgumentException($"Cannot find request transformer {_options.Value.DefaultRequestTransformerType}.");
}
}
else
{
if (transformers.Count is not 1)
{
throw new ArgumentException("Cannot determine the exclusive request transformer.");
}
transformer = transformers[0];
}

transformer ??= transformersArray.FirstOrDefault();

if (transformer is null)
{
throw new ArgumentException("Cannot find any request transformer.", nameof(transformer));
throw new ArgumentException($"Cannot find the specific request values type request transformer.");
}

// The order of deciding transformer.PreferSubClaimType is :
// 1. context.Data.PreferSubClaimType >
// 2. _options.Value.PreferSubClaimType
transformer.PreferSubClaimType = data.PreferSubClaimType ?? _options.Value.PreferSubClaimType;

// The order of deciding transformer.PreferSubClaimType is :
// 1. context.Data.PreferSubClaimType >
// The order of deciding transformer.Issuer is :
// 1. context.Data.Issuer >
// 2. null (if this issuer is null, it will be ignored)
transformer.Issuer = data.Issuer;

Expand Down
27 changes: 19 additions & 8 deletions src/Casbin.AspNetCore.Core/RequestTransformersCache.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Casbin.Model;
using Microsoft.Extensions.DependencyInjection;

namespace Casbin.AspNetCore.Authorization
{
public class RequestTransformersCache : IRequestTransformersCache
public class RequestTransformersCache<TRequest> : IRequestTransformersCache<TRequest>
where TRequest : IRequestValues
{
private readonly IServiceProvider _serviceProvider;
private readonly IDictionary<Type, IRequestTransformer<TRequest>> _cache = new Dictionary<Type, IRequestTransformer<TRequest>>();
private readonly List<IRequestTransformer<TRequest>> _transformers;

public IReadOnlyList<IRequestTransformer<TRequest>> Transformers => _transformers;

public RequestTransformersCache(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
Initial();
IEnumerable<IRequestTransformer<TRequest>?>? transformers = serviceProvider.GetServices<IRequestTransformer<TRequest>>();
foreach (var transformer in transformers)
{
if (transformer is null)
{
continue;
}
_cache[transformer.GetType()] = transformer;
}
_transformers = _cache.Values.ToList();
}

public IEnumerable<IRequestTransformer>? Transformers { get; set; }

private void Initial()
public bool TryGetTransformer(Type type, out IRequestTransformer<TRequest>? transformer)
{
Transformers ??= _serviceProvider.GetServices<IRequestTransformer>().ToArray();
return _cache.TryGetValue(type, out transformer);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,17 @@ public class BasicRequestTransformer : RequestTransformer
{
public override string PreferSubClaimType { get; set; } = ClaimTypes.NameIdentifier;

public override ValueTask<TRequest> TransformAsync<TRequest>(ICasbinAuthorizationContext<TRequest> context,
ICasbinAuthorizationData<TRequest> data)
public override ValueTask<StringRequestValues> TransformAsync(
ICasbinAuthorizationContext<StringRequestValues> context,
ICasbinAuthorizationData<StringRequestValues> data)
{
ref var values = ref data.Values;
if (values is not RequestValues<string, string, string, string, string> v)
{
return new ValueTask<TRequest>(data.Values);
}
string? value1 = values.Value1;
string? value2 = values.Value2;
values.TrySetValue(0, SubTransform(context, data));
values.TrySetValue(1, v.Value1);
values.TrySetValue(2, v.Value2);
return new ValueTask<TRequest>(data.Values);
values.TrySetValue(1, value1);
values.TrySetValue(2, value2);
return new ValueTask<StringRequestValues>(data.Values);
}

protected virtual string SubTransform<TRequest>(ICasbinAuthorizationContext<TRequest> context, ICasbinAuthorizationData<TRequest> data)
Expand Down
Loading

0 comments on commit e73d261

Please sign in to comment.