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
34 changes: 12 additions & 22 deletions controller/src/classes/OdrlController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { getDefaultSession } from "@inrupt/solid-client-authn-browser";
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";
Expand Down Expand Up @@ -57,23 +56,21 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
/**
* Updates existing policies according to present rule changes
* @param updates All requested changes
* @returns
* @returns
*/
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 store = await service.fetchPolicies();
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'){
Expand All @@ -88,7 +85,7 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
modifiedPolicyIds.add(policy.id);
}
continue;

}
const policy = policiesMap.get(update.policyId);
if (!policy) continue;
Expand All @@ -115,12 +112,12 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
const savePromises = Array.from(modifiedPolicyIds).map(async (policyId) => {
const policy = policiesMap.get(policyId)!;
if(policy.rules.length == 0){
await service.deletePolicy(webId, policyId);
await service.deletePolicy(policyId);
}
else{
// Convert JS Policy object back to Turtle format
const turtleText = interpreter.policyToTurtle(webId, policy);
await service.putPolicy(webId, policyId, turtleText);
const turtleText = interpreter.policyToTurtle(policy);
await service.putPolicy(policyId, turtleText);
}
});

Expand All @@ -133,11 +130,7 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri
* @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);
const store = await new ODRLPolicyService(this.authorizationServerURL).fetchPolicies();
return new PolicyInterpreter().storeToPolicies(store, resourceUrl);
}

Expand All @@ -160,20 +153,17 @@ export class ODRLController<T extends Record<keyof T, BaseSubject<keyof T & stri

// ! added for access requests
async requestAccess(permission: { accessRequest: AccessRequestObject}): Promise<void> {
const webid = getDefaultSession().info.webId!;
permission.accessRequest.requestingParty = webid;
await new ODRLAccessRequestService(this.authorizationServerURL).requestAccess(permission.accessRequest);
return new ODRLAccessRequestService(this.authorizationServerURL).requestAccess(permission.accessRequest);
}

async handleAccessRequest(requestId: string, status: 'accepted' | 'denied'): Promise<void> {
const webid = getDefaultSession().info.webId!;
await new ODRLAccessRequestService(this.authorizationServerURL).acceptOrDenyAccess(requestId, webid, status);
return new ODRLAccessRequestService(this.authorizationServerURL).acceptOrDenyAccess(requestId, status);
}

async getAccessRequests(): Promise<{
asRequestingParty: AccessRequestObject[];
asResourceOwner: AccessRequestObject[];
}> {
return new ODRLAccessRequestService(this.authorizationServerURL).retrieveAccessRequests(getDefaultSession().info.webId!);
return new ODRLAccessRequestService(this.authorizationServerURL).retrieveAccessRequests();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { ODRLPermissionManager } from "./OdrlPermissionManager";
export class PublicManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends ODRLPermissionManager<T> implements IPermissionManager<T> {

//. NOTE: Currently, it doesn't do any recursive permission setting on containers
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions)
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[], owner: string): Promise<void> {
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions, owner)
}

async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
Expand Down
4 changes: 2 additions & 2 deletions controller/src/classes/permissionManager/odrl/WebIdManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { ODRLPolicyService } from "../../utils/OdrlPolicyService";
export class WebIdManager<T extends Record<keyof T, BaseSubject<keyof T & string>>> extends ODRLPermissionManager<T> implements IPermissionManager<T> {

// Create an action for this resource and this subject with the given permissions
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]): Promise<void> {
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions, subject.selector!.url);
async createPermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[], owner: string): Promise<void> {
await new ODRLPolicyService(this.authorizationServerURL).insertActionRule(resource, permissions, subject.selector!.url, owner);
}

