Use expressions as validators
This commit is contained in:
1 parent
08e3933c4a
commit
fdda68434b
7 files changed
+87
-90
No files matched your search
+41
-30
@@ -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);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
export default function pass(): number {
|
||||
return 1;
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
+1
-3
@@ -303,9 +303,7 @@ export interface Preset {
|
||||
export type PermissionSet = {
|
||||
[resource: string]: {
|
||||
access: number;
|
||||
validate?: {
|
||||
[validator: string]: boolean;
|
||||
};
|
||||
validate?: Expression;
|
||||
filter: Expression;
|
||||
};
|
||||
}[];
|
||||
+20
-19
@@ -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<void> {
|
||||
.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(
|
||||
|
||||
+18
-16
@@ -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<ValidationErrors> {
|
||||
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}`;
|
||||
|
||||
|
||||
Reference in new issue
Block a user