Skip to content

Commit

Permalink
improve protected pointers, inherited read/write permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
benStre committed Dec 6, 2023
1 parent 768f993 commit 5f65e7a
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 26 deletions.
13 changes: 11 additions & 2 deletions compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1975,7 +1975,7 @@ export class Compiler {

// remember if js type def modules should be added to this scope
if (SCOPE.addJSTypeDefs == undefined) {
let receiver = Compiler.builder.getScopeReceiver(SCOPE);
const receiver = Compiler.builder.getScopeReceiver(SCOPE);
SCOPE.addJSTypeDefs = !!jsTypeDefModule && receiver != Runtime.endpoint && receiver != LOCAL_ENDPOINT;
}

Expand Down Expand Up @@ -2220,6 +2220,14 @@ export class Compiler {
return;
}

// pointer is sent to receiver, so he gets access (TODO: improve)
if (Runtime.OPTIONS.PROTECT_POINTERS) {
const receiver = Compiler.builder.getScopeReceiver(SCOPE);
if (receiver !== Runtime.endpoint) {
p.grantAccessTo(receiver)
}
}

// pre extract per default
if ((<compiler_scope>SCOPE).extract_pointers && action_type == ACTION_TYPE.GET) {
Compiler.builder.insertExtractedVariable(<compiler_scope>SCOPE, BinaryCode.POINTER, buffer2hex(p.id_buffer));
Expand Down Expand Up @@ -2597,7 +2605,7 @@ export class Compiler {
serializeValue: (v:any, SCOPE:compiler_scope):any => {
if (SCOPE.serialized_values.has(v)) return SCOPE.serialized_values.get(v);
else {
let receiver = Compiler.builder.getScopeReceiver(SCOPE);
const receiver = Compiler.builder.getScopeReceiver(SCOPE);
const s = Runtime.serializeValue(v, receiver);
SCOPE.serialized_values.set(v,s);
return s;
Expand All @@ -2614,6 +2622,7 @@ export class Compiler {
}
options = options.parent_scope?.options;
}
if (!SCOPE.options?.to) SCOPE.options.to = receiver;
return receiver;
},

Expand Down
43 changes: 43 additions & 0 deletions datex_short.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,37 @@ export function pointer<T>(value:RefOrValue<T>, property?:unknown): unknown {

}


/**
* Add endpoint to allowed_access list
* @param endpoint
*/
export function grantAccess(value: any, endpoint: string|Endpoint) {
const pointer = Pointer.pointerifyValue(value);
if (pointer instanceof Pointer) pointer.grantAccessTo(typeof endpoint == "string" ? f(endpoint as "@") : endpoint)
else throw new Error("Cannot set read permissions for non-pointer value")
}

/**
* Grant public access for pointer
* @param endpoint
*/
export function grantPublicAccess(value: any) {
const pointer = Pointer.pointerifyValue(value);
if (pointer instanceof Pointer) pointer.grantPublicAccess()
else throw new Error("Cannot set read permissions for non-pointer value")
}

/**
* Remove endpoint from allowed_access list
* @param endpoint
*/
export function revokeAccess(value: any, endpoint: string|Endpoint) {
const pointer = Pointer.pointerifyValue(value);
if (pointer instanceof Pointer) pointer.revokeAccessFor(typeof endpoint == "string" ? f(endpoint as "@") : endpoint)
else throw new Error("Cannot set read permissions for non-pointer value")
}

export const $$ = pointer;

interface $fn {
Expand Down Expand Up @@ -445,6 +476,10 @@ export async function once<T>(id_or_init:string|(()=>Promise<T>|T), _init?:()=>P

const _once = once;
type val = typeof val;
type grantAccess = typeof grantAccess
type grantPublicAccess = typeof grantPublicAccess
type revokeAccess = typeof revokeAccess

declare global {
const eternal: undefined
const lazyEternal: undefined
Expand All @@ -456,6 +491,10 @@ declare global {
const eternalVar: (customIdentifier:string)=>undefined
const lazyEternalVar: (customIdentifier:string)=>undefined
const once: typeof _once;

const grantAccess: grantAccess;
const grantPublicAccess: grantPublicAccess;
const revokeAccess: revokeAccess;
}


Expand Down Expand Up @@ -583,6 +622,10 @@ Object.defineProperty(globalThis, 'observeAndInit', {value:Ref.observeAndInit.bi
Object.defineProperty(globalThis, 'unobserve', {value:Ref.unobserve.bind(Ref), configurable:false})
Object.defineProperty(globalThis, 'isolate', {value:Ref.disableCapturing.bind(Ref), configurable:false})

Object.defineProperty(globalThis, 'grantAccess', {value:grantAccess, configurable:false})
Object.defineProperty(globalThis, 'grantPublicAccess', {value:grantPublicAccess, configurable:false})
Object.defineProperty(globalThis, 'revokeAccess', {value:revokeAccess, configurable:false})

// @ts-ignore
globalThis.get = get
// @ts-ignore
Expand Down
29 changes: 28 additions & 1 deletion docs/manual/04 Pointer Synchronisation.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ x.val // -> 42
This pointer is now accessible on any other endpoint in the supranet.

> [!NOTE]
> Per default, pointers have no read/write restrictions and can be accessed by any endpoint. This can be prevented by defining pointer permissions. This behaviour might also change in the future.
> Per default, pointers have no read/write restrictions and can be accessed by any endpoint. This behaviour can be disabled by setting the [`PROTECT_POINTERS` runtime flag](#protecting-pointers)
### Pointer IDs

Expand Down Expand Up @@ -114,6 +114,32 @@ x.val // -> 10
Pointer synchronisation does not just work with primitive values,
but also with objects, maps, sets, etc.

## Protecting Pointers

Per default, pointers have no read/write restrictions and can be accessed by any endpoint.
This behaviour will probably change in the future.
To disable default read/write access for all remote endpoints, you can set

```ts
Datex.Runtime.OPTIONS.PROTECT_POINTERS = true
```

Now, pointers are only accessible by remote endpoints if they are explicitly sent to the endpoint from the
origin endpoint.

You can also explicitly grant access for a pointer to a specific endpoint:

```ts
const x = $$("private content");
grantAccess(x, '@user1'); // @user1 can now read/write x
revokeAccess(x, '@user1'); // @user1 can no longer read/write x
```

You can also make a pointer publicly accessible by all endpoints:
```ts
grantPublicAccess(x); // any endpoint can now read/write x
```


## Global Garbage Collection (GGC)

Expand All @@ -140,3 +166,4 @@ const x1 = await $.ABCDEF
const x2 = await $.ABCDEF
assert (x1 === x2)
```

13 changes: 11 additions & 2 deletions js_adapter/js_class_adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,15 +889,24 @@ function _old_publicStaticClass(original_class:Class) {
// set allowed endpoints for this method
//static_scope.setAllowedEndpointsForProperty(name, this.method_a_filters.get(name))

let dx_function = Pointer.proxifyValue(DatexFunction.createFromJSFunction(current_value, original_class, name), true, undefined, false, true) ; // generate <Function>
const dx_function = Pointer.proxifyValue(DatexFunction.createFromJSFunction(current_value, original_class, name), true, undefined, false, true) ; // generate <Function>

// public function
const ptr = Pointer.pointerifyValue(dx_function);
if (ptr instanceof Pointer) ptr.grantPublicAccess(true);

static_scope.setVariable(name, dx_function); // add <Function> to static scope
}

// field
else {
// set static value (datexified)
let setProxifiedValue = (val:any) => static_scope.setVariable(name, Pointer.proxifyValue(val, true, undefined, false, true));
const setProxifiedValue = (val:any) => {
static_scope.setVariable(name, Pointer.proxifyValue(val, true, undefined, false, true))
// public function
const ptr = Pointer.proxifyValue(val);
if (ptr instanceof Pointer) ptr.grantPublicAccess(true);
};
setProxifiedValue(current_value);

/*** handle new value assignments to this property: **/
Expand Down
4 changes: 2 additions & 2 deletions network/network_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export abstract class NetworkUtils {
static _get_keys_data = {scope_name:"network", sign:false, filter:undefined};
static get_keys (endpoint:Endpoint):Promise<[ArrayBuffer, ArrayBuffer]> {
if (!this._get_keys) this._get_keys = getProxyFunction("get_keys", this._get_keys_data);
this._get_keys_data.filter = Runtime.main_node ?? Runtime.endpoint
this._get_keys_data.filter = Runtime.main_node
return this._get_keys(endpoint)
}

Expand All @@ -19,7 +19,7 @@ export abstract class NetworkUtils {

static add_push_channel (channel:string, data:object):Promise<any> {
if (!this._add_push_channel) this._add_push_channel = getProxyFunction("add_push_channel", this._add_push_channel_data);
this._add_push_channel_data.filter = Runtime.main_node ?? Runtime.endpoint
this._add_push_channel_data.filter = Runtime.main_node
return this._add_push_channel(channel, data)
}
}
27 changes: 17 additions & 10 deletions runtime/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,6 @@ export class Crypto {
// keys not found, request from network
else {
return this.requestKeys(endpoint.main)
.catch(() => this.requestKeys(endpoint))
}
}

Expand All @@ -156,18 +155,25 @@ export class Crypto {
if (verify_key && !(verify_key instanceof ArrayBuffer)) throw new ValueError("Invalid verify key");
if (enc_key && !(enc_key instanceof ArrayBuffer)) throw new ValueError("Invalid encryption key");

// always bind to main endpoint
endpoint = endpoint.main;

if (this.public_keys.has(endpoint)) return false; // keys already exist

const storage_item_key = "keys_"+endpoint;
if (await Storage.hasItem(storage_item_key)) return false; // keys already in storage

try {
this.public_keys.set(endpoint, [
try {
const keys = [
verify_key ? await Crypto.importVerifyKey(verify_key) : null,
enc_key ? await Crypto.importEncKey(enc_key): null
])
this.public_keys_exported.set(endpoint, [verify_key, enc_key]);
await Storage.setItem(storage_item_key, [verify_key, enc_key]);
] as [CryptoKey | null, CryptoKey | null];
const exportedKeys = [verify_key, enc_key] as [ArrayBuffer, ArrayBuffer];

this.public_keys.set(endpoint.main, keys)
this.public_keys_exported.set(endpoint, exportedKeys);

await Storage.setItem(storage_item_key, exportedKeys);
return true;
} catch(e) {
logger.error(e);
Expand Down Expand Up @@ -237,7 +243,8 @@ export class Crypto {
// convert to CryptoKeys
try {
const keys:[CryptoKey, CryptoKey] = [await this.importVerifyKey(exported_keys[0])||null, await this.importEncKey(exported_keys[1])||null];
this.public_keys.set(endpoint, keys);
this.public_keys.set(endpoint.main, keys);
logger.debug("saving keys for " + endpoint);
resolve(keys);
this.#waiting_key_requests.delete(endpoint); // remove from key promises
return;
Expand Down Expand Up @@ -308,9 +315,9 @@ export class Crypto {

private static saveOwnPublicKeysInEndpointKeyMap () {
// save in local endpoint key storage
if (!this.public_keys.has(Runtime.endpoint)) this.public_keys.set(Runtime.endpoint, [null,null]);
(<[CryptoKey?, CryptoKey?]>this.public_keys.get(Runtime.endpoint))[0] = this.rsa_verify_key;
(<[CryptoKey?, CryptoKey?]>this.public_keys.get(Runtime.endpoint))[1] = this.rsa_enc_key;
if (!this.public_keys.has(Runtime.endpoint)) this.public_keys.set(Runtime.endpoint.main, [null,null]);
(<[CryptoKey?, CryptoKey?]>this.public_keys.get(Runtime.endpoint.main))[0] = this.rsa_verify_key;
(<[CryptoKey?, CryptoKey?]>this.public_keys.get(Runtime.endpoint.main))[1] = this.rsa_enc_key;
}

// returns current public verify + encrypt keys
Expand Down
48 changes: 45 additions & 3 deletions runtime/pointers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// deno-lint-ignore-file no-namespace
import { Endpoint, endpoints, endpoint_name, IdEndpoint, Person, Target, target_clause, LOCAL_ENDPOINT } from "../types/addressing.ts";
import { Endpoint, endpoints, endpoint_name, IdEndpoint, Person, Target, target_clause, LOCAL_ENDPOINT, BROADCAST } from "../types/addressing.ts";
import { NetworkError, PermissionError, PointerError, RuntimeError, ValueError } from "../types/errors.ts";
import { Compiler, PrecompiledDXB } from "../compiler/compiler.ts";
import { DX_PTR, DX_REACTIVE_METHODS, DX_VALUE, INVALID, NOT_EXISTING, SET_PROXY, SHADOW_OBJECT, UNKNOWN_TYPE, VOID } from "./constants.ts";
Expand Down Expand Up @@ -1453,7 +1453,7 @@ export class Pointer<T = any> extends Ref<T> {

this.loading_pointers.delete(id_string);

// check read permissions (works if PROTECT_POINTERS enabled)
// check read permissions
pointer.assertEndpointCanRead(SCOPE?.sender)

return pointer;
Expand Down Expand Up @@ -1807,7 +1807,7 @@ export class Pointer<T = any> extends Ref<T> {
sealed:boolean = false; // can the value be changed from the client side? (otherwise, it can only be changed via DATEX calls)
#scheduler: UpdateScheduler|null = null // has fixed update_interval

#allowed_access: target_clause // who has access to this pointer?, undefined = all
#allowed_access?: target_clause // who has access to this pointer?, undefined = all

#garbage_collectable = false;
#garbage_collected = false;
Expand Down Expand Up @@ -1852,13 +1852,22 @@ export class Pointer<T = any> extends Ref<T> {
* @returns
*/
public assertEndpointCanRead(endpoint?: Endpoint) {
// logger.error(this.val)
// console.log("assert " + endpoint, Logical.matches(endpoint, this.allowed_access, Target), !(
// Runtime.OPTIONS.PROTECT_POINTERS
// && !(endpoint == Runtime.endpoint)
// && this.is_origin
// && (!endpoint || !Logical.matches(endpoint, this.allowed_access, Target))
// && (endpoint && !Runtime.trustedEndpoints.get(endpoint.main)?.includes("protected-pointer-access"))
// ), this.allowed_access)
if (
Runtime.OPTIONS.PROTECT_POINTERS
&& !(endpoint == Runtime.endpoint)
&& this.is_origin
&& (!endpoint || !Logical.matches(endpoint, this.allowed_access, Target))
&& (endpoint && !Runtime.trustedEndpoints.get(endpoint.main)?.includes("protected-pointer-access"))
) {
console.log("inv",new Error().stack)
throw new PermissionError("Endpoint has no read permissions for this pointer")
}
}
Expand All @@ -1869,6 +1878,37 @@ export class Pointer<T = any> extends Ref<T> {
}


/**
* add endpoint to allowed_access list
* @param endpoint
*/
public grantAccessTo(endpoint: Endpoint, _force = false) {
if (!_force && !Runtime.OPTIONS.PROTECT_POINTERS) throw new Error("Read permissions are not enabled per default (set Datex.Runtime.OPTIONS.PROTECT_POINTERS to true)")
if (!this.#allowed_access || this.#allowed_access == BROADCAST) this.#allowed_access = new Disjunction()
if (this.#allowed_access instanceof Disjunction) this.#allowed_access.add(endpoint)
else throw new Error("Invalid access filter, cannot add endpoint (TODO)")
}


/**
* add public access to pointer
* @param endpoint
*/
public grantPublicAccess(_force = false) {
if (!_force && !Runtime.OPTIONS.PROTECT_POINTERS) throw new Error("Read permissions are not enabled per default (set Datex.Runtime.OPTIONS.PROTECT_POINTERS to true)")
this.#allowed_access = BROADCAST;
}


/**
* remove endpoint from allowed_access list
* @param endpoint
*/
public revokeAccessFor(endpoint: Endpoint, _force = false) {
if (!_force && !Runtime.OPTIONS.PROTECT_POINTERS) throw new Error("Read permissions are not enabled per default (set Datex.Runtime.OPTIONS.PROTECT_POINTERS to true)")
if (this.#allowed_access instanceof Disjunction) this.#allowed_access.delete(endpoint)
else throw new Error("Invalid access filter, cannot add endpoint (TODO)")
}


/**
Expand Down Expand Up @@ -2557,6 +2597,8 @@ export class Pointer<T = any> extends Ref<T> {
}
catch (e) {
if (e !== Pointer.WEAK_EFFECT_DISPOSED) console.error(e);
// invalid result, no update
return;
}
// always cleanup capturing
finally {
Expand Down
Loading

0 comments on commit 5f65e7a

Please sign in to comment.