From aef8afd7a307cb539a7b32826f530fd1709aaa8a Mon Sep 17 00:00:00 2001 From: Elias Crum Date: Fri, 9 Aug 2024 14:05:17 +0200 Subject: [PATCH 01/15] groundwork for privacy editing functionality --- src/components/EditPrivacy.vue | 48 +++++++++++++++++++++++++++------- src/components/privacyEdit.ts | 14 +++++++--- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index f08178f..34f1991 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -8,7 +8,7 @@ class="mx-auto" color="indigo-darken-3" > - + {{ dirContents }} @@ -17,13 +17,41 @@ + data() { + return { + webId: "", + podURLs: [], + pod: "", + dirContents: "", + }; + }, + methods: { + /* + Calls getPodURLs() from fileUpload.ts to obtain a list of pods from the logged-in user's webID. + Obtains 'pod' variable (URL path to user's Pod). + */ + async getPodURL() { + this.webId = currentWebId(); // fetches user webID from login.ts + this.podURLs = await getPodURLs(this.webId); // calls async function to get Pod URLs + this.pod = this.podURLs[0]; // can fix this to handle multiple pods (<< FUTURE >>) + }, + async getPodData() { + this.dirContents = await obtainSolidDataset(this.pod + "uploads/"); + }, + }, + mounted() { + // Delays the execution of these functions on page reload (to avoid async-related errors) + setTimeout(() => { + this.getPodURL(); + }, 200); + setTimeout(() => { + this.getPodData(); + }, 400); + }, +}; + diff --git a/src/components/privacyEdit.ts b/src/components/privacyEdit.ts index d1f29f7..2536940 100644 --- a/src/components/privacyEdit.ts +++ b/src/components/privacyEdit.ts @@ -16,14 +16,20 @@ import { WithAcl, } from "@inrupt/solid-client"; +import { fetch } from "@inrupt/solid-client-authn-browser"; + /** * Gets the a SolidDataset from the current Pod's /Uploads container. * * @param podURL The URL of the current Pod represented as a string * @returns a SolidDataset representation of the Pod's /Uploads container */ -async function obtainSolidDataset(podURL: string): Promise { - return await getSolidDatasetWithAcl(podURL); +async function obtainSolidDataset(podURL: string): Promise { + const dataWacl = await getSolidDataset(podURL+"uploads/example.ttl", {fetch: fetch}) + console.log(dataWacl) + return dataWacl; + // can't get items as SoldDatasets?? Something weird with file types I think? + // Need way to get all the resources found in a directory... (not sure how to do this?) } /** @@ -59,7 +65,7 @@ function obtainACL(datasetWithAcl: SolidDataset & WithServerResourceInfo & WithA /** * Changes the access control settings of the "Uploads" container using the editACL() method from Inrupt * Function changes rights based on user-input decisions. - * + * * @param currentACL The current ACL file for the Uploads container * @param newAccessWebID The user(s) [as list of WebID strings] that will obtain new access rights * @param rights a 4 item list that contains boolean values for [read, append, write, control] access rights @@ -84,4 +90,4 @@ async function editACL(currentACL: AclDataset, newAccessWebIDs: string[], rights // const newACL = createAclFromFallbackAcl(await getResourceInfo(containerURL)); //} -export default { obtainSolidDataset, obtainACL, editACL } \ No newline at end of file +export { obtainSolidDataset, obtainACL, editACL } \ No newline at end of file From 4cbb7418e48371bf345c39799b795a9778efe14a Mon Sep 17 00:00:00 2001 From: ecrum19 Date: Tue, 17 Sep 2024 20:51:55 +0200 Subject: [PATCH 02/15] front-end Privacy-editing component progress --- src/components/EditPrivacy.vue | 266 +++++++++++++++++++++++++++++++-- 1 file changed, 251 insertions(+), 15 deletions(-) diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index 34f1991..37fa965 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -1,19 +1,112 @@ + + From 4de0ae7579e1a9e2d172ddc9623732ac12d6d743 Mon Sep 17 00:00:00 2001 From: ecrum19 Date: Wed, 18 Sep 2024 20:44:00 +0200 Subject: [PATCH 03/15] back-end work --- src/components/fileUpload.ts | 6 ++- src/components/privacyEdit.ts | 85 ++++++++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/components/fileUpload.ts b/src/components/fileUpload.ts index b976d17..007f896 100644 --- a/src/components/fileUpload.ts +++ b/src/components/fileUpload.ts @@ -19,6 +19,8 @@ async function getPodURLs(webid: string): Promise { } catch (error) { pods = ["Error: probably not logged in"]; } + console.log(pods); + console.log(webid); return pods; } @@ -30,6 +32,7 @@ async function getPodURLs(webid: string): Promise { */ async function handleFiles(fileList: FileList, podURL: string): Promise { const outputList: string[] = []; + console.log(podURL); for (let i = 0; i < fileList.length; i++) { const upload: string = await uploadToPod(`${podURL}uploads/${fileList[i].name}`, fileList[i], fetch); outputList.push(upload); @@ -90,7 +93,7 @@ function uploadSuccess(uploadedFiles: string[]): boolean { let success = false; uploadedFiles.forEach((up: string) => { console.log(up) - if (up !== undefined) { + if (up !== undefined || up !== 'error') { success = true } else { success = false @@ -104,6 +107,7 @@ function derefrenceFile(inputFile: File & WithResourceInfo): string[] { return [inputFile.name, String(inputFile.size), inputFile.internal_resourceInfo.sourceIri] } catch (error) { console.error('Error', error) + return ["error"]; } } diff --git a/src/components/privacyEdit.ts b/src/components/privacyEdit.ts index 2536940..d560875 100644 --- a/src/components/privacyEdit.ts +++ b/src/components/privacyEdit.ts @@ -14,10 +14,32 @@ import { SolidDataset, WithServerResourceInfo, WithAcl, + createThing, + getSourceUrl, + ThingPersisted, + UrlString, + responseToResourceInfo, + isRawData, + responseToSolidDataset, + getEffectiveAccess, + } from "@inrupt/solid-client"; - import { fetch } from "@inrupt/solid-client-authn-browser"; + + +export function hasAccess( + resource: Parameters[0], + access: Array["user"]>, +): boolean { + const actualAccess = getEffectiveAccess(resource); + const hasRequiredAccess = access.every( + (requiredAccess) => actualAccess.user[requiredAccess] === true, + ); + return hasRequiredAccess; +} + + /** * Gets the a SolidDataset from the current Pod's /Uploads container. * @@ -84,10 +106,69 @@ async function editACL(currentACL: AclDataset, newAccessWebIDs: string[], rights // await saveAclFor(currentACL, updatedAcl); } + +export type FileData = WithServerResourceInfo & { + blob: Blob; + etag: string | null; +}; + +/** + * Function to fetch file data from a pod. + * + */ +const fetcher = async ([url, _webId]: [ + UrlString, + UrlString | undefined, +]): Promise => { + const urlObject = new URL(url); + // Ensure that when we fetch a Container that contains an `index.html`, + // the server doesn't serve us that HTML file: + const headers = urlObject.pathname.endsWith("/") + ? { Accept: "text/turtle" } + : { + // Otherwise ask the server to give us Turtle if it _can_ be served as + // Turtle. If not, serve it up in the most appropriate Content-Type: + Accept: "text/turtle;q=1.0, */*;q=0.5", + }; + const response = await fetch(url, { headers: headers }); + const resourceInfo = responseToResourceInfo(response); + if (isRawData(resourceInfo)) { + return { + ...resourceInfo, + blob: await response.blob(), + etag: response.headers.get("ETag"), + }; + } + if (response.headers.get("Content-Type") === "application/ld+json") { + // Some Solid servers (at least NSS) will default to serving content + // available as JSON-LD as JSON-LD. Since we only ship a Turtle parser, + // re-request it as Turtle instead: + return await getSolidDataset(url, { fetch: fetch }); + } + const dataset = await responseToSolidDataset(response); + return dataset; +}; + +function getResourceUrl(url: UrlString): UrlString { + const resourceUrl = new URL(url); + resourceUrl.hash = ""; + return resourceUrl.href; +} + +export function useResource(url: UrlString | null) { + const resourceUrl = url ? getResourceUrl(url) : null; + const sessionInfo = useSessionInfo(); + const resource = useSwr( + resourceUrl ? [resourceUrl, sessionInfo?.webId] : null, + fetcher, + ); + + + // //async function generateDefaultACL(containerURL: string) { // const resourceDetails = await getResourceInfo(containerURL); // const newACL = createAclFromFallbackAcl(await getResourceInfo(containerURL)); //} -export { obtainSolidDataset, obtainACL, editACL } \ No newline at end of file +export { obtainSolidDataset, obtainACL, editACL, fetcher } \ No newline at end of file From dd82947abe2514321dbf143e48231e4d4f7fe79f Mon Sep 17 00:00:00 2001 From: Elias Crum Date: Thu, 19 Sep 2024 15:42:41 +0200 Subject: [PATCH 04/15] lots of privacy component progress --- package.json | 2 + src/components/EditPrivacy.vue | 36 ++++----- src/components/PodBrowser.vue | 5 +- src/components/PodLogin.vue | 8 +- src/components/PodUpload.vue | 6 +- src/components/Unused.ts | 77 +++++++++++++++++++ src/components/fileUpload.ts | 20 +---- src/components/getData.ts | 88 ++++++++++++++++++++++ src/components/login.ts | 21 +++++- src/components/privacyEdit.ts | 132 ++++++++------------------------- 10 files changed, 250 insertions(+), 145 deletions(-) create mode 100644 src/components/Unused.ts create mode 100644 src/components/getData.ts diff --git a/package.json b/package.json index f6f6ddf..41315a8 100644 --- a/package.json +++ b/package.json @@ -26,12 +26,14 @@ "@vue/eslint-config-typescript": "^9.1.0", "core-js": "^3.8.3", "fs": "^0.0.1-security", + "swrv": "^0.10.0", "vue": "^3.2.13", "vue-router": "^4.0.13", "vuetify": "^3.5.14" }, "devDependencies": { "@babel/plugin-transform-private-methods": "^7.24.1", + "@tsconfig/node20": "^20.1.4", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "eslint": "^7.32.0", diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index 37fa965..82ab4d0 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -110,17 +110,18 @@ + diff --git a/src/components/getData.ts b/src/components/getData.ts index 4e0476e..ff48fa5 100644 --- a/src/components/getData.ts +++ b/src/components/getData.ts @@ -43,6 +43,7 @@ export type WorkingData = (SolidDataset & WithServerResourceInfo) | FileData; * @retuns dataset ... */ export async function fetchData(url: UrlString): Promise { + console.log(url) const urlObject = new URL(url); // Ensure that when we fetch a Container that contains an `index.html`, // the server doesn't serve us that HTML file: @@ -73,6 +74,7 @@ export async function fetchData(url: UrlString): Promise { return dataset; } + /** * gets a resource URL -- * diff --git a/src/components/login.ts b/src/components/login.ts index bbbc503..3d843c0 100644 --- a/src/components/login.ts +++ b/src/components/login.ts @@ -84,6 +84,7 @@ async function getPodURLs(): Promise { let pods: string[] = []; try { pods = await getPodUrlAll(session.info.webId, { fetch: fetch }); + console.log(pods) } catch (error) { pods = ["Error: probably not logged in"]; } @@ -118,4 +119,4 @@ async function handleRedirectAfterPageLoad(): Promise { -export { startLogin, isLoggedin, handleRedirectAfterPageLoad, currentWebId, getPodURLs, redirectToHomepage, redirectToLogin, logOut, session, podURL } +export { startLogin, isLoggedin, handleRedirectAfterPageLoad, currentWebId, getPodURLs, redirectToHomepage, redirectToLogin, logOut, session } From de23e82e4bf7482f1ee25170e0f3acac0c0bac4d Mon Sep 17 00:00:00 2001 From: ecrum19 Date: Wed, 25 Sep 2024 09:43:53 +0200 Subject: [PATCH 06/15] added ability to display dataset acls + now adding access to acl --- src/components/EditPrivacy.vue | 122 ++++++++++++++++++++++++++------- src/components/getData.ts | 20 +++++- src/components/login.ts | 1 - src/components/privacyEdit.ts | 53 +++++--------- 4 files changed, 135 insertions(+), 61 deletions(-) diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index 97f7545..bf8bf36 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -1,5 +1,5 @@ @@ -346,6 +358,7 @@ import { mergeProps } from "vue"; import { getContainedResourceUrlAll, getLinkedResourceUrlAll, + getResourceAcl, } from "@inrupt/solid-client"; import { changeAcl, checkUrl, generateAcl } from "./privacyEdit"; import { currentWebId, getPodURLs } from "./login"; @@ -361,14 +374,9 @@ export default { name: "PrivacyComponent", data() { return { - filters: [ - { label: "Containers" }, - { label: "Resources" }, - ], - viewingFilters: { - containers: true, - resources: true, - }, + filters: ["containers", "resources"], + filterValues: [true, true], + filterMenuOpen: false, showSharedIndex: null, showFormIndex: null, userUrl: "", @@ -382,7 +390,7 @@ export default { }, permissionsString: "", webId: "", - podList: "", + podList: [], dirContents: WorkingData, containerContents: WorkingData, hasAcl: null, @@ -400,6 +408,12 @@ export default { methods: { // methods that provide logic for UI interaction + /* + Keeps the filter menu open while toggling the viewing options + */ + keepMenuOpen() { + this.filterMenuClosed = false; // Keep the menu open after clicking an item + }, /* Checks if the input item url is a container */ @@ -625,7 +639,7 @@ export default { * @param path the URL of the resource or container for which access rights are to be displayed * ... */ - async getSpecificAclData(path) { + async getSpecificAclData(path) { this.hasAcl = await fetchPermissionsData(path); // value is either .acl obj OR null (if .acl does not exist) if (this.hasAcl !== null) { this.hasAccess = await fetchAclAgents(path); @@ -642,9 +656,8 @@ export default { * ... */ async makeNewAcl(path) { - await generateAcl(path) - } - + await generateAcl(path, this.webId); + }, }, mounted() { // Delays the execution of these functions on page reload (to avoid async-related errors) @@ -723,6 +736,8 @@ nav { margin-top: 5px; font-family: "Courier New", Courier, monospace; } +.the-filter { +} /* sidenav */ .side-nav.floating { diff --git a/src/components/fileUpload.ts b/src/components/fileUpload.ts index 3cec576..2ecf80a 100644 --- a/src/components/fileUpload.ts +++ b/src/components/fileUpload.ts @@ -1,6 +1,7 @@ import { WithResourceInfo, overwriteFile, + createContainerAt, } from "@inrupt/solid-client"; import { fetch } from "@inrupt/solid-client-authn-browser"; import { mimeTypes } from "./mime_types.js"; @@ -93,4 +94,8 @@ function derefrenceFile(inputFile: File & WithResourceInfo): string[] { } } +async function createContainer(url: string) { + // maybe this is necessary but will reassess +} + export { handleFiles, getMimeType, uploadSuccess, derefrenceFile }; diff --git a/src/components/privacyEdit.ts b/src/components/privacyEdit.ts index 267e18e..a293fd1 100644 --- a/src/components/privacyEdit.ts +++ b/src/components/privacyEdit.ts @@ -129,8 +129,9 @@ export const { freeze } = Object; * @returns */ export function createNewAcl( - targetResource: WorkingData, + targetResource: WorkingData ): AclDataset { + // initialize empty ACL const emptyResourceAcl: AclDataset = freeze({ ...createSolidDataset(), internal_accessTo: getSourceUrl(targetResource), @@ -140,46 +141,52 @@ export function createNewAcl( linkedResources: {}, }, }); + return emptyResourceAcl; } /** * Adds a new .acl file with default permissions (i.e. only owner has "Control" access) * - * @param currentACL The current ACL file for the Uploads container - * @param newAccessWebID The user(s) [as list of WebID strings] that will obtain new access rights + * @param resourceURL The URL of the conatainer that needs an ACL configured + * @param userWebId The webID that base level access rights should be granted to * @param rights a 4 item list that contains boolean values for [read, append, write, control] access rights */ -export async function generateAcl(resourceURL: string) { +export async function generateAcl(resourceURL: string, userWebId: string) { + // create blank ACL at current location const location = await fetchData(resourceURL); const newAcl = createNewAcl(location); - const baseAclRules = [] - // const initializedNewAcl = baseAclRules.reduce - console.log(initializedNewAcl) - - // const savedDataset = await saveSolidDatasetAt( - // location.internal_resourceInfo.aclUrl, - // newAcl, - // { fetch: fetch }, - // ); - - // const savedAclDataset: AclDataset & typeof savedDataset = { - // ...savedDataset, - // internal_accessTo: getSourceUrl(location), - // }; + // add base user permissions + const userAccess:Permissions = { + read: true, + append: true, + write: true, + control: true + } - console.log(newAcl) + // add that root ACL info to empty acl + const updatedNewAcl = setAgentResourceAccess( + newAcl, + userWebId, + userAccess + ); + const savedDataset = await saveSolidDatasetAt( + location.internal_resourceInfo.aclUrl, + updatedNewAcl, + { fetch: fetch }, + ); - // logic to make new .acl here + const savedAclDataset: AclDataset & typeof savedDataset = { + ...savedDataset, + internal_accessTo: getSourceUrl(location), + }; + console.log(savedAclDataset) } - - - // //async function generateDefaultACL(containerURL: string) { // const resourceDetails = await getResourceInfo(containerURL); // const newACL = createAclFromFallbackAcl(await getResourceInfo(containerURL)); -//} +// } From 14e317817dbe8013dd7ee69768edce28f288fed7 Mon Sep 17 00:00:00 2001 From: Elias Crum Date: Thu, 3 Oct 2024 14:02:37 +0200 Subject: [PATCH 13/15] cleaning,commenting, and polishing --- src/components/EditPrivacy.vue | 56 +++++++++++++--------------------- src/components/fileUpload.ts | 24 +++++++-------- src/components/getData.ts | 53 +++++--------------------------- src/components/privacyEdit.ts | 56 +++++++++++----------------------- 4 files changed, 58 insertions(+), 131 deletions(-) diff --git a/src/components/EditPrivacy.vue b/src/components/EditPrivacy.vue index 7ea47a6..0b1450e 100644 --- a/src/components/EditPrivacy.vue +++ b/src/components/EditPrivacy.vue @@ -354,21 +354,16 @@