Frontend extension with CasbinJsGetPermissionForUser
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..27b5c94 --- /dev/null +++ b/.gitignore
@@ -0,0 +1,32 @@ + +#Ignore thumbnails created by Windows +Thumbs.db +#Ignore files built by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +.vs/ +#Nuget packages folder +packages/
diff --git a/src/Casbin.Extension.Frontend.Tests/Assets/acl/model.conf b/src/Casbin.Extension.Frontend.Tests/Assets/acl/model.conf new file mode 100644 index 0000000..5dfb42a --- /dev/null +++ b/src/Casbin.Extension.Frontend.Tests/Assets/acl/model.conf
@@ -0,0 +1,11 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && r.obj == p.obj && r.act == p.act \ No newline at end of file
diff --git a/src/Casbin.Extension.Frontend.Tests/Assets/acl/policy.csv b/src/Casbin.Extension.Frontend.Tests/Assets/acl/policy.csv new file mode 100644 index 0000000..740b196 --- /dev/null +++ b/src/Casbin.Extension.Frontend.Tests/Assets/acl/policy.csv
@@ -0,0 +1,4 @@ +p, alice, data1, read +p, alice, data1, write +p, alice, data2, write +p, bob, data2, write \ No newline at end of file
diff --git a/src/Casbin.Extension.Frontend.Tests/Casbin.Extension.Frontend.Tests.csproj b/src/Casbin.Extension.Frontend.Tests/Casbin.Extension.Frontend.Tests.csproj new file mode 100644 index 0000000..0f71cfc --- /dev/null +++ b/src/Casbin.Extension.Frontend.Tests/Casbin.Extension.Frontend.Tests.csproj
@@ -0,0 +1,30 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" /> + <PackageReference Include="xunit" Version="2.4.1" /> + <PackageReference Include="xunit.analyzers" Version="0.10.0" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> + </PackageReference> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Casbin.Extension.Frontend\Casbin.Extension.Frontend.csproj" /> + </ItemGroup> + + <ItemGroup> + <None Update="Assets\acl\model.conf"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="Assets\acl\policy.csv"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + </ItemGroup> + +</Project>
diff --git a/src/Casbin.Extension.Frontend.Tests/ExtensionsTest.cs b/src/Casbin.Extension.Frontend.Tests/ExtensionsTest.cs new file mode 100644 index 0000000..9c71d3a --- /dev/null +++ b/src/Casbin.Extension.Frontend.Tests/ExtensionsTest.cs
@@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Xunit; + +namespace Casbin.Extension.Frontend.Tests +{ + using Helpers; + + public class ExtensionsTest + { + private const string ACL = "acl"; + private readonly EnforcerBuilder _enforcerBuilder; + + public ExtensionsTest() + { + _enforcerBuilder = new EnforcerBuilder(); + } + + [Fact] + public void PolicyForAcl__SchouldBe__Right() + { + var enforcer = _enforcerBuilder.GetEnforcer(ACL); + var permissions = enforcer.CasbinJsGetPermissionForUser("alice"); + + Assert.Equal(new List<string> { "data1" }, permissions["read"]); + Assert.Equal(new List<string> { "data1", "data2" }, permissions["write"]); + } + } +}
diff --git a/src/Casbin.Extension.Frontend.Tests/Helpers/EnforcerBuilder.cs b/src/Casbin.Extension.Frontend.Tests/Helpers/EnforcerBuilder.cs new file mode 100644 index 0000000..817dd9e --- /dev/null +++ b/src/Casbin.Extension.Frontend.Tests/Helpers/EnforcerBuilder.cs
@@ -0,0 +1,26 @@ +using NetCasbin; +using NetCasbin.Abstractions; +using System.Collections.Concurrent; + +namespace Casbin.Extension.Frontend.Tests.Helpers +{ + internal class EnforcerBuilder + { + private const string MODEL_PATH_TEMPLATE = "Assets/{0}/model.conf"; + private const string POLICY_PATH_TEMPLATE = "Assets/{0}/policy.csv"; + private readonly ConcurrentDictionary<string, IEnforcer> _enforcers; + + internal EnforcerBuilder() + { + _enforcers = new ConcurrentDictionary<string, IEnforcer>(); + } + + + internal IEnforcer GetEnforcer(string schema) + { + var modelPath = string.Format(MODEL_PATH_TEMPLATE, schema); + var policyPath = string.Format(POLICY_PATH_TEMPLATE, schema); + return _enforcers.GetOrAdd(schema, (_) => new Enforcer(modelPath, policyPath)); + } + } +}
diff --git a/src/Casbin.Extension.Frontend.sln b/src/Casbin.Extension.Frontend.sln new file mode 100644 index 0000000..51c5c5e --- /dev/null +++ b/src/Casbin.Extension.Frontend.sln
@@ -0,0 +1,36 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30711.63 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Casbin.Extension.Frontend", "Casbin.Extension.Frontend\Casbin.Extension.Frontend.csproj", "{3E8BC33A-0A71-4626-AFA3-AA6FF5E8E5B3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Casbin.Extension.Frontend.Tests", "Casbin.Extension.Frontend.Tests\Casbin.Extension.Frontend.Tests.csproj", "{2D067971-2493-4F2A-848F-2BD772B5F264}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E87C1D09-9513-45A7-8806-281CDF015224}" + ProjectSection(SolutionItems) = preProject + README.md = README.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3E8BC33A-0A71-4626-AFA3-AA6FF5E8E5B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E8BC33A-0A71-4626-AFA3-AA6FF5E8E5B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E8BC33A-0A71-4626-AFA3-AA6FF5E8E5B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E8BC33A-0A71-4626-AFA3-AA6FF5E8E5B3}.Release|Any CPU.Build.0 = Release|Any CPU + {2D067971-2493-4F2A-848F-2BD772B5F264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D067971-2493-4F2A-848F-2BD772B5F264}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D067971-2493-4F2A-848F-2BD772B5F264}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D067971-2493-4F2A-848F-2BD772B5F264}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B1779BB5-B202-458E-BB2D-6134F97AAF14} + EndGlobalSection +EndGlobal
diff --git a/src/Casbin.Extension.Frontend/Casbin.Extension.Frontend.csproj b/src/Casbin.Extension.Frontend/Casbin.Extension.Frontend.csproj new file mode 100644 index 0000000..8c33038 --- /dev/null +++ b/src/Casbin.Extension.Frontend/Casbin.Extension.Frontend.csproj
@@ -0,0 +1,11 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFrameworks>netstandard2.0;net45</TargetFrameworks> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Casbin.NET" Version="1.4.0" /> + </ItemGroup> + +</Project>
diff --git a/src/Casbin.Extension.Frontend/Extensions.cs b/src/Casbin.Extension.Frontend/Extensions.cs new file mode 100644 index 0000000..d3e83e8 --- /dev/null +++ b/src/Casbin.Extension.Frontend/Extensions.cs
@@ -0,0 +1,41 @@ +using NetCasbin.Abstractions; + +namespace Casbin.Extension.Frontend +{ + using Models; + + public static class Extensions + { + /// <summary> + /// Get permission set for the user. + /// </summary> + /// <example> + /// { + /// "read": [ "data1", "data2" ], + /// "write": [ "data1" ] + /// } + /// </example> + /// <param name="enforcer">Application's enforcer</param> + /// <param name="user">Name of user from a policy</param> + /// <returns></returns> + public static ISubjectPermissions CasbinJsGetPermissionForUser(this IEnforcer enforcer, string user) + { + var policy = enforcer.GetImplicitPermissionsForUser(user); + var permissions = new SubjectPermissions(policy.Count); + for (var i = 0; i < policy.Count; i++) + { + var obj = policy[i][1]; + var act = policy[i][2]; + + if (!permissions.ContainsKey(act)) + { + permissions[act] = new SubjectObjects(); + } + + (permissions[act] as SubjectObjects).Add(obj); + } + + return permissions; + } + } +}
diff --git a/src/Casbin.Extension.Frontend/Models/SubjectActions.cs b/src/Casbin.Extension.Frontend/Models/SubjectActions.cs new file mode 100644 index 0000000..fa5c208 --- /dev/null +++ b/src/Casbin.Extension.Frontend/Models/SubjectActions.cs
@@ -0,0 +1,27 @@ +using System.Collections.Generic; + +namespace Casbin.Extension.Frontend.Models +{ + public interface ISubjectObjects : IReadOnlyList<string> + { + } + + internal class SubjectObjects : List<string>, ISubjectObjects + { + } + + /// <summary> + /// [act] = [] { obj } + /// </summary> + public interface ISubjectPermissions : IReadOnlyDictionary<string, ISubjectObjects> + { + } + + internal class SubjectPermissions : Dictionary<string, ISubjectObjects>, ISubjectPermissions + { + internal SubjectPermissions(int capacity) + : base(capacity) + { + } + } +}
diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..0d2064f --- /dev/null +++ b/src/README.md
@@ -0,0 +1,29 @@ +Casbin.NET Extensions +Helpful extensions for Casbin.NET, such as frontend extension to casbin.js. + +## Frontend + +Example of [advanced usage](https://casbin.org/docs/en/frontend#advanced-usage) of authZ + +```csharp +// {...} +using Casbin.Extension.Frontend; + +[Route("api/casbin")] +public class CasbinController : Controller +{ + private readonly IEnforcer _enforcer; + + public CasbinController(IEnforcer enforcer) + { + _enforcer = enforcer; + } + + [HttpGet] + public ActionResult Get([FromQuery("casbin_subject")] string user) + { + var permissions = _enforcer.CasbinJsGetPermissionForUser(user); + return Json(new { perm = permissions }) + } +} +``` \ No newline at end of file