Skip to content

Commit 19a05e8

Browse files
committed
Add Entity Restrictions
1 parent fc533e7 commit 19a05e8

20 files changed

Lines changed: 1008 additions & 120 deletions

OpenFlow/src/DatabaseConnection.ts

Lines changed: 100 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import { ValueRecorder } from "@opentelemetry/api-metrics"
88
import { Span } from "@opentelemetry/api";
99
import { Logger } from "./Logger";
1010
import { Auth } from "./Auth";
11+
import { flush } from "pm2";
12+
const { JSONPath } = require('jsonpath-plus');
13+
14+
15+
1116
// tslint:disable-next-line: typedef
1217
const safeObjectID = (s: string | number | ObjectID) => ObjectID.isValid(s) ? new ObjectID(s) : null;
1318
const isoDatePattern = new RegExp(/\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z)/);
@@ -822,8 +827,10 @@ export class DatabaseConnection {
822827
span.addEvent("ensureResource");
823828
span.addEvent("verityToken");
824829
const user: TokenUser = Crypt.verityToken(jwt);
825-
826830
item = this.ensureResource(item);
831+
if (!await this.CheckEntityRestriction(user, collectionname, item, span)) {
832+
throw Error("Create " + item._type + " access denied");
833+
}
827834
span.addEvent("traversejsonencode");
828835
DatabaseConnection.traversejsonencode(item);
829836
let name = item.name;
@@ -840,7 +847,7 @@ export class DatabaseConnection {
840847
Base.addRight(item, user._id, user.name, [Rights.full_control]);
841848
}
842849
if (collectionname != "audit") { Logger.instanse.silly("[" + user.username + "][" + collectionname + "] Adding " + item._type + " " + name + " to database"); }
843-
if (!this.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); }
850+
if (!DatabaseConnection.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); }
844851

