Skip to content

Commit 67a182f

Browse files
committed
impl initial perms plugin
1 parent 92f5472 commit 67a182f

File tree

2 files changed

+163
-0
lines changed

2 files changed

+163
-0
lines changed

src/brickperms/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as bricklib from '../bricklib/index.js';
2+
export * from './permission.js';
3+
4+
bricklib.plugin.newPlugin('brickperms', () => {
5+
/* no-op */
6+
});

src/brickperms/permission.ts

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
/**
2+
* Base permission parsing and checking.
3+
*/
4+
5+
/*
6+
Priority levels (e.g.: a.b.c):
7+
1. negated exact match (-a.b.c)
8+
2. exact match (a.b.c)
9+
3. negated close match (-a.b.*)
10+
4. close match (a.b.*)
11+
5. more specific negated ancestor (-a.b)
12+
6. more specific ancestor (a.b)
13+
7. less specific negated ancestor (-a.*)
14+
8. less specific ancestor (a.*)
15+
*/
16+
17+
18+
/**
19+
* Check if the permission node `perm` is allowed given the constraints in
20+
* `tagList`.
21+
* @param perm The perm node.
22+
* @param tagList Array of perm tags.
23+
* @returns True when matched, false otherwise.
24+
*/
25+
export function checkPerm(perm: string, tagList: string[]): boolean
26+
{
27+
const permLvls = splitPermNodeLvls(perm);
28+
const permDepth = getPermDepth(perm);
29+
30+
let lastSpec = 0;
31+
let lastMatched = false;
32+
33+
for (const tag of tagList) {
34+
const tagToks = tokenizePermTag(tag);
35+
const negate = tagToks[0] == '-';
36+
37+
if (!matchSpecs(permLvls, tagToks))
38+
continue; /* unrelated tag (a.c.* has nothing to do with a.x.b) */
39+
40+
/* exact match */
41+
if (!hasWildcard(tag) && negate && permDepth == getPermDepth(tag))
42+
return false;
43+
44+
const tagSpec = getSpecificity(tagToks);
45+
if (tagSpec < lastSpec)
46+
continue;
47+
48+
lastMatched = tagSpec == lastSpec
49+
? (negate ? false : lastMatched) : !negate;
50+
lastSpec = tagSpec;
51+
}
52+
53+
return lastMatched;
54+
}
55+
56+
/**
57+
* Match permission node `perm` with tag `tag`, regardless of the
58+
* negation flag. This can match inheritance and wildcards.
59+
* @param perm The split-ed perm node.
60+
* @param tag The tokenized perm tag.
61+
* @returns True if match, false otherwise.
62+
*/
63+
export function matchSpecs(perm: string[], tag: string[]): boolean
64+
{
65+
if (['+', '-'].includes(tag[0]))
66+
tag = tag.slice(1);
67+
if (tag.length > perm.length)
68+
return false;
69+
70+
for (let i = 0; i < tag.length; i++) {
71+
const permLvl = perm[i];
72+
const tagLvl = tag[i];
73+
74+
if (tagLvl == '*')
75+
continue;
76+
if (permLvl != tagLvl)
77+
return false;
78+
}
79+
80+
return true;
81+
}
82+
83+
/**
84+
* Check whether a permission tag has a wildcard.
85+
* @param tag The perm tag.
86+
* @returns True if tag includes a '*'.
87+
*/
88+
export function hasWildcard(tag: string): boolean
89+
{
90+
return /(^[+-]?|\.)\*(\.|$)/.test(tag);
91+
}
92+
93+
/**
94+
* Returns the specificity of a permission tag.
95+
* @param perm The tokenized perm tag.
96+
* @returns The specificity.
97+
*/
98+
export function getSpecificity(perm: string[]): number
99+
{
100+
if (['+', '-'].includes(perm[0]))
101+
perm = perm.slice(1);
102+
let val = 0;
103+
104+
/* 'a.*' > '*.b' */
105+
perm.forEach((lvl, idx) => {
106+
if (lvl == '*')
107+
val += (idx + 1) / (perm.length + 1);
108+
else
109+
val += (perm.length + 1) / (idx + 1);
110+
});
111+
112+
return val;
113+
}
114+
115+
/**
116+
* Returns the number of levels a permission node has.
117+
* @param perm The perm node.
118+
* @returns The number of levels.
119+
*/
120+
export function getPermDepth(perm: string): number
121+
{
122+
let num = 1;
123+
for (const c of perm)
124+
if (c == '.')
125+
num++;
126+
return num;
127+
}
128+
129+
/**
130+
* Tokenize a permission tag.
131+
* @param perm The perm tag.
132+
* @returns An array of perm tag tokens.
133+
*/
134+
export function tokenizePermTag(perm: string): string[]
135+
{
136+
let result: string[] = [];
137+
138+
if ('+-'.includes(perm[0])) {
139+
result = [ perm[0] ];
140+
perm = perm.slice(1);
141+
}
142+
143+
return result.concat(perm.split('.'));
144+
}
145+
146+
/**
147+
* Split a permission node's levels.
148+
* @param perm The perm node.
149+
* @returns An array of perm levels.
150+
* @throws This can throw errors.
151+
*/
152+
export function splitPermNodeLvls(perm: string): string[]
153+
{
154+
if (hasWildcard(perm))
155+
throw new Error('Permission nodes cannot have wildcards');
156+
return perm.split('.');
157+
}

0 commit comments

Comments
 (0)