Use expressions as validators

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-06-05 00:17:36 -07:00
1 parent 08e3933c4a
commit fdda68434b
7 files changed
+82 -85

No files matched your search

+41 -30
View File
@@ -1,8 +1,5 @@
import pass from "./validators/pass"; import { PermissionSet, Expression } from "../types";
import test from "./validators/test"; import { evaluate } from "./expression";
import { PermissionSet } from "../types";
export const validators = { pass, test };
export default class Authorizer { export default class Authorizer {
private permissionSets: PermissionSet[]; private permissionSets: PermissionSet[];
@@ -46,20 +43,16 @@ export default class Authorizer {
if (this.validatorCache.has(resource)) if (this.validatorCache.has(resource))
return this.validatorCache.get(resource); return this.validatorCache.get(resource);
let funcs = {}; const validators: Expression[] = [];
for (const permissionSet of this.permissionSets) { for (const permissionSet of this.permissionSets) {
for (const perm of permissionSet) { for (const perm of permissionSet) {
if (perm[resourceType]) { if (
if (perm[resourceType].access >= 3) { perm[resourceType] &&
if (perm[resourceType].validate) { perm[resourceType].access >= 3 &&
for (const [k, v] of Object.entries(perm[resourceType].validate)) perm[resourceType].validate
funcs[k] = v; )
} validators.push(perm[resourceType].validate);
} else {
funcs = {};
}
}
} }
} }
@@ -68,23 +61,41 @@ export default class Authorizer {
mutation: any, mutation: any,
any: any any: any
): boolean => { ): boolean => {
let valid = false; if (!validators.length) return false;
for (const [k, v] of Object.entries(funcs)) {
if (v) { const object = {
const res = validators[k](
resourceType,
resource,
mutationType, mutationType,
mutation, mutation,
any 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 value;
};
const res = evaluate(
validators.length > 1 ? ["OR", validators] : validators[0],
valueFunction,
Date.now()
); );
return !Array.isArray(res) && !!res;
if (res > 0) valid = true;
else if (res < 0) return false;
}
}
return valid;
}; };
this.validatorCache.set(resource, validator); this.validatorCache.set(resource, validator);
-3
View File
@@ -1,3 +0,0 @@
export default function pass(): number {
return 1;
}
-12
View File
@@ -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;
}
+7 -7
View File
@@ -35,7 +35,7 @@ interface Permissions {
[resource: string]: { [resource: string]: {
access: number; access: number;
filter: Expression; filter: Expression;
validate: { [validator: string]: boolean }; validate?: Expression;
}; };
}; };
}; };
@@ -351,15 +351,15 @@ function refresh(callback): void {
if (!permissions[p.role][p.access]) if (!permissions[p.role][p.access])
permissions[p.role][p.access] = {}; permissions[p.role][p.access] = {};
const validate = {};
permissions[p.role][p.access][p.resource] = { permissions[p.role][p.access][p.resource] = {
access: p.access, access: p.access,
filter: parse(p.filter || "true"), filter: parse(p.filter || "true")
validate: validate
}; };
if (p.validate) {
for (const v of (p.validate || "").split(",")) permissions[p.role][p.access][p.resource].validate = parse(
validate[v.trim()] = true; p.validate
);
}
} }
resolve(permissions); resolve(permissions);
+1 -3
View File
@@ -303,9 +303,7 @@ export interface Preset {
export type PermissionSet = { export type PermissionSet = {
[resource: string]: { [resource: string]: {
access: number; access: number;
validate?: { validate?: Expression;
[validator: string]: boolean;
};
filter: Expression; filter: Expression;
}; };
}[]; }[];
+20 -19
View File
@@ -3,30 +3,32 @@ import { MongoClient } from "mongodb";
import { get } from "../lib/config"; import { get } from "../lib/config";
import { hashPassword, generateSalt } from "../lib/auth"; import { hashPassword, generateSalt } from "../lib/auth";
const pass = "true";
const permissionsData = [ const permissionsData = [
{ role: "admin", resource: "devices", access: 3, validate: ["pass"] }, { role: "admin", resource: "devices", access: 3, validate: pass },
{ role: "admin", resource: "faults", access: 3, validate: ["pass"] }, { role: "admin", resource: "faults", access: 3, validate: pass },
{ role: "admin", resource: "files", access: 3, validate: ["pass"] }, { role: "admin", resource: "files", access: 3, validate: pass },
{ role: "admin", resource: "presets", access: 3, validate: ["pass"] }, { role: "admin", resource: "presets", access: 3, validate: pass },
{ role: "admin", resource: "provisions", access: 3, validate: ["pass"] }, { role: "admin", resource: "provisions", access: 3, validate: pass },
{ role: "admin", resource: "config", access: 3, validate: ["pass"] }, { role: "admin", resource: "config", access: 3, validate: pass },
{ role: "admin", resource: "permissions", access: 3, validate: ["pass"] }, { role: "admin", resource: "permissions", access: 3, validate: pass },
{ role: "admin", resource: "users", access: 3, validate: ["pass"] }, { role: "admin", resource: "users", access: 3, validate: pass },
{ {
role: "admin", role: "admin",
resource: "virtualParameters", resource: "virtualParameters",
access: 3, access: 3,
validate: ["pass"] validate: pass
}, },
{ role: "view", resource: "devices", access: 2, validate: [] }, { role: "view", resource: "devices", access: 2 },
{ role: "view", resource: "faults", access: 2, validate: [] }, { role: "view", resource: "faults", access: 2 },
{ role: "view", resource: "files", access: 2, validate: [] }, { role: "view", resource: "files", access: 2 },
{ role: "view", resource: "presets", access: 2, validate: [] }, { role: "view", resource: "presets", access: 2 },
{ role: "view", resource: "provisions", access: 2, validate: [] }, { role: "view", resource: "provisions", access: 2 },
{ role: "view", resource: "config", access: 2, validate: [] }, { role: "view", resource: "config", access: 2 },
{ role: "view", resource: "users", access: 2, validate: [] }, { role: "view", resource: "users", access: 2 },
{ role: "view", resource: "permissions", access: 2, validate: [] }, { role: "view", resource: "permissions", access: 2 },
{ role: "view", resource: "virtualParameters", access: 2, validate: [] } { role: "view", resource: "virtualParameters", access: 2 }
]; ];
const usersData = [ const usersData = [
@@ -194,7 +196,6 @@ async function run(): Promise<void> {
.insertMany(objs) .insertMany(objs)
); );
const perms = permissionsData.map(p => { const perms = permissionsData.map(p => {
p.validate = (p.validate || []).join(",");
return Object.assign({ _id: `${p.role}:${p.resource}:${p.access}` }, p); return Object.assign({ _id: `${p.role}:${p.resource}:${p.access}` }, p);
}); });
inserts.push( inserts.push(
+13 -11
View File
@@ -9,7 +9,6 @@ import memoize from "../lib/common/memoize";
import putFormComponent from "./put-form-component"; import putFormComponent from "./put-form-component";
import * as overlay from "./overlay"; import * as overlay from "./overlay";
import * as smartQuery from "./smart-query"; import * as smartQuery from "./smart-query";
import { validators } from "../lib/common/authorizer";
import { map, parse, stringify } from "../lib/common/expression-parser"; import { map, parse, stringify } from "../lib/common/expression-parser";
import filterComponent from "./filter-component"; import filterComponent from "./filter-component";
import { Children, ClosureComponent, Component } from "mithril"; import { Children, ClosureComponent, Component } from "mithril";
@@ -43,13 +42,7 @@ const attributes = [
type: "combo", type: "combo",
options: ["1: count", "2: read", "3: write"] options: ["1: count", "2: read", "3: write"]
}, },
{ { id: "validate", label: "Validate", unsortable: true }
id: "validate",
label: "Validators",
type: "multi",
options: Object.keys(validators),
unsortable: true
}
]; ];
const unpackSmartQuery = memoize(query => { const unpackSmartQuery = memoize(query => {
@@ -79,16 +72,25 @@ function putActionHandler(action, _object): Promise<ValidationErrors> {
else if (object.access === "1: count") object.access = 1; else if (object.access === "1: count") object.access = 1;
else return void resolve({ access: "Invalid access level" }); else return void resolve({ access: "Invalid access level" });
object.filter = object.filter || ""; if (object.filter) {
try { try {
memoizedParse(object.filter); object.filter = stringify(memoizedParse(object.filter));
} catch (err) { } catch (err) {
return void resolve({ return void resolve({
filter: "Filter must be valid expression" 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}`; const id = `${object.role}:${object.resource}:${object.access}`;