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 { 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 res = validators[k](
|
|
||||||
resourceType,
|
|
||||||
resource,
|
|
||||||
mutationType,
|
|
||||||
mutation,
|
|
||||||
any
|
|
||||||
);
|
|
||||||
|
|
||||||
if (res > 0) valid = true;
|
const object = {
|
||||||
else if (res < 0) return false;
|
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);
|
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]: {
|
[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
@@ -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
@@ -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(
|
||||||
|
|||||||
+18
-16
@@ -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}`;
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user