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
20 changes: 10 additions & 10 deletions packages/uma/src/controller/BaseController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ConflictHttpError } from '@solid/community-server';
import { UCRulesStorage } from "../ucp/storage/UCRulesStorage";
import { ReadOnlyStore, UCRulesStorage } from '../ucp/storage/UCRulesStorage';
import { getLoggerFor } from 'global-logger-factory';
import { Parser, Store } from 'n3';
import { writeStore } from "../util/ConvertUtil";
Expand All @@ -18,8 +18,8 @@ export abstract class BaseController {
protected readonly store: UCRulesStorage,
protected sanitizePost: (store: Store, clientID: string) => Promise<{ result: Store, id: string }>,
protected sanitizeDelete: (store: Store, entityID: string, clientID: string) => Promise<void>,
protected sanitizeGets: (store: Store, clientID: string) => Promise<Store>,
protected sanitizeGet: (store: Store, entityID: string, clientID: string) => Promise<Store>,
protected sanitizeGets: (store: ReadOnlyStore, clientID: string) => Promise<ReadOnlyStore>,
protected sanitizeGet: (store: ReadOnlyStore, entityID: string, clientID: string) => Promise<ReadOnlyStore>,
protected sanitizePatch: (store: Store, entityID: string, clientID: string, patchInformation: string) => Promise<void>
) { }

