From fc5615dd18147f7076af51840f460ddb94a16ab9 Mon Sep 17 00:00:00 2001 From: NuttyShrimp Date: Fri, 11 Oct 2024 13:43:25 +0200 Subject: [PATCH 01/35] feat(header): move header to layout & add new request page --- loama/src/components/header/HeaderTab.vue | 5 +++-- loama/src/components/layouts/HeaderLayout.vue | 13 +++++++++++++ loama/src/router/index.ts | 19 ++++++++++++++++--- loama/src/views/HomeView.vue | 7 ------- loama/src/views/RequestView.vue | 5 +++++ 5 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 loama/src/components/layouts/HeaderLayout.vue create mode 100644 loama/src/views/RequestView.vue diff --git a/loama/src/components/header/HeaderTab.vue b/loama/src/components/header/HeaderTab.vue index ac7ad5f..5884e2a 100644 --- a/loama/src/components/header/HeaderTab.vue +++ b/loama/src/components/header/HeaderTab.vue @@ -1,7 +1,7 @@ diff --git a/loama/src/router/index.ts b/loama/src/router/index.ts index 5cff680..1c70724 100644 --- a/loama/src/router/index.ts +++ b/loama/src/router/index.ts @@ -1,9 +1,11 @@ import { createRouter, createWebHistory } from 'vue-router' import HomeView from '@/views/HomeView.vue' import LoginView from '@/views/LoginView.vue' +import RequestView from '@/views/RequestView.vue' import { store } from 'loama-app' import { listPodUrls } from 'loama-common' import { activeController } from 'loama-controller' +import HeaderLayout from '@/components/layouts/HeaderLayout.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), @@ -14,9 +16,20 @@ const router = createRouter({ component: LoginView }, { - path: `/home/:filePath(.*)`, - name: 'home', - component: HomeView + path: "/", + component: HeaderLayout, + children: [ + { + path: `/home/:filePath(.*)`, + name: 'home', + component: HomeView + }, + { + path: "/request", + name: "request", + component: RequestView, + } + ] } ] }) diff --git a/loama/src/views/HomeView.vue b/loama/src/views/HomeView.vue index fc0d76a..9bd1ab3 100644 --- a/loama/src/views/HomeView.vue +++ b/loama/src/views/HomeView.vue @@ -1,9 +1,4 @@ diff --git a/loama/src/views/RequestView.vue b/loama/src/views/RequestView.vue new file mode 100644 index 0000000..ec9a5fa --- /dev/null +++ b/loama/src/views/RequestView.vue @@ -0,0 +1,5 @@ + + From a4a639df4dd79bf57b20142f0b4e5b45b887f1a8 Mon Sep 17 00:00:00 2001 From: NuttyShrimp Date: Fri, 11 Oct 2024 15:16:21 +0200 Subject: [PATCH 02/35] feat(controller): add controls for resources.json --- controller/src/classes/Controller.ts | 21 +++++++ controller/src/classes/stores/BaseStore.ts | 12 +++- controller/src/classes/stores/InruptStore.ts | 59 ++++++++++++++++++-- controller/src/types/index.ts | 5 ++ controller/src/types/modules.ts | 19 ++++++- 5 files changed, 109 insertions(+), 7 deletions(-) diff --git a/controller/src/classes/Controller.ts b/controller/src/classes/Controller.ts index 753ddd9..ae52db1 100644 --- a/controller/src/classes/Controller.ts +++ b/controller/src/classes/Controller.ts @@ -306,4 +306,25 @@ export class Controller> }, permissionsPerSubject) } } + + async allowAccessRequest(resourceUrl: string) { + const resources = await this.store.getCurrentResources(); + if (resources.items.includes(resourceUrl)) { + return; + } + resources.items.push(resourceUrl); + + await this.store.saveToRemoteResources(); + } + + async denyAccessRequest(resourceUrl: string) { + const resources = await this.store.getCurrentResources(); + if (!resources.items.includes(resourceUrl)) { + return; + } + const idx = resources.items.indexOf(resourceUrl); + resources.items.splice(idx, 1); + + await this.store.saveToRemoteResources(); + } } diff --git a/controller/src/classes/stores/BaseStore.ts b/controller/src/classes/stores/BaseStore.ts index 4b207e8..0b8e4ae 100644 --- a/controller/src/classes/stores/BaseStore.ts +++ b/controller/src/classes/stores/BaseStore.ts @@ -1,9 +1,10 @@ import { SubjectKey } from "@/types/modules"; -import { BaseSubject, Index } from "../../types"; +import { BaseSubject, Index, Resources } from "../../types"; export abstract class BaseStore>> { protected podUrl?: string = undefined; protected index?: Index = undefined; + protected resources?: Resources = undefined; setPodUrl(url: string): void { this.podUrl = url; @@ -13,6 +14,15 @@ export abstract class BaseStore; + + async getCurrentResources() { + if (!this.resources) { + this.resources = await this.getOrCreateResources(); + } + return this.resources as Resources; + } + abstract getOrCreateIndex(): Promise>; async getCurrentIndex>() { diff --git a/controller/src/classes/stores/InruptStore.ts b/controller/src/classes/stores/InruptStore.ts index 4fcdd83..2eabfa3 100644 --- a/controller/src/classes/stores/InruptStore.ts +++ b/controller/src/classes/stores/InruptStore.ts @@ -1,8 +1,9 @@ import { FetchError, getFile, overwriteFile, saveFileInContainer, WithResourceInfo } from "@inrupt/solid-client"; -import { BaseSubject, Index } from "../../types"; +import { BaseSubject, Index, Resources } from "../../types"; import { IStore } from "../../types/modules"; import { BaseStore } from "./BaseStore"; import { getDefaultSession, Session } from "@inrupt/solid-client-authn-browser"; +import { setPublicAccess } from "@inrupt/solid-client/universal"; /** * A store implementation using the inrupt sdk to actually communicate with the pod @@ -12,6 +13,7 @@ import { getDefaultSession, Session } from "@inrupt/solid-client-authn-browser"; export class InruptStore>> extends BaseStore implements IStore { // TODO this should be a more resilient path: be.ugent.idlab.knows.solid.loama.index.js or smth private indexPath = "index.json"; + private resourcesPath = "resources.json"; private session: Session; constructor() { @@ -19,12 +21,46 @@ export class InruptStore this.session = getDefaultSession() } - private indexToIndexFile(): File { - return new File([JSON.stringify(this.index)], this.indexPath, { + private toJsonFile(data: unknown, path: string): File { + return new File([JSON.stringify(data)], path, { type: "application/json", }); } + async getOrCreateResources(): Promise { + if (!this.podUrl) { + throw new Error("Cannot get current resources file: pod location is not set"); + } + + if (this.resources) { + return this.resources; + } + + const resourcesPath = `${this.podUrl}${this.resourcesPath}`; + let file: (Blob & WithResourceInfo) | undefined = undefined + try { + file = await getFile(resourcesPath, { fetch: this.session.fetch }) + } catch (error: unknown) { + if (error instanceof FetchError && error.statusCode === 404) { + this.resources = { id: resourcesPath, items: [] } + file = await saveFileInContainer( + this.podUrl, + this.toJsonFile(this.resources, this.resourcesPath), + { fetch: this.session.fetch } + ); + await setPublicAccess(resourcesPath, { read: true }, { fetch: this.session.fetch }); + } + } + + const fileText = await file?.text(); + this.resources = JSON.parse(fileText ?? "{}"); + if (!this.resources) { + throw new Error("Resources not found or invalid"); + } + + return this.resources; + } + // NOTE: Possible will move the podUrl to the parameters, this works for the current POC async getOrCreateIndex(): Promise> { if (!this.podUrl) { @@ -44,7 +80,7 @@ export class InruptStore this.index = { id: indexUrl, items: [] } file = await saveFileInContainer( this.podUrl, - this.indexToIndexFile(), + this.toJsonFile(this.index, this.indexPath), { fetch: this.session.fetch } ); } @@ -67,7 +103,20 @@ export class InruptStore throw new Error("Index not found or invalid"); } - overwriteFile(this.index.id, this.indexToIndexFile(), { + overwriteFile(this.index.id, this.toJsonFile(this.index, this.indexPath), { + fetch: this.session.fetch, + }); + } + + async saveToRemoteResources() { + if (!this.resources) { + await this.getOrCreateResources(); + } + if (!this.resources) { + throw new Error("Resources not found or invalid"); + } + + overwriteFile(this.resources.id, this.toJsonFile(this.resources, this.resourcesPath), { fetch: this.session.fetch, }); } diff --git a/controller/src/types/index.ts b/controller/src/types/index.ts index 7eeb5a1..d3b93c1 100644 --- a/controller/src/types/index.ts +++ b/controller/src/types/index.ts @@ -1,5 +1,10 @@ import { url } from "loama-common"; +export interface Resources { + id: url; + items: string[] +} + /** * The json schema for the index configuration file inside a Solid data pod. */ diff --git a/controller/src/types/modules.ts b/controller/src/types/modules.ts index f299986..700aa52 100644 --- a/controller/src/types/modules.ts +++ b/controller/src/types/modules.ts @@ -1,4 +1,4 @@ -import { BaseSubject, Index, IndexItem, Permission, ResourcePermissions, SubjectPermissions } from "../types"; +import { BaseSubject, Index, IndexItem, Permission, ResourcePermissions, Resources, SubjectPermissions } from "../types"; export type SubjectKey = keyof T & string; export type SubjectType> = T[K]; @@ -30,6 +30,9 @@ export interface IController[]> getResourcePermissionList(resourceUrl: string): Promise> + + allowAccessRequest(resourceUrl: string): Promise + denyAccessRequest(resourceUrl: string): Promise } export interface IStore>> { @@ -55,6 +58,20 @@ export interface IStore> * Saves the index to the pod */ saveToRemoteIndex(): Promise; + + /** + * Returns the currently stored index or calls getOrCreateIndex if the index is not set + */ + getCurrentResources(): Promise; + + /** + * Tries to retrieve the stored index.json from the pod. If it doesn't exist, it creates an empty one. + */ + getOrCreateResources(): Promise; + /** + * Saves the index to the pod + */ + saveToRemoteResources(): Promise; } export interface ISubjectResolver> { From 1c8b197f1c52fee5349c030de11e5e64a2d09103 Mon Sep 17 00:00:00 2001 From: NuttyShrimp Date: Fri, 11 Oct 2024 17:01:00 +0200 Subject: [PATCH 03/35] feat(loama): add toggle to allow access request for a resource --- controller/src/classes/Controller.ts | 11 ++++++- .../inrupt/InruptPermissionManager.ts | 4 +++ controller/src/types/index.ts | 1 + controller/src/types/modules.ts | 3 +- .../src/components/explorer/SelectedEntry.vue | 32 ++++++++++++++++++- loama/src/lib/state.ts | 7 +++- 6 files changed, 54 insertions(+), 4 deletions(-) diff --git a/controller/src/classes/Controller.ts b/controller/src/classes/Controller.ts index ae52db1..bab0962 100644 --- a/controller/src/classes/Controller.ts +++ b/controller/src/classes/Controller.ts @@ -195,6 +195,7 @@ export class Controller> const configs: SubjectConfig[] = Object.values(this.subjectConfigs); const index = await this.store.getCurrentIndex(); + const requestAccessResources = await this.store.getCurrentResources(); const resourcesToSkip = index.items.filter(i => { if (!i.resource.includes(containerUrl)) { return false; @@ -265,6 +266,7 @@ export class Controller> } else { arr.push({ resourceUrl: v.resource, + canRequestAccess: requestAccessResources.items.includes(v.resource), permissionsPerSubject: [{ permissions: v.permissions, subject: v.subject, @@ -281,12 +283,14 @@ export class Controller> // Need to put it in a variable because the type declaration vanishes const configs: SubjectConfig[] = Object.values(this.subjectConfigs); const index = await this.store.getCurrentIndex(); + const requestAccessResources = await this.store.getCurrentResources(); const results = await Promise.allSettled(configs.map(c => c.manager.getRemotePermissions(resourceUrl))) let permissionsPerSubject = index.items.filter(i => i.resource === resourceUrl) return { resourceUrl, + canRequestAccess: requestAccessResources.items.includes(resourceUrl), permissionsPerSubject: results.reduce[]>((arr, v) => { if (v.status === "fulfilled") { v.value.forEach(remotePps => { @@ -307,6 +311,11 @@ export class Controller> } } + async canRequestAccessToResource(resourceUrl: string) { + const resources = await this.store.getCurrentResources(); + return resources.items.includes(resourceUrl); + } + async allowAccessRequest(resourceUrl: string) { const resources = await this.store.getCurrentResources(); if (resources.items.includes(resourceUrl)) { @@ -317,7 +326,7 @@ export class Controller> await this.store.saveToRemoteResources(); } - async denyAccessRequest(resourceUrl: string) { + async disallowAccessRequest(resourceUrl: string) { const resources = await this.store.getCurrentResources(); if (!resources.items.includes(resourceUrl)) { return; diff --git a/controller/src/classes/permissionManager/inrupt/InruptPermissionManager.ts b/controller/src/classes/permissionManager/inrupt/InruptPermissionManager.ts index 1d9bf12..c91d194 100644 --- a/controller/src/classes/permissionManager/inrupt/InruptPermissionManager.ts +++ b/controller/src/classes/permissionManager/inrupt/InruptPermissionManager.ts @@ -87,11 +87,15 @@ export abstract class InruptPermissionManager> { export interface ResourcePermissions> { resourceUrl: url; + canRequestAccess: boolean; permissionsPerSubject: SubjectPermissions[] } diff --git a/controller/src/types/modules.ts b/controller/src/types/modules.ts index 700aa52..24720ad 100644 --- a/controller/src/types/modules.ts +++ b/controller/src/types/modules.ts @@ -31,8 +31,9 @@ export interface IController> + canRequestAccessToResource(resourceUrl: string): Promise allowAccessRequest(resourceUrl: string): Promise - denyAccessRequest(resourceUrl: string): Promise + disallowAccessRequest(resourceUrl: string): Promise } export interface IStore>> { diff --git a/loama/src/components/explorer/SelectedEntry.vue b/loama/src/components/explorer/SelectedEntry.vue index 9da0f49..10d0a46 100644 --- a/loama/src/components/explorer/SelectedEntry.vue +++ b/loama/src/components/explorer/SelectedEntry.vue @@ -6,11 +6,17 @@ -
+