async deletePermissions<K extends SubjectKey<T>>(resource: string, subject: T[K], permissions: Permission[]) {
Expand Down
54 changes: 54 additions & 0 deletions controller/src/classes/utils/Authentication.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export interface AuthenticationContext {
accessToken: string;
identifier: string;
}

/**
* Normalizes an OIDC subject identifier to an IRI.
* Solid WebIDs are already IRIs; plain OIDC `sub` values (e.g. a username or UUID)
* are prepended with the server-side base to match how the policy AS stores them.
* Update this base when the server-side convention changes.
*/
const NON_IRI_IDENTIFIER_BASE = 'http://example.com/id/';
const IRI_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:\S*$/;

export function normalizeIdentifierToIri(identifier: string): string {
if (IRI_PATTERN.test(identifier)) {
return identifier;
}
return `${NON_IRI_IDENTIFIER_BASE}${encodeURIComponent(identifier)}`;
}

let authContext: AuthenticationContext | undefined;

export function setAuthenticationContext(context: AuthenticationContext): void {
authContext = {
...context,
identifier: normalizeIdentifierToIri(context.identifier),
};
}

export function clearAuthenticationContext(): void {
authContext = undefined;
}

function requireAuthenticationContext(): AuthenticationContext {
if (!authContext) {
throw new Error('User not logged in');
}
return authContext;
}

export function authenticatedFetch(url: string, options?: RequestInit): Promise<Response> {
const { accessToken } = requireAuthenticationContext();
const headers = new Headers(options?.headers);
headers.set('Authorization', `Bearer ${accessToken}`);
return fetch(url, {
...options,
headers,
});
}

export function getLoggedInIdentifier(): string {
return requireAuthenticationContext().identifier;
}
58 changes: 28 additions & 30 deletions controller/src/classes/utils/OdrlAccessRequestService.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import { authenticatedFetch, getLoggedInIdentifier } from './Authentication';
import { AccessRequest } from "@/types/modules";
import { QueryEngine } from "@comunica/query-sparql";
import { Parser, Store } from "n3";
import { v4 as uuid } from 'uuid';

export class ODRLAccessRequestService {

private readonly queryEngine = new QueryEngine();
private readonly parser = new Parser({ format: 'text/turtle' });

constructor(
private readonly authorizationServerURL: string
) {}

/**
* Place a POST request to create an access request to an UMA backend
* @param accessRequest - all infromation regarding an access request
* Place a POST request to create an access request to a UMA backend
* @param accessRequest - all information regarding an access request
*/
public requestAccess = async (accessRequest: AccessRequest): Promise<void> => {
const response = await fetch(
const response = await authenticatedFetch(
`${this.authorizationServerURL}/requests`, {
method: 'POST',
headers: {
'authorization': `WebID ${encodeURIComponent(accessRequest.requestingParty)}`
}, body: await this.accessRequestToJson(accessRequest)
body: await this.accessRequestToJson(accessRequest)
}
);

Expand All @@ -32,13 +31,13 @@ export class ODRLAccessRequestService {
/**
* Transform accessrequest to required JSON format
* @param accessRequest - all information regarding an access request
* @returns
* @returns
*/
private accessRequestToJson = async (accessRequest: AccessRequest): Promise<string> => {
const payload: any = {
resource_id: accessRequest.target,
resource_scopes:
accessRequest.actions.map(action =>
resource_scopes:
accessRequest.actions.map(action =>
action.startsWith('http') ? action : `http://www.w3.org/ns/odrl/2/${action}`
)
};
Expand All @@ -65,19 +64,16 @@ export class ODRLAccessRequestService {
/**
* Place a PATCH request to update an access request to an UMA backend
* @param accessRequestID - ID of the access request to update
* @param resourceOwner - user credentials of the resource owner
* @param status - new status for the update, must either be 'accepted' or 'denied'
*/
public acceptOrDenyAccess = async (
accessRequestID: string,
resourceOwner: string,
status: 'accepted' | 'denied'
): Promise<void> => {
const response = await fetch(
const response = await authenticatedFetch(
`${this.authorizationServerURL}/requests/${encodeURIComponent(accessRequestID)}`, {
method: 'PATCH',
headers: {
'authorization': `WebID ${encodeURIComponent(resourceOwner)}`,
'content-type': 'application/json'
}, body: JSON.stringify({ status: status })
}
Expand All @@ -88,16 +84,12 @@ export class ODRLAccessRequestService {

/**
* Retrieve all access requests related to the given resource owner or requesting party
* @param resourceOwnerOrRequestingPartyID - ID of the resource owner or requesting party
*/
public retrieveAccessRequests = async (resourceOwnerOrRequestingPartyID: string): Promise<{ asRequestingParty: AccessRequest[], asResourceOwner: AccessRequest[] }> => {
public retrieveAccessRequests = async (): Promise<{ asRequestingParty: AccessRequest[], asResourceOwner: AccessRequest[] }> => {
const [ requestsResponse, policiesResponse ] = await Promise.all(
['/requests', '/policies'].map((endpoint) => fetch(
['/requests', '/policies'].map((endpoint) => authenticatedFetch(
`${this.authorizationServerURL}${endpoint}`, {
method: 'GET',
headers: {
'authorization': `WebID ${encodeURIComponent(resourceOwnerOrRequestingPartyID)}`
}
}
))
);
Expand All @@ -113,12 +105,13 @@ export class ODRLAccessRequestService {
const requestsStore = new Store(this.parser.parse(requestsText));
const policiesStore = new Store(this.parser.parse(policiesText));

const id = getLoggedInIdentifier();
const requestingPartyBindings = await this.queryEngine.queryBindings(
this.accessRequestForRequestingParty(resourceOwnerOrRequestingPartyID), { sources: [requestsStore] }
this.accessRequestForRequestingParty(id), { sources: [requestsStore] }
);

const resourceOwnerBindings = await this.queryEngine.queryBindings(
this.accessRequestForResourceOwner(resourceOwnerOrRequestingPartyID), { sources: [requestsStore, policiesStore] }
this.accessRequestForResourceOwner(id), { sources: [requestsStore, policiesStore] }
);

return {
Expand All @@ -128,9 +121,9 @@ export class ODRLAccessRequestService {
}

/**
* Transform raw bindings to AccessRequest objects
* @param bindings
* @returns
* Transform raw bindings to AccessRequest objects
* @param bindings
* @returns
*/
private bindingsToAccessRequest = async (bindings: any): Promise<AccessRequest[]> => {
const requestsMap = new Map<string, AccessRequest>();
Expand Down Expand Up @@ -202,8 +195,8 @@ export class ODRLAccessRequestService {
/**
* Fetches all access requests submitted by a given WebId
* Returns a SPARQL query string
* @param requestingPartyID
* @returns
* @param requestingPartyID
* @returns
*/
private readonly accessRequestForRequestingParty = (requestingPartyID: string): string => `
PREFIX ex: <http://example.org/>
Expand Down Expand Up @@ -234,8 +227,13 @@ export class ODRLAccessRequestService {
/**
* Fetches all access requests controlled by a given WebId
* Returns a SPARQL query string
* @param resourceOwnerID
* @returns
*
* An ID being the resource owner is determined by there being a policy owned by this ID targeting this resource.
* If there is no policy yet for this resource,
* this function will not be able to determine that the given ID is the owner.
*
* @param resourceOwnerID
* @returns
*/
private readonly accessRequestForResourceOwner = (resourceOwnerID: string): string => `
PREFIX ex: <http://example.org/>
Expand Down
Loading