Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
ee57a28
feat: added first version of the script to add policies and permissions
bramcomyn Aug 18, 2025
b40d8cf
feat: setup cold start script
bramcomyn Aug 19, 2025
63944ac
fix: small bugs in cold start script + added npm script for easier ru…
bramcomyn Aug 19, 2025
14e16b9
docs: documented cold start problem and how to fix it
bramcomyn Aug 19, 2025
4a35607
docs: updated example to reflect latest change in cold start script
bramcomyn Aug 19, 2025
ce15ec5
feat: added more dynamic way to assign AS + documented this approach
bramcomyn Aug 19, 2025
19212ce
refactor: switch back to the static AS assignment and describe new id…
bramcomyn Aug 19, 2025
b89fa22
docs: link to proposal repositor
bramcomyn Aug 19, 2025
d781e62
docs: minor error in documentation of as assignment proposal fixed
bramcomyn Aug 19, 2025
3810302
feat: introduced manual and automatic refresh in ResourceExplorer.vue
bramcomyn Aug 20, 2025
7612af3
refactor: took ownership of the Controller class by renaming it (and …
bramcomyn Aug 20, 2025
da2483b
refactor: making controller instance global state among all components
bramcomyn Aug 20, 2025
3075e65
refactor: managed to make controller a global state for all its refer…
bramcomyn Aug 20, 2025
f8381ba
feat: provided hot swapping of controller instances in controller store
bramcomyn Aug 20, 2025
c35c667
docs: documented the access requests and grants flow
bramcomyn Aug 21, 2025
e9dc695
docs: documented minor details discussed with Wout
bramcomyn Aug 21, 2025
a367c0d
docs: added step 1 of the content negotiation procotol
bramcomyn Aug 21, 2025
79f1294
docs: removed comment
bramcomyn Aug 21, 2025
91f80d2
docs: specified step 2 of the content negotiation protocol
bramcomyn Aug 21, 2025
f52c18c
docs: specified steps 3 and 4 of the content negotiation protocol
bramcomyn Aug 21, 2025
01ab360
docs: final update to request-grant model vs dscn protocol
bramcomyn Aug 21, 2025
505723c
docs: changed prefixes in first example to SOTW prefixes etc
bramcomyn Aug 22, 2025
b789e30
docs: fixing typo + adding TODO for visualization
bramcomyn Aug 22, 2025
d455da1
docs: but they were fooled, for one more typo was fixed
bramcomyn Aug 22, 2025
cf650ac
docs: fix ttl prefixes in examples
bramcomyn Aug 22, 2025
3ae6148
docs: updated PATCH query
bramcomyn Aug 22, 2025
f16487d
fix: cold start script runs with tsx instead of ts-node
bramcomyn Aug 28, 2025
af06628
feat: added AS selection in header context menu component
bramcomyn Aug 29, 2025
91f49ed
feat: implemented controller store
bramcomyn Aug 29, 2025
70547ff
feat: subject permission table & resource explorer now make use of th…
bramcomyn Aug 29, 2025
c746320
feat: updated cold start script and documentation for requested changes
bramcomyn Aug 29, 2025
6757c0b
feat: cold start resources generation
bramcomyn Aug 29, 2025
fcb2876
feat: global controller is configurable from frontend
bramcomyn Aug 29, 2025
6f95c0a
docs: updated as-assignment.md
bramcomyn Aug 31, 2025
c446dc7
refactor: removing old access requests-related components
bramcomyn Sep 7, 2025
b10da36
feat: instantiating new access requests-related components
bramcomyn Sep 7, 2025
49809f3
fix: styling in panel container for resources tab was too high and wide
bramcomyn Sep 7, 2025
2c7f782
feat: showcase request access component implemented
bramcomyn Sep 8, 2025
51e5f41
feat: showcase grant access component implemented
bramcomyn Sep 8, 2025
1f0999b
feat: tooltips and styling for access requests and grants and header …
bramcomyn Sep 9, 2025
b493000
feat: added defaults for all values in cold start script
bramcomyn Sep 11, 2025
8844c17
feat: updated cold start script + its documentation
bramcomyn Sep 15, 2025
9e35dda
feat: added default action to cold start script
bramcomyn Sep 15, 2025
d3fee59
feat: hooked up frontend to backend
bramcomyn Sep 16, 2025
60ff1bc
fix: bug showing incoming access request both in 'your' as in 'incoming'
bramcomyn Sep 16, 2025
658a2a7
fix: removing uid from access requests
bramcomyn Sep 16, 2025
0dcede9
feat: minor changes in frontend design + fixing patch request
bramcomyn Sep 17, 2025
5d9ca59
feat: introduced simple auto refresh for GrantAccess and RequestAccess
bramcomyn Sep 18, 2025
666f682
Update documentation/cold-start.md
bramcomyn Sep 18, 2025
0ab79f9
Update documentation/cold-start.md
bramcomyn Sep 18, 2025
c3bf3cd
Update documentation/cold-start.md
bramcomyn Sep 18, 2025
6b8285e
feat: added .env options for policy and resource in cold start script
bramcomyn Sep 19, 2025
2293567
Merge branch 'feat/access-requests' of github.com:bramcomyn/loama int…
bramcomyn Sep 19, 2025
331c525
docs: added demo video
bramcomyn Sep 19, 2025
895bab4
docs Update documentation/access_grants_vs_dsnp.md
woutslabbinck Oct 9, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@
"node": ">= 20"
},
"dependencies": {
"@comunica/query-sparql": "^4.3.0",
"@inrupt/solid-client": "^2.0.1",
"@inrupt/solid-client-authn-browser": "^2.2.4",
"@inrupt/vocab-common-rdf": "^1.0.5",
"core-js": "^3.38.1",
"loama-common": "^1.0.0"
"loama-common": "^1.0.0",
"n3": "^1.26.0"
},
"devDependencies": {
"@types/core-js": "^2.5.8",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { BaseSubject, Index, IndexItem, Permission, ResourcePermissions, Resources, SubjectPermissions } from "../types";
import { BaseSubject, Index, IndexItem, Permission, ResourcePermissions, Resources } from "../types";
import { IAccessRequest, IController, IInboxConstructor, IStore, IStoreConstructor, SubjectConfig, SubjectConfigs, SubjectKey, SubjectType } from "../types/modules";
import { type AccessRequest as AccessRequestObject } from "../types/modules";
import { AccessRequest } from "./accessRequests/AccessRequest";
import { InruptAccessRequest } from "./accessRequests/InruptAccessRequest";
import { ODRLAccessRequest } from "./accessRequests/OdrlAccessRequest";
import { Mutex } from "./utils/Mutex";
import { ODRLAccessRequestService } from "./utils/OdrlAccessRequestService";

export class Controller<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends Mutex implements IController<T> {
/**
* Controller which makes it calls to the backend AS through ODRL requests.
* Makes use of the Inrupt SDK to authenticate users.
*/
export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends Mutex implements IController<T> {
private index: IStore<Index<T[keyof T & string]>>;
private resources: IStore<Resources>;
private accessRequest: AccessRequest;
private subjectConfigs: SubjectConfigs<T>;
private authorizationServerURL: string;

// TODO : Find a better way of constructing the controller with all the different modules
constructor(storeConstructor: IStoreConstructor, inboxConstructor: IInboxConstructor, subjects: SubjectConfigs<T>) {
constructor(storeConstructor: IStoreConstructor, inboxConstructor: IInboxConstructor, subjects: SubjectConfigs<T>, authorizationServerURL: string) {
super();
// There is currently no "easy" solution to get around the as IStore...
this.index = new storeConstructor("index.json", () => ({ id: "", items: [] })) as IStore<Index<T[keyof T & string]>>;
this.resources = new storeConstructor("resources.json", () => ({ id: "", items: [] })) as IStore<Resources>;;
this.accessRequest = new InruptAccessRequest(this as unknown as Controller<{}>, inboxConstructor, this.resources);
this.accessRequest = new ODRLAccessRequest(this as unknown as ODRLController<{}>, inboxConstructor, this.resources);
this.subjectConfigs = subjects;
this.authorizationServerURL = authorizationServerURL;
}

private getSubjectConfig<K extends SubjectKey<T>>(subject: T[K]): SubjectConfig<T, T[K]> {
Expand Down Expand Up @@ -146,4 +154,22 @@ export class Controller<T extends Record<keyof T, BaseSubject<keyof T & string>>
}
return this as unknown as IController<Record<K, B>>
}

// ! added for access requests
async requestAccess(permission: { action: string; resource: string; }): Promise<void> {
const webid = getDefaultSession().info.webId!;
await new ODRLAccessRequestService(this.authorizationServerURL).requestAccess(permission.resource, webid, permission.action);
}

async handleAccessRequest(requestId: string, status: 'accepted' | 'denied'): Promise<void> {
const webid = getDefaultSession().info.webId!;
await new ODRLAccessRequestService(this.authorizationServerURL).acceptOrDenyAccess(requestId, webid, status);
}

async getAccessRequests(): Promise<{
asRequestingParty: AccessRequestObject[];
asResourceOwner: AccessRequestObject[];
}> {
return new ODRLAccessRequestService(this.authorizationServerURL).retrieveAccessRequests(getDefaultSession().info.webId!);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Resources } from "@/types";
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { deleteFile } from "@inrupt/solid-client";

export class InruptAccessRequest extends AccessRequest implements IAccessRequest {
export class ODRLAccessRequest extends AccessRequest implements IAccessRequest {
constructor(controller: IController<{}>, inboxConstructor: IInboxConstructor, resourcesStore: IStore<Resources>) {
super(controller, inboxConstructor, resourcesStore)
}
Expand Down
4 changes: 2 additions & 2 deletions controller/src/classes/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export * from './permissionManager/inrupt';
export * from './permissionManager/odrl';
export * from './stores/InruptStore';
export * from './stores/BaseStore';
export * from './subjectResolvers';
export * from './Controller';
export * from './OdrlController';
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Access, AccessModes, getGroupAccessAll, getResourceInfoWithAcl } from "@inrupt/solid-client";
import { BaseSubject, IndexItem, Permission } from "../../../types";
import { IPermissionManager, SubjectKey } from "../../../types/modules";
import { InruptPermissionManager } from "./InruptPermissionManager";
import { ODRLPermissionManager } from "./OdrlPermissionManager";
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { setAgentAccess } from "@inrupt/solid-client/universal";
import { cacheBustedSessionFetch } from "../../../util";

export class GroupManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends InruptPermissionManager<T> implements IPermissionManager<T> {
export class GroupManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends ODRLPermissionManager<T> implements IPermissionManager<T> {
// This is a replacement for https://github.com/inrupt/solid-client-js/blob/eb8e86f61458ec76fa2244f7b38b7d7983bbd810/src/access/wac.ts#L262 because it is not exposed in the inrupt library
private async getGroupAccessAll(resource: string): Promise<Record<string, Access> | null> {
const session = getDefaultSession();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { Access, AccessModes, getSolidDataset, getThingAll } from "@inrupt/solid
import { SubjectPermissions, BaseSubject, IndexItem, Permission, ResourcePermissions } from "../../../types";
import { SubjectKey, TargetSubjects } from "../../../types/modules";
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { ODRL } from "../../../classes/utils/PolicyParser";
import { PolicyInterpreter } from "../../../classes/utils/PolicyInterpreter";
import { PolicyService } from "../../../classes/utils/PolicyService";
import { ODRL } from "../../utils/PolicyParser";
import { PolicyInterpreter } from "../../utils/PolicyInterpreter";
import { ODRLPolicyService } from "../../utils/OdrlPolicyService";
import { Store } from 'n3';

const ACCESS_MODES_TO_PERMISSION_MAPPING: Record<keyof (AccessModes & Access), Permission> = {
Expand All @@ -16,12 +16,13 @@ const ACCESS_MODES_TO_PERMISSION_MAPPING: Record<keyof (AccessModes & Access), P
controlWrite: Permission.Control,
}

/**
* A permission manager implementation using the inrupt sdk to actually update the ACL
* The "Inrupt" prefix is to indicate the usage of the inrupt sdk
* This permission manager can be used without the the InruptStore
*/
export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> {
export abstract class ODRLPermissionManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> {

protected readonly authorizationServerURL: string;

constructor(authorizationServerURL: string) {
this.authorizationServerURL = authorizationServerURL;
}

protected AccessModesToPermissions(accessModes: AccessModes | Access): Permission[] {
const permissions = new Set<Permission>();
Expand Down Expand Up @@ -84,7 +85,7 @@ export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubj
* TODO: split in subject
*/
public async getTargetPermissionsForUser(assignerId: string, assigneeId: string, targetId: string): Promise<Permission[]> {
const store: Store = await new PolicyService().fetchPolicies(assignerId);
const store: Store = await new ODRLPolicyService(this.authorizationServerURL).fetchPolicies(assignerId);
const target: TargetSubjects = new PolicyInterpreter().permissionsForOneResource(targetId, store);

// If there are no private permissions, or no private permissions for the assignee, return the public ones (or nothing if they don't exist)
Expand All @@ -105,7 +106,7 @@ export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubj
}

// Retrieve our policies
const store = await new PolicyService().fetchPolicies(webId);
const store = await new ODRLPolicyService(this.authorizationServerURL).fetchPolicies(webId);

// Get detailed info about the target
const interpreter = new PolicyInterpreter();
Expand Down Expand Up @@ -164,7 +165,7 @@ export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubj
throw new Error("User not logged in");
}

const store = await new PolicyService().fetchPolicies(webId);
const store = await new ODRLPolicyService(this.authorizationServerURL).fetchPolicies(webId);

// Collect target urls
const targetUrls = Array.from(new Set(store.getQuads(null, ODRL('target'), null, null).map(q => q.object.id)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { PolicyService } from "../../../classes/utils/PolicyService";
import { ODRLPolicyService } from "../../utils/OdrlPolicyService";
import { BaseSubject, IndexItem, Permission } from "../../../types";
import { IPermissionManager, SubjectKey } from "../../../types/modules";
import { InruptPermissionManager } from "./InruptPermissionManager";
import { ODRLPermissionManager } from "./OdrlPermissionManager";

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

//. NOTE: Currently, it doesn't do any recursive permission setting on containers
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
await new PolicyService().insertActionRule(resource, permissions)
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions)
}

async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
await new PolicyService().deleteActionRule(resource, permissions)
await new ODRLPolicyService(this.authorizationServerURL).deleteActionRule(resource, permissions)

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { BaseSubject, IndexItem, Permission } from "../../../types";
import { IPermissionManager, SubjectKey } from "../../../types/modules";
import { InruptPermissionManager } from "./InruptPermissionManager";
import { PolicyService } from "../../utils/PolicyService";
import { ODRLPermissionManager } from "./OdrlPermissionManager";
import { ODRLPolicyService } from "../../utils/OdrlPolicyService";

export class WebIdManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends InruptPermissionManager<T> implements IPermissionManager<T> {
export class WebIdManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends ODRLPermissionManager<T> implements IPermissionManager<T> {

// Create an action for this resource and this subject with the given permissions
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
await new PolicyService().insertActionRule(resource, permissions, subject.selector!.url);
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions, subject.selector!.url);
}

async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
await new PolicyService().deleteActionRule(resource, permissions, subject.selector!.url)
await new ODRLPolicyService(this.authorizationServerURL).deleteActionRule(resource, permissions, subject.selector!.url)
}

async editPermissions<K extends SubjectKey<T>>(resource: string, item: IndexItem, subject: T[K], permissions: Permission[]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './GroupManager';
export * from './InruptPermissionManager';
export * from './OdrlPermissionManager';
export * from './PublicManager';
export * from './WebIdManager';
Loading