Skip to content

Commit 6591266

Browse files
authored
SR-190: Access control conversion (#25)
1 parent 8d84f27 commit 6591266

10 files changed

Lines changed: 434 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@types/n3": "^1",
4646
"@types/node": "^24",
4747
"n3": "^2",
48+
"rdf-isomorphic": "^2.0.1",
4849
"typescript": "^6"
4950
},
5051
"engines": {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { TranslationError } from "./TranslationError.js"
2+
3+
export class AcpToWacError extends TranslationError {
4+
constructor(property: string, cause?: any) {
5+
super("acp", property, "WAC", cause)
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export class TranslationError extends Error {
2+
constructor(prefix: string, property: string, target: string, cause?: any) {
3+
super(`${prefix}:${property} cannot be translated to ${target}`)
4+
this.name = this.constructor.name
5+
this.cause = cause
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { TranslationError } from "./TranslationError.js"
2+
3+
export class WacToAcpError extends TranslationError {
4+
constructor(property: string, cause?: any) {
5+
super("acl", property, "ACP", cause)
6+
}
7+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { AccessControl, AccessControlResource, AcrDataset } from "../acp/mod.js"
2+
import type { DatasetCore } from "@rdfjs/types"
3+
import { Authorization } from "../wac/mod.js"
4+
import { ACL, ACP, FOAF } from "../vocabulary/mod.js"
5+
import { AcpToWacError } from "./AcpToWacError.js"
6+
7+
export function acpToWac(source: AcrDataset, target: DatasetCore) {
8+
if (source.acr === undefined) {
9+
return
10+
}
11+
12+
const auth = new Authorization(source.acr.factory.blankNode(), target, source.acr.factory)
13+
auth.type.add(ACL.Authorization)
14+
15+
processAcr(source.acr, auth)
16+
}
17+
18+
function processAcr(source: AccessControlResource, target: Authorization) {
19+
if (source.accessControl.size > 0) {
20+
target.accessTo = source.resource
21+
}
22+
23+
if (source.memberAccessControl.size > 0) {
24+
target.default = source.resource
25+
}
26+
27+
for (const ac of source.accessControl) {
28+
processAc(ac, target)
29+
}
30+
31+
for (const ac of source.memberAccessControl) {
32+
processAc(ac, target)
33+
}
34+
}
35+
36+
function processAc(source: AccessControl, target: Authorization) {
37+
for (const policy of source.apply) {
38+
if (policy.deny.size > 0) {
39+
throw new AcpToWacError("deny")
40+
}
41+
42+
if (policy.anyOf.size > 0) {
43+
throw new AcpToWacError("anyOf")
44+
}
45+
46+
if (policy.noneOf.size > 0) {
47+
throw new AcpToWacError("noneOf")
48+
}
49+
50+
for (const mode of policy.allow) {
51+
target.mode.add(mode)
52+
}
53+
54+
for (const matcher of policy.allOf) {
55+
if (matcher.client.size > 0) {
56+
throw new AcpToWacError("client")
57+
}
58+
59+
if (matcher.issuer.size > 0) {
60+
throw new AcpToWacError("issuer")
61+
}
62+
63+
if (matcher.vc.size > 0) {
64+
throw new AcpToWacError("vc")
65+
}
66+
67+
for (const agent of matcher.agent) {
68+
if (agent === ACP.CreatorAgent) {
69+
throw new AcpToWacError("CreatorAgent")
70+
}
71+
72+
if (agent === ACP.OwnerAgent) {
73+
throw new AcpToWacError("OwnerAgent")
74+
}
75+
76+
if (agent === ACP.PublicAgent) {
77+
target.agent.add(FOAF.Agent)
78+
} else if (agent === ACP.AuthenticatedAgent) {
79+
target.agent.add(ACL.AuthenticatedAgent)
80+
} else {
81+
target.agent.add(agent)
82+
}
83+
}
84+
}
85+
}
86+
}

src/accessControlConversion/mod.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export * from "./wacToAcp.js"
2+
export * from "./acpToWac.js"
3+
export * from "./WacToAcpError.js"
4+
export * from "./TranslationError.js"
5+
export * from "./AcpToWacError.js"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { AclResource, Authorization, } from "../wac/mod.js"
2+
import { AccessControl, AccessControlResource, Matcher, Policy } from "../acp/mod.js"
3+
import type { DatasetCore } from "@rdfjs/types"
4+
import { WacToAcpError } from "./WacToAcpError.js"
5+
import { ACL, ACP, FOAF } from "../vocabulary/mod.js"
6+
7+
export function wacToAcp(source: AclResource, target: DatasetCore) {
8+
for (const auth of source.authorizations) {
9+
processAuthorization(auth, target)
10+
}
11+
}
12+
13+
function processAuthorization(source: Authorization, target: DatasetCore) {
14+
if (source.origin.size > 0) throw new WacToAcpError("origin")
15+
16+
if (source.accessTo !== undefined) {
17+
const acr = new AccessControlResource(source.factory.blankNode(), target, source.factory)
18+
populatePolicy(acr, source, source.accessTo, acr.accessControl)
19+
}
20+
21+
if (source.default !== undefined) {
22+
const acr = new AccessControlResource(source.factory.blankNode(), target, source.factory)
23+
populatePolicy(acr, source, source.default, acr.memberAccessControl)
24+
}
25+
}
26+
27+
function populatePolicy(acr: AccessControlResource, auth: Authorization, resource: string, accessControls: Set<AccessControl>) {
28+
const accessControl = new AccessControl(acr.factory.blankNode(), acr.dataset, acr.factory)
29+
const policy = new Policy(acr.factory.blankNode(), acr.dataset, acr.factory)
30+
const matcher = new Matcher(acr.factory.blankNode(), acr.dataset, acr.factory)
31+
32+
accessControls.add(accessControl)
33+
accessControl.apply.add(policy)
34+
policy.allOf.add(matcher)
35+
36+
acr.resource = resource
37+
38+
for (const mode of auth.mode) {
39+
policy.allow.add(mode)
40+
}
41+
42+
for (const agent of auth.agent) {
43+
if (agent === FOAF.Agent) {
44+
matcher.agent.add(ACP.PublicAgent)
45+
} else if (agent === ACL.AuthenticatedAgent) {
46+
matcher.agent.add(ACP.AuthenticatedAgent)
47+
} else matcher.agent.add(agent)
48+
}
49+
50+
for (const member of auth.agentGroup?.hasMember ?? []) {
51+
matcher.agent.add(member)
52+
}
53+
}

src/mod.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ export * from "./acp/mod.js"
22
export * from "./solid/mod.js"
33
export * from "./wac/mod.js"
44
export * from "./webid/mod.js"
5+
export * from "./accessControlConversion/mod.js"

src/vocabulary/acp.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ export const ACP = {
1010
vc: "http://www.w3.org/ns/solid/acp#vc",
1111
allow: "http://www.w3.org/ns/solid/acp#allow",
1212
deny: "http://www.w3.org/ns/solid/acp#deny",
13+
CreatorAgent: "http://www.w3.org/ns/solid/acp#CreatorAgent",
14+
OwnerAgent: "http://www.w3.org/ns/solid/acp#OwnerAgent",
15+
PublicAgent: "http://www.w3.org/ns/solid/acp#PublicAgent",
16+
AuthenticatedAgent: "http://www.w3.org/ns/solid/acp#AuthenticatedAgent",
1317
anyOf: "http://www.w3.org/ns/solid/acp#anyOf",
1418
allOf: "http://www.w3.org/ns/solid/acp#allOf",
1519
noneOf: "http://www.w3.org/ns/solid/acp#noneOf",

0 commit comments

Comments
 (0)