Skip to content

Commit d4d32ef

Browse files
authored
Merge pull request #5 from RockSolidKnowledge/FirstSample
First sample
2 parents 44ba40d + 5046269 commit d4d32ef

48 files changed

Lines changed: 1807 additions & 4 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ riderModule.iml
55
/_ReSharper.Caches/
66
**/.DS_Store
77

8-
src/CSharp/.idea/.idea.Rsk.AuthZen/.idea/
9-
8+
.idea
9+
*.DotSettings.user
1010
src/Typescript/dist/
1111
src/Typescript/node_modules
12-
13-
src/CSharp/.idea/
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk.Web">
2+
<PropertyGroup>
3+
<TargetFramework>net10.0</TargetFramework>
4+
<Nullable>enable</Nullable>
5+
<ImplicitUsings>enable</ImplicitUsings>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="Rsk.Enforcer.AuthZen" Version="6.1.1"/>
10+
</ItemGroup>
11+
12+
<ItemGroup>
13+
<None Remove="Policies\global.alfa" />
14+
<EmbeddedResource Include="Policies\global.alfa" />
15+
<None Remove="Policies\expenses.alfa" />
16+
<EmbeddedResource Include="Policies\expenses.alfa" />
17+
</ItemGroup>
18+
</Project>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
@page
2+
@model AuthZenPolicyServer.Pages.HomeModel
3+
@{
4+
ViewData["Title"] = "Home";
5+
}
6+
7+
<style>
8+
.alfa-keyword { color: #005cc5; font-weight: bold; }
9+
.alfa-literal { color: #032f62; }
10+
.alfa-number { color: #22863a; }
11+
.alfa-comment { color: #6a737d; font-style: italic; }
12+
.alfa-brace { color: #d73a49; font-weight: bold; }
13+
</style>
14+
15+
<div class="container mt-5">
16+
<h1 class="mb-4">Welcome to the AuthZen Policy Server</h1>
17+
<p>This server is responsible for serving and managing the expense claim authorization decisions.</p>
18+
@foreach (var policy in Model.PolicyFiles)
19+
{
20+
<h3 class="mt-5">Policy: <span class="text-primary">@policy.Name</span></h3>
21+
<div class="card bg-light border-primary mt-3 mb-5">
22+
<div class="card-body">
23+
<pre class="mb-0 text-dark" style="white-space: pre-wrap; background: #f8f9fa; border-left: 5px solid #0d6efd; padding: 1em;"><code>@Html.Raw(Model.PolicyHtml(policy.Content))</code></pre>
24+
</div>
25+
</div>
26+
}
27+
</div>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
using Microsoft.AspNetCore.Html;
2+
using Microsoft.AspNetCore.Mvc.RazorPages;
3+
using System.Text.RegularExpressions;
4+
using System.Reflection;
5+
6+
namespace AuthZenPolicyServer.Pages
7+
{
8+
public class HomeModel : PageModel
9+
{
10+
public string PolicyText { get; set; } = string.Empty;
11+
public List<(string Name, string Content)> PolicyFiles { get; set; } = new();
12+
13+
public void OnGet()
14+
{
15+
var assembly = Assembly.GetExecutingAssembly();
16+
var resources = assembly.GetManifestResourceNames()
17+
.Where(r => r.Contains(".Policies.") && r.EndsWith(".alfa"));
18+
foreach (var resource in resources)
19+
{
20+
using var stream = assembly.GetManifestResourceStream(resource);
21+
using var reader = new StreamReader(stream!);
22+
var content = reader.ReadToEnd();
23+
var name = resource.Substring(resource.LastIndexOf(".Policies.") + 10);
24+
PolicyFiles.Add((name, content));
25+
}
26+
}
27+
28+
public static string ColorizePolicy(string policy)
29+
{
30+
// Colorize comments first
31+
string result = Regex.Replace(policy, "//.*", "<span class='alfa-comment'>$0</span>", RegexOptions.None, TimeSpan.FromSeconds(1));
32+
// Colorize strings
33+
result = Regex.Replace(result, "\"([^\"]*)\"", "<span class='alfa-literal'>\"$1\"</span>", RegexOptions.None, TimeSpan.FromSeconds(1));
34+
// Keywords
35+
string[] keywords = new[] { "namespace", "import", "attribute", "policyset", "policy", "apply", "firstApplicable", "denyUnlessPermit", "permitUnlessDeny", "target", "clause", "rule", "condition", "permit", "deny", "on", "advice" , "money" , "int" , "double" , "time" , "obligation" , "string" , "date" , "let"};
36+
foreach (var keyword in keywords)
37+
{
38+
result = Regex.Replace(result, $@"\b{keyword}\b", $"<span class='alfa-keyword'>{keyword}</span>", RegexOptions.None, TimeSpan.FromSeconds(1));
39+
}
40+
// Numbers
41+
result = Regex.Replace(result, "(?<=\\s|^)([0-9]+(\\.[0-9]+)?)(?=\\s|$)", "<span class='alfa-number'>$1</span>", RegexOptions.None, TimeSpan.FromSeconds(1));
42+
// Braces
43+
result = result.Replace("{", "<span class='alfa-brace'>{</span>");
44+
result = result.Replace("}", "<span class='alfa-brace'>}</span>");
45+
// HTML encode everything except our tags
46+
result = Regex.Replace(result, "(<[^>]+>|[^<]+)", match =>
47+
{
48+
if (match.Value.StartsWith("<"))
49+
return match.Value; // leave tags alone
50+
return System.Net.WebUtility.HtmlEncode(match.Value);
51+
});
52+
// Fix double-encoding of quotes inside attributes
53+
result = result.Replace("&#39;", "'").Replace("&quot;", "\"");
54+
return result;
55+
}
56+
57+
public HtmlString PolicyHtml(string policy) => new HtmlString(ColorizePolicy(policy));
58+
}
59+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@namespace AuthZenPolicyServer.Pages
2+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
namespace acmeCorp
2+
{
3+
4+
import Oasis.Functions.*
5+
import Oasis.Attributes.*
6+
import Enforcer.AuthZen.*
7+
8+
attribute ExpenseTotal { id ="total" type=money category=resourceCat}
9+
attribute ApproverId { id ="approver" type=string category=resourceCat}
10+
11+
//
12+
// Policies for handling the creation, and approval of expense claims
13+
//
14+
policyset expenses
15+
{
16+
target clause ResourceType == "expenses"
17+
apply denyUnlessPermit
18+
19+
policy CreateExpenseClaim
20+
policy SubmitExpenseClaim
21+
policy ViewClaimsToApprove
22+
policy ApproveAndRejectClaims
23+
}
24+
25+
policy CreateExpenseClaim {
26+
target clause Action == "CreateClaim"
27+
apply denyUnlessPermit
28+
29+
rule CanCreateExpenseClaim {
30+
condition Role == "employee"
31+
permit
32+
}
33+
34+
on deny
35+
{
36+
advice authZenContext
37+
{
38+
error = "Must be an employee to create a claim"
39+
}
40+
}
41+
}
42+
43+
policy SubmitExpenseClaim
44+
{
45+
apply permitUnlessDeny
46+
47+
target clause Action == "SubmitClaim"
48+
rule {
49+
condition ExpenseTotal > 1000:money and Role == "employee"
50+
deny
51+
on deny
52+
{
53+
advice authZenContext
54+
{
55+
error = "Claim must be less than 1000 GBP"
56+
}
57+
}
58+
}
59+
rule {
60+
condition Role != "employee"
61+
deny
62+
63+
on deny
64+
{
65+
advice authZenContext
66+
{
67+
error = "Must be an employee to submit a claim"
68+
}
69+
}
70+
}
71+
}
72+
73+
policy ViewClaimsToApprove
74+
{
75+
target clause Action == "ListClaimsToApprove"
76+
apply denyUnlessPermit
77+
78+
rule CanListClaims {
79+
condition Role == "manager"
80+
permit
81+
}
82+
83+
on deny
84+
{
85+
advice authZenContext
86+
{
87+
error = "Must be a manager to approve claims"
88+
}
89+
}
90+
}
91+
92+
policy ApproveAndRejectClaims
93+
{
94+
target clause Action == "AcceptClaim" or Action == "RejectClaim"
95+
apply permitUnlessDeny
96+
97+
rule {
98+
condition Subject.Identifier != ApproverId and Role == "manager"
99+
deny
100+
}
101+
}
102+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace acmeCorp
2+
{
3+
policyset global
4+
{
5+
apply firstApplicable
6+
policy expenses
7+
}
8+
}
9+
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using AuthZenPolicyServer;
2+
using Rsk.Enforcer;
3+
using Rsk.Enforcer.AuthZen;
4+
using Rsk.Enforcer.PAP.Store;
5+
using Rsk.Enforcer.PEP;
6+
7+
public class Program
8+
{
9+
public static void Main(string[] args)
10+
{
11+
var builder = WebApplication.CreateBuilder(args);
12+
13+
builder.Services.AddRazorPages();
14+
builder.Services
15+
.AddEnforcer("acmeCorp.global",options =>
16+
{
17+
options.Licensee = "DEMO";
18+
options.LicenseKey = "Get a free license from https://www.identityserver.com/products/enforcer";
19+
})
20+
.AddPolicyEnforcementPoint(o => o.Bias = PepBias.Deny)
21+
.AddAuthZen()
22+
.AddAuthZenAdvice()
23+
.AddPolicyAttributeProvider<SubjectAttributeProvider>()
24+
.AddEmbeddedPolicyStore(typeof(Program).Assembly, "AuthZenPolicyServer.Policies");
25+
26+
var app = builder.Build();
27+
28+
app.UseEnforcerAuthZen();
29+
app.UseStaticFiles();
30+
app.UseRouting();
31+
app.MapRazorPages();
32+
app.MapGet("/", context =>
33+
{
34+
context.Response.Redirect("/Home");
35+
return Task.CompletedTask;
36+
});
37+
app.Run();
38+
}
39+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"$schema": "https://json.schemastore.org/launchsettings.json",
3+
"profiles": {
4+
"http": {
5+
"commandName": "Project",
6+
"dotnetRunMessages": true,
7+
"launchBrowser": true,
8+
"applicationUrl": "http://localhost:5208",
9+
"environmentVariables": {
10+
"ASPNETCORE_ENVIRONMENT": "Development"
11+
}
12+
},
13+
"https": {
14+
"commandName": "Project",
15+
"dotnetRunMessages": true,
16+
"launchBrowser": true,
17+
"applicationUrl": "https://localhost:7064;http://localhost:5208",
18+
"environmentVariables": {
19+
"ASPNETCORE_ENVIRONMENT": "Development"
20+
}
21+
}
22+
}
23+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using Rsk.Enforcer.Oasis.PolicyModel;
2+
using Rsk.Enforcer.PIP;
3+
using Rsk.Enforcer.PolicyModels;
4+
5+
namespace AuthZenPolicyServer;
6+
7+
public class AcmeCorpPerson()
8+
{
9+
[PolicyAttributeValue(PolicyAttributeCategories.Subject, "role")]
10+
public IEnumerable<string> Roles { get; init; } = [];
11+
}
12+
13+
public class SubjectAttributeProvider : RecordAttributeValueProvider<AcmeCorpPerson>
14+
{
15+
private static readonly Dictionary<string, AcmeCorpPerson> people = new()
16+
{
17+
["bob"] = new AcmeCorpPerson() { Roles = ["employee"]},
18+
["alice"] = new AcmeCorpPerson() { Roles = ["employee","manager"]},
19+
};
20+
21+
protected override async Task<AcmeCorpPerson> GetRecordValue(IAttributeResolver attributeResolver,
22+
CancellationToken ct)
23+
{
24+
IReadOnlyCollection<string>? identifiers = await attributeResolver
25+
.Resolve<string>(Rsk.Enforcer.Oasis.Attributes.Subject.Identifier, ct);
26+
27+
string? identifier = identifiers.SingleOrDefault();
28+
if (identifier == null) return null!;
29+
30+
if (people.TryGetValue(identifier, out AcmeCorpPerson? person))
31+
{
32+
return person;
33+
}
34+
35+
return null!;
36+
}
37+
}

0 commit comments

Comments
 (0)