Skip to content

Commit df7e7e1

Browse files
committed
feat: First implementation
1 parent 1a1eb12 commit df7e7e1

Some content is hidden

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

51 files changed

+3801
-123
lines changed

.componentsignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
[]
1+
[
2+
"WeakMap"
3+
]

README.md

Lines changed: 134 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,135 @@
1-
# Solid Authorization Evaluator
1+
# Solid Policy Engine
22

3-
TODO
3+
This package provides support for both [Web Access Control](https://solidproject.org/TR/2021/wac-20210711)(WAC)
4+
and [Access Control Policies](https://solid.github.io/authorization-panel/acp-specification/)(ACP) authorization.
5+
6+
## Main Components
7+
8+
### AuthorizationManager
9+
10+
This is the minimal interface of external functions a policy engine needs to be able to determine a result.
11+
12+
Due to the nature of Solid, an engine needs a way to find the parent container of a resource.
13+
This is done through the `getParent` function,
14+
which should return the identifier of the parent container,
15+
or `undefined` if the input is a root storage container.
16+
17+
Besides that the engine also needs a way to receive the relevant authorization data for a resource.
18+
In the case of WAC this would be the contents of the corresponding ACL resource.
19+
The `getAuthorizationData` should return this data if it exists,
20+
and `undefined` otherwise.
21+
22+
This package does not provide an implementation of this interface as this depends on the Solid server implementation.
23+
It is recommended to have some form of caching for `getAuthorizationData`.
24+
25+
### PolicyEngine
26+
27+
This is the core interface for the package.
28+
`getPermissions` is used to determine the permissions,
29+
while `getPermissionsWithReport` does the same but also generates an RDF report indicating how the result was achieved.
30+
The contents of the report depend on the type of authorization,
31+
as this will differ between WAC and ACP.
32+
33+
### AclPermissionsEngine
34+
35+
An implementation of `PolicyEngine` that converts ACL permissions to more generic permissions.
36+
It takes into account how ACL permissions have to be interpreted.
37+
Specifically applies the following three rules:
38+
39+
* `acl:Write` implies `acl:Append`.
40+
* To create a new resource you need `acl:Write` on the target, and `acl:Append` on the parent.
41+
* To delete a resource, you need `acl:Write` on the target, and `acl:Write` on the parent.
42+
43+
## Web Access Control
44+
45+
These are the classes and interfaces specifically for WAC.
46+
47+
### WacRepository / ManagedWacRepository
48+
49+
The `WacRepository` interface is used to determine the WAC authorization objects
50+
that are relevant when determining permissions for the given target.
51+
The `ManagedWacRepository` is an actual implementation that makes use of a `AuthorizationManager`
52+
to achieve this goal.
53+
54+
### WacPolicyEngine
55+
56+
The `WacPolicyEngine` is an implementation of `PolicyEngine` for WAC authorization.
57+
It requires a `WacRepository` to do the initial filtering.
58+
It then uses an `AccessChecker` to determine which of these authorizations are valid
59+
and generates its result based on that.
60+
61+
### Access Checker
62+
63+
There are several ways a WAC authorization might be valid:
64+
the credentials could have a matching agent,
65+
the agent could be part of the correct class,
66+
or the agent could be part of a matching group.
67+
68+
For each of those there is a separate access checker,
69+
and the result of these can then be combined using a `UnionAccessChecker`.
70+
In practice this means you generally want to define your `AccessChecker` as follows:
71+
72+
```ts
73+
const accessChecker = new UnionAccessChecker([
74+
new AgentAccessChecker(),
75+
new AgentClassAccessChecker(),
76+
new AgentGroupAccessChecker(),
77+
])
78+
```
79+
80+
## Access Control Policies
81+
82+
These are the classes and interfaces specifically for ACP.
83+
These work similarly to the WAC classes.
84+
85+
### AcpRepository / ManagedAcpRepository
86+
87+
The `AcpRepository` interface is used to determine the ACP authorization objects
88+
that are relevant when determining permissions for the given target.
89+
The `ManagedAcpRepository` is an actual implementation that makes use of a `AuthorizationManager`
90+
to achieve this goal.
91+
92+
### AcpPolicyEngine
93+
94+
The `AcpPolicyEngine` is an implementation of `PolicyEngine` for ACP authorization.
95+
It requires a `AcpRepository` to do the initial filtering.
96+
97+
## Example
98+
99+
Below is an example of how these classes can be set up and used to generate a permission report.
100+
The example focuses on WAC, but would be quite similar for ACP.
101+
102+
```ts
103+
// The manager is an external object, dependent on the server implementation
104+
async function generateReport(
105+
target: string,
106+
credentials: Credentials,
107+
manager: AuthorizationManager,
108+
permissions?: string[]
109+
): Promise<PermissionReport> {
110+
// The AccessChecker determines if WAC authorizations are valid
111+
const accessChecker = new UnionAccessChecker([
112+
new AgentAccessChecker(),
113+
new AgentClassAccessChecker(),
114+
new AgentGroupAccessChecker(),
115+
]);
116+
117+
// The engine needs a repository to get the authorizations
118+
const wacEngine = new WacPolicyEngine(accessChecker, new ManagedWacRepository(wacManager));
119+
120+
// This engine will make sure the ACL permissions get interpreted correctly
121+
const engine = new AclPermissionsEngine(wacEngine, manager);
122+
123+
// The engine can then generate a report for the given target and credentials
124+
const report = await engine.getPermissionsWithReport(target, credentials, permissions);
125+
}
126+
```
127+
128+
## Components.js
129+
130+
The config folder contains [Components.js](https://github.com/LinkedSoftwareDependencies/Components.js/) configurations
131+
which can be used in your project to add the necessary authorization components.
132+
`acp.json` contains the necessary parts for ACP authorization,
133+
and `wac.json` those for WAC.
134+
135+
urn:solidlab:policy-engine:AuthorizationManager
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/policy-engine/^0.0.0/components/context.jsonld",
3+
"@graph": [
4+
{
5+
"comment": "Checks access based on the agent being authenticated or not.",
6+
"@id": "urn:solidlab:policy-engine:AgentClassAccessChecker",
7+
"@type": "AgentClassAccessChecker"
8+
}
9+
]
10+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/policy-engine/^0.0.0/components/context.jsonld",
3+
"@graph": [
4+
{
5+
"comment": "Checks if the agent belongs to a group that has access.",
6+
"@id": "urn:solidlab:policy-engine:AgentGroupAccessChecker",
7+
"@type": "AgentGroupAccessChecker"
8+
}
9+
]
10+
}

config/access-checkers/agent.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/policy-engine/^0.0.0/components/context.jsonld",
3+
"@graph": [
4+
{
5+
"comment": "Checks if a specific WebID has access.",
6+
"@id": "urn:solidlab:policy-engine:AgentAccessChecker",
7+
"@type": "AgentAccessChecker"
8+
}
9+
]
10+
}

config/acp.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/policy-engine/^7.0.0/components/context.jsonld",
3+
"@graph": [
4+
{
5+
"@id": "urn:solidlab:policy-engine:AcpManager",
6+
"@type": "ManagedAcpRepository",
7+
"manager": "urn:solidlab:policy-engine:AuthorizationManager"
8+
},
9+
10+
{
11+
"@id": "urn:solidlab:policy-engine:AcpPolicyEngine",
12+
"@type": "AcpPolicyEngine",
13+
"repository": { "@id": "urn:solidlab:policy-engine:AcpManager" }
14+
},
15+
16+
{
17+
"@id": "urn:solidlab:policy-engine:PolicyEngine",
18+
"@type": "AclPermissionsEngine",
19+
"engine": { "@id": "urn:solidlab:policy-engine:AcpPolicyEngine" },
20+
"manager": { "@id": "urn:solidlab:policy-engine:AuthorizationManager" }
21+
}
22+
]
23+
}

config/dummy.json

Lines changed: 0 additions & 3 deletions
This file was deleted.

config/wac.json

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solidlab/policy-engine/^7.0.0/components/context.jsonld",
3+
"import": [
4+
"sae:config/access-checkers/agent.json",
5+
"sae:config/access-checkers/agent-class.json",
6+
"sae:config/access-checkers/agent-group.json"
7+
],
8+
"@graph": [
9+
{
10+
"@id": "urn:solidlab:policy-engine:WacManager",
11+
"@type": "ManagedWacRepository",
12+
"manager": "urn:solidlab:policy-engine:AuthorizationManager"
13+
},
14+
15+
{
16+
"@id": "urn:solidlab:policy-engine:AccessChecker",
17+
"@type": "UnionAccessChecker",
18+
"handlers": [
19+
{ "@id": "urn:solidlab:policy-engine:AgentAccessChecker" },
20+
{ "@id": "urn:solidlab:policy-engine:AgentClassAccessChecker" },
21+
{ "@id": "urn:solidlab:policy-engine:AgentGroupAccessChecker" }
22+
]
23+
},
24+
25+
{
26+
"@id": "urn:solidlab:policy-engine:WacPolicyEngine",
27+
"@type": "WacPolicyEngine",
28+
"accessChecker": { "@id": "urn:solidlab:policy-engine:AccessChecker" },
29+
"repository": { "@id": "urn:solidlab:policy-engine:WacManager" }
30+
},
31+
32+
{
33+
"@id": "urn:solidlab:policy-engine:PolicyEngine",
34+
"@type": "AclPermissionsEngine",
35+
"engine": { "@id": "urn:solidlab:policy-engine:WacPolicyEngine" },
36+
"manager": { "@id": "urn:solidlab:policy-engine:AuthorizationManager" }
37+
}
38+
]
39+
}

eslint.config.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
const opinionated = require('opinionated-eslint-config');
22

33
module.exports = opinionated({
4+
// Don't want to lint test assets, or TS snippets in markdown files
5+
ignores: [ '**/*.md' ],
46
typescript: {
57
tsconfigPath: [ './tsconfig.json', './scripts/tsconfig.json', './test/tsconfig.json' ],
68
},

0 commit comments

Comments
 (0)