feat: add enforce context (#88)
Signed-off-by: nodece <nodeces@gmail.com>
This commit is contained in:
parent
e13f7f9dd7
commit
c42450d6fd
|
@ -1,5 +1,8 @@
|
|||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
export const example = {
|
||||
export const example: Record<
|
||||
string,
|
||||
{ name: string; model: string; policy: string; request: string; customConfig?: string; enforceContext?: string }
|
||||
> = {
|
||||
basic: {
|
||||
name: 'ACL',
|
||||
model: `[request_definition]
|
||||
|
@ -16,7 +19,8 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act`,
|
|||
policy: `p, alice, data1, read
|
||||
p, bob, data2, write`,
|
||||
request: `alice, data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
basic_with_root: {
|
||||
name: 'ACL with superuser',
|
||||
|
@ -34,7 +38,8 @@ m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"`,
|
|||
policy: `p, alice, data1, read
|
||||
p, bob, data2, write`,
|
||||
request: `alice, data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
basic_without_resources: {
|
||||
name: 'ACL without resources',
|
||||
|
@ -52,7 +57,8 @@ m = r.sub == p.sub && r.act == p.act`,
|
|||
policy: `p, alice, read
|
||||
p, bob, write`,
|
||||
request: `alice, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
basic_without_users: {
|
||||
name: 'ACL without users',
|
||||
|
@ -70,7 +76,8 @@ m = r.obj == p.obj && r.act == p.act`,
|
|||
policy: `p, data1, read
|
||||
p, data2, write`,
|
||||
request: `data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac: {
|
||||
name: 'RBAC',
|
||||
|
@ -95,7 +102,8 @@ p, data2_admin, data2, write
|
|||
|
||||
g, alice, data2_admin`,
|
||||
request: `alice, data2, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac_with_resource_roles: {
|
||||
name: 'RBAC with resource roles',
|
||||
|
@ -125,8 +133,8 @@ g2, data2, data_group`,
|
|||
alice, data1, write
|
||||
alice, data2, read
|
||||
alice, data2, write `,
|
||||
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac_with_domains: {
|
||||
name: 'RBAC with domains/tenants',
|
||||
|
@ -152,7 +160,8 @@ p, admin, domain2, data2, write
|
|||
g, alice, admin, domain1
|
||||
g, bob, admin, domain2`,
|
||||
request: `alice, domain1, data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac_with_pattern: {
|
||||
name: 'RBAC with pattern',
|
||||
|
@ -192,7 +201,8 @@ g, /book/:id, pen_admin
|
|||
matchingForGFunction: 'keyMatch2',
|
||||
matchingDomainForGFunction: undefined
|
||||
};
|
||||
})();`
|
||||
})();`,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac_with_all_pattern: {
|
||||
name: 'RBAC with all pattern',
|
||||
|
@ -233,7 +243,8 @@ g, /book/:id, book_group, *`,
|
|||
matchingForGFunction: 'keyMatch2',
|
||||
matchingDomainForGFunction: 'keyMatch2'
|
||||
};
|
||||
})();`
|
||||
})();`,
|
||||
enforceContext: undefined
|
||||
},
|
||||
rbac_with_deny: {
|
||||
name: 'RBAC with deny-override',
|
||||
|
@ -260,7 +271,8 @@ p, alice, data2, write, deny
|
|||
g, alice, data2_admin`,
|
||||
request: `alice, data1, read
|
||||
alice, data2, write`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
abac: {
|
||||
name: 'ABAC',
|
||||
|
@ -278,7 +290,8 @@ m = r.sub == r.obj.Owner`,
|
|||
policy: '',
|
||||
request: `alice, { Owner: 'alice'}
|
||||
alice, { Owner: 'bob'}`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
abac_with_policy_rule: {
|
||||
name: 'ABAC with policy rule',
|
||||
|
@ -296,7 +309,8 @@ m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act`,
|
|||
policy: `p, r.sub.Age > 18 && r.sub.Age < 60, /data1, read
|
||||
`,
|
||||
request: `{ Age: 30}, /data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
keymatch: {
|
||||
name: 'RESTful (KeyMatch)',
|
||||
|
@ -319,7 +333,8 @@ p, bob, /bob_data/*, POST
|
|||
|
||||
p, cathy, /cathy_data, (GET)|(POST)`,
|
||||
request: 'alice, /alice_data/hello, GET',
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
keymatch2: {
|
||||
name: 'RESTful (KeyMatch2)',
|
||||
|
@ -338,7 +353,8 @@ m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)`,
|
|||
p, alice, /alice_data2/:id/using/:resId, GET`,
|
||||
request: `alice, /alice_data/hello, GET
|
||||
alice, /alice_data/hello, POST`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
ipmatch: {
|
||||
name: 'IP match',
|
||||
|
@ -357,7 +373,8 @@ m = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.act`,
|
|||
p, 10.0.0.0/16, data2, write`,
|
||||
request: `192.168.2.1, data1, read
|
||||
10.0.2.3, data2, write`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
},
|
||||
priority: {
|
||||
name: 'Priority',
|
||||
|
@ -388,7 +405,8 @@ p, bob, data2, write, deny
|
|||
|
||||
g, bob, data2_allow_group`,
|
||||
request: `alice, data1, read`,
|
||||
customConfig: undefined
|
||||
customConfig: undefined,
|
||||
enforceContext: undefined
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -409,5 +427,11 @@ export const defaultCustomConfig = `(function() {
|
|||
matchingDomainForGFunction: undefined
|
||||
};
|
||||
})();`;
|
||||
export type Example = typeof example;
|
||||
export type ModelKind = keyof Example;
|
||||
export const defaultEnforceContext = `{
|
||||
"r": "r",
|
||||
"p": "p",
|
||||
"e": "e",
|
||||
"m": "m"
|
||||
}`;
|
||||
|
||||
export type ModelKind = string;
|
||||
|
|
|
@ -8,9 +8,9 @@ import RunTest from './run-test';
|
|||
import { ModelKind } from './casbin-mode/example';
|
||||
import { Settings } from './settings';
|
||||
import styled from 'styled-components';
|
||||
// import { useLocalStorage } from './use-local-storage';
|
||||
import Share, { ShareFormat } from './share';
|
||||
import Copy from './copy';
|
||||
import { defaultEnforceContextData, SetupEnforceContext } from './setup-enforce-context';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
|
@ -26,6 +26,7 @@ export const EditorScreen = () => {
|
|||
const [requestResult, setRequestResult] = useState('');
|
||||
const [customConfig, setCustomConfig] = useState('');
|
||||
const [share, setShare] = useState('');
|
||||
const [enforceContextData, setEnforceContextData] = useState(new Map(defaultEnforceContextData));
|
||||
|
||||
function setPolicyPersistent(text: string): void {
|
||||
set(Persist.POLICY, text);
|
||||
|
@ -47,6 +48,12 @@ export const EditorScreen = () => {
|
|||
setRequest(text);
|
||||
}
|
||||
|
||||
function setEnforceContextDataPersistent(map: Map<string, string>): void {
|
||||
const text = JSON.stringify(Object.fromEntries(map));
|
||||
set(Persist.ENFORCE_CONTEXT, text);
|
||||
setEnforceContextData(new Map(map));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const hash = window.location.hash.slice(1);
|
||||
if (hash) {
|
||||
|
@ -59,6 +66,10 @@ export const EditorScreen = () => {
|
|||
setModelTextPersistent(sharedContent.model);
|
||||
setCustomConfigPersistent(sharedContent.customConfig);
|
||||
setRequestPersistent(sharedContent.request);
|
||||
setRequestPersistent(sharedContent.request);
|
||||
if (sharedContent.enforceContext) {
|
||||
setEnforceContextDataPersistent(new Map(Object.entries(sharedContent.enforceContext)));
|
||||
}
|
||||
setRequestResult('');
|
||||
window.location.hash = ''; // prevent duplicate load
|
||||
setEcho(<Echo>Shared Content Loaded.</Echo>);
|
||||
|
@ -74,6 +85,7 @@ export const EditorScreen = () => {
|
|||
setModelText(get(Persist.MODEL, modelKind));
|
||||
setRequest(get(Persist.REQUEST, modelKind));
|
||||
setCustomConfig(get(Persist.CUSTOM_FUNCTION, modelKind));
|
||||
setEnforceContextData(new Map(Object.entries(JSON.parse(get(Persist.ENFORCE_CONTEXT, modelKind)!))));
|
||||
}, [modelKind]);
|
||||
|
||||
function handleShare(v: JSX.Element | string) {
|
||||
|
@ -127,7 +139,10 @@ export const EditorScreen = () => {
|
|||
|
||||
<FlexRow>
|
||||
<EditorContainer>
|
||||
<HeaderTitle>Request</HeaderTitle>
|
||||
<FlexRow>
|
||||
<HeaderTitle>Request</HeaderTitle>
|
||||
<SetupEnforceContext data={enforceContextData} onChange={setEnforceContextDataPersistent} />
|
||||
</FlexRow>
|
||||
<RequestEditor text={request} onChange={setRequestPersistent} />
|
||||
</EditorContainer>
|
||||
<EditorContainer>
|
||||
|
@ -144,6 +159,7 @@ export const EditorScreen = () => {
|
|||
policy={policy}
|
||||
customConfig={customConfig}
|
||||
request={request}
|
||||
enforceContextData={enforceContextData}
|
||||
onResponse={v => {
|
||||
if (isValidElement(v)) {
|
||||
setEcho(v);
|
||||
|
@ -153,7 +169,14 @@ export const EditorScreen = () => {
|
|||
}}
|
||||
/>
|
||||
{!share ? (
|
||||
<Share onResponse={v => handleShare(v)} model={modelText} policy={policy} customConfig={customConfig} request={request} />
|
||||
<Share
|
||||
onResponse={v => handleShare(v)}
|
||||
model={modelText}
|
||||
policy={policy}
|
||||
customConfig={customConfig}
|
||||
request={request}
|
||||
enforceContext={Object.entries(enforceContextData)}
|
||||
/>
|
||||
) : (
|
||||
<Copy
|
||||
content={share}
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { defaultCustomConfig, example, ModelKind } from './casbin-mode/example';
|
||||
import { defaultCustomConfig, defaultEnforceContext, example, ModelKind } from './casbin-mode/example';
|
||||
|
||||
export const DEFAULT_MODEL: ModelKind = 'basic';
|
||||
export const DEFAULT_MODEL = 'basic';
|
||||
|
||||
export enum Persist {
|
||||
MODEL,
|
||||
POLICY,
|
||||
REQUEST,
|
||||
CUSTOM_FUNCTION
|
||||
CUSTOM_FUNCTION,
|
||||
ENFORCE_CONTEXT
|
||||
}
|
||||
|
||||
function getKey(persist: Persist, modelName: string) {
|
||||
|
@ -22,7 +23,7 @@ export function setSelectedModel(value: string) {
|
|||
window.localStorage.setItem(Persist[Persist.MODEL], value);
|
||||
}
|
||||
|
||||
export function get(persist: Persist, modelName: ModelKind = DEFAULT_MODEL) {
|
||||
export function get(persist: Persist, modelName: ModelKind = DEFAULT_MODEL): string {
|
||||
const data = window.localStorage.getItem(getKey(persist, modelName));
|
||||
|
||||
if (data) {
|
||||
|
@ -39,6 +40,8 @@ export function get(persist: Persist, modelName: ModelKind = DEFAULT_MODEL) {
|
|||
return m.request;
|
||||
case Persist.CUSTOM_FUNCTION:
|
||||
return m.customConfig ? m.customConfig : defaultCustomConfig;
|
||||
case Persist.ENFORCE_CONTEXT:
|
||||
return m.enforceContext ? m.enforceContext : defaultEnforceContext;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import { Button, Echo } from '../ui';
|
||||
import { DefaultRoleManager, newEnforcer, newModel, StringAdapter, Util } from 'casbin';
|
||||
import { newEnforceContext } from './setup-enforce-context';
|
||||
|
||||
interface RunTestProps {
|
||||
model: string;
|
||||
|
@ -8,6 +9,7 @@ interface RunTestProps {
|
|||
policy: string;
|
||||
customConfig: string;
|
||||
request: string;
|
||||
enforceContextData: Map<string, string>;
|
||||
onResponse: (com: JSX.Element | any[]) => void;
|
||||
// parseABAC: boolean;
|
||||
}
|
||||
|
@ -146,7 +148,9 @@ async function enforcer(props: RunTestProps) {
|
|||
}
|
||||
|
||||
const rvals = parseABACRequest(n);
|
||||
result.push(await e.enforce(...rvals));
|
||||
const ctx = newEnforceContext(props.enforceContextData);
|
||||
|
||||
result.push(await e.enforce(ctx, ...rvals));
|
||||
}
|
||||
|
||||
const stopTime = performance.now();
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
import styled from 'styled-components';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { EnforceContext } from 'casbin';
|
||||
|
||||
const EnforceContextInput = styled.input`
|
||||
width: 20px;
|
||||
margin: 0 5px;
|
||||
`;
|
||||
interface SetupEnforceContextProps {
|
||||
data: Map<string, string>;
|
||||
onChange: (data: Map<string, string>) => void;
|
||||
}
|
||||
|
||||
const r = 'r';
|
||||
const p = 'p';
|
||||
const e = 'e';
|
||||
const m = 'm';
|
||||
|
||||
export const defaultEnforceContextData = new Map<string, string>([
|
||||
[r, r],
|
||||
[p, p],
|
||||
[e, e],
|
||||
[m, m]
|
||||
]);
|
||||
|
||||
export const newEnforceContext = (data: Map<string, string>) => {
|
||||
return new EnforceContext(data.get(r)!, data.get(p)!, data.get(e)!, data.get(m)!);
|
||||
};
|
||||
|
||||
export const SetupEnforceContext = ({ onChange, data }: SetupEnforceContextProps) => {
|
||||
const [enforceContextData, setEnforceContextData] = useState(new Map(defaultEnforceContextData));
|
||||
const handleEnforceContextChange = (key: string, value: string) => {
|
||||
onChange(data.set(key, value));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setEnforceContextData(data);
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EnforceContextInput
|
||||
value={enforceContextData.get(r)}
|
||||
placeholder={r}
|
||||
onChange={event => handleEnforceContextChange(r, event.target.value)}
|
||||
/>
|
||||
<EnforceContextInput
|
||||
value={enforceContextData.get(p)}
|
||||
placeholder={p}
|
||||
onChange={event => handleEnforceContextChange(p, event.target.value)}
|
||||
/>
|
||||
<EnforceContextInput
|
||||
value={enforceContextData.get(e)}
|
||||
placeholder={e}
|
||||
onChange={event => handleEnforceContextChange(e, event.target.value)}
|
||||
/>
|
||||
<EnforceContextInput
|
||||
value={enforceContextData.get(m)}
|
||||
placeholder={m}
|
||||
onChange={event => handleEnforceContextChange(m, event.target.value)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -10,6 +10,7 @@ export interface ShareFormat {
|
|||
policy: string;
|
||||
customConfig: string;
|
||||
request: string;
|
||||
enforceContext: object;
|
||||
}
|
||||
|
||||
async function dpaste(content: string) {
|
||||
|
@ -32,7 +33,8 @@ const Share = (props: ShareProps) => {
|
|||
model: props.model,
|
||||
policy: props.policy,
|
||||
customConfig: props.customConfig,
|
||||
request: props.request
|
||||
request: props.request,
|
||||
enforceContext: props.enforceContext
|
||||
};
|
||||
dpaste(JSON.stringify(shareContent)).then((url: string) => {
|
||||
setSharing(false);
|
||||
|
|
Loading…
Reference in New Issue