Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file added assets/loama-extended.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/loama-short.drawio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
281 changes: 47 additions & 234 deletions controller/src/classes/Controller.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,6 @@ export class GroupManager<T extends Record<keyof T, BaseSubject<keyof T & string
isEnabled: true,
}))
}

type = 'groupManager'
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { Access, AccessModes, getSolidDataset, getThingAll } from "@inrupt/solid-client";
import { SubjectPermissions, BaseSubject, IndexItem, Permission, ResourcePermissions } from "../../../types";
import { SubjectKey } from "../../../types/modules";
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 { Store } from 'n3';

const ACCESS_MODES_TO_PERMISSION_MAPPING: Record<keyof (AccessModes & Access), Permission> = {
read: Permission.Read,
Expand All @@ -12,7 +16,6 @@ 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
Expand Down Expand Up @@ -75,38 +78,107 @@ export abstract class InruptPermissionManager<T extends Record<keyof T, BaseSubj
return accessModes;
}

abstract getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string): Promise<SubjectPermissions<T[K]>[]>
/**
* Function to specifically get the permission list for an assignee on a certain target
*
* TODO: split in subject
*/
public async getTargetPermissionsForUser(assignerId: string, assigneeId: string, targetId: string): Promise<Permission[]> {
const store: Store = await new PolicyService().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)
if (!target.private || !target.private.get(assigneeId)) return Array.from(target.public?.permissions! ?? [])

return Array.from(target.private.get(assigneeId)?.permissions!) ?? []
}

async getContainerPermissionList(containerUrl: string, resourceToSkip: string[] = []) {

public async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string): Promise<SubjectPermissions<T[K]>[]> {
// Extract our webID
const session = getDefaultSession();
// this request is cached, so it doesn't matter if it's emitted multiple times in a short span
const dataset = await getSolidDataset(containerUrl, { fetch: session.fetch });
const results = await Promise.allSettled(
getThingAll(dataset)
.map(async (resource) => {
if (resourceToSkip.includes(resource.url)) {
return {
resourceUrl: resource.url,
// We can set this to false as this is the default & getting doubled checked in the controller
canRequestAccess: false,
permissionsPerSubject: [],
}
}
return {
resourceUrl: resource.url,
// We can set this to false as this is the default & getting doubled checked in the controller
canRequestAccess: false,
permissionsPerSubject: await this.getRemotePermissions(resource.url)
}
const webId = session.info.webId;

// We must be logged on
if (!webId) {
throw new Error("User not logged in");
}

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

// Get detailed info about the target
const interpreter = new PolicyInterpreter();
const target: TargetSubjects = interpreter.permissionsForOneResource(resourceUrl, store);


if (target) {
const subjectPermissions: SubjectPermissions<T[K]>[] = [];
// Add the owner information
subjectPermissions.push({
subject: {
type: "webId",
selector: { url: target.assigner }
} as unknown as T[K],
permissions: [Permission.Append, Permission.Control, Permission.Create, Permission.Read, Permission.Write],
isEnabled: true,
targetId: target.targetUrl
})

// Add the public information
if (target.public && target.public.permissions.size > 0) subjectPermissions.push({
subject: {
type: "public",
} as unknown as T[K],
permissions: Array.from(target.public.permissions),
isEnabled: true, // Not yet implemented, there is no odrl equivalent?
targetId: target.targetUrl
})

// Add the private subjects
if (target.private) target.private.forEach(subject => {
if (subject.permissions.size > 0) subjectPermissions.push({
subject: {
type: "webId",
selector: { url: subject.subject }
} as unknown as T[K],
permissions: Array.from(subject.permissions),
isEnabled: true, // Not yet implemented, there is no odrl equivalent?
targetId: target.targetUrl
})
)
return results.reduce<ResourcePermissions<T[keyof T]>[]>((arr, v) => {
if (v.status == "fulfilled") {
arr.push(v.value);
}
return arr;
}, [])
})
return subjectPermissions;
}

return [];
}


async getContainerPermissionList(containerUrl: string, resourceToSkip: string[] = []): Promise<ResourcePermissions<T[keyof T]>[]> {
// Extract our webID
const session = getDefaultSession();
const webId = session.info.webId;

// We must be logged on
if (!webId) {
throw new Error("User not logged in");
}

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

// Collect target urls
const targetUrls = Array.from(new Set(store.getQuads(null, ODRL('target'), null, null).map(q => q.object.id)));
const resourcePermissions: ResourcePermissions<T[keyof T]>[] = []
for (const targetUrl of targetUrls) {
const perms = await this.getRemotePermissions(targetUrl);
resourcePermissions.push({
resourceUrl: targetUrl,
canRequestAccess: true, // TODO: based on proper access logic
permissionsPerSubject: perms
})
}

return resourcePermissions;
}

shouldDeleteOnAllRevoked() { return true }
Expand Down
44 changes: 10 additions & 34 deletions controller/src/classes/permissionManager/inrupt/PublicManager.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,25 @@
import { AccessModes, getSolidDataset, getThingAll } from "@inrupt/solid-client";
import { BaseSubject, IndexItem, Permission, ResourcePermissions } from "../../../types";
import { PolicyService } from "../../../classes/utils/PolicyService";
import { BaseSubject, IndexItem, Permission } from "../../../types";
import { IPermissionManager, SubjectKey } from "../../../types/modules";
import { InruptPermissionManager } from "./InruptPermissionManager";
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { getPublicAccess, setPublicAccess } from "@inrupt/solid-client/universal";
import { cacheBustedSessionFetch } from "../../../util";

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

private async updateACL<K extends SubjectKey<T>>(resource: string, subject: T[K], accessModes: Partial<AccessModes>) {
const session = getDefaultSession();
await setPublicAccess(resource, accessModes, {
fetch: session.fetch
})
}

//. 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> {
const accessModes = this.permissionsToAccessModes(permissions, []);
await this.updateACL(resource, subject, accessModes)
await new PolicyService().insertActionRule(resource, permissions)
}

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

}

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

