mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-15 05:41:51 +00:00
More work on API permissions
This commit is contained in:
parent
79b0adf81a
commit
c9a21c64bf
14 changed files with 318 additions and 85 deletions
|
@ -7,7 +7,7 @@ import pick from "lodash.pick";
|
|||
import https from "https";
|
||||
import { ApiUserInfo } from "../data/ApiUserInfo";
|
||||
import { ApiUserInfoData } from "../data/entities/ApiUserInfo";
|
||||
import { ApiPermissions } from "../data/ApiPermissions";
|
||||
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
|
||||
import { ok } from "./responses";
|
||||
|
||||
interface IPassportApiUser {
|
||||
|
@ -71,7 +71,7 @@ export function initAuth(app: express.Express) {
|
|||
|
||||
const apiLogins = new ApiLogins();
|
||||
const apiUserInfo = new ApiUserInfo();
|
||||
const apiPermissions = new ApiPermissions();
|
||||
const apiPermissionAssignments = new ApiPermissionAssignments();
|
||||
|
||||
// Initialize API tokens
|
||||
passport.use(
|
||||
|
@ -105,7 +105,7 @@ export function initAuth(app: express.Express) {
|
|||
const user = await simpleDiscordAPIRequest(accessToken, "users/@me");
|
||||
|
||||
// Make sure the user is able to access at least 1 guild
|
||||
const permissions = await apiPermissions.getByUserId(user.id);
|
||||
const permissions = await apiPermissionAssignments.getByUserId(user.id);
|
||||
if (permissions.length === 0) {
|
||||
cb(null, {});
|
||||
return;
|
||||
|
|
|
@ -1,35 +1,38 @@
|
|||
import express from "express";
|
||||
import passport from "passport";
|
||||
import express, { Request, Response } from "express";
|
||||
import { AllowedGuilds } from "../data/AllowedGuilds";
|
||||
import { ApiPermissions } from "../data/ApiPermissions";
|
||||
import { clientError, error, ok, serverError, unauthorized } from "./responses";
|
||||
import { clientError, ok, serverError, unauthorized } from "./responses";
|
||||
import { Configs } from "../data/Configs";
|
||||
import { ApiRoles } from "../data/ApiRoles";
|
||||
import { validateGuildConfig } from "../configValidator";
|
||||
import yaml, { YAMLException } from "js-yaml";
|
||||
import { apiTokenAuthHandlers } from "./auth";
|
||||
import { ApiPermissions, hasPermission, permissionArrToSet } from "@shared/apiPermissions";
|
||||
import { ApiPermissionAssignments } from "../data/ApiPermissionAssignments";
|
||||
|
||||
export function initGuildsAPI(app: express.Express) {
|
||||
const allowedGuilds = new AllowedGuilds();
|
||||
const apiPermissions = new ApiPermissions();
|
||||
const apiPermissionAssignments = new ApiPermissionAssignments();
|
||||
const configs = new Configs();
|
||||
|
||||
app.get("/guilds/available", ...apiTokenAuthHandlers(), async (req, res) => {
|
||||
app.get("/guilds/available", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
|
||||
const guilds = await allowedGuilds.getForApiUser(req.user.userId);
|
||||
res.json(guilds);
|
||||
});
|
||||
|
||||
app.get("/guilds/:guildId/config", ...apiTokenAuthHandlers(), async (req, res) => {
|
||||
const permissions = await apiPermissions.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permissions) return unauthorized(res);
|
||||
app.get("/guilds/:guildId/config", ...apiTokenAuthHandlers(), async (req: Request, res: Response) => {
|
||||
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permAssignment || !hasPermission(permissionArrToSet(permAssignment.permissions), ApiPermissions.ReadConfig)) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
const config = await configs.getActiveByKey(`guild-${req.params.guildId}`);
|
||||
res.json({ config: config ? config.config : "" });
|
||||
});
|
||||
|
||||
app.post("/guilds/:guildId/config", ...apiTokenAuthHandlers(), async (req, res) => {
|
||||
const permissions = await apiPermissions.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permissions || ApiRoles[permissions.role] < ApiRoles.Editor) return unauthorized(res);
|
||||
const permAssignment = await apiPermissionAssignments.getByGuildAndUserId(req.params.guildId, req.user.userId);
|
||||
if (!permAssignment || !hasPermission(permissionArrToSet(permAssignment.permissions), ApiPermissions.EditConfig)) {
|
||||
return unauthorized(res);
|
||||
}
|
||||
|
||||
let config = req.body.config;
|
||||
if (config == null) return clientError(res, "No config supplied");
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
} from "typeorm";
|
||||
import { BaseGuildRepository } from "./BaseGuildRepository";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
import { ApiPermissionTypes } from "./ApiPermissionAssignments";
|
||||
|
||||
export class AllowedGuilds extends BaseRepository {
|
||||
private allowedGuilds: Repository<AllowedGuild>;
|
||||
|
@ -33,8 +34,8 @@ export class AllowedGuilds extends BaseRepository {
|
|||
.innerJoin(
|
||||
"api_permissions",
|
||||
"api_permissions",
|
||||
"api_permissions.guild_id = allowed_guilds.id AND api_permissions.user_id = :userId",
|
||||
{ userId },
|
||||
"api_permissions.guild_id = allowed_guilds.id AND api_permissions.type = :type AND api_permissions.target_id = :userId",
|
||||
{ type: ApiPermissionTypes.User, userId },
|
||||
)
|
||||
.getMany();
|
||||
}
|
||||
|
|
36
backend/src/data/ApiPermissionAssignments.ts
Normal file
36
backend/src/data/ApiPermissionAssignments.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { getRepository, Repository } from "typeorm";
|
||||
import { ApiPermissionAssignment } from "./entities/ApiPermissionAssignment";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export enum ApiPermissionTypes {
|
||||
User = "USER",
|
||||
Role = "ROLE",
|
||||
}
|
||||
|
||||
export class ApiPermissionAssignments extends BaseRepository {
|
||||
private apiPermissions: Repository<ApiPermissionAssignment>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.apiPermissions = getRepository(ApiPermissionAssignment);
|
||||
}
|
||||
|
||||
getByUserId(userId) {
|
||||
return this.apiPermissions.find({
|
||||
where: {
|
||||
type: ApiPermissionTypes.User,
|
||||
target_id: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getByGuildAndUserId(guildId, userId) {
|
||||
return this.apiPermissions.findOne({
|
||||
where: {
|
||||
guild_id: guildId,
|
||||
type: ApiPermissionTypes.User,
|
||||
target_id: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
import { getRepository, Repository } from "typeorm";
|
||||
import { ApiPermission } from "./entities/ApiPermission";
|
||||
import { BaseRepository } from "./BaseRepository";
|
||||
|
||||
export class ApiPermissions extends BaseRepository {
|
||||
private apiPermissions: Repository<ApiPermission>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.apiPermissions = getRepository(ApiPermission);
|
||||
}
|
||||
|
||||
getByUserId(userId) {
|
||||
return this.apiPermissions.find({
|
||||
where: {
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getByGuildAndUserId(guildId, userId) {
|
||||
return this.apiPermissions.findOne({
|
||||
where: {
|
||||
guild_id: guildId,
|
||||
user_id: userId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
export enum ApiRoles {
|
||||
Viewer = 1,
|
||||
Editor,
|
||||
Manager,
|
||||
ServerOwner,
|
||||
}
|
|
@ -2,19 +2,23 @@ import { Entity, Column, PrimaryColumn, ManyToOne, JoinColumn } from "typeorm";
|
|||
import { ApiUserInfo } from "./ApiUserInfo";
|
||||
|
||||
@Entity("api_permissions")
|
||||
export class ApiPermission {
|
||||
export class ApiPermissionAssignment {
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
guild_id: string;
|
||||
|
||||
@Column()
|
||||
@PrimaryColumn()
|
||||
user_id: string;
|
||||
type: string;
|
||||
|
||||
@Column()
|
||||
role: string;
|
||||
@PrimaryColumn()
|
||||
target_id: string;
|
||||
|
||||
@ManyToOne(type => ApiUserInfo, userInfo => userInfo.permissions)
|
||||
@JoinColumn({ name: "user_id" })
|
||||
@Column("simple-array")
|
||||
permissions: string[];
|
||||
|
||||
@ManyToOne(type => ApiUserInfo, userInfo => userInfo.permissionAssignments)
|
||||
@JoinColumn({ name: "target_id" })
|
||||
userInfo: ApiUserInfo;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Entity, Column, PrimaryColumn, OneToMany } from "typeorm";
|
||||
import { ApiLogin } from "./ApiLogin";
|
||||
import { ApiPermission } from "./ApiPermission";
|
||||
import { ApiPermissionAssignment } from "./ApiPermissionAssignment";
|
||||
|
||||
export interface ApiUserInfoData {
|
||||
username: string;
|
||||
|
@ -23,6 +23,6 @@ export class ApiUserInfo {
|
|||
@OneToMany(type => ApiLogin, login => login.userInfo)
|
||||
logins: ApiLogin[];
|
||||
|
||||
@OneToMany(type => ApiPermission, perm => perm.userInfo)
|
||||
permissions: ApiPermission[];
|
||||
@OneToMany(type => ApiPermissionAssignment, p => p.userInfo)
|
||||
permissionAssignments: ApiPermissionAssignment[];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import { MigrationInterface, QueryRunner, TableColumn, TableIndex } from "typeorm";
|
||||
|
||||
export class AddTypeAndPermissionsToApiPermissions1573158035867 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropPrimaryKey("api_permissions");
|
||||
await queryRunner.dropIndex("api_permissions", "IDX_5e371749d4cb4a5191f35e26f6");
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"api_permissions",
|
||||
new TableColumn({
|
||||
name: "type",
|
||||
type: "varchar",
|
||||
length: "16",
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.renameColumn("api_permissions", "user_id", "target_id");
|
||||
|
||||
await queryRunner.createPrimaryKey("api_permissions", ["guild_id", "type", "target_id"]);
|
||||
|
||||
await queryRunner.dropColumn("api_permissions", "role");
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"api_permissions",
|
||||
new TableColumn({
|
||||
name: "permissions",
|
||||
type: "text",
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.query(`
|
||||
UPDATE api_permissions
|
||||
SET type="USER",
|
||||
permissions="EDIT_CONFIG"
|
||||
`);
|
||||
|
||||
await queryRunner.createIndex(
|
||||
"api_permissions",
|
||||
new TableIndex({
|
||||
columnNames: ["type", "target_id"],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<any> {
|
||||
await queryRunner.dropIndex("api_permissions", "IDX_e06d750f13e6a4b4d3d6b847a9");
|
||||
|
||||
await queryRunner.dropColumn("api_permissions", "permissions");
|
||||
|
||||
await queryRunner.addColumn(
|
||||
"api_permissions",
|
||||
new TableColumn({
|
||||
name: "role",
|
||||
type: "varchar",
|
||||
length: "32",
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.dropPrimaryKey("api_permissions");
|
||||
|
||||
await queryRunner.renameColumn("api_permissions", "target_id", "user_id");
|
||||
|
||||
await queryRunner.dropColumn("api_permissions", "type");
|
||||
|
||||
await queryRunner.createIndex(
|
||||
"api_permissions",
|
||||
new TableIndex({
|
||||
columnNames: ["user_id"],
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createPrimaryKey("api_permissions", ["guild_id", "user_id"]);
|
||||
}
|
||||
}
|
|
@ -4,5 +4,36 @@
|
|||
<p>
|
||||
<img class="inline-block w-16 mr-4" style="vertical-align: -20px" src="../../img/squint.png"> Or here
|
||||
</p>
|
||||
<permission-tree :tree="tree" :granted-permissions="grantedPermissions" :on-change="onChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { ApiPermissions, permissionHierarchy } from "@shared/apiPermissions";
|
||||
import PermissionTree from "./PermissionTree.vue";
|
||||
import { applyStateToPermissionHierarchy } from "./permissionTreeUtils";
|
||||
|
||||
export default {
|
||||
components: {PermissionTree},
|
||||
data() {
|
||||
return {
|
||||
tree: [],
|
||||
grantedPermissions: new Set([ApiPermissions.EditConfig]),
|
||||
managerPermissions: new Set([ApiPermissions.ManageAccess])
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.tree = applyStateToPermissionHierarchy(permissionHierarchy, this.grantedPermissions, this.managerPermissions);
|
||||
},
|
||||
methods: {
|
||||
updateTreeState() {
|
||||
this.tree = applyStateToPermissionHierarchy(permissionHierarchy, this.grantedPermissions, this.managerPermissions);
|
||||
},
|
||||
|
||||
onChange() {
|
||||
console.log('changed!', this.grantedPermissions);
|
||||
this.updateTreeState();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
62
dashboard/src/components/dashboard/PermissionTree.vue
Normal file
62
dashboard/src/components/dashboard/PermissionTree.vue
Normal file
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<ul class="nostyles">
|
||||
<li v-for="[permission, treeState, subTree] in tree" :class="{locked: treeState.locked}">
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
:checked="grantedPermissions.has(permission) || treeState.redundant"
|
||||
v-on:input="togglePermission(permission)"
|
||||
:disabled="treeState.locked || treeState.redundant">
|
||||
<span>{{ permissionNames[permission] }}</span>
|
||||
</label>
|
||||
<permission-tree v-if="subTree && subTree.length"
|
||||
:tree="subTree"
|
||||
:granted-permissions="grantedPermissions"
|
||||
:on-change="onChange" />
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
ul {
|
||||
list-style: none;
|
||||
|
||||
& ul {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.locked > label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts">
|
||||
import { ApiPermissions, permissionNames } from "@shared/apiPermissions";
|
||||
import { PropType } from "vue";
|
||||
import { TPermissionHierarchyWithState } from "./permissionTreeUtils";
|
||||
|
||||
export default {
|
||||
name: 'permission-tree',
|
||||
props: {
|
||||
tree: Array as PropType<TPermissionHierarchyWithState>,
|
||||
grantedPermissions: Set as PropType<Set<ApiPermissions>>,
|
||||
onChange: Function
|
||||
},
|
||||
data() {
|
||||
return { permissionNames };
|
||||
},
|
||||
methods: {
|
||||
togglePermission(permission) {
|
||||
if (this.grantedPermissions.has(permission)) {
|
||||
this.grantedPermissions.delete(permission);
|
||||
} else {
|
||||
this.grantedPermissions.add(permission);
|
||||
}
|
||||
|
||||
if (this.onChange) {
|
||||
this.onChange();
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
43
dashboard/src/components/dashboard/permissionTreeUtils.ts
Normal file
43
dashboard/src/components/dashboard/permissionTreeUtils.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { ApiPermissions, hasPermission, TPermissionHierarchy } from "@shared/apiPermissions";
|
||||
|
||||
export type TPermissionHierarchyState = {
|
||||
locked: boolean;
|
||||
redundant: boolean;
|
||||
};
|
||||
|
||||
export type TApiPermissionWithState = [ApiPermissions, TPermissionHierarchyState, TPermissionHierarchyWithState?];
|
||||
export type TPermissionHierarchyWithState = TApiPermissionWithState[];
|
||||
|
||||
/**
|
||||
* @param tree
|
||||
* @param grantedPermissions Permissions granted to the user being edited
|
||||
* @param managerPermissions Permissions granted to the user who's editing the other user's permissions
|
||||
* @param entireTreeIsGranted
|
||||
*/
|
||||
export function applyStateToPermissionHierarchy(
|
||||
tree: TPermissionHierarchy,
|
||||
grantedPermissions: Set<ApiPermissions>,
|
||||
managerPermissions: Set<ApiPermissions> = new Set(),
|
||||
entireTreeIsGranted = false,
|
||||
): TPermissionHierarchyWithState {
|
||||
const result: TPermissionHierarchyWithState = [];
|
||||
|
||||
for (const item of tree) {
|
||||
const [perm, nested] = Array.isArray(item) ? item : [item];
|
||||
|
||||
// Can't edit permissions you don't have yourself
|
||||
const locked = !hasPermission(managerPermissions, perm);
|
||||
const permissionWithState: TApiPermissionWithState = [perm, { locked, redundant: entireTreeIsGranted }];
|
||||
|
||||
if (nested) {
|
||||
const subTreeGranted = entireTreeIsGranted || grantedPermissions.has(perm);
|
||||
permissionWithState.push(
|
||||
applyStateToPermissionHierarchy(nested, grantedPermissions, managerPermissions, subTreeGranted),
|
||||
);
|
||||
}
|
||||
|
||||
result.push(permissionWithState);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -2,12 +2,12 @@ import { ApiPermissions, hasPermission } from "./apiPermissions";
|
|||
import test from "ava";
|
||||
|
||||
test("Directly granted permissions match", t => {
|
||||
t.is(hasPermission([ApiPermissions.ManageAccess], ApiPermissions.ManageAccess), true);
|
||||
t.is(hasPermission([ApiPermissions.ManageAccess], ApiPermissions.Owner), false);
|
||||
t.is(hasPermission(new Set([ApiPermissions.ManageAccess]), ApiPermissions.ManageAccess), true);
|
||||
t.is(hasPermission(new Set([ApiPermissions.ManageAccess]), ApiPermissions.Owner), false);
|
||||
});
|
||||
|
||||
test("Implicitly granted permissions by hierarchy match", t => {
|
||||
t.is(hasPermission([ApiPermissions.ManageAccess], ApiPermissions.EditConfig), true);
|
||||
t.is(hasPermission([ApiPermissions.ManageAccess], ApiPermissions.ReadConfig), true);
|
||||
t.is(hasPermission([ApiPermissions.EditConfig], ApiPermissions.ManageAccess), false);
|
||||
t.is(hasPermission(new Set([ApiPermissions.ManageAccess]), ApiPermissions.EditConfig), true);
|
||||
t.is(hasPermission(new Set([ApiPermissions.ManageAccess]), ApiPermissions.ReadConfig), true);
|
||||
t.is(hasPermission(new Set([ApiPermissions.EditConfig]), ApiPermissions.ManageAccess), false);
|
||||
});
|
||||
|
|
|
@ -5,26 +5,36 @@ export enum ApiPermissions {
|
|||
ReadConfig = "READ_CONFIG",
|
||||
}
|
||||
|
||||
interface IPermissionHierarchy extends Partial<Record<ApiPermissions, IPermissionHierarchy>> {}
|
||||
const reverseApiPermissions = Object.entries(ApiPermissions).reduce((map, [key, value]) => {
|
||||
map[value] = key;
|
||||
return map;
|
||||
}, {});
|
||||
|
||||
export const permissionHierarchy: IPermissionHierarchy = {
|
||||
[ApiPermissions.Owner]: {
|
||||
[ApiPermissions.ManageAccess]: {
|
||||
[ApiPermissions.EditConfig]: {
|
||||
[ApiPermissions.ReadConfig]: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
export const permissionNames = {
|
||||
[ApiPermissions.Owner]: "Server owner",
|
||||
[ApiPermissions.ManageAccess]: "Manage dashboard access",
|
||||
[ApiPermissions.EditConfig]: "Edit config",
|
||||
[ApiPermissions.ReadConfig]: "Read config",
|
||||
};
|
||||
|
||||
export type TPermissionHierarchy = Array<ApiPermissions | [ApiPermissions, TPermissionHierarchy]>;
|
||||
|
||||
export const permissionHierarchy: TPermissionHierarchy = [
|
||||
[ApiPermissions.Owner, [[ApiPermissions.ManageAccess, [[ApiPermissions.EditConfig, [ApiPermissions.ReadConfig]]]]]],
|
||||
];
|
||||
|
||||
export function permissionArrToSet(permissions: string[]): Set<ApiPermissions> {
|
||||
return new Set(permissions.filter(p => reverseApiPermissions[p])) as Set<ApiPermissions>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether granted permissions include the specified permission, taking into account permission hierarchy i.e.
|
||||
* that in the case of nested permissions, having a top level permission implicitly grants you any permissions nested
|
||||
* under it as well
|
||||
*/
|
||||
export function hasPermission(grantedPermissions: ApiPermissions[], permissionToCheck: ApiPermissions): boolean {
|
||||
export function hasPermission(grantedPermissions: Set<ApiPermissions>, permissionToCheck: ApiPermissions): boolean {
|
||||
// Directly granted
|
||||
if (grantedPermissions.includes(permissionToCheck)) {
|
||||
if (grantedPermissions.has(permissionToCheck)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -37,15 +47,17 @@ export function hasPermission(grantedPermissions: ApiPermissions[], permissionTo
|
|||
}
|
||||
|
||||
function checkTreeForPermission(
|
||||
tree: IPermissionHierarchy,
|
||||
grantedPermissions: ApiPermissions[],
|
||||
tree: TPermissionHierarchy,
|
||||
grantedPermissions: Set<ApiPermissions>,
|
||||
permission: ApiPermissions,
|
||||
): boolean {
|
||||
for (const [perm, nested] of Object.entries(tree)) {
|
||||
for (const item of tree) {
|
||||
const [perm, nested] = Array.isArray(item) ? item : [item];
|
||||
|
||||
// Top-level permission granted, implicitly grant all nested permissions as well
|
||||
if (grantedPermissions.includes(perm as ApiPermissions)) {
|
||||
if (grantedPermissions.has(perm)) {
|
||||
// Permission we were looking for was found nested under this permission -> granted
|
||||
if (treeIncludesPermission(nested, permission)) {
|
||||
if (nested && treeIncludesPermission(nested, permission)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -55,7 +67,7 @@ function checkTreeForPermission(
|
|||
}
|
||||
|
||||
// Top-level permission not granted, check further nested permissions
|
||||
if (checkTreeForPermission(nested, grantedPermissions, permission)) {
|
||||
if (nested && checkTreeForPermission(nested, grantedPermissions, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -63,13 +75,15 @@ function checkTreeForPermission(
|
|||
return false;
|
||||
}
|
||||
|
||||
function treeIncludesPermission(tree: IPermissionHierarchy, permission: ApiPermissions): boolean {
|
||||
for (const [perm, nested] of Object.entries(tree)) {
|
||||
function treeIncludesPermission(tree: TPermissionHierarchy, permission: ApiPermissions): boolean {
|
||||
for (const item of tree) {
|
||||
const [perm, nested] = Array.isArray(item) ? item : [item];
|
||||
|
||||
if (perm === permission) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const nestedResult = treeIncludesPermission(nested, permission);
|
||||
const nestedResult = nested && treeIncludesPermission(nested, permission);
|
||||
if (nestedResult) {
|
||||
return true;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue