Skip to content

Commit 16a4ed4

Browse files
Merge pull request #44 from lennertdr/development
This merge contains a first working version of LOAMA that manages ODRL policies and communicates with the policy management API of an User-Managed Access (UMA) Authorization Server (AS) (https://github.com/SolidLabResearch/user-managed-access/tree/feat/policy-endpoint). Extra issues will be made regarding how to get started with this functionality.
2 parents ab18e49 + 3e62c72 commit 16a4ed4

19 files changed

Lines changed: 792 additions & 355 deletions

assets/loama-extended.drawio.png

32 KB
Loading

assets/loama-short.drawio.png

17.5 KB
Loading

controller/src/classes/Controller.ts

Lines changed: 47 additions & 234 deletions
Large diffs are not rendered by default.

controller/src/classes/permissionManager/inrupt/GroupManager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,6 @@ export class GroupManager<T extends Record<keyof T, BaseSubject<keyof T & string
5858
isEnabled: true,
5959
}))
6060
}
61+
62+
type = 'groupManager'
6163
}

controller/src/classes/permissionManager/inrupt/InruptPermissionManager.ts

Lines changed: 102 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Access, AccessModes, getSolidDataset, getThingAll } from "@inrupt/solid-client";
22
import { SubjectPermissions, BaseSubject, IndexItem, Permission, ResourcePermissions } from "../../../types";
3-
import { SubjectKey } from "../../../types/modules";
3+
import { SubjectKey, TargetSubjects } from "../../../types/modules";
44
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
5+
import { ODRL } from "../../../classes/utils/PolicyParser";
6+
import { PolicyInterpreter } from "../../../classes/utils/PolicyInterpreter";
7+
import { PolicyService } from "../../../classes/utils/PolicyService";
8+
import { Store } from 'n3';
59

610
const ACCESS_MODES_TO_PERMISSION_MAPPING: Record<keyof (AccessModes & Access), Permission> = {
711
read: Permission.Read,
@@ -12,7 +16,6 @@ const ACCESS_MODES_TO_PERMISSION_MAPPING: Record<keyof (AccessModes & Access), P
1216
controlWrite: Permission.Control,
1317
}
1418

15-
1619
/**
1720
* A permission manager implementation using the inrupt sdk to actually update the ACL
1821
* The "Inrupt" prefix is to indicate the usage of the inrupt sdk
@@ -75,38 +78,107 @@ export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubj
7578
return accessModes;
7679
}
7780

78-
abstract getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string): Promise<SubjectPermissions<T[K]>[]>
81+
/**
82+
* Function to specifically get the permission list for an assignee on a certain target
83+
*
84+
* TODO: split in subject
85+
*/
86+
public async getTargetPermissionsForUser(assignerId: string, assigneeId: string, targetId: string): Promise<Permission[]> {
87+
const store: Store = await new PolicyService().fetchPolicies(assignerId);
88+
const target: TargetSubjects = new PolicyInterpreter().permissionsForOneResource(targetId, store);
89+
90+
// If there are no private permissions, or no private permissions for the assignee, return the public ones (or nothing if they don't exist)
91+
if (!target.private || !target.private.get(assigneeId)) return Array.from(target.public?.permissions! ?? [])
92+
93+
return Array.from(target.private.get(assigneeId)?.permissions!) ?? []
94+
}
7995

80-
async getContainerPermissionList(containerUrl: string, resourceToSkip: string[] = []) {
96+
97+
public async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string): Promise<SubjectPermissions<T[K]>[]> {
98+
// Extract our webID
8199
const session = getDefaultSession();
82-
// this request is cached, so it doesn't matter if it's emitted multiple times in a short span
83-
const dataset = await getSolidDataset(containerUrl, { fetch: session.fetch });
84-
const results = await Promise.allSettled(
85-
getThingAll(dataset)
86-
.map(async (resource) => {
87-
if (resourceToSkip.includes(resource.url)) {
88-
return {
89-
resourceUrl: resource.url,
90-
// We can set this to false as this is the default & getting doubled checked in the controller
91-
canRequestAccess: false,
92-
permissionsPerSubject: [],
93-
}
94-
}
95-
return {
96-
resourceUrl: resource.url,
97-
// We can set this to false as this is the default & getting doubled checked in the controller
98-
canRequestAccess: false,
99-
permissionsPerSubject: await this.getRemotePermissions(resource.url)
100-
}
100+
const webId = session.info.webId;
101+
102+
// We must be logged on
103+
if (!webId) {
104+
throw new Error("User not logged in");
105+
}
106+
107+
// Retrieve our policies
108+
const store = await new PolicyService().fetchPolicies(webId);
109+
110+
// Get detailed info about the target
111+
const interpreter = new PolicyInterpreter();
112+
const target: TargetSubjects = interpreter.permissionsForOneResource(resourceUrl, store);
113+
114+
115+
if (target) {
116+
const subjectPermissions: SubjectPermissions<T[K]>[] = [];
117+
// Add the owner information
118+
subjectPermissions.push({
119+
subject: {
120+
type: "webId",
121+
selector: { url: target.assigner }
122+
} as unknown as T[K],
123+
permissions: [Permission.Append, Permission.Control, Permission.Create, Permission.Read, Permission.Write],
124+
isEnabled: true,
125+
targetId: target.targetUrl
126+
})
127+
128+
// Add the public information
129+
if (target.public && target.public.permissions.size > 0) subjectPermissions.push({
130+
subject: {
131+
type: "public",
132+
} as unknown as T[K],
133+
permissions: Array.from(target.public.permissions),
134+
isEnabled: true, // Not yet implemented, there is no odrl equivalent?
135+
targetId: target.targetUrl
136+
})
137+
138+
// Add the private subjects
139+
if (target.private) target.private.forEach(subject => {
140+
if (subject.permissions.size > 0) subjectPermissions.push({
141+
subject: {
142+
type: "webId",
143+
selector: { url: subject.subject }
144+
} as unknown as T[K],
145+
permissions: Array.from(subject.permissions),
146+
isEnabled: true, // Not yet implemented, there is no odrl equivalent?
147+
targetId: target.targetUrl
101148
})
102-
)
103-
return results.reduce<ResourcePermissions<T[keyof T]>[]>((arr, v) => {
104-
if (v.status == "fulfilled") {
105-
arr.push(v.value);
106-
}
107-
return arr;
108-
}, [])
149+
})
150+
return subjectPermissions;
151+
}
152+
153+
return [];
154+
}
155+
156+
157+
async getContainerPermissionList(containerUrl: string, resourceToSkip: string[] = []): Promise<ResourcePermissions<T[keyof T]>[]> {
158+
// Extract our webID
159+
const session = getDefaultSession();
160+
const webId = session.info.webId;
161+
162+
// We must be logged on
163+
if (!webId) {
164+
throw new Error("User not logged in");
165+
}
166+
167+
const store = await new PolicyService().fetchPolicies(webId);
168+
169+
// Collect target urls
170+
const targetUrls = Array.from(new Set(store.getQuads(null, ODRL('target'), null, null).map(q => q.object.id)));
171+
const resourcePermissions: ResourcePermissions<T[keyof T]>[] = []
172+
for (const targetUrl of targetUrls) {
173+
const perms = await this.getRemotePermissions(targetUrl);
174+
resourcePermissions.push({
175+
resourceUrl: targetUrl,
176+
canRequestAccess: true, // TODO: based on proper access logic
177+
permissionsPerSubject: perms
178+
})
179+
}
109180

181+
return resourcePermissions;
110182
}
111183

112184
shouldDeleteOnAllRevoked() { return true }
Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,25 @@
1-
import { AccessModes, getSolidDataset, getThingAll } from "@inrupt/solid-client";
2-
import { BaseSubject, IndexItem, Permission, ResourcePermissions } from "../../../types";
1+
import { PolicyService } from "../../../classes/utils/PolicyService";
2+
import { BaseSubject, IndexItem, Permission } from "../../../types";
33
import { IPermissionManager, SubjectKey } from "../../../types/modules";
44
import { InruptPermissionManager } from "./InruptPermissionManager";
5-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
6-
import { getPublicAccess, setPublicAccess } from "@inrupt/solid-client/universal";
7-
import { cacheBustedSessionFetch } from "../../../util";
85

96
export class PublicManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends InruptPermissionManager<T> implements IPermissionManager<T> {
107

11-
private async updateACL<K extends SubjectKey<T>>(resource: string, subject: T[K], accessModes: Partial<AccessModes>) {
12-
const session = getDefaultSession();
13-
await setPublicAccess(resource, accessModes, {
14-
fetch: session.fetch
15-
})
16-
}
17-
188
//. NOTE: Currently, it doesn't do any recursive permission setting on containers
199
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
20-
const accessModes = this.permissionsToAccessModes(permissions, []);
21-
await this.updateACL(resource, subject, accessModes)
10+
await new PolicyService().insertActionRule(resource, permissions)
2211
}
2312

24-
async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K]) {
25-
await this.updateACL(resource, subject, {});
13+
async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
14+
await new PolicyService().deleteActionRule(resource, permissions)
15+
2616
}
2717

2818
async editPermissions<K extends SubjectKey<T>>(resource: string, item: IndexItem, subject: T[K], permissions: Permission[]) {
29-
const accessModes = this.editPermissionsToAccessModes(item, permissions);
30-
await this.updateACL(resource, subject, accessModes)
19+
// not needed
3120
}
3221

33-
async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string) {
34-
const session = getDefaultSession();
35-
const publicAccess = await getPublicAccess(resourceUrl, { fetch: cacheBustedSessionFetch(session) })
36-
37-
if (!publicAccess) {
38-
return [];
39-
}
40-
41-
return [{
42-
subject: {
43-
type: "public",
44-
} as T[K],
45-
permissions: this.AccessModesToPermissions(publicAccess),
46-
isEnabled: true,
47-
}]
48-
}
22+
type = 'public'
23+
24+
4925
}
Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,22 @@
1-
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
21
import { BaseSubject, IndexItem, Permission } from "../../../types";
32
import { IPermissionManager, SubjectKey } from "../../../types/modules";
43
import { InruptPermissionManager } from "./InruptPermissionManager";
5-
import { getAgentAccessAll, setAgentAccess } from "@inrupt/solid-client/universal";
6-
import { AccessModes, } from "@inrupt/solid-client";
7-
import { cacheBustedSessionFetch } from "../../../util";
4+
import { PolicyService } from "../../utils/PolicyService";
85

96
export class WebIdManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends InruptPermissionManager<T> implements IPermissionManager<T> {
10-
private async updateACL<K extends SubjectKey<T>>(resource: string, subject: T[K], accessModes: Partial<AccessModes>) {
11-
const session = getDefaultSession();
12-
if (!subject.selector?.url) {
13-
throw new Error("Missing url selector on WebID subject")
14-
}
15-
await setAgentAccess(resource, subject.selector.url, accessModes, {
16-
fetch: session.fetch
17-
})
18-
}
19-
//. NOTE: Currently, it doesn't do any recursive permission setting on containers
7+
8+
// Create an action for this resource and this subject with the given permissions
209
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
21-
const accessModes = this.permissionsToAccessModes(permissions, []);
22-
await this.updateACL(resource, subject, accessModes)
10+
await new PolicyService().insertActionRule(resource, permissions, subject.selector!.url);
2311
}
2412

25-
async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K]) {
26-
await this.updateACL(resource, subject, {});
13+
async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
14+
await new PolicyService().deleteActionRule(resource, permissions, subject.selector!.url)
2715
}
2816

2917
async editPermissions<K extends SubjectKey<T>>(resource: string, item: IndexItem, subject: T[K], permissions: Permission[]) {
30-
const accessModes = this.editPermissionsToAccessModes(item, permissions);
31-
await this.updateACL(resource, subject, accessModes)
18+
// not needed
3219
}
3320

34-
async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string) {
35-
const session = getDefaultSession();
36-
const agentAccess = await getAgentAccessAll(resourceUrl, { fetch: cacheBustedSessionFetch(session) });
37-
38-
if (!agentAccess) {
39-
return [];
40-
}
41-
42-
return Object.entries(agentAccess).map(([url, access]) => ({
43-
// @ts-expect-error selector is required for webId
44-
subject: {
45-
type: "webId",
46-
selector: { url },
47-
} as T[K],
48-
permissions: this.AccessModesToPermissions(access),
49-
isEnabled: true,
50-
}))
51-
}
21+
type = "webId"
5222
}

0 commit comments

Comments
 (0)