diff --git a/lib/common/authorizer.ts b/lib/common/authorizer.ts index a399c49..2af104b 100644 --- a/lib/common/authorizer.ts +++ b/lib/common/authorizer.ts @@ -1,8 +1,5 @@ -import pass from "./validators/pass"; -import test from "./validators/test"; -import { PermissionSet } from "../types"; - -export const validators = { pass, test }; +import { PermissionSet, Expression } from "../types"; +import { evaluate } from "./expression"; export default class Authorizer { private permissionSets: PermissionSet[]; @@ -46,20 +43,16 @@ export default class Authorizer { if (this.validatorCache.has(resource)) return this.validatorCache.get(resource); - let funcs = {}; + const validators: Expression[] = []; for (const permissionSet of this.permissionSets) { for (const perm of permissionSet) { - if (perm[resourceType]) { - if (perm[resourceType].access >= 3) { - if (perm[resourceType].validate) { - for (const [k, v] of Object.entries(perm[resourceType].validate)) - funcs[k] = v; - } - } else { - funcs = {}; - } - } + if ( + perm[resourceType] && + perm[resourceType].access >= 3 && + perm[resourceType].validate + ) + validators.push(perm[resourceType].validate); } } @@ -68,23 +61,41 @@ export default class Authorizer { mutation: any, any: any ): boolean => { - let valid = false; - for (const [k, v] of Object.entries(funcs)) { - if (v) { - const res = validators[k]( - resourceType, - resource, - mutationType, - mutation, - any - ); + if (!validators.length) return false; - if (res > 0) valid = true; - else if (res < 0) return false; + const object = { + mutationType, + mutation, + resourceType, + object: resource, + options: any + }; + + const valueFunction = (paramName): any => { + const entry = paramName.split(".", 1)[0]; + paramName = paramName.slice(entry.length + 1); + let value = null; + if (["mutation", "options"].includes(entry)) { + value = object[entry]; + for (const seg of paramName.split(".")) { + // typeof null is "object" + if (value != null && typeof value !== "object") value = null; + else value = value[seg]; + if (value == null) break; + } + } else if (object[entry]) { + value = object[entry][paramName]; } - } - return valid; + return value; + }; + + const res = evaluate( + validators.length > 1 ? ["OR", validators] : validators[0], + valueFunction, + Date.now() + ); + return !Array.isArray(res) && !!res; }; this.validatorCache.set(resource, validator); diff --git a/lib/common/validators/pass.ts b/lib/common/validators/pass.ts deleted file mode 100644 index f532102..0000000 --- a/lib/common/validators/pass.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function pass(): number { - return 1; -} diff --git a/lib/common/validators/test.ts b/lib/common/validators/test.ts deleted file mode 100644 index 0732976..0000000 --- a/lib/common/validators/test.ts +++ /dev/null @@ -1,12 +0,0 @@ -export default function test(resourceType, resource): number { - if (resourceType === "devices") { - if (resource["Tags.test"] && resource["Tags.test"].value[0]) return 1; - } else if (resourceType === "presets") { - if (resource["_id"].startsWith("test-")) - // TODO check "test" tag too - return 1; - } else if (resourceType === "provisions") { - if (resource["_id"].startsWith("test-")) return 1; - } - return -1; -} diff --git a/lib/local-cache.ts b/lib/local-cache.ts index e60c41a..b84185d 100644 --- a/lib/local-cache.ts +++ b/lib/local-cache.ts @@ -35,7 +35,7 @@ interface Permissions { [resource: string]: { access: number; filter: Expression; - validate: { [validator: string]: boolean }; + validate?: Expression; }; }; }; @@ -351,15 +351,15 @@ function refresh(callback): void { if (!permissions[p.role][p.access]) permissions[p.role][p.access] = {}; - const validate = {}; permissions[p.role][p.access][p.resource] = { access: p.access, - filter: parse(p.filter || "true"), - validate: validate + filter: parse(p.filter || "true") }; - - for (const v of (p.validate || "").split(",")) - validate[v.trim()] = true; + if (p.validate) { + permissions[p.role][p.access][p.resource].validate = parse( + p.validate + ); + } } resolve(permissions); diff --git a/lib/types.ts b/lib/types.ts index 3323ee6..cdbe09b 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -303,9 +303,7 @@ export interface Preset { export type PermissionSet = { [resource: string]: { access: number; - validate?: { - [validator: string]: boolean; - }; + validate?: Expression; filter: Expression; }; }[]; diff --git a/tools/configure-ui b/tools/configure-ui index 714dc96..d45b1df 100755 --- a/tools/configure-ui +++ b/tools/configure-ui @@ -3,30 +3,32 @@ import { MongoClient } from "mongodb"; import { get } from "../lib/config"; import { hashPassword, generateSalt } from "../lib/auth"; +const pass = "true"; + const permissionsData = [ - { role: "admin", resource: "devices", access: 3, validate: ["pass"] }, - { role: "admin", resource: "faults", access: 3, validate: ["pass"] }, - { role: "admin", resource: "files", access: 3, validate: ["pass"] }, - { role: "admin", resource: "presets", access: 3, validate: ["pass"] }, - { role: "admin", resource: "provisions", access: 3, validate: ["pass"] }, - { role: "admin", resource: "config", access: 3, validate: ["pass"] }, - { role: "admin", resource: "permissions", access: 3, validate: ["pass"] }, - { role: "admin", resource: "users", access: 3, validate: ["pass"] }, + { role: "admin", resource: "devices", access: 3, validate: pass }, + { role: "admin", resource: "faults", access: 3, validate: pass }, + { role: "admin", resource: "files", access: 3, validate: pass }, + { role: "admin", resource: "presets", access: 3, validate: pass }, + { role: "admin", resource: "provisions", access: 3, validate: pass }, + { role: "admin", resource: "config", access: 3, validate: pass }, + { role: "admin", resource: "permissions", access: 3, validate: pass }, + { role: "admin", resource: "users", access: 3, validate: pass }, { role: "admin", resource: "virtualParameters", access: 3, - validate: ["pass"] + validate: pass }, - { role: "view", resource: "devices", access: 2, validate: [] }, - { role: "view", resource: "faults", access: 2, validate: [] }, - { role: "view", resource: "files", access: 2, validate: [] }, - { role: "view", resource: "presets", access: 2, validate: [] }, - { role: "view", resource: "provisions", access: 2, validate: [] }, - { role: "view", resource: "config", access: 2, validate: [] }, - { role: "view", resource: "users", access: 2, validate: [] }, - { role: "view", resource: "permissions", access: 2, validate: [] }, - { role: "view", resource: "virtualParameters", access: 2, validate: [] } + { role: "view", resource: "devices", access: 2 }, + { role: "view", resource: "faults", access: 2 }, + { role: "view", resource: "files", access: 2 }, + { role: "view", resource: "presets", access: 2 }, + { role: "view", resource: "provisions", access: 2 }, + { role: "view", resource: "config", access: 2 }, + { role: "view", resource: "users", access: 2 }, + { role: "view", resource: "permissions", access: 2 }, + { role: "view", resource: "virtualParameters", access: 2 } ]; const usersData = [ @@ -194,7 +196,6 @@ async function run(): Promise { .insertMany(objs) ); const perms = permissionsData.map(p => { - p.validate = (p.validate || []).join(","); return Object.assign({ _id: `${p.role}:${p.resource}:${p.access}` }, p); }); inserts.push( diff --git a/ui/permissions-page.ts b/ui/permissions-page.ts index 9ca59c1..529748e 100644 --- a/ui/permissions-page.ts +++ b/ui/permissions-page.ts @@ -9,7 +9,6 @@ import memoize from "../lib/common/memoize"; import putFormComponent from "./put-form-component"; import * as overlay from "./overlay"; import * as smartQuery from "./smart-query"; -import { validators } from "../lib/common/authorizer"; import { map, parse, stringify } from "../lib/common/expression-parser"; import filterComponent from "./filter-component"; import { Children, ClosureComponent, Component } from "mithril"; @@ -43,13 +42,7 @@ const attributes = [ type: "combo", options: ["1: count", "2: read", "3: write"] }, - { - id: "validate", - label: "Validators", - type: "multi", - options: Object.keys(validators), - unsortable: true - } + { id: "validate", label: "Validate", unsortable: true } ]; const unpackSmartQuery = memoize(query => { @@ -79,16 +72,25 @@ function putActionHandler(action, _object): Promise { else if (object.access === "1: count") object.access = 1; else return void resolve({ access: "Invalid access level" }); - object.filter = object.filter || ""; - try { - memoizedParse(object.filter); - } catch (err) { - return void resolve({ - filter: "Filter must be valid expression" - }); + if (object.filter) { + try { + object.filter = stringify(memoizedParse(object.filter)); + } catch (err) { + return void resolve({ + filter: "Filter must be valid expression" + }); + } } - object.validate = (object.validate || []).join(","); + if (object.validate) { + try { + object.validate = stringify(memoizedParse(object.validate)); + } catch (err) { + return void resolve({ + validate: "Validate must be valid expression" + }); + } + } const id = `${object.role}:${object.resource}:${object.access}`;