async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string) {
const session = getDefaultSession();
const publicAccess = await getPublicAccess(resourceUrl, { fetch: cacheBustedSessionFetch(session) })

if (!publicAccess) {
return [];
}

return [{
subject: {
type: "public",
} as T[K],
permissions: this.AccessModesToPermissions(publicAccess),
isEnabled: true,
}]
}
type = 'public'


}
46 changes: 8 additions & 38 deletions controller/src/classes/permissionManager/inrupt/WebIdManager.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,22 @@
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
import { BaseSubject, IndexItem, Permission } from "../../../types";
import { IPermissionManager, SubjectKey } from "../../../types/modules";
import { InruptPermissionManager } from "./InruptPermissionManager";
import { getAgentAccessAll, setAgentAccess } from "@inrupt/solid-client/universal";
import { AccessModes, } from "@inrupt/solid-client";
import { cacheBustedSessionFetch } from "../../../util";
import { PolicyService } from "../../utils/PolicyService";

export class WebIdManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends InruptPermissionManager<T> implements IPermissionManager<T> {
private async updateACL<K extends SubjectKey<T>>(resource: string, subject: T[K], accessModes: Partial<AccessModes>) {
const session = getDefaultSession();
if (!subject.selector?.url) {
throw new Error("Missing url selector on WebID subject")
}
await setAgentAccess(resource, subject.selector.url, accessModes, {
fetch: session.fetch
})
}
//. NOTE: Currently, it doesn't do any recursive permission setting on containers

// 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> {
const accessModes = this.permissionsToAccessModes(permissions, []);
await this.updateACL(resource, subject, accessModes)
await new PolicyService().insertActionRule(resource, permissions, subject.selector!.url);
}

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

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

async getRemotePermissions<K extends SubjectKey<T>>(resourceUrl: string) {
const session = getDefaultSession();
const agentAccess = await getAgentAccessAll(resourceUrl, { fetch: cacheBustedSessionFetch(session) });

if (!agentAccess) {
return [];
}

return Object.entries(agentAccess).map(([url, access]) => ({
// @ts-expect-error selector is required for webId
subject: {
type: "webId",
selector: { url },
} as T[K],
permissions: this.AccessModesToPermissions(access),
isEnabled: true,
}))
}
type = "webId"
}
Loading
Loading