Skip to content

Commit

Permalink
feat(controller): Add logic to retrieve access request responses
Browse files Browse the repository at this point in the history
  • Loading branch information
NuttyShrimp committed Nov 18, 2024
1 parent 499363f commit d5f8314
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 38 deletions.
87 changes: 84 additions & 3 deletions controller/src/classes/accessRequests/AccessRequest.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { getLinkedResourceUrlAll, getResourceInfo } from "@inrupt/solid-client";
import { AccessRequestMessage, Permission, ResourceAccessRequestNode, Resources } from "../../types";
import { getDatetime, getLinkedResourceUrlAll, getResourceInfo, getStringNoLocale, getStringNoLocaleAll, getThingAll, getUrl } from "@inrupt/solid-client";
import { AccessRequestMessage, Permission, RequestResponseMessage, ResourceAccessRequestNode, Resources } from "../../types";
import { IAccessRequest, IController, IInbox, IInboxConstructor, IStore } from "../../types/modules";
import { cacheBustedFetch } from "../../util";

const REQUEST_RESPONSE_TYPES = ["as:Accept", "as:Reject"]

export abstract class AccessRequest implements IAccessRequest {
private resources: IStore<Resources>;
private inbox: IInbox<unknown>;
Expand Down Expand Up @@ -162,7 +164,86 @@ export abstract class AccessRequest implements IAccessRequest {

async loadAccessRequests() {
const messages = await this.inbox.getMessages();
return messages as AccessRequestMessage[];
const parsedMessages: AccessRequestMessage[] = [];
for (let [url, message] of Object.entries(messages)) {
const allThings = getThingAll(message)
const appendRequest = allThings.filter(t => t.predicates["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"].namedNodes?.includes("tbd:AppendRequest"))?.[0];

// This message does not contain a tbd:AppendRequest
if (!appendRequest) {
continue
}

const authorizationUrl = getUrl(appendRequest, "as:object");
if (!authorizationUrl) {
throw new Error(`Message with id ${url} does has no reference to an authorization object`);
}

const authorizationThing = allThings.filter(t => t.url === authorizationUrl)?.[0];
if (!authorizationThing) {
throw new Error(`Message with id ${url} does not contain the referenced authorization object`);
}

const entryTarget = getStringNoLocale(authorizationThing, "acl:accessTo");
if (!entryTarget) {
console.error("Inbox contains appendRequest without resource target");
continue;
}

const entry: AccessRequestMessage = {
id: url,
actor: getStringNoLocale(appendRequest, "as:actor") ?? "Unknown actor",
requestedAt: getDatetime(appendRequest, "as:published") ?? new Date(),
target: entryTarget,
permissions: [...(authorizationThing.predicates["acl:mode"].namedNodes ?? ["acl:Read"])],
}
parsedMessages.push(entry);
}
return parsedMessages;
}

async loadRequestResponses() {
const messages = await this.inbox.getMessages();
const parsedMessages: RequestResponseMessage[] = [];
for (let [url, message] of Object.entries(messages)) {
const allThings = getThingAll(message)
const responseThing = allThings.filter(t => {
const type = t.predicates["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"].namedNodes?.[0];
if (!type) return false;
return REQUEST_RESPONSE_TYPES.includes(type)
})?.[0];

// This message does not contain a tbd:AppendRequest
if (!responseThing) {
continue
}

const authorizationUrl = getUrl(responseThing, "as:object");
if (!authorizationUrl) {
throw new Error(`Message with id ${url} does has no reference to an authorization object`);
}

const authorizationThing = allThings.filter(t => t.url === authorizationUrl)?.[0];
if (!authorizationThing) {
throw new Error(`Message with id ${url} does not contain the referenced authorization object`);
}

const entryTarget = getStringNoLocale(authorizationThing, "acl:accessTo");
if (!entryTarget) {
console.error(`Access request response without target resource in inbox`)
continue
}
console.log(authorizationThing)

const entry: RequestResponseMessage = {
id: url,
target: entryTarget,
isAccepted: responseThing.predicates["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"].namedNodes?.[0] === "as:Accept",
permissions: [...(authorizationThing.predicates["acl:mode"].namedNodes ?? [])]
}
parsedMessages.push(entry);
}
return parsedMessages;
}

abstract removeRequest(messageUrl: string): Promise<void>;
Expand Down
36 changes: 6 additions & 30 deletions controller/src/classes/stores/InruptInboxStore.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { IInbox } from "@/types/modules";
import { createContainerAt, FetchError, getContainedResourceUrlAll, getDatetime, getResourceInfo, getSolidDataset, getStringNoLocale, getThingAll, getUrl, isContainer } from "@inrupt/solid-client";
import { createContainerAt, FetchError, getContainedResourceUrlAll, getResourceInfo, getSolidDataset, isContainer, SolidDataset, WithResourceInfo } from "@inrupt/solid-client";
import { BaseStore } from "./BaseStore";
import { getDefaultSession, Session } from "@inrupt/solid-client-authn-browser";
import { cacheBustedSessionFetch } from "../../util";

// A modified store which does not retrieve the data from the server and only does append actions
export class InruptInboxStore<M> extends BaseStore<M[]> implements IInbox<M> {
Expand Down Expand Up @@ -55,37 +56,12 @@ export class InruptInboxStore<M> extends BaseStore<M[]> implements IInbox<M> {
}

async getMessages() {
const inboxDataSet = await getSolidDataset(this.getDataUrl(), { fetch: this.session.fetch });
const inboxDataSet = await getSolidDataset(this.getDataUrl(), { fetch: cacheBustedSessionFetch(this.session) });
const messageUrls = getContainedResourceUrlAll(inboxDataSet);
const messages: unknown[] = [];
const messages: Record<string, SolidDataset & WithResourceInfo> = {};
for (let url of messageUrls) {
const message = await getSolidDataset(url, { fetch: this.session.fetch });
const allThings = getThingAll(message)
const appendRequest = allThings.filter(t => t.predicates["http://www.w3.org/1999/02/22-rdf-syntax-ns#type"].namedNodes?.includes("tbd:AppendRequest"))?.[0];

// This message does not contain a tbd:AppendRequest
if (!appendRequest) {
continue
}

const authorizationUrl = getUrl(appendRequest, "as:object");
if (!authorizationUrl) {
throw new Error(`Message with id ${url} does has no reference to an authorization object`);
}

const authorizationThing = allThings.filter(t => t.url === authorizationUrl)?.[0];
if (!authorizationThing) {
throw new Error(`Message with id ${url} does not contain the referenced authorization object`);
}

const entry = {
id: url,
actor: getStringNoLocale(appendRequest, "as:actor"),
requestedAt: getDatetime(appendRequest, "as:published"),
target: getStringNoLocale(authorizationThing, "acl:accessTo"),
permissions: authorizationThing.predicates["acl:mode"].namedNodes,
}
messages.push(entry);
const message = await getSolidDataset(url, { fetch: cacheBustedSessionFetch(this.session) });
messages[url] = message;
}
return messages;
}
Expand Down
9 changes: 8 additions & 1 deletion controller/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ export interface ResourceAccessRequestNode {
export type AccessRequestMessage = {
id: string;
actor: string;
requestedAt: string;
requestedAt: Date;
target: string;
permissions: string[];
}

export type RequestResponseMessage = {
id: string;
isAccepted: boolean;
target: string;
permissions: string[];
}
9 changes: 5 additions & 4 deletions controller/src/types/modules.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { AccessRequestMessage, BaseSubject, Index, IndexItem, Permission, ResourceAccessRequestNode, ResourcePermissions, SubjectPermissions } from "../types";
import { SolidDataset, WithResourceInfo } from "@inrupt/solid-client";
import { AccessRequestMessage, BaseSubject, Index, IndexItem, Permission, RequestResponseMessage, ResourceAccessRequestNode, ResourcePermissions, SubjectPermissions } from "../types";

export type SubjectKey<T> = keyof T & string;
export type SubjectType<T, K extends SubjectKey<T>> = T[K];
Expand Down Expand Up @@ -48,8 +49,8 @@ export interface IAccessRequest {
sendRequestNotification(originWebId: string, resources: string[]): Promise<void>;
sendResponseNotification(type: "accept" | "reject", message: AccessRequestMessage): Promise<void>;

loadAccessRequests(): Promise<AccessRequestMessage[]>

loadAccessRequests(): Promise<AccessRequestMessage[]>;
loadRequestResponses(): Promise<RequestResponseMessage[]>;
removeRequest(messageUrl: string): Promise<void>;
}

Expand Down Expand Up @@ -91,7 +92,7 @@ export interface IStore<T> {
}

export interface IInbox<T = unknown> extends IStore<T[]> {
getMessages(): Promise<unknown[]>;
getMessages(): Promise<Record<string, SolidDataset & WithResourceInfo>>;
}

export interface ISubjectResolver<T extends BaseSubject<string>> {
Expand Down

0 comments on commit d5f8314

Please sign in to comment.