@openfga/frontend-utils
Version:
Exposes helpful utilities for building authoring experiences of OpenFGA Models.
125 lines (124 loc) • 6.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.AuthorizationModelGraphBuilder = void 0;
const graph_typings_1 = require("./graph.typings");
class AuthorizationModelGraphBuilder {
constructor(authorizationModel, store) {
this.authorizationModel = authorizationModel;
this.store = store;
this._graph = { nodes: [], edges: [] };
this.buildGraph();
}
static getStoreId(storeName) {
return `store|${storeName}`;
}
static getTypeId(typeId) {
return `type|${typeId}`;
}
static getRelationId(typeId, relationKey) {
return `${typeId}.relation|${relationKey}`;
}
buildGraph() {
var _a, _b, _c;
const storeName = ((_a = this.store) === null || _a === void 0 ? void 0 : _a.name) || ((_b = this.store) === null || _b === void 0 ? void 0 : _b.id) || "Store";
const rootId = AuthorizationModelGraphBuilder.getStoreId(storeName);
const authorizationModelGraph = {
nodes: [{ id: rootId, label: storeName, group: graph_typings_1.GraphNodeGroup.StoreName }],
edges: [],
};
(_c = this.authorizationModel.type_definitions) === null || _c === void 0 ? void 0 : _c.forEach((typeDef) => {
const graph = this.getTypeGraph(typeDef, authorizationModelGraph);
authorizationModelGraph.nodes = authorizationModelGraph.nodes.concat(graph.nodes);
authorizationModelGraph.edges = authorizationModelGraph.edges.concat(graph.edges);
});
this._graph = authorizationModelGraph;
}
// A relation definition has self if `this` is in the relation definition
checkIfRelationAssignable(relationDef) {
var _a, _b, _c, _d;
return !!(relationDef.this ||
((_a = relationDef.difference) === null || _a === void 0 ? void 0 : _a.base.this) ||
((_b = relationDef.difference) === null || _b === void 0 ? void 0 : _b.subtract.this) ||
(((_c = relationDef.intersection) === null || _c === void 0 ? void 0 : _c.child) || []).some((child) => this.checkIfRelationAssignable(child)) ||
(((_d = relationDef.union) === null || _d === void 0 ? void 0 : _d.child) || []).some((child) => this.checkIfRelationAssignable(child)));
}
// Get the sources that can be assignable to a relation
getAssignableSourcesForRelation(relationDef, relationMetadata) {
const assignableSources = { types: [], relations: [], conditions: [], publicTypes: [], isAssignable: false };
// If this is not used anywhere, then it's not assignable
if (!this.checkIfRelationAssignable(relationDef)) {
return assignableSources;
}
const assignable = relationMetadata.directly_related_user_types;
assignable === null || assignable === void 0 ? void 0 : assignable.forEach((relationRef) => {
// TODO: wildcard and conditions
if (!(relationRef.relation || relationRef.wildcard || relationRef.condition)) {
return;
}
// TODO: Mark relations as assignable once supported
if (relationRef.relation) {
assignableSources.relations.push(AuthorizationModelGraphBuilder.getRelationId(relationRef.type, relationRef.relation));
return;
}
assignableSources.isAssignable = true;
assignableSources.types.push(AuthorizationModelGraphBuilder.getTypeId(relationRef.type));
});
return assignableSources;
}
addRelationToRelationEdge(typeGraph, typeId, fromRelationKey, toRelation) {
typeGraph.edges.push({
from: AuthorizationModelGraphBuilder.getRelationId(typeId, fromRelationKey),
to: AuthorizationModelGraphBuilder.getRelationId(typeId, toRelation.relation),
group: graph_typings_1.GraphEdgeGroup.RelationToRelation,
dashes: true,
});
}
getTypeGraph(typeDef, authorizationModelGraph, { showAssignable } = {}) {
const typeId = AuthorizationModelGraphBuilder.getTypeId(typeDef.type);
const typeGraph = {
nodes: [{ id: typeId, label: typeDef.type, group: graph_typings_1.GraphNodeGroup.Type }],
edges: [{ from: authorizationModelGraph.nodes[0].id, to: typeId, group: graph_typings_1.GraphEdgeGroup.StoreToType }],
};
const relationDefs = (typeDef === null || typeDef === void 0 ? void 0 : typeDef.relations) || {};
Object.keys(relationDefs).forEach((relationKey) => {
var _a, _b, _c;
const relationId = AuthorizationModelGraphBuilder.getRelationId(typeId, relationKey);
const relationDef = relationDefs[relationKey] || {};
const assignableSources = this.getAssignableSourcesForRelation(relationDef, ((_b = (_a = typeDef.metadata) === null || _a === void 0 ? void 0 : _a.relations) === null || _b === void 0 ? void 0 : _b[relationKey]) || {});
const isAssignable = assignableSources.isAssignable;
// If a relation definition does not have this, then we call it a `permission`, e.g. not directly assignable
typeGraph.nodes.push({
id: relationId,
label: relationKey,
group: isAssignable ? graph_typings_1.GraphNodeGroup.AssignableRelation : graph_typings_1.GraphNodeGroup.NonassignableRelation,
});
if (showAssignable) {
// TODO: Support assignable relations and wildcards, and conditionals
assignableSources.types.forEach((assignableSource) => {
typeGraph.edges.push({
from: AuthorizationModelGraphBuilder.getTypeId(assignableSource),
to: relationId,
group: graph_typings_1.GraphEdgeGroup.AssignableSourceToRelation,
});
});
}
// TODO: Support - 1. AND, 2. BUT NOT, 3. Nested relations, 4. Tuple to Userset
typeGraph.edges.push({ from: typeId, to: relationId, group: graph_typings_1.GraphEdgeGroup.TypeToRelation });
if (relationDef.computedUserset) {
this.addRelationToRelationEdge(typeGraph, typeId, relationKey, relationDef.computedUserset);
}
else {
(((_c = relationDef.union) === null || _c === void 0 ? void 0 : _c.child) || []).forEach((child) => {
if (child.computedUserset) {
this.addRelationToRelationEdge(typeGraph, typeId, relationKey, child.computedUserset);
}
});
}
});
return typeGraph;
}
get graph() {
return this._graph;
}
}
exports.AuthorizationModelGraphBuilder = AuthorizationModelGraphBuilder;