First commit
This commit is contained in:
parent
c305247a1e
commit
a1bd00e542
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:prettier/recommended",
|
||||
"prettier/@typescript-eslint"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-interface": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# vs code
|
||||
.vscode
|
||||
|
||||
# temp
|
||||
.tmp
|
||||
|
||||
# IDEA or android-studio
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
dist
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 140
|
||||
}
|
13
README.md
13
README.md
|
@ -1 +1,12 @@
|
|||
# casbin-editor
|
||||
# Casbin-editor
|
||||
Use the Casbin-editor to write your Casbin model and policy in your web browser.
|
||||
It provides functionality such as syntax highlighting and code completion, just like an IDE for a programming language.
|
||||
|
||||
Try it at: http://casbin.org/editor/
|
||||
|
||||
## Getting started
|
||||
|
||||
```shell script
|
||||
yarn install
|
||||
yarn start
|
||||
```
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
{
|
||||
"name": "casbin-editor",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"keywords": [],
|
||||
"main": "src/index.js",
|
||||
"dependencies": {
|
||||
"@reach/router": "1.2.1",
|
||||
"@types/react": "^16.9.11",
|
||||
"@types/react-dom": "^16.9.4",
|
||||
"casbin": "^3.0.6",
|
||||
"codemirror": "5.48.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "16.8.6",
|
||||
"react-codemirror2": "6.0.0",
|
||||
"react-dom": "16.8.6",
|
||||
"styled-components": "4.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/codemirror": "^0.0.80",
|
||||
"@types/reach__router": "^1.2.6",
|
||||
"@types/styled-components": "^4.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.7.0",
|
||||
"@typescript-eslint/parser": "^2.7.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-config-prettier": "^6.5.0",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"husky": "^3.0.9",
|
||||
"prettier": "^1.19.1",
|
||||
"pretty-quick": "^2.0.1",
|
||||
"react-scripts": "^3.2.0",
|
||||
"typescript": "^3.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"lint": "eslint . --ext .js,.ts,.tsx"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "yarn lint && pretty-quick --staged"
|
||||
}
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, shrink-to-fit=no"
|
||||
/>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<style>
|
||||
.CodeMirror {
|
||||
font-family: Arial, monospace;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>Casbin Online Editor</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,163 @@
|
|||
// @ts-nocheck
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
CodeMirror.defineMode('casbin-conf', function() {
|
||||
function tokenBase(stream, state) {
|
||||
const ch = stream.peek();
|
||||
if (ch === '[') {
|
||||
if (stream.match('[request_definition')) {
|
||||
state.sec = 'r';
|
||||
stream.skipTo(']');
|
||||
stream.eat(']');
|
||||
return 'header';
|
||||
} else if (stream.match('[policy_definition')) {
|
||||
state.sec = 'p';
|
||||
stream.skipTo(']');
|
||||
stream.eat(']');
|
||||
return 'header';
|
||||
} else if (stream.match('[role_definition')) {
|
||||
state.sec = 'g';
|
||||
stream.skipTo(']');
|
||||
stream.eat(']');
|
||||
return 'header';
|
||||
} else if (stream.match('[policy_effect')) {
|
||||
state.sec = 'e';
|
||||
stream.skipTo(']');
|
||||
stream.eat(']');
|
||||
return 'header';
|
||||
} else if (stream.match('[matchers')) {
|
||||
state.sec = 'm';
|
||||
stream.skipTo(']');
|
||||
stream.eat(']');
|
||||
return 'header';
|
||||
} else {
|
||||
state.sec = '';
|
||||
stream.skipToEnd();
|
||||
return '';
|
||||
}
|
||||
} else if (ch === '#') {
|
||||
stream.skipToEnd();
|
||||
return 'comment';
|
||||
} else if (ch === '"') {
|
||||
stream.skipToEnd();
|
||||
stream.skipTo('"');
|
||||
stream.eat('"');
|
||||
return 'string';
|
||||
} else if (ch === '=') {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
state.after_equal = true;
|
||||
stream.eat('=');
|
||||
}
|
||||
|
||||
if (stream.sol()) {
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
state.after_equal = false;
|
||||
}
|
||||
|
||||
if (state.sec === '') {
|
||||
stream.skipToEnd();
|
||||
return '';
|
||||
}
|
||||
|
||||
if (stream.sol()) {
|
||||
if (state.sec !== '') {
|
||||
if ((state.sec === 'g' && stream.match(new RegExp('^g[2-9]?'))) || stream.match(state.sec)) {
|
||||
if (stream.peek() === ' ' || stream.peek() === '=') {
|
||||
return 'builtin';
|
||||
} else {
|
||||
state.sec = '';
|
||||
stream.next();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
state.sec = '';
|
||||
stream.next();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
stream.next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.after_equal) {
|
||||
stream.next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.sec === 'r' || state.sec === 'p') {
|
||||
// Match: r = [sub], [obj], [act]
|
||||
// p = [sub], [obj], [act]
|
||||
if (state.comma) {
|
||||
state.comma = false;
|
||||
if (stream.match(new RegExp('^[_a-zA-Z][_a-zA-Z0-9]*'))) {
|
||||
return 'property';
|
||||
}
|
||||
}
|
||||
if (stream.eat(',') || stream.eat(' ')) {
|
||||
state.comma = true;
|
||||
return '';
|
||||
}
|
||||
} else if (state.sec === 'e') {
|
||||
// Match: e = some(where (p.[eft] == allow))
|
||||
if (state.dot) {
|
||||
state.dot = false;
|
||||
if (stream.match(new RegExp('^[_a-zA-Z][_a-zA-Z0-9]*'))) {
|
||||
return 'property';
|
||||
}
|
||||
}
|
||||
if (stream.eat('.')) {
|
||||
state.dot = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
// Match: e = some(where ([p].eft == allow))
|
||||
if (stream.match('p') && stream.peek() === '.') {
|
||||
return 'builtin';
|
||||
}
|
||||
|
||||
// Match: e = [some]([where] (p.eft == allow))
|
||||
if (stream.match('some') || stream.match('where') || stream.match('priority')) {
|
||||
return 'keyword';
|
||||
}
|
||||
|
||||
// Match: e = some(where (p.eft == [allow]))
|
||||
if (stream.match('allow') || stream.match('deny')) {
|
||||
return 'string';
|
||||
}
|
||||
} else if (state.sec === 'm') {
|
||||
// Match: m = r.[sub] == p.[sub] && r.[obj] == p.[obj] && r.[act] == p.[act]
|
||||
if (state.dot) {
|
||||
state.dot = false;
|
||||
if (stream.match(new RegExp('^[_a-zA-Z][_a-zA-Z0-9]*'))) {
|
||||
return 'property';
|
||||
}
|
||||
}
|
||||
if (stream.eat('.')) {
|
||||
state.dot = true;
|
||||
return '';
|
||||
}
|
||||
|
||||
// Match: m = [r].sub == [p].sub && [r].obj == [p].obj && [r].act == [p].act
|
||||
if ((stream.match('r') || stream.match('p')) && stream.peek() === '.') {
|
||||
return 'builtin';
|
||||
}
|
||||
|
||||
// Match: m = [g](r.sub, p.sub) && r.obj == p.obj && r.act == p.act
|
||||
if (stream.match(new RegExp('^[_a-zA-Z][_a-zA-Z0-9]*')) && stream.peek() === '(') {
|
||||
return 'def';
|
||||
}
|
||||
}
|
||||
|
||||
stream.next();
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return { tokenize: tokenBase };
|
||||
},
|
||||
token: function(stream, state) {
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
// @ts-nocheck
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
CodeMirror.defineMode('casbin-csv', function() {
|
||||
function tokenBase(stream, state) {
|
||||
const ch = stream.peek();
|
||||
|
||||
if (ch === '#') {
|
||||
stream.skipToEnd();
|
||||
return 'comment';
|
||||
} else if (ch === ',') {
|
||||
stream.eat(',');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (stream.sol() && stream.match('p')) {
|
||||
return 'def';
|
||||
}
|
||||
if (stream.sol() && (stream.match('g2') || stream.match('g'))) {
|
||||
return 'keyword';
|
||||
}
|
||||
|
||||
if (stream.skipTo(',')) {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
stream.skipToEnd();
|
||||
return 'property';
|
||||
|
||||
// stream.next();
|
||||
}
|
||||
|
||||
return {
|
||||
startState: function() {
|
||||
return { tokenize: tokenBase };
|
||||
},
|
||||
token: function(stream, state) {
|
||||
return state.tokenize(stream, state);
|
||||
}
|
||||
};
|
||||
});
|
|
@ -0,0 +1,192 @@
|
|||
/* eslint-disable */
|
||||
|
||||
const exampleModel = {
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == p.sub && r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_with_root:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_without_resources:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == p.sub && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_without_users:
|
||||
'[request_definition]\n' +
|
||||
'r = obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[role_definition]\n' +
|
||||
'g = _, _\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_resource_roles:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[role_definition]\n' +
|
||||
'g = _, _\n' +
|
||||
'g2 = _, _\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_domains:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, dom, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, dom, obj, act\n' +
|
||||
'\n' +
|
||||
'[role_definition]\n' +
|
||||
'g = _, _, _\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_deny:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act, eft\n' +
|
||||
'\n' +
|
||||
'[role_definition]\n' +
|
||||
'g = _, _\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow)) && !some(where (p.eft == deny))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
abac:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == r.obj.Owner',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
keymatch:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
keymatch2:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
ipmatch:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = some(where (p.eft == allow))\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.act',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
priority:
|
||||
'[request_definition]\n' +
|
||||
'r = sub, obj, act\n' +
|
||||
'\n' +
|
||||
'[policy_definition]\n' +
|
||||
'p = sub, obj, act, eft\n' +
|
||||
'\n' +
|
||||
'[role_definition]\n' +
|
||||
'g = _, _\n' +
|
||||
'\n' +
|
||||
'[policy_effect]\n' +
|
||||
'e = priority(p.eft) || deny\n' +
|
||||
'\n' +
|
||||
'[matchers]\n' +
|
||||
'm = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act'
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
|
||||
export default exampleModel;
|
|
@ -0,0 +1,79 @@
|
|||
/* eslint-disable */
|
||||
|
||||
const examplePolicy = {
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic: 'p, alice, data1, read\n' + 'p, bob, data2, write',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_with_root: 'p, alice, data1, read\n' + 'p, bob, data2, write',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_without_resources: 'p, alice, read\n' + 'p, bob, write',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
basic_without_users: 'p, data1, read\n' + 'p, data2, write',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac:
|
||||
'p, alice, data1, read\n' +
|
||||
'p, bob, data2, write\n' +
|
||||
'p, data2_admin, data2, read\n' +
|
||||
'p, data2_admin, data2, write\n' +
|
||||
'\n' +
|
||||
'g, alice, data2_admin',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_resource_roles:
|
||||
'p, alice, data1, read\n' +
|
||||
'p, bob, data2, write\n' +
|
||||
'p, data_group_admin, data_group, write\n' +
|
||||
'\n' +
|
||||
'g, alice, data_group_admin\n' +
|
||||
'g2, data1, data_group\n' +
|
||||
'g2, data2, data_group',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_domains:
|
||||
'p, admin, domain1, data1, read\n' +
|
||||
'p, admin, domain1, data1, write\n' +
|
||||
'p, admin, domain2, data2, read\n' +
|
||||
'p, admin, domain2, data2, write\n' +
|
||||
'\n' +
|
||||
'g, alice, admin, domain1\n' +
|
||||
'g, bob, admin, domain2',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
rbac_with_deny:
|
||||
'p, alice, data1, read, allow\n' +
|
||||
'p, bob, data2, write, allow\n' +
|
||||
'p, data2_admin, data2, read, allow\n' +
|
||||
'p, data2_admin, data2, write, allow\n' +
|
||||
'p, alice, data2, write, deny\n' +
|
||||
'\n' +
|
||||
'g, alice, data2_admin',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
abac: '',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
keymatch:
|
||||
'p, alice, /alice_data/*, GET\n' +
|
||||
'p, alice, /alice_data/resource1, POST\n' +
|
||||
'\n' +
|
||||
'p, bob, /alice_data/resource2, GET\n' +
|
||||
'p, bob, /bob_data/*, POST\n' +
|
||||
'\n' +
|
||||
'p, cathy, /cathy_data, (GET)|(POST)',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
keymatch2: 'p, alice, /alice_data/:resource, GET\n' + 'p, alice, /alice_data2/:id/using/:resId, GET',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
ipmatch: 'p, 192.168.2.0/24, data1, read\n' + 'p, 10.0.0.0/16, data2, write',
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
priority:
|
||||
'p, alice, data1, read, allow\n' +
|
||||
'p, data1_deny_group, data1, read, deny\n' +
|
||||
'p, data1_deny_group, data1, write, deny\n' +
|
||||
'p, alice, data1, write, allow\n' +
|
||||
'\n' +
|
||||
'g, alice, data1_deny_group\n' +
|
||||
'\n' +
|
||||
'p, data2_allow_group, data2, read, allow\n' +
|
||||
'p, bob, data2, read, deny\n' +
|
||||
'p, bob, data2, write, deny\n' +
|
||||
'\n' +
|
||||
'g, bob, data2_allow_group'
|
||||
/////////////////////////////////////////////////////////////////////////
|
||||
};
|
||||
|
||||
export default examplePolicy;
|
|
@ -0,0 +1,18 @@
|
|||
/* eslint-disable */
|
||||
const exampleRequest = {
|
||||
basic: 'alice, data1, read',
|
||||
basic_with_root: 'alice, data1, read',
|
||||
basic_without_resources: 'alice, read',
|
||||
basic_without_users: 'data1, read',
|
||||
rbac: 'alice, data2, read',
|
||||
rbac_with_resource_roles: 'alice, data1, read\n' + 'alice, data1, write\n' + 'alice, data2, read\n' + 'alice, data2, write ',
|
||||
rbac_with_domains: 'alice, domain1, data1, read',
|
||||
rbac_with_deny: 'alice, data1, read\n' + 'alice, data2, write',
|
||||
abac: 'Not support',
|
||||
keymatch: 'alice, /alice_data/hello, GET',
|
||||
keymatch2: 'alice, /alice_data/hello, GET\n' + 'alice, /alice_data/hello, POST',
|
||||
ipmatch: 'Not support',
|
||||
priority: 'alice, data1, read'
|
||||
};
|
||||
|
||||
export default exampleRequest;
|
|
@ -0,0 +1,154 @@
|
|||
import React, { CSSProperties, useEffect, useState } from 'react';
|
||||
import { Controlled as CodeMirror } from 'react-codemirror2';
|
||||
import { get, Persist, set } from './persist';
|
||||
|
||||
import * as codemirror from 'codemirror';
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/monokai.css';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/addon/selection/active-line';
|
||||
import 'codemirror/addon/edit/matchbrackets';
|
||||
import 'codemirror/addon/display/placeholder';
|
||||
import './casbin-mode/casbin-conf';
|
||||
import './casbin-mode/casbin-csv';
|
||||
|
||||
interface CasbinCodeMirror {
|
||||
model: string;
|
||||
options: codemirror.EditorConfiguration;
|
||||
style?: CSSProperties;
|
||||
onChange: (text: string) => void;
|
||||
persist: Persist;
|
||||
}
|
||||
|
||||
interface EditorProps {
|
||||
model: string;
|
||||
onChange?: (text: string) => void;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const CasbinCodeMirror = (props: CasbinCodeMirror) => {
|
||||
const [value, setValue] = useState(get(props.persist, props.model));
|
||||
|
||||
const { model, onChange, persist } = props;
|
||||
|
||||
useEffect(() => {
|
||||
const modelText = get(persist, model);
|
||||
setValue(modelText);
|
||||
onChange(modelText);
|
||||
}, [model, persist, onChange]);
|
||||
|
||||
return (
|
||||
<div style={props.style}>
|
||||
<CodeMirror
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setValue(value);
|
||||
props.onChange(value);
|
||||
set(props.persist, value);
|
||||
}}
|
||||
options={props.options}
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
CasbinCodeMirror.defaultProps = {
|
||||
onChange: () => {}
|
||||
};
|
||||
|
||||
export const CustomFunctionEditor = (props: EditorProps) => {
|
||||
return (
|
||||
<CasbinCodeMirror
|
||||
persist={Persist.CUSTOM_FUNCTION}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'javascript',
|
||||
lineWrapping: true,
|
||||
theme: 'monokai'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ModelEditor = (props: EditorProps) => {
|
||||
return (
|
||||
<CasbinCodeMirror
|
||||
persist={Persist.MODEL}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'casbin-conf',
|
||||
lineWrapping: true,
|
||||
theme: 'monokai'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PolicyEditor = (props: EditorProps) => {
|
||||
return (
|
||||
<CasbinCodeMirror
|
||||
persist={Persist.POLICY}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'casbin-csv',
|
||||
lineWrapping: true,
|
||||
theme: 'monokai'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const RequestEditor = (props: EditorProps) => {
|
||||
return (
|
||||
<CasbinCodeMirror
|
||||
persist={Persist.REQUEST}
|
||||
options={{
|
||||
lineNumbers: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'casbin-csv',
|
||||
lineWrapping: true,
|
||||
theme: 'monokai'
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
interface RequestResultEditorProps {
|
||||
value: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const RequestResultEditor = (props: RequestResultEditorProps) => {
|
||||
return (
|
||||
<div style={props.style}>
|
||||
<CodeMirror
|
||||
onBeforeChange={() => {}}
|
||||
value={props.value}
|
||||
options={{
|
||||
readOnly: true,
|
||||
indentUnit: 4,
|
||||
styleActiveLine: true,
|
||||
matchBrackets: true,
|
||||
mode: 'javascript',
|
||||
lineWrapping: true,
|
||||
theme: 'monokai'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,94 @@
|
|||
import React, { isValidElement, useState } from 'react';
|
||||
import SelectModel from './select-model';
|
||||
import { Button, EditorContainer, FlexRow, HeaderTitle } from '../ui';
|
||||
import { getSelectedModel, reset } from './persist';
|
||||
import { CustomFunctionEditor, ModelEditor, PolicyEditor, RequestEditor, RequestResultEditor } from './editor';
|
||||
import { RouteComponentProps } from '@reach/router';
|
||||
import Syntax from './syntax';
|
||||
import RunTest from './run-test';
|
||||
|
||||
interface Props extends RouteComponentProps {}
|
||||
|
||||
export const EditorScreen = (props: Props) => {
|
||||
const [model, setModel] = useState(getSelectedModel());
|
||||
const [modelText, setModelText] = useState('');
|
||||
const [policy, setPolicy] = useState('');
|
||||
const [fn, setFn] = useState('');
|
||||
const [request, setRequest] = useState('');
|
||||
const [echo, setEcho] = useState<JSX.Element>(<></>);
|
||||
const [requestResult, setRequestResult] = useState('');
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlexRow>
|
||||
<EditorContainer>
|
||||
<FlexRow>
|
||||
<HeaderTitle>Model</HeaderTitle>
|
||||
<SelectModel
|
||||
onChange={value => {
|
||||
setModel(value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const ok = window.confirm('Confirm Reset?');
|
||||
if (ok) {
|
||||
reset(model);
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
style={{ marginLeft: 8 }}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</FlexRow>
|
||||
<ModelEditor model={model} onChange={setModelText} />
|
||||
</EditorContainer>
|
||||
<EditorContainer>
|
||||
<HeaderTitle>Policy</HeaderTitle>
|
||||
<PolicyEditor model={model} onChange={setPolicy} />
|
||||
</EditorContainer>
|
||||
</FlexRow>
|
||||
|
||||
<FlexRow>
|
||||
<EditorContainer>
|
||||
<HeaderTitle>Request</HeaderTitle>
|
||||
<RequestEditor model={model} onChange={setRequest} />
|
||||
</EditorContainer>
|
||||
<EditorContainer>
|
||||
<HeaderTitle>Enforcement Result</HeaderTitle>
|
||||
<RequestResultEditor value={requestResult} />
|
||||
</EditorContainer>
|
||||
</FlexRow>
|
||||
|
||||
<FlexRow>
|
||||
<EditorContainer>
|
||||
<FlexRow>
|
||||
<HeaderTitle>Custom Function</HeaderTitle>
|
||||
<Button onClick={() => setVisible(!visible)}>TOGGLE</Button>
|
||||
</FlexRow>
|
||||
{visible && <CustomFunctionEditor model={model} onChange={setFn} />}
|
||||
</EditorContainer>
|
||||
</FlexRow>
|
||||
|
||||
<div style={{ padding: 8 }}>
|
||||
<Syntax model={modelText} onResponse={component => setEcho(component)} />
|
||||
<RunTest
|
||||
model={modelText}
|
||||
policy={policy}
|
||||
fn={fn}
|
||||
request={request}
|
||||
onResponse={v => {
|
||||
if (isValidElement(v)) {
|
||||
setEcho(v);
|
||||
} else if (Array.isArray(v)) {
|
||||
setRequestResult(v.join('\n'));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div style={{ display: 'inline-block' }}>{echo}</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
import exampleModel from './casbin-mode/example-model';
|
||||
import examplePolicy from './casbin-mode/example-policy';
|
||||
import exampleRequest from './casbin-mode/example-request';
|
||||
|
||||
export const DEFAULT_MODEL = 'basic';
|
||||
|
||||
export enum Persist {
|
||||
MODEL,
|
||||
POLICY,
|
||||
REQUEST,
|
||||
CUSTOM_FUNCTION
|
||||
}
|
||||
|
||||
function getKey(persist: Persist, modelName: string) {
|
||||
return `${modelName.toUpperCase()}_${Persist[persist]}`;
|
||||
}
|
||||
|
||||
export function getSelectedModel() {
|
||||
const v = window.localStorage.getItem(Persist.MODEL.toString());
|
||||
return v ? v : DEFAULT_MODEL;
|
||||
}
|
||||
|
||||
export function setSelectedModel(value: string) {
|
||||
window.localStorage.setItem(Persist[Persist.MODEL], value);
|
||||
}
|
||||
|
||||
export function get(persist: Persist, modelName = DEFAULT_MODEL) {
|
||||
const data = window.localStorage.getItem(getKey(persist, modelName));
|
||||
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
switch (persist) {
|
||||
case Persist.MODEL:
|
||||
// @ts-ignore
|
||||
return exampleModel[modelName];
|
||||
case Persist.POLICY:
|
||||
// @ts-ignore
|
||||
return examplePolicy[modelName];
|
||||
case Persist.REQUEST:
|
||||
// @ts-ignore
|
||||
return exampleRequest[modelName];
|
||||
case Persist.CUSTOM_FUNCTION:
|
||||
return `var fns = {}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function set(persist: Persist, text: string) {
|
||||
const modelName = getSelectedModel() || DEFAULT_MODEL;
|
||||
window.localStorage.setItem(getKey(persist, modelName), text);
|
||||
}
|
||||
|
||||
export function reset(modelName: string) {
|
||||
for (const m in Persist) {
|
||||
if (!Persist.hasOwnProperty(m)) {
|
||||
continue;
|
||||
}
|
||||
const index = parseInt(m, 10);
|
||||
if (!isNaN(index)) {
|
||||
continue;
|
||||
}
|
||||
window.localStorage.removeItem(getKey(index, modelName));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react';
|
||||
import { Button, Echo } from '../ui';
|
||||
import { newEnforcer, newModel, StringAdapter } from 'casbin';
|
||||
|
||||
interface RunTestProps {
|
||||
model: string;
|
||||
policy: string;
|
||||
fn: string;
|
||||
request: string;
|
||||
onResponse: (com: JSX.Element | boolean[]) => void;
|
||||
}
|
||||
|
||||
const RunTest = (props: RunTestProps) => {
|
||||
return (
|
||||
<Button
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={async () => {
|
||||
const startTime = performance.now();
|
||||
const result = [];
|
||||
try {
|
||||
const e = await newEnforcer(newModel(props.model), new StringAdapter(props.policy));
|
||||
|
||||
const fnString = props.fn;
|
||||
if (fnString) {
|
||||
try {
|
||||
const fns: any = {};
|
||||
// eslint-disable-next-line
|
||||
eval(`${fnString}`);
|
||||
if (fns) {
|
||||
Object.keys(fns).forEach(key => e.addFunction(key, fns[key]));
|
||||
}
|
||||
} catch (e) {
|
||||
props.onResponse(<Echo>Please check syntax in Custom Function Editor.</Echo>);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (const n of props.request.split('\n')) {
|
||||
const p = n
|
||||
.split(',')
|
||||
.map(n => n.trim())
|
||||
.filter(n => n);
|
||||
|
||||
if (!p || p.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
result.push(await e.enforce(...p));
|
||||
}
|
||||
|
||||
const stopTime = performance.now();
|
||||
|
||||
props.onResponse(<Echo>{'Done in ' + ((stopTime - startTime) / 1000.0).toFixed(2) + 's'}</Echo>);
|
||||
props.onResponse(result);
|
||||
} catch (e) {
|
||||
props.onResponse(<Echo type={'error'}>{e.message}</Echo>);
|
||||
props.onResponse([]);
|
||||
}
|
||||
}}
|
||||
>
|
||||
RUN THE TEST
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default RunTest;
|
|
@ -0,0 +1,42 @@
|
|||
import React from 'react';
|
||||
import { getSelectedModel, setSelectedModel } from './persist';
|
||||
|
||||
interface SelectModelProps {
|
||||
onChange: (value: string) => void;
|
||||
}
|
||||
|
||||
const SelectModel = (props: SelectModelProps) => {
|
||||
return (
|
||||
<select
|
||||
defaultValue={getSelectedModel()}
|
||||
onChange={e => {
|
||||
const model = e.target.value;
|
||||
setSelectedModel(model);
|
||||
props.onChange(model);
|
||||
}}
|
||||
>
|
||||
<option value="" disabled>
|
||||
Select your model
|
||||
</option>
|
||||
<option value="basic">ACL</option>
|
||||
<option value="basic_with_root">ACL with superuser</option>
|
||||
<option value="basic_without_resources">ACL without resources</option>
|
||||
<option value="basic_without_users">ACL without users</option>
|
||||
<option value="rbac">RBAC</option>
|
||||
<option value="rbac_with_resource_roles">RBAC with resource roles</option>
|
||||
<option value="rbac_with_domains">RBAC with domains/tenants</option>
|
||||
<option value="rbac_with_deny">RBAC with deny-override</option>
|
||||
<option value="abac">ABAC</option>
|
||||
<option value="keymatch">RESTful (KeyMatch)</option>
|
||||
<option value="keymatch2">RESTful (KeyMatch2)</option>
|
||||
<option value="ipmatch">IP match</option>
|
||||
<option value="priority">Priority</option>
|
||||
</select>
|
||||
);
|
||||
};
|
||||
|
||||
SelectModel.defaultProps = {
|
||||
onChange: console.log,
|
||||
defaultValue: 'basic'
|
||||
};
|
||||
export default SelectModel;
|
|
@ -0,0 +1,28 @@
|
|||
import React from 'react';
|
||||
import { Button, Echo } from '../ui';
|
||||
import { Config } from 'casbin/lib/config';
|
||||
|
||||
interface SyntaxProps {
|
||||
model: string;
|
||||
onResponse: (com: JSX.Element) => void;
|
||||
}
|
||||
|
||||
const Syntax = (props: SyntaxProps) => {
|
||||
return (
|
||||
<Button
|
||||
style={{ marginRight: 8 }}
|
||||
onClick={() => {
|
||||
try {
|
||||
Config.newConfigFromText(props.model);
|
||||
props.onResponse(<Echo>passed</Echo>);
|
||||
} catch (e) {
|
||||
props.onResponse(<Echo type={'error'}>{e.message}</Echo>);
|
||||
}
|
||||
}}
|
||||
>
|
||||
SYNTAX VALIDATE
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Syntax;
|
|
@ -0,0 +1,30 @@
|
|||
import React from 'react';
|
||||
import { Router } from '@reach/router';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { EditorScreen } from './editor';
|
||||
import { Footer } from './ui';
|
||||
import 'normalize.css/normalize.css';
|
||||
|
||||
const App = () => (
|
||||
<>
|
||||
<Router>
|
||||
<EditorScreen path="/" />
|
||||
</Router>
|
||||
|
||||
<Footer>
|
||||
<a
|
||||
style={{ color: '#FFFFFF', textDecoration: 'none' }}
|
||||
title="casbin-editor on GitHub"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
href="https://github.com/nodece/casbin-editor"
|
||||
>
|
||||
Github
|
||||
</a>
|
||||
<span style={{ color: '#FFFFFF', float: 'right' }}>Copyright © {new Date().getFullYear()} Casbin contributors.</span>
|
||||
</Footer>
|
||||
</>
|
||||
);
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
ReactDOM.render(<App />, rootElement);
|
|
@ -0,0 +1 @@
|
|||
/// <reference types="react-scripts" />
|
|
@ -0,0 +1,63 @@
|
|||
import styled from 'styled-components';
|
||||
|
||||
export const Button = styled.button`
|
||||
border: 1px solid #443d80;
|
||||
border-radius: 3px;
|
||||
color: #443d80;
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
line-height: 1.2em;
|
||||
padding: 10px;
|
||||
text-decoration: none !important;
|
||||
text-transform: uppercase;
|
||||
transition: background 0.3s, color 0.3s;
|
||||
|
||||
:hover {
|
||||
background: #443d80;
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
export const HeaderTitle = styled.h4`
|
||||
padding: 8px;
|
||||
`;
|
||||
|
||||
export const FlexRow = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const EditorContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
interface EchoProps {
|
||||
type?: 'pass' | 'error';
|
||||
}
|
||||
|
||||
const error = '#db4545';
|
||||
const pass = '#39aa56';
|
||||
|
||||
export const Echo = styled.span<EchoProps>`
|
||||
color: ${(props: EchoProps) => {
|
||||
switch (props.type) {
|
||||
case 'error':
|
||||
return error;
|
||||
case 'pass':
|
||||
return pass;
|
||||
}
|
||||
}};
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
`;
|
||||
|
||||
Echo.defaultProps = {
|
||||
type: 'pass'
|
||||
};
|
||||
|
||||
export const Footer = styled.div`
|
||||
padding: 1em;
|
||||
background: #222;
|
||||
`;
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"jsx": "react",
|
||||
"moduleResolution": "node",
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
Loading…
Reference in New Issue