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