Skip to content
Open
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
172 changes: 89 additions & 83 deletions controller/src/classes/OdrlController.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
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 { ODRLAccessRequest } from "./accessRequests/OdrlAccessRequest";
import { BaseSubject, Index, Permission, Resources } from "../types";
import { IController, IInboxConstructor, IStore, IStoreConstructor, SubjectConfig, SubjectConfigs, SubjectKey, SubjectType } from "../types/modules";
import { type AccessRequest as AccessRequestObject, Policy, Rule, RuleUpdate } from "../types/modules";
import { Mutex } from "./utils/Mutex";
import { ODRLAccessRequestService } from "./utils/OdrlAccessRequestService";
import { ODRLPolicyService } from "./utils/OdrlPolicyService";
import { PolicyInterpreter } from "./utils/PolicyInterpreter";
import { v4 as uuidv4 } from 'uuid';

/**
* Controller which makes it calls to the backend AS through ODRL requests.
Expand All @@ -14,7 +15,6 @@ import { ODRLAccessRequestService } from "./utils/OdrlAccessRequestService";
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;

Expand All @@ -24,7 +24,6 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
// 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 ODRLAccessRequest(this as unknown as ODRLController<{}>, inboxConstructor, this.resources);
this.subjectConfigs = subjects;
this.authorizationServerURL = authorizationServerURL;
}
Expand All @@ -40,10 +39,6 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
private async updateItem<K extends SubjectKey<T>>(resourceUrl: string, subject: SubjectType<T, K>, permissions: Permission[], alwaysKeepItem = false) {
}

AccessRequest(): IAccessRequest {
return this.accessRequest;
}

async setPodUrl(podUrl: string) {
}

Expand All @@ -60,70 +55,94 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
}

/**
* Assemble the information for a subject in a resource
* Updates existing policies according to present rule changes
* @param updates All requested changes
* @returns
*/
async getItem<K extends SubjectKey<T>>(resourceUrl: string, subject: SubjectType<T, K>): Promise<IndexItem<T[K]> | undefined> {
const subjectConfig = this.getSubjectConfig(subject)
const subjects = await subjectConfig.manager.getRemotePermissions<K>(resourceUrl);
const subjectPermission = subjects.find(entry => subjectConfig.resolver.checkMatch(entry.subject, subject))
if (!subjectPermission) return undefined
return {
id: "string",
requestId: "string",
isEnabled: subjectPermission.isEnabled,
permissions: [...subjectPermission.permissions ?? []],
resource: resourceUrl,
subject: subject,
} as IndexItem<T[K]>
}