845852
span.addEvent("encryptentity");
846853
item = this.encryptentity(item) as T;
@@ -959,6 +966,9 @@ export class DatabaseConnection {
959966
setTimeout(() => OAuthProvider.LoadClients(), 1000);
960967
}
961968
}
969+
if (collectionname === "config" && item._type === "restriction") {
970+
this.EntityRestrictions = null;
971+
}
962972
span.addEvent("traversejsondecode");
963973
DatabaseConnection.traversejsondecode(item);
964974
if (Config.log_inserts) Logger.instanse.debug("[" + user.username + "][" + collectionname + "] inserted " + item.name);
@@ -992,6 +1002,11 @@ export class DatabaseConnection {
9921002
for (let i = 0; i < items.length; i++) {
9931003
let item = this.ensureResource(items[i]);
9941004
DatabaseConnection.traversejsonencode(item);
1005+
1006+
if (!await this.CheckEntityRestriction(user, collectionname, item, span)) {
1007+
continue;
1008+
}
1009+
9951010
let name = item.name;
9961011
if (NoderedUtil.IsNullEmpty(name)) name = item._name;
9971012
if (NoderedUtil.IsNullEmpty(name)) name = "Unknown";
@@ -1006,7 +1021,7 @@ export class DatabaseConnection {
10061021
Base.addRight(item, user._id, user.name, [Rights.full_control]);
10071022
}
10081023
if (collectionname != "audit") { Logger.instanse.silly("[" + user.username + "][" + collectionname + "] Adding " + item._type + " " + name + " to database"); }
1009-
if (!this.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); }
1024+
if (!DatabaseConnection.hasAuthorization(user, item, Rights.create)) { throw new Error("Access denied, no authorization to InsertOne " + item._type + " " + name + " to database"); }
10101025

10111026
item = this.encryptentity(item) as T;
10121027

@@ -1191,8 +1206,7 @@ export class DatabaseConnection {
11911206
if (q.item === null || q.item === undefined) { throw Error("Cannot update null item"); }
11921207
await this.connect(span);
11931208
const user: TokenUser = Crypt.verityToken(q.jwt);
1194-
if (!this.hasAuthorization(user, (q.item as Base), Rights.update)) {
1195-
const again = this.hasAuthorization(user, (q.item as Base), Rights.update);
1209+
if (!DatabaseConnection.hasAuthorization(user, (q.item as Base), Rights.update)) {
11961210
throw new Error("Access denied, no authorization to UpdateOne");
11971211
}
11981212
if (q.collectionname === "files") { q.collectionname = "fs.files"; }
@@ -1209,8 +1223,7 @@ export class DatabaseConnection {
12091223
if (NoderedUtil.IsNullEmpty(name)) name = "Unknown";
12101224
original = await this.getbyid<T>(q.item._id, q.collectionname, q.jwt, span);
12111225
if (!original) { throw Error("item not found!"); }
1212-
if (!this.hasAuthorization(user, original, Rights.update)) {
1213-
const again = this.hasAuthorization(user, original, Rights.update);
1226+
if (!DatabaseConnection.hasAuthorization(user, original, Rights.update)) {
12141227
throw new Error("Access denied, no authorization to UpdateOne " + q.item._type + " " + name + " to database");
12151228
}
12161229
if (q.collectionname === "users" && !NoderedUtil.IsNullEmpty(q.item._type) && !NoderedUtil.IsNullEmpty(q.item.name)) {
@@ -1230,6 +1243,11 @@ export class DatabaseConnection {
12301243
const keys: string[] = Object.keys(original);
12311244
for (let i: number = 0; i < keys.length; i++) {
12321245
let key: string = keys[i];
1246+
if (key == "username" && q.collectionname == "users" && q.item._type == "user") {
1247+
if (!user.HasRoleName("admins")) {
1248+
q.item[key] = original[key];
1249+
}
1250+
}
12331251
if (key === "_created") {
12341252
q.item[key] = new Date(original[key]);
12351253
} else if (key === "_createdby" || key === "_createdbyid") {
@@ -1251,6 +1269,9 @@ export class DatabaseConnection {
12511269
q.item._version = original._version;
12521270
}
12531271
q.item = this.ensureResource(q.item);
1272+
if (original._type != q.item._type && !await this.CheckEntityRestriction(user, q.collectionname, q.item, span)) {
1273+
throw Error("Create " + q.item._type + " access denied");
1274+
}
12541275
DatabaseConnection.traversejsonencode(q.item);
12551276
q.item = this.encryptentity(q.item);
12561277
const hasUser: Ace = q.item._acl.find(e => e._id === user._id);
@@ -1271,6 +1292,8 @@ export class DatabaseConnection {
12711292
let key: string = keys[i];
12721293
if (key === "_created") {
12731294
(q.item as any).metadata[key] = new Date((original as any).metadata[key]);
1295+
} else if (key === "_type") {
1296+
(q.item as any).metadata[key] = (original as any).metadata[key];
12741297
} else if (key === "_createdby" || key === "_createdbyid") {
12751298
(q.item as any).metadata[key] = (original as any).metadata[key];
12761299
} else if (key === "_modifiedby" || key === "_modifiedbyid" || key === "_modified") {
@@ -1416,6 +1439,9 @@ export class DatabaseConnection {
14161439
} else {
14171440
(q.item as any).metadata = this.decryptentity<T>((q.item as any).metadata);
14181441
}
1442+
if (q.collectionname === "config" && q.item._type === "restriction") {
1443+
this.EntityRestrictions = null;
1444+
}
14191445
DatabaseConnection.traversejsondecode(q.item);
14201446
q.result = q.item;
14211447
} catch (error) {
@@ -1447,7 +1473,7 @@ export class DatabaseConnection {
14471473
if (q.item === null || q.item === undefined) { throw Error("Cannot update null item"); }
14481474
await this.connect();
14491475
const user: TokenUser = Crypt.verityToken(q.jwt);
1450-
if (!this.hasAuthorization(user, q.item, Rights.update)) { throw new Error("Access denied, no authorization to UpdateMany"); }
1476+
if (!DatabaseConnection.hasAuthorization(user, q.item, Rights.update)) { throw new Error("Access denied, no authorization to UpdateMany"); }
14511477

14521478
if (q.collectionname === "users" && q.item._type === "user" && q.item.hasOwnProperty("newpassword")) {
14531479
(q.item as any).passwordhash = await Crypt.hash((q.item as any).newpassword);
@@ -1496,6 +1522,7 @@ export class DatabaseConnection {
14961522
}
14971523

14981524
if ((q.item["$set"]) === undefined) { (q.item["$set"]) = {} };
1525+
if (q.item["$set"]._type) delete q.item["$set"]._type;
14991526
(q.item["$set"])._modifiedby = user.name;
15001527
(q.item["$set"])._modifiedbyid = user._id;
15011528
(q.item["$set"])._modified = new Date(new Date().toISOString());
@@ -1567,7 +1594,7 @@ export class DatabaseConnection {
15671594
else if (exists.length > 1) {
15681595
throw JSON.stringify(query) + " is not uniqe, more than 1 item in collection matches this";
15691596
}
1570-
if (!this.hasAuthorization(user, q.item, Rights.update)) {
1597+
if (!DatabaseConnection.hasAuthorization(user, q.item, Rights.update)) {
15711598
Base.addRight(q.item, user._id, user.name, [Rights.full_control], false);
15721599
this.ensureResource(q.item);
15731600
}
@@ -1976,14 +2003,47 @@ export class DatabaseConnection {
19762003
}
19772004
return item;
19782005
}
2006+
public EntityRestrictions: EntityRestriction[] = null;
2007+
async CheckEntityRestriction(user: TokenUser, collection: string, item: Base, parent: Span): Promise<boolean> {
2008+
if (this.EntityRestrictions == null) {
2009+
const rootjwt = Crypt.rootToken()
2010+
this.EntityRestrictions = await this.query<EntityRestriction>({ "_type": "restriction" }, null, 1, 0, null, "config", rootjwt, null, null, parent);
2011+
let allowadmins = new EntityRestriction();
2012+
allowadmins.copyperm = false; allowadmins.collection = ""; allowadmins.paths = ["$."];
2013+
Base.addRight(allowadmins, WellknownIds.admins, "admins", [Rights.create]);
2014+
this.EntityRestrictions.push(allowadmins);
2015+
for (let i = 0; i < this.EntityRestrictions.length; i++) {
2016+
this.EntityRestrictions[i] = EntityRestriction.assign(this.EntityRestrictions[i]);
2017+
}
2018+
}
2019+
const defaultAllow: boolean = false;
2020+
let result: boolean = false;
2021+
const authorized = this.EntityRestrictions.filter(x => x.IsAuthorized(user) && (x.collection == collection || x.collection == ""));
2022+
const matches = authorized.filter(x => x.IsMatch(item) && (x.collection == collection || x.collection == ""));
2023+
const copyperm = authorized.filter(x => x.copyperm && (x.collection == collection || x.collection == ""));
2024+
if (!defaultAllow && matches.length == 0) return false; // no hits, if not allowed return false
2025+
if (matches.length > 0) result = true;
2026+
2027+
for (let cr of copyperm) {
2028+
for (let a of cr._acl) {
2029+
let bits = [];
2030+
for (let i = 0; i < 10; i++) {
2031+
if (Ace.isBitSet(a, i)) bits.push(i);
2032+
2033+
}
2034+
Base.addRight(item, a._id, a.name, bits, false);
2035+
}
2036+
}
2037+
return result;
2038+
}
19792039
/**
19802040
* Validated user has rights to perform the requested action ( create is missing! )
19812041
* @param {TokenUser} user User requesting permission
19822042
* @param {any} item Item permission is needed on
19832043
* @param {Rights} action Permission wanted (create, update, delete)
19842044
* @returns boolean Is allowed
19852045
*/
1986-
hasAuthorization(user: TokenUser, item: Base, action: number): boolean {
2046+
static hasAuthorization(user: TokenUser, item: Base, action: number): boolean {
19872047
if (Config.api_bypass_perm_check) { return true; }
19882048
if (user._id === WellknownIds.root) { return true; }
19892049
if (action === Rights.create || action === Rights.delete) {
@@ -2473,3 +2533,33 @@ export class DatabaseConnection {
24732533
}
24742534
}
24752535

2536+
export class EntityRestriction extends Base {
2537+
public collection: string;
2538+
public copyperm: boolean;
2539+
public paths: string[];
2540+
constructor(
2541+
) {
2542+
super();
2543+
this._type = "restriction";
2544+
}
2545+
static assign<EntityRestriction>(o: any): EntityRestriction {
2546+
if (typeof o === 'string' || o instanceof String) {
2547+
return Object.assign(new EntityRestriction(), JSON.parse(o.toString()));
2548+
}
2549+
return Object.assign(new EntityRestriction(), o);
2550+
}
2551+
public IsMatch(object: object): boolean {
2552+
for (let path of this.paths) {
2553+
const result = JSONPath({ path, json: { a: object } });
2554+
if (result && result.length > 0) return true;
2555+
}
2556+
return false;
2557+
}
2558+
public IsAuthorized(user: TokenUser | User): boolean {
2559+
return DatabaseConnection.hasAuthorization(user as TokenUser, this, Rights.create);
2560+
}
2561+
// public IsAllowed(user: TokenUser | User, object: object, NoMatchValue: boolean) {
2562+
// if (!this.IsMatch(object)) return NoMatchValue;
2563+
// return this.IsAuthorized(user);
2564+
// }
2565+
}

OpenFlow/src/Messages/Message.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2757,7 +2757,7 @@ export class Message {
27572757
Base.addRight(msg.metadata, WellknownIds.filestore_admins, "filestore admins", [Rights.full_control]);
27582758
}
27592759
msg.metadata = Config.db.ensureResource(msg.metadata);
2760-
if (!Config.db.hasAuthorization(user, msg.metadata, Rights.create)) { throw new Error("Access denied, no authorization to save file"); }
2760+
if (!DatabaseConnection.hasAuthorization(user, msg.metadata, Rights.create)) { throw new Error("Access denied, no authorization to save file"); }
27612761
msg.id = await this._SaveFile(readable, msg.filename, msg.mimeType, msg.metadata);
27622762
} catch (error) {
27632763
if (NoderedUtil.IsNullUndefinded(msg)) { (msg as any) = {}; }
@@ -2880,7 +2880,7 @@ export class Message {
28802880
Base.addRight(msg.metadata, user._id, user.name, [Rights.full_control]);
28812881
}
28822882
Base.addRight(msg.metadata, WellknownIds.filestore_admins, "filestore admins", [Rights.full_control]);
2883-
if (!Config.db.hasAuthorization(user, msg.metadata, Rights.update)) { throw new Error("Access denied, no authorization to update file"); }
2883+
if (!DatabaseConnection.hasAuthorization(user, msg.metadata, Rights.update)) { throw new Error("Access denied, no authorization to update file"); }
28842884

28852885
msg.metadata = Config.db.ensureResource(msg.metadata);
28862886
const fsc = Config.db.db.collection("fs.files");

OpenFlow/src/public/CommonControllers.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ export class entityCtrl<T> {
484484
public errormessage: string = "";
485485

486486
public static $inject = [
487+
"$rootScope",
487488
"$scope",
488489
"$location",
489490
"$routeParams",
@@ -492,6 +493,7 @@ export class entityCtrl<T> {
492493
"api"
493494
];
494495
constructor(
496+
public $rootScope: ng.IRootScopeService,
495497
public $scope: ng.IScope,
496498
public $location: ng.ILocationService,
497499
public $routeParams: ng.route.IRouteParamsService,

0 commit comments

Comments
 (0)