Expand All @@ -30,7 +30,7 @@ export abstract class BaseController {
* @returns results serialized in Turtle and status code 200,
* or an empty body with status 404 if nothing was found
*/
private async get(sanitizeGet: () => Promise<Store>): Promise<{ message: string, status: number }> {
private async get(sanitizeGet: () => Promise<ReadOnlyStore>): Promise<{ message: string, status: number }> {
const store = await sanitizeGet();

const message = store.size > 0 ? await writeStore(store) : '';
Expand Down Expand Up @@ -92,7 +92,7 @@ export abstract class BaseController {
* - 204 if deletion was successful
*/
public async deleteEntity(entityID: string, clientID: string): Promise<{ status: number }> {
const filteredStore = new Store(await this.store.getStore());
const filteredStore = new Store(await this.store.getStore() as Store);
await this.sanitizeDelete(filteredStore, entityID, clientID);
const diff = (await this.store.getStore()).difference(filteredStore);
await this.store.removeData(diff as Store);
Expand All @@ -114,12 +114,12 @@ export abstract class BaseController {
*/
public async patchEntity(entityID: string, patchInformation: string, clientID: string, isolate: boolean = true): Promise<HttpHandlerResponse<string>> {
let response: HttpHandlerResponse<string> = { status: 204, body: '' };
let filteredStore = new Store(await this.store.getStore());
let filteredStore = new Store(await this.store.getStore() as Store);
let omitStore: Store;

if (isolate) { // requires isolating all information about the entity provided, as e.g. the patchinformation has a query to be executed
filteredStore = await this.sanitizeGet(filteredStore, entityID, clientID);
omitStore = new Store(await this.store.getStore());
filteredStore = await this.sanitizeGet(filteredStore, entityID, clientID) as Store;
omitStore = new Store(await this.store.getStore() as Store);
omitStore.removeQuads([ ...filteredStore]);
}

Expand All @@ -130,14 +130,14 @@ export abstract class BaseController {
// * bonus: filters out extra quads
// ! drawback: PATCH may still be used to DELETE all information about the entity
// TODO: check if PATCH is smth we want for all resources, make patchEntity optional otherwise
filteredStore = await this.sanitizeGet(filteredStore, entityID, clientID) || filteredStore;
filteredStore = await this.sanitizeGet(filteredStore, entityID, clientID) as Store || filteredStore;
omitStore!.addAll(filteredStore);
filteredStore = omitStore!;
}

const originalStore = await this.store.getStore();
const remove = originalStore.difference(filteredStore);
const add = filteredStore.difference(originalStore);
const add = filteredStore.difference(originalStore as Store);

if (remove.size > 0) {
await this.store.removeData(remove as Store);
Expand Down
6 changes: 3 additions & 3 deletions packages/uma/src/policies/authorizers/SimpleOdrlAuthorizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { DataFactory as DF, Quad_Subject, Store } from 'n3';
import { ODRL } from 'odrl-evaluator';
import { CLIENTID, WEBID } from '../../credentials/Claims';
import { ClaimSet } from '../../credentials/ClaimSet';
import { UCRulesStorage } from '../../ucp/storage/UCRulesStorage';
import { ReadOnlyStore, UCRulesStorage } from '../../ucp/storage/UCRulesStorage';
import { Permission } from '../../views/Permission';
import { Authorizer } from './Authorizer';

Expand Down Expand Up @@ -63,7 +63,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
return permissions;
}

protected getPermissions(policies: Store, claims: ClaimSet, resource: string, scope: string):
protected getPermissions(policies: ReadOnlyStore, claims: ClaimSet, resource: string, scope: string):
Permission[] | undefined {
this.logger.info(`Evaluating Request ${scope}, ${resource} with claims ${JSON.stringify(claims)}`);
const targets = [ DF.namedNode(resource), ...policies.getObjects(resource, ODRL.terms.partOf, null)];
Expand Down Expand Up @@ -153,7 +153,7 @@ export class SimpleOdrlAuthorizer implements Authorizer {
* and undefined if any constraint is too complex to evaluate.
* Only supports purpose (for client ID) and dateTime constraints.
*/
protected validateConstraints(rule: Quad_Subject, policies: Store, claims: ClaimSet): boolean | undefined {
protected validateConstraints(rule: Quad_Subject, policies: ReadOnlyStore, claims: ClaimSet): boolean | undefined {
const constraints = policies.getObjects(rule, ODRL.terms.constraint, null).map(constraint => ({
leftOperand: policies.getObjects(constraint, ODRL.terms.leftOperand, null)[0],
operator: policies.getObjects(constraint, ODRL.terms.operator, null)[0],
Expand Down
4 changes: 2 additions & 2 deletions packages/uma/src/routes/Collection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ODRL } from 'odrl-evaluator';
import { WEBID } from '../credentials/Claims';
import { CredentialParser } from '../credentials/CredentialParser';
import { Verifier } from '../credentials/verify/Verifier';
import { UCRulesStorage } from '../ucp/storage/UCRulesStorage';
import { ReadOnlyStore, UCRulesStorage } from '../ucp/storage/UCRulesStorage';
import { DC, ODRL_P, OWL } from '../ucp/util/Vocabularies';
import { writeStore } from '../util/ConvertUtil';
import {
Expand Down Expand Up @@ -178,7 +178,7 @@ export class CollectionRequestHandler extends HttpHandler {
/**
* Verifies if the user is allowed to modify the given collection.
*/
protected async verifyOwnership(subject: NamedNode, userId: string, store?: Store): Promise<void> {
protected async verifyOwnership(subject: NamedNode, userId: string, store?: ReadOnlyStore): Promise<void> {
const userNode = DF.namedNode(userId);
store = store ?? await this.policies.getStore();

Expand Down
10 changes: 5 additions & 5 deletions packages/uma/src/routes/ResourceRegistration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
import { getLoggerFor } from 'global-logger-factory';
import { DataFactory as DF, NamedNode, Quad, Quad_Subject, Store } from 'n3';
import { randomUUID } from 'node:crypto';
import { UCRulesStorage } from '../ucp/storage/UCRulesStorage';
import { ReadOnlyStore, UCRulesStorage } from '../ucp/storage/UCRulesStorage';
import { DC, ODRL, ODRL_P, OWL } from '../ucp/util/Vocabularies';
import {
HttpHandler,
Expand Down Expand Up @@ -248,7 +248,7 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
* @param previous - The previous {@link ResourceDescription}, in case this is an update.
*/
protected async updateCollections(
policyStore: Store,
policyStore: ReadOnlyStore,
id: string,
owner: string,
description: ResourceDescription,
Expand Down Expand Up @@ -307,7 +307,7 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
* @param previous - The previous {@link ResourceDescription}, in case this is an update.
*/
protected async updateRelations(
policyStore: Store,
policyStore: ReadOnlyStore,
id: string,
description: ResourceDescription,
previous?: ResourceDescription
Expand Down Expand Up @@ -417,7 +417,7 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
* @param entries - {@link CollectionMetadata} objects to parse.
* @param policyStore - {@link Store} with the relevant triples to update.
*/
protected generatePartOfTriples(part: NamedNode, entries: CollectionMetadata[], policyStore: Store): Quad[] {
protected generatePartOfTriples(part: NamedNode, entries: CollectionMetadata[], policyStore: ReadOnlyStore): Quad[] {
const quads: Quad[] = [];
for (const entry of entries) {
const collectionIds = this.findCollectionIds(entry, policyStore);
Expand All @@ -439,7 +439,7 @@ export class ResourceRegistrationRequestHandler extends HttpHandler {
* @param entry - Relevant {@link CollectionMetadata}.
* @param data - {@link Store} in which to find the matching triples.
*/
protected findCollectionIds(entry: CollectionMetadata, data: Store): Quad_Subject[] {
protected findCollectionIds(entry: CollectionMetadata, data: ReadOnlyStore): Quad_Subject[] {
const sourceMatches = data.getSubjects(ODRL.terms.source, entry.source, null);
if (entry.reverse) {
const blankQuads = sourceMatches.flatMap((subject): Quad[] =>
Expand Down
10 changes: 5 additions & 5 deletions packages/uma/src/ucp/storage/DirectoryUCRulesStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { extractQuadsRecursive } from '../util/Util';
import { UCRulesStorage } from "./UCRulesStorage";
import { ReadOnlyStore, UCRulesStorage } from './UCRulesStorage';
import * as path from 'path'
import * as fs from 'fs'
import { Parser, Store, Writer } from 'n3';
Expand Down Expand Up @@ -28,9 +28,9 @@ export class DirectoryUCRulesStorage implements UCRulesStorage {
this.baseIRI = baseIRI;
}

public async getStore(): Promise<Store> {
public async getStore(): Promise<ReadOnlyStore> {
if (this.filesRead) {
return new Store(this.store);
return this.store;
}

const parser = new Parser({ baseIRI: this.baseIRI });
Expand All @@ -51,13 +51,13 @@ export class DirectoryUCRulesStorage implements UCRulesStorage {
}


public async removeData(data: Store): Promise<void> {
public async removeData(data: ReadOnlyStore): Promise<void> {
// Make sure the files have been read into memory
await this.getStore();
this.store.removeQuads(data.getQuads(null, null, null, null));
}

public async getRule(identifier: string): Promise<Store> {
public async getRule(identifier: string): Promise<ReadOnlyStore> {
const allRules = await this.getStore()
return extractQuadsRecursive(allRules, identifier);
}
Expand Down
12 changes: 6 additions & 6 deletions packages/uma/src/ucp/storage/MemoryUCRulesStorage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DataFactory, Store } from 'n3';
import { extractQuadsRecursive } from '../util/Util';
import { UCRulesStorage } from './UCRulesStorage';
import { ReadOnlyStore, UCRulesStorage } from './UCRulesStorage';

const { namedNode } = DataFactory;

Expand All @@ -11,16 +11,16 @@ export class MemoryUCRulesStorage implements UCRulesStorage {
this.store = new Store();
}

public async getStore(): Promise<Store> {
return new Store(this.store);
public async getStore(): Promise<ReadOnlyStore> {
return this.store;
}


public async addRule(rule: Store): Promise<void> {
public async addRule(rule: ReadOnlyStore): Promise<void> {
this.store.addQuads(rule.getQuads(null, null, null, null))
}

public async getRule(identifier: string): Promise<Store> {
public async getRule(identifier: string): Promise<ReadOnlyStore> {
// currently doesn't check whether it is actually an odrl:rule
return extractQuadsRecursive(this.store, identifier)
}
Expand All @@ -36,7 +36,7 @@ export class MemoryUCRulesStorage implements UCRulesStorage {
this.deleteRule(ruleID);
}

public async removeData(data: Store): Promise<void> {
public async removeData(data: ReadOnlyStore): Promise<void> {
this.store.removeQuads(data.getQuads(null, null, null, null));
}
}
29 changes: 25 additions & 4 deletions packages/uma/src/ucp/storage/UCRulesStorage.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { Store } from "n3";

/**
* A read-only view of an N3 Store.
* All write/mutation methods are excluded.
*/
export type ReadOnlyStore = Omit<
Store,
| 'addAll'
| 'deleteMatches'
| 'add'
| 'addQuad'
| 'addQuads'
| 'delete'
| 'import'
| 'removeQuad'
| 'removeQuads'
| 'remove'
| 'removeMatches'
| 'deleteGraph'
| 'clear'
>;

export interface UCRulesStorage {
getStore: () => Promise<Store>;
getStore: () => Promise<ReadOnlyStore>;
/**
* Add a single Usage Control Rule to the storage
* @param rule
* @returns
*/
addRule: (rule: Store) => Promise<void>;
addRule: (rule: ReadOnlyStore) => Promise<void>;
/**
* Get a Usage Control Rule from the storage
* @param identifier
* @returns
*/
getRule: (identifier: string) => Promise<Store>;
getRule: (identifier: string) => Promise<ReadOnlyStore>;
/**
* Delete a Usage Control Rule from the storage
* @param identifier
Expand All @@ -30,5 +51,5 @@ export interface UCRulesStorage {
* Removes specific triples from the storage.
* @param data
*/
removeData: (data: Store) => Promise<void>;
removeData: (data: ReadOnlyStore) => Promise<void>;
}
3 changes: 2 additions & 1 deletion packages/uma/src/ucp/util/Util.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Store } from "n3";
import { ReadOnlyStore } from '../storage/UCRulesStorage';

/**
* A recursive search algorithm that gives all quads that a subject can reach (working with circles)
Expand All @@ -7,7 +8,7 @@ import { Store } from "n3";
* @param subjectIRI
* @param existing IRIs that already have done the recursive search (IRIs in there must not be searched for again)
*/
export function extractQuadsRecursive(store: Store, subjectIRI: string, existing?: string[]): Store {
export function extractQuadsRecursive(store: ReadOnlyStore, subjectIRI: string, existing?: string[]): Store {
const tempStore = new Store();
const subjectIRIQuads = store.getQuads(subjectIRI, null, null, null);

Expand Down
5 changes: 3 additions & 2 deletions packages/uma/src/util/ConvertUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Prefixes, Store, Writer } from 'n3';
import { Prefixes, Writer } from 'n3';
import { parse, stringify } from 'node:querystring';
import { NamedNode } from '@rdfjs/types';
import { ReadOnlyStore } from '../ucp/storage/UCRulesStorage';

/**
* Converts a x-www-form-urlencoded string to a JSON object.
Expand Down Expand Up @@ -49,7 +50,7 @@ export function isIri(input: string): boolean {
/**
* Write an N3 store to a string (in turtle format)
*/
export async function writeStore(store: Store, prefixes: Prefixes<NamedNode | string> = {}): Promise<string> {
export async function writeStore(store: ReadOnlyStore, prefixes: Prefixes<NamedNode | string> = {}): Promise<string> {
const writer = new Writer({ format: 'text/turtle', prefixes });
writer.addQuads(store.getQuads(null, null, null, null));

Expand Down
4 changes: 2 additions & 2 deletions packages/uma/src/util/routeSpecific/sanitizeUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Store } from "n3";
import { ReadOnlyStore } from '../../ucp/storage/UCRulesStorage';

/**
* Check whether all subjects in the new store
Expand All @@ -11,7 +11,7 @@ import { Store } from "n3";
* @param newStore the store containing new data
* @returns true if no subjects are already defined, false otherwise
*/
export const noAlreadyDefinedSubjects = (store: Store, newStore: Store): boolean =>
export const noAlreadyDefinedSubjects = (store: ReadOnlyStore, newStore: ReadOnlyStore): boolean =>
newStore.getSubjects(null, null, null)
.every((subject) => store.countQuads(subject, null, null, null) === 0);
export class ConflictError extends Error {
Expand Down
2 changes: 0 additions & 2 deletions test/integration/Base.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { setGlobalLoggerFactory, WinstonLoggerFactory } from 'global-logger-fact
import { Parser, Writer } from 'n3';
import { readFile } from 'node:fs/promises';
import * as path from 'node:path';
import { promises } from 'node:timers';
import { getDefaultCssVariables, getPorts, instantiateFromConfig } from '../util/ServerUtil';
import { generateCredentials } from '../util/UmaUtil';

Expand Down Expand Up @@ -92,7 +91,6 @@ describe('A server setup', (): void => {

describe('using public namespace authorization', (): void => {
it('RS: provides immediate read access.', async(): Promise<void> => {
await promises.setTimeout(1000);
const publicResource = `http://localhost:${cssPort}/alice/profile/card`;

const publicResponse = await fetch(publicResource);
Expand Down
Loading