async addPermission<K extends SubjectKey<T>>(resourceUrl: string, addedPermission: Permission, subject: SubjectType<T, K>) {
const release = await this.acquire();
try {

// 1. Create a new permission for the subject
await this.getSubjectConfig(subject).manager.createPermissions(resourceUrl, subject, [addedPermission])

// 2. Let the manager add the permission, return the updated version
const webId = getDefaultSession().info.webId!;
const permissions = await this.getSubjectConfig(subject).manager.getTargetPermissionsForUser(webId, subject.selector?.url ?? "", resourceUrl);

return permissions;
} catch (e) {
throw e;
} finally {
release();
async updatePolicy(updates: RuleUpdate[]): Promise<void> {

if (updates.length === 0) return;

const webId = getDefaultSession().info.webId;
if (!webId) throw new Error("User not logged in");
const service = new ODRLPolicyService(this.authorizationServerURL);

const store = await service.fetchPolicies(webId);
const interpreter = new PolicyInterpreter();
const allPolicies = interpreter.storeToPolicies(store);

const policiesMap = new Map<string, Policy>(allPolicies.map(p => [p.id, p]));
const modifiedPolicyIds = new Set<string>();

for (const update of updates) {
if (!update.policyId){
if(update.updateType == 'add'){
//create policy
const policy: Policy = {
id: `http://example.org/${uuidv4()}`,
rules: [update.rule],
type: 'Agreement'
};

policy.rules[0].id = `http://example.org/${policy.id}-Rule-${(uuidv4()).toString()}`;
policiesMap.set(policy.id, policy);
modifiedPolicyIds.add(policy.id);
}
continue;

}
const policy = policiesMap.get(update.policyId);
if (!policy) continue;
const ruleIndex = policy.rules.findIndex(r => r.id === update.rule.id);

if(update.rule.id == ""){
update.rule.id = `http://example.org/${update.policyId}-Rule-${uuidv4()}`;
}


if (update.updateType === 'remove' && ruleIndex !== -1) {
policy.rules.splice(ruleIndex, 1);
modifiedPolicyIds.add(policy.id);
} else if (update.updateType === 'edit' && ruleIndex !== -1) {
policy.rules[ruleIndex] = update.rule;
modifiedPolicyIds.add(policy.id);
} else if (update.updateType === 'add') {
if (ruleIndex === -1) policy.rules.push(update.rule);
else policy.rules[ruleIndex] = update.rule;
modifiedPolicyIds.add(policy.id);
}
}
}

async removeSubject<K extends SubjectKey<T>>(resourceUrl: string, subject: SubjectType<T, K>) {
const subjectConfig = this.getSubjectConfig(subject);
const item = await this.getItem(resourceUrl, subject);
const savePromises = Array.from(modifiedPolicyIds).map(async (policyId) => {
const policy = policiesMap.get(policyId)!;
if(policy.rules.length == 0){
await service.deletePolicy(webId, policyId);
}
else{
// Convert JS Policy object back to Turtle format
const turtleText = interpreter.policyToTurtle(webId, policy);
await service.putPolicy(webId, policyId, turtleText);
}
});

await subjectConfig.manager.deletePermissions(resourceUrl, subject, item?.permissions ?? []);
await Promise.all(savePromises);
}

async removePermission<K extends SubjectKey<T>>(resourceUrl: string, removedPermission: Permission, subject: SubjectType<T, K>) {
const release = await this.acquire()
try {

// 1. Delete a permission for the subject
await this.getSubjectConfig(subject).manager.deletePermissions(resourceUrl, subject, [removedPermission]);

// 2. Let the manager delete the permission, return the updated version
const webId = getDefaultSession().info.webId!;
const permissions = await this.getSubjectConfig(subject).manager.getTargetPermissionsForUser(webId, subject.selector!.url, resourceUrl);

return permissions

} catch (error) {
return []
} finally {
release();
/**
* Get all policies the logged in user has control over
* @param resourceUrl Not Implemented - Can be used to filter down in multi-pod scenarios
* @returns Policy object
*/
async getResourcePolicies(resourceUrl: string): Promise<Policy[]> {
const webId = getDefaultSession().info.webId;
if (!webId) {
throw new Error("User not logged in");
}
const store = await new ODRLPolicyService(this.authorizationServerURL).fetchPolicies(webId);
return new PolicyInterpreter().storeToPolicies(store, resourceUrl);
}


async enablePermissions<K extends SubjectKey<T>>(resource: string, subject: SubjectType<T, K>) {
// won't fix
}
Expand All @@ -132,21 +151,6 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
// won't fix
}

async getContainerPermissionList(containerUrl: string): Promise<ResourcePermissions<T[keyof T]>[]> {
return this.getSubjectConfig({ type: "public" } as T[SubjectKey<T>]).manager.getContainerPermissionList(containerUrl);
}

// NOTE: Do we want to force this to only use the index stored in the store?
async getResourcePermissionList(resourceUrl: string): Promise<ResourcePermissions<T[keyof T]>> {
const result = await this.getSubjectConfig({ type: "public" } as T[SubjectKey<T>]).manager.getRemotePermissions(resourceUrl);

return {
resourceUrl,
canRequestAccess: true, // TODO
permissionsPerSubject: result
};
}

isSubjectSupported<K extends string, B extends BaseSubject<K>>(subject: BaseSubject<K>): IController<Record<K, B>> {
if (!this.subjectConfigs[subject.type as unknown as keyof T]) {

Expand All @@ -156,9 +160,11 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
}

// ! added for access requests
async requestAccess(permission: { action: string; resource: string; }): Promise<void> {
async requestAccess(permission: { accessRequest: AccessRequestObject}): Promise<void> {
console.log('aaa-Requesting');
const webid = getDefaultSession().info.webId!;
await new ODRLAccessRequestService(this.authorizationServerURL).requestAccess(permission.resource, webid, permission.action);
permission.accessRequest.requestingParty = webid;
await new ODRLAccessRequestService(this.authorizationServerURL).requestAccess(permission.accessRequest);
}

async handleAccessRequest(requestId: string, status: 'accepted' | 'denied'): Promise<void> {
Expand All @@ -172,4 +178,4 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
}> {
return new ODRLAccessRequestService(this.authorizationServerURL).retrieveAccessRequests(getDefaultSession().info.webId!);
}
}
}
Loading