├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .github
├── semantic.yml
└── workflows
│ └── main.yml
├── .gitignore
├── .prettierrc
├── .releaserc.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── casbin-logo.png
├── examples
├── abac_model.conf
├── abac_rule_model.conf
├── abac_rule_policy.csv
├── basic_inverse_policy.csv
├── basic_keyget2_model.conf
├── basic_keyget2_policy.csv
├── basic_model.conf
├── basic_policy.csv
├── basic_with_root_model.conf
├── basic_without_resources_model.conf
├── basic_without_resources_policy.csv
├── basic_without_users_model.conf
├── basic_without_users_policy.csv
├── glob_model.conf
├── glob_policy.csv
├── in_operator_model.conf
├── ipmatch_model.conf
├── ipmatch_policy.csv
├── issues
│ ├── node_casbin_150_model.conf
│ └── node_casbin_150_policy.csv
├── keymatch2_model.conf
├── keymatch2_policy.csv
├── keymatch_custom_model.conf
├── keymatch_model.conf
├── keymatch_policy.csv
├── mulitple_policy.csv
├── priority_indeterminate_policy.csv
├── priority_model.conf
├── priority_model_explicit.conf
├── priority_policy.csv
├── priority_policy_explicit.csv
├── priority_policy_explicit_update.csv
├── rbac_g2_model.conf
├── rbac_g2_policy.csv
├── rbac_model.conf
├── rbac_policy.csv
├── rbac_with_all_pattern_model.conf
├── rbac_with_all_pattern_policy.csv
├── rbac_with_deny_model.conf
├── rbac_with_deny_policy.csv
├── rbac_with_domain_pattern_model.conf
├── rbac_with_domain_pattern_policy.csv
├── rbac_with_domains_model.conf
├── rbac_with_domains_policy.csv
├── rbac_with_hierarchical_domains_model.conf
├── rbac_with_hierarchical_domains_policy.csv
├── rbac_with_hierarchy_policy.csv
├── rbac_with_hierarchy_with_domains_policy.csv
├── rbac_with_not_deny_model.conf
├── rbac_with_pattern_model.conf
├── rbac_with_pattern_policy.csv
├── rbac_with_resource_roles_model.conf
├── rbac_with_resource_roles_policy.csv
├── subject_priority_model.conf
├── subject_priority_model_with_domain.conf
├── subject_priority_policy.csv
└── subject_priority_policy_with_domain.csv
├── jest.config.js
├── package.json
├── src
├── cachedEnforcer.ts
├── config.ts
├── constants.ts
├── coreEnforcer.ts
├── effect
│ ├── defaultEffector.ts
│ ├── defaultEffectorStream.ts
│ ├── effector.ts
│ ├── effectorStream.ts
│ └── index.ts
├── enforceContext.ts
├── enforcer.ts
├── frontend.ts
├── global.d.ts
├── index.ts
├── internalEnforcer.ts
├── log
│ ├── defaultLogger.ts
│ ├── index.ts
│ ├── logUtil.ts
│ └── logger.ts
├── managementEnforcer.ts
├── model
│ ├── assertion.ts
│ ├── functionMap.ts
│ ├── index.ts
│ └── model.ts
├── persist
│ ├── adapter.ts
│ ├── batchAdapter.ts
│ ├── batchFileAdapter.ts
│ ├── defaultFilteredAdapter.ts
│ ├── fileAdapter.ts
│ ├── fileSystem.ts
│ ├── filteredAdapter.ts
│ ├── helper.ts
│ ├── index.ts
│ ├── stringAdapter.ts
│ ├── updatableAdapter.ts
│ ├── watcher.ts
│ └── watcherEx.ts
├── rbac
│ ├── defaultRoleManager.ts
│ ├── index.ts
│ └── roleManager.ts
├── syncedEnforcer.ts
└── util
│ ├── builtinOperators.ts
│ ├── index.ts
│ ├── ip.ts
│ └── util.ts
├── test
├── cachedEnforcer.test.ts
├── config
│ ├── config.test.ts
│ └── testini.ini
├── enforcer.test.ts
├── frontend.test.ts
├── managementAPI.test.ts
├── model.test.ts
├── model
│ └── model.test.ts
├── persist
│ ├── fileSystem.test.ts
│ └── helper.test.ts
├── rbac
│ └── defaultRoleManager.test.ts
├── rbacAPI.test.ts
├── rbacHierarchicalDomain.test.ts
├── rbacwDomainAPI.test.ts
├── syncedEnforcer.test.ts
└── util.test.ts
├── tsconfig.cjs.json
├── tsconfig.esm.json
├── tsconfig.json
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | lib
2 | examples
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "@typescript-eslint/parser",
3 | "extends": ["plugin:@typescript-eslint/recommended", "prettier/@typescript-eslint", "plugin:prettier/recommended"],
4 | "rules": {
5 | "prettier/prettier": "error",
6 | "eqeqeq": "error",
7 | "@typescript-eslint/no-explicit-any": "off",
8 | "@typescript-eslint/no-unused-vars": "off",
9 | "@typescript-eslint/explicit-function-return-type": [
10 | "error",
11 | {
12 | "allowExpressions": true
13 | }
14 | ],
15 | "@typescript-eslint/no-non-null-assertion": ["error"]
16 | },
17 | "env": {
18 | "jest": true,
19 | "node": true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casbin/node-casbin/7aedfcfeeb3eeb1e46e39f3a99e85ba18f948bf4/.gitattributes
--------------------------------------------------------------------------------
/.github/semantic.yml:
--------------------------------------------------------------------------------
1 | # Always validate the PR title AND all the commits
2 | titleAndCommits: true
3 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: main
2 | on: [push, pull_request]
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | strategy:
7 | fail-fast: false
8 | matrix:
9 | node-version: [^18, ^20]
10 | steps:
11 | - uses: actions/checkout@v2
12 |
13 | - uses: actions/setup-node@v1
14 | with:
15 | node-version: ${{ matrix.node-version }}
16 | - run: yarn install
17 | - run: yarn lint
18 | - run: yarn test
19 | - run: yarn coverage
20 | - name: Coveralls
21 | uses: coverallsapp/github-action@master
22 | with:
23 | github-token: ${{ secrets.GITHUB_TOKEN }}
24 | flag-name: run-${{ matrix.node-version }}
25 | parallel: true
26 |
27 | semantic-release:
28 | needs: [test]
29 | runs-on: ubuntu-latest
30 | steps:
31 | - uses: actions/checkout@v2
32 |
33 | - name: Run semantic-release
34 | if: github.repository == 'casbin/node-casbin' && github.event_name == 'push'
35 | run: yarn install && yarn semantic-release
36 | env:
37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
38 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
39 |
40 | finish:
41 | needs: test
42 | runs-on: ubuntu-latest
43 | steps:
44 | - name: Coveralls Finished
45 | uses: coverallsapp/github-action@master
46 | with:
47 | github-token: ${{ secrets.github_token }}
48 | parallel-finished: true
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | node_modules
4 | lib
5 | yarn-error.log
6 | package-lock.json
7 | coverage
8 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "printWidth": 140,
5 | "endOfLine": "auto"
6 | }
7 |
--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug": true,
3 | "plugins": [
4 | "@semantic-release/commit-analyzer",
5 | "@semantic-release/release-notes-generator",
6 | "@semantic-release/npm",
7 | [
8 | "@semantic-release/changelog",
9 | {
10 | "changelogFile": "CHANGELOG.md"
11 | }
12 | ],
13 | [
14 | "@semantic-release/git",
15 | {
16 | "assets": ["package.json", "CHANGELOG.md"],
17 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
18 | }
19 | ],
20 | "@semantic-release/github"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Node-Casbin
2 |
3 | [![NPM version][npm-image]][npm-url]
4 | [![NPM download][download-image]][download-url]
5 | [](https://packagephobia.now.sh/result?p=casbin)
6 | [](https://codebeat.co/projects/github-com-casbin-node-casbin-master)
7 | [](https://github.com/casbin/node-casbin/actions)
8 | [](https://coveralls.io/github/casbin/node-casbin?branch=master)
9 | [](https://github.com/casbin/node-casbin/releases/latest)
10 | [](https://discord.gg/S5UjpzGZjN)
11 |
12 | [npm-image]: https://img.shields.io/npm/v/casbin.svg?style=flat-square
13 | [npm-url]: https://npmjs.org/package/casbin
14 | [download-image]: https://img.shields.io/npm/dm/casbin.svg?style=flat-square
15 | [download-url]: https://npmjs.org/package/casbin
16 |
17 |
18 | Sponsored by
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | Build auth with fraud prevention, faster. Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.
28 |
29 |
30 |
31 | 💖 [**Looking for an open-source identity and access management solution like Okta, Auth0, Keycloak ? Learn more about: Casdoor**](https://casdoor.org/)
32 |
33 |
34 |
35 | **News**: still worry about how to write the correct `node-casbin` policy? [Casbin online editor](http://casbin.org/editor) is coming to help!
36 |
37 | 
38 |
39 | `node-casbin` is a powerful and efficient open-source access control library for Node.JS projects. It provides support for enforcing authorization based on various [access control models](https://wikipedia.org/wiki/Computer_security_model).
40 |
41 | ## All the languages supported by Casbin:
42 |
43 | | [](https://github.com/casbin/casbin) | [](https://github.com/casbin/jcasbin) | [](https://github.com/casbin/node-casbin) | [](https://github.com/php-casbin/php-casbin) |
44 | | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
45 | | [Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin) | [PHP-Casbin](https://github.com/php-casbin/php-casbin) |
46 | | production-ready | production-ready | production-ready | production-ready |
47 |
48 | | [](https://github.com/casbin/pycasbin) | [](https://github.com/casbin-net/Casbin.NET) | [](https://github.com/casbin/casbin-cpp) | [](https://github.com/casbin/casbin-rs) |
49 | | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
50 | | [PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin-CPP](https://github.com/casbin/casbin-cpp) | [Casbin-RS](https://github.com/casbin/casbin-rs) |
51 | | production-ready | production-ready | beta-test | production-ready |
52 |
53 | ## Documentation
54 |
55 | https://casbin.org/docs/overview
56 |
57 | ## Installation
58 |
59 | ```shell script
60 | # NPM
61 | npm install casbin --save
62 |
63 | # Yarn
64 | yarn add casbin
65 | ```
66 |
67 | ## Get started
68 |
69 | New a `node-casbin` enforcer with a model file and a policy file, see [Model](#official-model) section for details:
70 |
71 | ```node.js
72 | // For Node.js:
73 | const { newEnforcer } = require('casbin');
74 | // For browser:
75 | // import { newEnforcer } from 'casbin';
76 |
77 | const enforcer = await newEnforcer('basic_model.conf', 'basic_policy.csv');
78 | ```
79 |
80 | > **Note**: you can also initialize an enforcer with policy in DB instead of file, see [Persistence](#policy-persistence) section for details.
81 |
82 | Add an enforcement hook into your code right before the access happens:
83 |
84 | ```node.js
85 | const sub = 'alice'; // the user that wants to access a resource.
86 | const obj = 'data1'; // the resource that is going to be accessed.
87 | const act = 'read'; // the operation that the user performs on the resource.
88 |
89 | // Async:
90 | const res = await enforcer.enforce(sub, obj, act);
91 | // Sync:
92 | // const res = enforcer.enforceSync(sub, obj, act);
93 |
94 | if (res) {
95 | // permit alice to read data1
96 | } else {
97 | // deny the request, show an error
98 | }
99 | ```
100 |
101 | Besides the static policy file, `node-casbin` also provides API for permission management at run-time.
102 | For example, You can get all the roles assigned to a user as below:
103 |
104 | ```node.js
105 | const roles = await enforcer.getRolesForUser('alice');
106 | ```
107 |
108 | See [Policy management APIs](#policy-management) for more usage.
109 |
110 | ## Policy management
111 |
112 | Casbin provides two sets of APIs to manage permissions:
113 |
114 | - [Management API](https://casbin.org/docs/management-api): the primitive API that provides full support for Casbin policy management.
115 | - [RBAC API](https://casbin.org/docs/rbac-api): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code.
116 |
117 | ## Official Model
118 |
119 | https://casbin.org/docs/supported-models
120 |
121 | ## Policy persistence
122 |
123 | https://casbin.org/docs/adapters
124 |
125 | ## Policy consistence between multiple nodes
126 |
127 | https://casbin.org/docs/watchers
128 |
129 | ## Role manager
130 |
131 | https://casbin.org/docs/role-managers
132 |
133 | ## Contributors
134 |
135 | This project exists thanks to all the people who contribute.
136 |
137 |
138 | ## Backers
139 |
140 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/casbin#backer)]
141 |
142 |
143 |
144 | ## Sponsors
145 |
146 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/casbin#sponsor)]
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 | ## License
160 |
161 | This project is licensed under the [Apache 2.0 license](LICENSE).
162 |
163 | ## Contact
164 |
165 | If you have any issues or feature requests, please contact us. PR is welcomed.
166 |
167 | - https://github.com/casbin/node-casbin/issues
168 | - hsluoyz@gmail.com
169 | - Tencent QQ group: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885)
170 |
--------------------------------------------------------------------------------
/casbin-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/casbin/node-casbin/7aedfcfeeb3eeb1e46e39f3a99e85ba18f948bf4/casbin-logo.png
--------------------------------------------------------------------------------
/examples/abac_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == r.obj.Owner
--------------------------------------------------------------------------------
/examples/abac_rule_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub_rule, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/abac_rule_policy.csv:
--------------------------------------------------------------------------------
1 | p, r.sub.Age > 18, /data1, read
2 | p, r.sub.Age < 60, /data2, write
--------------------------------------------------------------------------------
/examples/basic_inverse_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, write
2 | p, bob, data2, read
--------------------------------------------------------------------------------
/examples/basic_keyget2_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj
3 |
4 | [policy_definition]
5 | p = sub, obj
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = keyGet2(r.obj, p.obj, 'id')
12 |
--------------------------------------------------------------------------------
/examples/basic_keyget2_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, /data/:id
2 |
--------------------------------------------------------------------------------
/examples/basic_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/basic_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read
2 | p, bob, data2, write
--------------------------------------------------------------------------------
/examples/basic_with_root_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"
--------------------------------------------------------------------------------
/examples/basic_without_resources_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, act
3 |
4 | [policy_definition]
5 | p = sub, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && r.act == p.act
--------------------------------------------------------------------------------
/examples/basic_without_resources_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, read
2 | p, bob, write
--------------------------------------------------------------------------------
/examples/basic_without_users_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = obj, act
3 |
4 | [policy_definition]
5 | p = obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/basic_without_users_policy.csv:
--------------------------------------------------------------------------------
1 | p, data1, read
2 | p, data2, write
--------------------------------------------------------------------------------
/examples/glob_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && globMatch(r.obj, p.obj) && r.act == p.act
--------------------------------------------------------------------------------
/examples/glob_policy.csv:
--------------------------------------------------------------------------------
1 | p, u1, /foo/*, read
2 | p, u2, /foo*, read
3 | p, u3, /*/foo/*, read
4 | p, u4, *, read
--------------------------------------------------------------------------------
/examples/in_operator_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub.Owner == r.obj.Owner && r.sub.Doc in (r.obj.Docs)
12 |
--------------------------------------------------------------------------------
/examples/ipmatch_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/ipmatch_policy.csv:
--------------------------------------------------------------------------------
1 | p, 192.168.2.0/24, data1, read
2 | p, 10.0.0.0/16, data2, write
--------------------------------------------------------------------------------
/examples/issues/node_casbin_150_model.conf:
--------------------------------------------------------------------------------
1 | # https://github.com/casbin/node-casbin/issues/150
2 | [request_definition]
3 | r = sub, obj, act
4 |
5 | [policy_definition]
6 | p = sub, obj, act
7 |
8 | [role_definition]
9 | g = _, _
10 |
11 | [policy_effect]
12 | e = some(where (p.eft == allow))
13 |
14 | [matchers]
15 | m = g(r.sub, p.sub) && r.obj == p.obj && regexMatch(r.act, p.act)
16 |
17 |
--------------------------------------------------------------------------------
/examples/issues/node_casbin_150_policy.csv:
--------------------------------------------------------------------------------
1 | p, book_admin , /book/1, GET
2 | p, pen_admin , /pen/1, GET
3 |
4 | g, *, book_admin
5 | g, *, pen_admin
6 |
--------------------------------------------------------------------------------
/examples/keymatch2_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)
--------------------------------------------------------------------------------
/examples/keymatch2_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, /alice_data/:resource, GET
2 | p, alice, /alice_data2/:id/using/:resId, GET
--------------------------------------------------------------------------------
/examples/keymatch_custom_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && keyMatchCustom(r.obj, p.obj) && regexMatch(r.act, p.act)
--------------------------------------------------------------------------------
/examples/keymatch_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [policy_effect]
8 | e = some(where (p.eft == allow))
9 |
10 | [matchers]
11 | m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)
--------------------------------------------------------------------------------
/examples/keymatch_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, /alice_data/*, GET
2 | p, alice, /alice_data/resource1, POST
3 |
4 | p, bob, /alice_data/resource2, GET
5 | p, bob, /bob_data/*, POST
6 |
7 | p, cathy, /cathy_data, (GET)|(POST)
--------------------------------------------------------------------------------
/examples/mulitple_policy.csv:
--------------------------------------------------------------------------------
1 | p2, alice, data1, read
2 | p2, bob, data2, write
--------------------------------------------------------------------------------
/examples/priority_indeterminate_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read, intdeterminate
--------------------------------------------------------------------------------
/examples/priority_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act, eft
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = priority(p.eft) || deny
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/priority_model_explicit.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = priority, sub, obj, act, eft
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = priority(p.eft) || deny
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/priority_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read, allow
2 | p, data1_deny_group, data1, read, deny
3 | p, data1_deny_group, data1, write, deny
4 | p, alice, data1, write, allow
5 |
6 | g, alice, data1_deny_group
7 |
8 | p, data2_allow_group, data2, read, allow
9 | p, bob, data2, read, deny
10 | p, bob, data2, write, deny
11 |
12 | g, bob, data2_allow_group
--------------------------------------------------------------------------------
/examples/priority_policy_explicit.csv:
--------------------------------------------------------------------------------
1 | p, 10, data1_deny_group, data1, read, deny
2 | p, 10, data1_deny_group, data1, write, deny
3 | p, 10, data2_allow_group, data2, read, allow
4 | p, 10, data2_allow_group, data2, write, allow
5 |
6 |
7 | p, 1, alice, data1, write, allow
8 | p, 1, alice, data1, read, allow
9 | p, 1, bob, data2, read, deny
10 |
11 | g, bob, data2_allow_group
12 | g, alice, data1_deny_group
13 |
--------------------------------------------------------------------------------
/examples/priority_policy_explicit_update.csv:
--------------------------------------------------------------------------------
1 | p, 10, data1_deny_group, data1, read, deny
2 | p, 10, data1_deny_group, data1, write, deny
3 | p, 10, data2_allow_group, data2, read, allow
4 | p, 10, data2_allow_group, data2, write, allow
5 |
6 |
7 | p, 1, alice, data1, write, allow
8 | p, 1, alice, data1, read, allow
9 | p, 1, bob, data2, read, deny
10 | p, 1, bob, data2, write, allow
11 |
12 | g, bob, data2_allow_group
13 | g, alice, data1_deny_group
14 |
--------------------------------------------------------------------------------
/examples/rbac_g2_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 | g2 = _, _
10 |
11 | [policy_effect]
12 | e = some(where (p.eft == allow))
13 |
14 | [matchers]
15 | m = g(r.sub, p.sub)
16 |
--------------------------------------------------------------------------------
/examples/rbac_g2_policy.csv:
--------------------------------------------------------------------------------
1 | p, admin, data1, read
2 |
3 | g2, alice, admin
4 |
--------------------------------------------------------------------------------
/examples/rbac_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read
2 | p, bob, data2, write
3 | p, data2_admin, data2, read
4 | p, data2_admin, data2, write
5 | g, alice, data2_admin
--------------------------------------------------------------------------------
/examples/rbac_with_all_pattern_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, dom, obj, act
3 |
4 | [policy_definition]
5 | p = sub, dom, obj, act
6 |
7 | [role_definition]
8 | g = _, _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = r.sub == p.sub && g(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_all_pattern_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, domain1, book_group, read
2 | p, alice, domain2, book_group, write
3 |
4 | g, /book/:id, book_group, *
--------------------------------------------------------------------------------
/examples/rbac_with_deny_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act, eft
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_deny_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read, allow
2 | p, bob, data2, write, allow
3 | p, data2_admin, data2, read, allow
4 | p, data2_admin, data2, write, allow
5 | p, alice, data2, write, deny
6 |
7 | g, alice, data2_admin
--------------------------------------------------------------------------------
/examples/rbac_with_domain_pattern_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, dom, obj, act
3 |
4 | [policy_definition]
5 | p = sub, dom, obj, act
6 |
7 | [role_definition]
8 | g = _, _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_domain_pattern_policy.csv:
--------------------------------------------------------------------------------
1 | p, admin, domain1, data1, read
2 | p, admin, domain1, data1, write
3 | p, admin, domain2, data2, read
4 | p, admin, domain2, data2, write
5 |
6 | g, alice, admin, *
7 | g, bob, admin, domain2
--------------------------------------------------------------------------------
/examples/rbac_with_domains_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, dom, obj, act
3 |
4 | [policy_definition]
5 | p = sub, dom, obj, act
6 |
7 | [role_definition]
8 | g = _, _, _
9 |
10 | [policy_effect]
11 | e = some(where (p.eft == allow))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_domains_policy.csv:
--------------------------------------------------------------------------------
1 | p, admin, domain1, data1, read
2 | p, admin, domain1, data1, write
3 | p, admin, domain2, data2, read
4 | p, admin, domain2, data2, write
5 |
6 | g, alice, admin, domain1
7 | g, bob, admin, domain2
--------------------------------------------------------------------------------
/examples/rbac_with_hierarchical_domains_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, dom, obj, act
3 |
4 | [policy_definition]
5 | p = sub, dom, obj, act
6 |
7 | [role_definition]
8 | g = _, _, _
9 | g2 = _, _
10 |
11 | [policy_effect]
12 | e = some(where (p.eft == allow))
13 |
14 | [matchers]
15 | m = g(r.sub, p.sub, r.dom) && g2(r.dom, p.dom) && r.obj == p.obj && regexMatch(r.act, p.act)
--------------------------------------------------------------------------------
/examples/rbac_with_hierarchical_domains_policy.csv:
--------------------------------------------------------------------------------
1 | p, admin, domain1, data1, (read|write)
2 | p, admin, domain2, data2, (read|write)
3 | p, admin, domain3, data3, (write)
4 | p, admin, sibling2, sdata2, (read|write)
5 |
6 | g, alice, admin, domain1
7 | g, bob, admin, domain2
8 | g2, domain1, domain2
9 | g2, domain1, sibling2
10 | g2, domain2, domain3
--------------------------------------------------------------------------------
/examples/rbac_with_hierarchy_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read
2 | p, bob, data2, write
3 | p, data1_admin, data1, read
4 | p, data1_admin, data1, write
5 | p, data2_admin, data2, read
6 | p, data2_admin, data2, write
7 |
8 | g, alice, admin
9 | g, admin, data1_admin
10 | g, admin, data2_admin
--------------------------------------------------------------------------------
/examples/rbac_with_hierarchy_with_domains_policy.csv:
--------------------------------------------------------------------------------
1 | p, role:reader, domain1, data1, read
2 | p, role:writer, domain1, data1, write
3 |
4 | g, role:global_admin, role:reader, domain1
5 | g, role:global_admin, role:writer, domain1
6 |
7 | g, alice, role:global_admin, domain1
8 |
9 |
--------------------------------------------------------------------------------
/examples/rbac_with_not_deny_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act, eft
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = !some(where (p_eft == deny))
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_pattern_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 | g2 = _, _
10 |
11 | [policy_effect]
12 | e = some(where (p.eft == allow))
13 |
14 | [matchers]
15 | m = g(r.sub, p.sub) && g2(r.obj, p.obj) && regexMatch(r.act, p.act)
--------------------------------------------------------------------------------
/examples/rbac_with_pattern_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, /pen/1, GET
2 | p, alice, /pen2/1, GET
3 | p, book_admin, book_group, GET
4 | p, pen_admin, pen_group, GET
5 |
6 | g, alice, book_admin
7 | g, bob, pen_admin
8 |
9 | g, /book/*, book_group
10 | g, cathy, /book/1/2/3/4/5
11 | g, cathy, pen_admin
12 |
13 | g2, /book/:id, book_group
14 | g2, /pen/:id, pen_group
15 |
16 | g2, /book2/{id}, book_group
17 | g2, /pen2/{id}, pen_group
--------------------------------------------------------------------------------
/examples/rbac_with_resource_roles_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act
6 |
7 | [role_definition]
8 | g = _, _
9 | g2 = _, _
10 |
11 | [policy_effect]
12 | e = some(where (p.eft == allow))
13 |
14 | [matchers]
15 | m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act
--------------------------------------------------------------------------------
/examples/rbac_with_resource_roles_policy.csv:
--------------------------------------------------------------------------------
1 | p, alice, data1, read
2 | p, bob, data2, write
3 | p, data_group_admin, data_group, write
4 |
5 | g, alice, data_group_admin
6 | g2, data1, data_group
7 | g2, data2, data_group
--------------------------------------------------------------------------------
/examples/subject_priority_model.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, act
3 |
4 | [policy_definition]
5 | p = sub, obj, act, eft
6 |
7 | [role_definition]
8 | g = _, _
9 |
10 | [policy_effect]
11 | e = subjectPriority(p.eft) || deny
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
15 |
--------------------------------------------------------------------------------
/examples/subject_priority_model_with_domain.conf:
--------------------------------------------------------------------------------
1 | [request_definition]
2 | r = sub, obj, dom, act
3 |
4 | [policy_definition]
5 | p = sub, obj, dom, act, eft
6 |
7 | [role_definition]
8 | g = _, _, _
9 |
10 | [policy_effect]
11 | e = subjectPriority(p.eft) || deny
12 |
13 | [matchers]
14 | m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act
15 |
--------------------------------------------------------------------------------
/examples/subject_priority_policy.csv:
--------------------------------------------------------------------------------
1 | p, root, data1, read, deny
2 | p, admin, data1, read, deny
3 |
4 | p, editor, data1, read, deny
5 | p, subscriber, data1, read, deny
6 |
7 | p, jane, data1, read, allow
8 | p, alice, data1, read, allow
9 |
10 | g, admin, root
11 |
12 | g, editor, admin
13 | g, subscriber, admin
14 |
15 | g, jane, editor
16 | g, alice, subscriber
17 |
--------------------------------------------------------------------------------
/examples/subject_priority_policy_with_domain.csv:
--------------------------------------------------------------------------------
1 | p, admin, data1, domain1, write, deny
2 | p, alice, data1, domain1, write, allow
3 | p, admin, data2, domain2, write, deny
4 | p, bob, data2, domain2, write, allow
5 |
6 | g, alice, admin, domain1
7 | g, bob, admin, domain2
8 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: 'node',
3 | transform: {
4 | '^.+\\.(ts|tsx)$': 'ts-jest',
5 | },
6 | moduleNameMapper: {
7 | 'csv-parse': '/node_modules/csv-parse/dist/cjs/sync.cjs',
8 | },
9 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
10 | };
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "casbin",
3 | "version": "5.38.0",
4 | "description": "An authorization library that supports access control models like ACL, RBAC, ABAC in Node.JS",
5 | "main": "lib/cjs/index.js",
6 | "typings": "lib/cjs/index.d.ts",
7 | "module": "lib/esm/index.js",
8 | "scripts": {
9 | "prepack": "run-s lint test build",
10 | "postpack": "run-s clean",
11 | "build": "run-s clean && run-p build:*",
12 | "build:cjs": "tsc -p tsconfig.cjs.json",
13 | "build:esm": "tsc -p tsconfig.esm.json",
14 | "test": "jest",
15 | "lint": "eslint . --ext .js,.ts",
16 | "fmt": "eslint . --ext .js,.ts --fix",
17 | "semantic-release": "semantic-release",
18 | "commit": "git-cz",
19 | "clean": "rimraf lib",
20 | "coverage": "jest --coverage"
21 | },
22 | "devDependencies": {
23 | "@semantic-release/changelog": "^5.0.1",
24 | "@semantic-release/commit-analyzer": "^8.0.1",
25 | "@semantic-release/git": "^9.0.0",
26 | "@semantic-release/github": "^7.2.3",
27 | "@semantic-release/npm": "^7.1.3",
28 | "@semantic-release/release-notes-generator": "^9.0.3",
29 | "@types/jest": "^26.0.20",
30 | "@types/node": "^10.5.3",
31 | "@types/picomatch": "^2.2.2",
32 | "@typescript-eslint/eslint-plugin": "^4.17.0",
33 | "@typescript-eslint/parser": "^4.17.0",
34 | "coveralls": "^3.0.2",
35 | "cz-conventional-changelog": "^3.2.0",
36 | "eslint": "^7.22.0",
37 | "eslint-config-prettier": "^6.12.0",
38 | "eslint-plugin-prettier": "^3.3.1",
39 | "husky": "^2.3.0",
40 | "jest": "^26.6.3",
41 | "lint-staged": "^8.1.7",
42 | "npm-run-all": "^4.1.5",
43 | "prettier": "^2.2.1",
44 | "pretty-quick": "^3.1.0",
45 | "rimraf": "^3.0.2",
46 | "semantic-release": "^17.4.4",
47 | "ts-jest": "^26.5.3",
48 | "tslint": "^5.11.0",
49 | "typescript": "^3.7.2"
50 | },
51 | "dependencies": {
52 | "@casbin/expression-eval": "^5.3.0",
53 | "await-lock": "^2.0.1",
54 | "buffer": "^6.0.3",
55 | "csv-parse": "^5.5.6",
56 | "minimatch": "^7.4.2"
57 | },
58 | "files": [
59 | "lib",
60 | "examples"
61 | ],
62 | "homepage": "http://casbin.org",
63 | "repository": {
64 | "type": "git",
65 | "url": "https://github.com/casbin/node-casbin.git"
66 | },
67 | "license": "Apache-2.0",
68 | "husky": {
69 | "hooks": {
70 | "prepare-commit-msg": "exec < /dev/tty && git cz --hook || true",
71 | "pre-commit": "yarn fmt && pretty-quick --staged"
72 | }
73 | },
74 | "config": {
75 | "commitizen": {
76 | "path": "./node_modules/cz-conventional-changelog"
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/cachedEnforcer.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Enforcer, newEnforcerWithClass } from './enforcer';
16 |
17 | // CachedEnforcer wraps Enforcer and provides decision cache
18 | export class CachedEnforcer extends Enforcer {
19 | private enableCache = true;
20 | private m = new Map();
21 |
22 | // invalidateCache deletes all the existing cached decisions.
23 | public invalidateCache(): void {
24 | this.m = new Map();
25 | }
26 |
27 | // setEnableCache determines whether to enable cache on e nforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions.
28 | public setEnableCache(enableCache: boolean): void {
29 | this.enableCache = enableCache;
30 | }
31 |
32 | private static canCache(...rvals: any[]): boolean {
33 | return rvals.every((n) => typeof n === 'string');
34 | }
35 |
36 | private static getCacheKey(...rvals: string[]): string {
37 | return rvals.join('$$');
38 | }
39 |
40 | private getCache(key: string): boolean | undefined {
41 | return this.m.get(key);
42 | }
43 |
44 | private setCache(key: string, value: boolean): void {
45 | this.m.set(key, value);
46 | }
47 |
48 | // enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
49 | // if rvals is not string , ingore the cache
50 | public async enforce(...rvals: any[]): Promise {
51 | if (!this.enableCache) {
52 | return super.enforce(...rvals);
53 | }
54 |
55 | let key = '';
56 | const cache = CachedEnforcer.canCache(...rvals);
57 |
58 | if (cache) {
59 | key = CachedEnforcer.getCacheKey(...rvals);
60 | const res = this.getCache(key);
61 |
62 | if (res !== undefined) {
63 | return res;
64 | }
65 | }
66 |
67 | const res = await super.enforce(...rvals);
68 |
69 | if (cache) {
70 | this.setCache(key, res);
71 | }
72 |
73 | return res;
74 | }
75 | }
76 |
77 | // newCachedEnforcer creates a cached enforcer via file or DB.
78 | export async function newCachedEnforcer(...params: any[]): Promise {
79 | return newEnforcerWithClass(CachedEnforcer, ...params);
80 | }
81 |
--------------------------------------------------------------------------------
/src/config.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { FileSystem, mustGetDefaultFileSystem } from './persist';
16 |
17 | // ConfigInterface defines the behavior of a Config implementation
18 | export interface ConfigInterface {
19 | getString(key: string): string;
20 |
21 | getStrings(key: string): string[];
22 |
23 | getBool(key: string): boolean;
24 |
25 | getInt(key: string): number;
26 |
27 | getFloat(key: string): number;
28 |
29 | set(key: string, value: string): void;
30 | }
31 |
32 | export class Config implements ConfigInterface {
33 | private static DEFAULT_SECTION = 'default';
34 | private static DEFAULT_COMMENT = '#';
35 | private static DEFAULT_COMMENT_SEM = ';';
36 | private static DEFAULT_MULTI_LINE_SEPARATOR = '\\';
37 |
38 | private data: Map>;
39 |
40 | private readonly fs?: FileSystem;
41 |
42 | private constructor(fs?: FileSystem) {
43 | this.data = new Map>();
44 | if (fs) {
45 | this.fs = fs;
46 | }
47 | }
48 |
49 | /**
50 | * newConfig create an empty configuration representation from file.
51 | *
52 | * @param confName the path of the model file.
53 | * @return the constructor of Config.
54 | * @deprecated use {@link newConfigFromFile} instead.
55 | */
56 | public static newConfig(confName: string): Config {
57 | return this.newConfigFromFile(confName);
58 | }
59 |
60 | /**
61 | * newConfigFromFile create an empty configuration representation from file.
62 | * @param path the path of the model file.
63 | * @param fs {@link FileSystem}
64 | */
65 | public static newConfigFromFile(path: string, fs?: FileSystem): Config {
66 | const config = new Config(fs);
67 | config.parse(path);
68 | return config;
69 | }
70 |
71 | /**
72 | * newConfigFromText create an empty configuration representation from text.
73 | *
74 | * @param text the model text.
75 | * @return the constructor of Config.
76 | */
77 | public static newConfigFromText(text: string): Config {
78 | const config = new Config();
79 | config.parseBuffer(Buffer.from(text));
80 | return config;
81 | }
82 |
83 | /**
84 | * addConfig adds a new section->key:value to the configuration.
85 | */
86 | private addConfig(section: string, option: string, value: string): boolean {
87 | if (section === '') {
88 | section = Config.DEFAULT_SECTION;
89 | }
90 | const hasKey = this.data.has(section);
91 | if (!hasKey) {
92 | this.data.set(section, new Map());
93 | }
94 |
95 | const item = this.data.get(section);
96 | if (item) {
97 | item.set(option, value);
98 | return item.has(option);
99 | } else {
100 | return false;
101 | }
102 | }
103 |
104 | private parse(path: string): void {
105 | const body = (this.fs ? this.fs : mustGetDefaultFileSystem()).readFileSync(path);
106 | this.parseBuffer(Buffer.isBuffer(body) ? body : Buffer.from(body));
107 | }
108 |
109 | private parseBuffer(buf: Buffer): void {
110 | const lines = buf
111 | .toString()
112 | .split('\n')
113 | .filter((v) => v);
114 | const linesCount = lines.length;
115 | let section = '';
116 | let currentLine = '';
117 | const seenSections = new Set();
118 |
119 | lines.forEach((n, index) => {
120 | let commentPos = n.indexOf(Config.DEFAULT_COMMENT);
121 | if (commentPos > -1) {
122 | n = n.slice(0, commentPos);
123 | }
124 | commentPos = n.indexOf(Config.DEFAULT_COMMENT_SEM);
125 | if (commentPos > -1) {
126 | n = n.slice(0, commentPos);
127 | }
128 |
129 | const line = n.trim();
130 | if (!line) {
131 | return;
132 | }
133 |
134 | const lineNumber = index + 1;
135 |
136 | if (line.startsWith('[') && line.endsWith(']')) {
137 | if (currentLine.length !== 0) {
138 | this.write(section, lineNumber - 1, currentLine);
139 | currentLine = '';
140 | }
141 | section = line.substring(1, line.length - 1);
142 | if (seenSections.has(section)) {
143 | throw new Error(`Duplicated section: ${section} at line ${lineNumber}`);
144 | }
145 | seenSections.add(section);
146 | } else {
147 | let shouldWrite = false;
148 | if (line.endsWith(Config.DEFAULT_MULTI_LINE_SEPARATOR)) {
149 | currentLine += line.substring(0, line.length - 1).trim();
150 | } else {
151 | currentLine += line;
152 | shouldWrite = true;
153 | }
154 | if (shouldWrite || lineNumber === linesCount) {
155 | this.write(section, lineNumber, currentLine);
156 | currentLine = '';
157 | }
158 | }
159 | });
160 | }
161 |
162 | private write(section: string, lineNum: number, line: string): void {
163 | const equalIndex = line.indexOf('=');
164 | if (equalIndex === -1) {
165 | throw new Error(`parse the content error : line ${lineNum}`);
166 | }
167 | const key = line.substring(0, equalIndex);
168 | const value = line.substring(equalIndex + 1);
169 | this.addConfig(section, key.trim(), value.trim());
170 | }
171 |
172 | public getBool(key: string): boolean {
173 | return !!this.get(key);
174 | }
175 |
176 | public getInt(key: string): number {
177 | return Number.parseInt(this.get(key), 10);
178 | }
179 |
180 | public getFloat(key: string): number {
181 | return Number.parseFloat(this.get(key));
182 | }
183 |
184 | public getString(key: string): string {
185 | return this.get(key);
186 | }
187 |
188 | public getStrings(key: string): string[] {
189 | const v = this.get(key);
190 | return v.split(',');
191 | }
192 |
193 | public set(key: string, value: string): void {
194 | if (!key) {
195 | throw new Error('key is empty');
196 | }
197 |
198 | let section = '';
199 | let option;
200 |
201 | const keys = key.toLowerCase().split('::');
202 | if (keys.length >= 2) {
203 | section = keys[0];
204 | option = keys[1];
205 | } else {
206 | option = keys[0];
207 | }
208 |
209 | this.addConfig(section, option, value);
210 | }
211 |
212 | public get(key: string): string {
213 | let section;
214 | let option;
215 |
216 | const keys = key.toLowerCase().split('::');
217 | if (keys.length >= 2) {
218 | section = keys[0];
219 | option = keys[1];
220 | } else {
221 | section = Config.DEFAULT_SECTION;
222 | option = keys[0];
223 | }
224 |
225 | const item = this.data.get(section);
226 | const itemChild = item && item.get(option);
227 |
228 | return itemChild ? itemChild : '';
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/src/constants.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2022 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export const enum EffectExpress {
16 | ALLOW = 'some(where (p_eft == allow))',
17 | DENY = '!some(where (p_eft == deny))',
18 | ALLOW_AND_DENY = 'some(where (p_eft == allow)) && !some(where (p_eft == deny))',
19 | PRIORITY = 'priority(p_eft) || deny',
20 | SUBJECT_PRIORITY = 'subjectPriority(p_eft) || deny',
21 | }
22 |
23 | export const enum FieldIndex {
24 | Domain = 'dom',
25 | Subject = 'sub',
26 | Object = 'obj',
27 | Priority = 'priority',
28 | }
29 |
--------------------------------------------------------------------------------
/src/effect/defaultEffector.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Effector } from './effector';
16 | import { EffectorStream } from './effectorStream';
17 | import { DefaultEffectorStream } from './defaultEffectorStream';
18 |
19 | /**
20 | * DefaultEffector is default effector for Casbin.
21 | */
22 | export class DefaultEffector implements Effector {
23 | newStream(expr: string): EffectorStream {
24 | return new DefaultEffectorStream(expr);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/effect/defaultEffectorStream.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { EffectorStream } from './effectorStream';
16 | import { Effect } from './effector';
17 | import { EffectExpress } from '../constants';
18 |
19 | /**
20 | * DefaultEffectorStream is the default implementation of EffectorStream.
21 | */
22 | export class DefaultEffectorStream implements EffectorStream {
23 | private done = false;
24 | private res = false;
25 | private rec = false;
26 | private readonly expr: string;
27 |
28 | constructor(expr: string) {
29 | this.expr = expr;
30 | }
31 |
32 | current(): boolean {
33 | return this.res;
34 | }
35 |
36 | public pushEffect(eft: Effect): [boolean, boolean, boolean] {
37 | switch (this.expr) {
38 | case EffectExpress.ALLOW:
39 | if (eft === Effect.Allow) {
40 | this.res = true;
41 | this.done = true;
42 | this.rec = true;
43 | }
44 | break;
45 | case EffectExpress.DENY:
46 | this.res = true;
47 | if (eft === Effect.Deny) {
48 | this.res = false;
49 | this.done = true;
50 | this.rec = true;
51 | }
52 | break;
53 | case EffectExpress.ALLOW_AND_DENY:
54 | if (eft === Effect.Allow) {
55 | this.res = true;
56 | this.rec = true;
57 | } else if (eft === Effect.Deny) {
58 | this.res = false;
59 | this.done = true;
60 | this.rec = true;
61 | } else {
62 | this.rec = false;
63 | }
64 | break;
65 | case EffectExpress.PRIORITY:
66 | case EffectExpress.SUBJECT_PRIORITY:
67 | if (eft !== Effect.Indeterminate) {
68 | this.res = eft === Effect.Allow;
69 | this.done = true;
70 | this.rec = true;
71 | }
72 | break;
73 | default:
74 | throw new Error('unsupported effect');
75 | }
76 | return [this.res, this.rec, this.done];
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/effect/effector.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Effect is the result for a policy rule.
16 | // Values for policy effect.
17 | import { EffectorStream } from './effectorStream';
18 |
19 | export enum Effect {
20 | Allow = 1,
21 | Indeterminate,
22 | Deny,
23 | }
24 |
25 | // Effector is the interface for Casbin effectors.
26 | export interface Effector {
27 | newStream(expr: string): EffectorStream;
28 | }
29 |
--------------------------------------------------------------------------------
/src/effect/effectorStream.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Effect } from './effector';
16 |
17 | // EffectorStream provides a stream interface
18 | export interface EffectorStream {
19 | current(): boolean;
20 |
21 | pushEffect(eft: Effect): [boolean, boolean, boolean];
22 | }
23 |
--------------------------------------------------------------------------------
/src/effect/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export * from './effector';
16 | export * from './effectorStream';
17 | export * from './defaultEffector';
18 | export * from './defaultEffectorStream';
19 |
--------------------------------------------------------------------------------
/src/enforceContext.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2023 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export class EnforceContext {
16 | public pType: string;
17 | public rType: string;
18 | public eType: string;
19 | public mType: string;
20 |
21 | constructor(rType: string, pType: string, eType: string, mType: string) {
22 | this.pType = pType;
23 | this.eType = eType;
24 | this.mType = mType;
25 | this.rType = rType;
26 | }
27 | }
28 |
29 | export const newEnforceContext = (index: string): EnforceContext => {
30 | return new EnforceContext('r' + index, 'p' + index, 'e' + index, 'm' + index);
31 | };
32 |
--------------------------------------------------------------------------------
/src/frontend.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Enforcer } from './enforcer';
16 | import { deepCopy } from './util';
17 |
18 | /**
19 | * Experiment!
20 | * getPermissionForCasbinJs returns a string include the whole model.
21 | * You can pass the returned string to the frontend and manage your webpage widgets and APIs with Casbin.js.
22 | * @param e the initialized enforcer
23 | * @param user the user
24 | */
25 | export async function casbinJsGetPermissionForUser(e: Enforcer, user?: string): Promise {
26 | const obj: any = {};
27 |
28 | const m = e.getModel().model;
29 | let s = '';
30 | s += '[request_definition]\n';
31 | s += `r = ${m.get('r')?.get('r')?.value.replace(/_/g, '.')}\n`;
32 | s += '[policy_definition]\n';
33 | s += `p = ${m.get('p')?.get('p')?.value.replace(/_/g, '.')}\n`;
34 | if (m.get('g')?.get('g') !== undefined) {
35 | s += '[role_definition]\n';
36 | s += `g = ${m.get('g')?.get('g')?.value}\n`;
37 | }
38 | s += '[policy_effect]\n';
39 | s += `e = ${m.get('e')?.get('e')?.value.replace(/_/g, '.')}\n`;
40 | s += '[matchers]\n';
41 | s += `m = ${m.get('m')?.get('m')?.value.replace(/_/g, '.')}`;
42 | obj['m'] = s;
43 |
44 | const policy = deepCopy(await e.getPolicy());
45 | const groupPolicy = deepCopy(await e.getGroupingPolicy());
46 |
47 | policy.forEach((item: string[]) => {
48 | item.unshift('p');
49 | });
50 |
51 | groupPolicy.forEach((item: string[]) => {
52 | item.unshift('g');
53 | });
54 |
55 | obj['p'] = [...policy, ...groupPolicy];
56 |
57 | return JSON.stringify(obj);
58 | }
59 |
--------------------------------------------------------------------------------
/src/global.d.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2023 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | declare const __non_webpack_require__: NodeRequireFunction;
16 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as Util from './util';
16 | import { setDefaultFileSystem } from './persist';
17 |
18 | export * from './config';
19 | export * from './enforcer';
20 | export * from './cachedEnforcer';
21 | export * from './syncedEnforcer';
22 | export * from './effect';
23 | export * from './model';
24 | export * from './persist';
25 | export * from './rbac';
26 | export * from './log';
27 | export * from './enforceContext';
28 | export * from './frontend';
29 | export { Util };
30 |
--------------------------------------------------------------------------------
/src/internalEnforcer.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { CoreEnforcer } from './coreEnforcer';
16 | import { BatchAdapter } from './persist';
17 | import { UpdatableAdapter } from './persist';
18 | import { PolicyOp } from './model';
19 |
20 | /**
21 | * InternalEnforcer = CoreEnforcer + Internal API.
22 | */
23 | export class InternalEnforcer extends CoreEnforcer {
24 | /**
25 | * addPolicyInternal adds a rule to the current policy.
26 | */
27 | protected async addPolicyInternal(sec: string, ptype: string, rule: string[], useWatcher: boolean): Promise {
28 | if (this.model.hasPolicy(sec, ptype, rule)) {
29 | return false;
30 | }
31 |
32 | if (this.adapter && this.autoSave) {
33 | try {
34 | await this.adapter.addPolicy(sec, ptype, rule);
35 | } catch (e) {
36 | if (e.message !== 'not implemented') {
37 | throw e;
38 | }
39 | }
40 | }
41 |
42 | if (useWatcher) {
43 | if (this.autoNotifyWatcher) {
44 | // error intentionally ignored
45 | if (this.watcherEx) {
46 | this.watcherEx.updateForAddPolicy(sec, ptype, ...rule);
47 | } else if (this.watcher) {
48 | this.watcher.update();
49 | }
50 | }
51 | }
52 |
53 | const ok = this.model.addPolicy(sec, ptype, rule);
54 |
55 | if (sec === 'g' && ok) {
56 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, [rule]);
57 | }
58 | return ok;
59 | }
60 |
61 | // addPolicies adds rules to the current policy.
62 | // removePolicies removes rules from the current policy.
63 | protected async addPoliciesInternal(sec: string, ptype: string, rules: string[][], useWatcher: boolean): Promise {
64 | for (const rule of rules) {
65 | if (this.model.hasPolicy(sec, ptype, rule)) {
66 | return false;
67 | }
68 | }
69 |
70 | if (this.autoSave) {
71 | if ('addPolicies' in this.adapter) {
72 | try {
73 | await this.adapter.addPolicies(sec, ptype, rules);
74 | } catch (e) {
75 | if (e.message !== 'not implemented') {
76 | throw e;
77 | }
78 | }
79 | } else {
80 | throw new Error('cannot to save policy, the adapter does not implement the BatchAdapter');
81 | }
82 | }
83 |
84 | if (useWatcher) {
85 | if (this.autoNotifyWatcher) {
86 | // error intentionally ignored
87 | if (this.watcherEx) {
88 | this.watcherEx.updateForAddPolicies(sec, ptype, ...rules);
89 | } else if (this.watcher) {
90 | this.watcher.update();
91 | }
92 | }
93 | }
94 |
95 | const [ok, effects] = await this.model.addPolicies(sec, ptype, rules);
96 | if (sec === 'g' && ok && effects?.length) {
97 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, effects);
98 | }
99 | return ok;
100 | }
101 |
102 | /**
103 | * updatePolicyInternal updates a rule from the current policy.
104 | */
105 | protected async updatePolicyInternal(
106 | sec: string,
107 | ptype: string,
108 | oldRule: string[],
109 | newRule: string[],
110 | useWatcher: boolean
111 | ): Promise {
112 | if (!this.model.hasPolicy(sec, ptype, oldRule)) {
113 | return false;
114 | }
115 |
116 | if (this.autoSave) {
117 | if ('updatePolicy' in this.adapter) {
118 | try {
119 | await this.adapter.updatePolicy(sec, ptype, oldRule, newRule);
120 | } catch (e) {
121 | if (e.message !== 'not implemented') {
122 | throw e;
123 | }
124 | }
125 | } else {
126 | throw new Error('cannot to update policy, the adapter does not implement the UpdatableAdapter');
127 | }
128 | }
129 |
130 | if (useWatcher) {
131 | if (this.autoNotifyWatcher) {
132 | // In fact I think it should wait for the respond, but they implement add_policy() like this
133 | // error intentionally ignored
134 | if (this.watcher) {
135 | this.watcher.update();
136 | }
137 | }
138 | }
139 |
140 | const ok = this.model.updatePolicy(sec, ptype, oldRule, newRule);
141 | if (sec === 'g' && ok) {
142 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, [oldRule]);
143 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyAdd, ptype, [newRule]);
144 | }
145 |
146 | return ok;
147 | }
148 |
149 | /**
150 | * removePolicyInternal removes a rule from the current policy.
151 | */
152 | protected async removePolicyInternal(sec: string, ptype: string, rule: string[], useWatcher: boolean): Promise {
153 | if (!this.model.hasPolicy(sec, ptype, rule)) {
154 | return false;
155 | }
156 |
157 | if (this.adapter && this.autoSave) {
158 | try {
159 | await this.adapter.removePolicy(sec, ptype, rule);
160 | } catch (e) {
161 | if (e.message !== 'not implemented') {
162 | throw e;
163 | }
164 | }
165 | }
166 |
167 | if (useWatcher) {
168 | if (this.autoNotifyWatcher) {
169 | // error intentionally ignored
170 | if (this.watcherEx) {
171 | this.watcherEx.updateForRemovePolicy(sec, ptype, ...rule);
172 | } else if (this.watcher) {
173 | this.watcher.update();
174 | }
175 | }
176 | }
177 |
178 | const ok = await this.model.removePolicy(sec, ptype, rule);
179 | if (sec === 'g' && ok) {
180 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, [rule]);
181 | }
182 | return ok;
183 | }
184 |
185 | // removePolicies removes rules from the current policy.
186 | protected async removePoliciesInternal(sec: string, ptype: string, rules: string[][], useWatcher: boolean): Promise {
187 | for (const rule of rules) {
188 | if (!this.model.hasPolicy(sec, ptype, rule)) {
189 | return false;
190 | }
191 | }
192 |
193 | if (this.autoSave) {
194 | if ('removePolicies' in this.adapter) {
195 | try {
196 | await this.adapter.removePolicies(sec, ptype, rules);
197 | } catch (e) {
198 | if (e.message !== 'not implemented') {
199 | throw e;
200 | }
201 | }
202 | } else {
203 | throw new Error('cannot to save policy, the adapter does not implement the BatchAdapter');
204 | }
205 | }
206 |
207 | if (useWatcher) {
208 | if (this.autoNotifyWatcher) {
209 | // error intentionally ignored
210 | if (this.watcherEx) {
211 | this.watcherEx.updateForRemovePolicies(sec, ptype, ...rules);
212 | } else if (this.watcher) {
213 | this.watcher.update();
214 | }
215 | }
216 | }
217 |
218 | const [ok, effects] = this.model.removePolicies(sec, ptype, rules);
219 | if (sec === 'g' && ok && effects?.length) {
220 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
221 | }
222 | return ok;
223 | }
224 |
225 | /**
226 | * removeFilteredPolicyInternal removes rules based on field filters from the current policy.
227 | */
228 | protected async removeFilteredPolicyInternal(
229 | sec: string,
230 | ptype: string,
231 | fieldIndex: number,
232 | fieldValues: string[],
233 | useWatcher: boolean
234 | ): Promise {
235 | if (this.adapter && this.autoSave) {
236 | try {
237 | await this.adapter.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
238 | } catch (e) {
239 | if (e.message !== 'not implemented') {
240 | throw e;
241 | }
242 | }
243 | }
244 |
245 | if (useWatcher) {
246 | if (this.autoNotifyWatcher) {
247 | // error intentionally ignored
248 | if (this.watcherEx) {
249 | this.watcherEx.updateForRemoveFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
250 | } else if (this.watcher) {
251 | this.watcher.update();
252 | }
253 | }
254 | }
255 |
256 | const [ok, effects] = this.model.removeFilteredPolicy(sec, ptype, fieldIndex, ...fieldValues);
257 | if (sec === 'g' && ok && effects?.length) {
258 | await this.buildIncrementalRoleLinks(PolicyOp.PolicyRemove, ptype, effects);
259 | }
260 | return ok;
261 | }
262 |
263 | /**
264 | * get field index in model.fieldMap.
265 | */
266 | public getFieldIndex(ptype: string, field: string): number {
267 | return this.model.getFieldIndex(ptype, field);
268 | }
269 |
270 | /**
271 | * set index of field
272 | */
273 | public setFieldIndex(ptype: string, field: string, index: number): void {
274 | const assertion = this.model.model.get('p')?.get(ptype);
275 | assertion?.fieldIndexMap.set(field, index);
276 | }
277 | }
278 |
--------------------------------------------------------------------------------
/src/log/defaultLogger.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Logger } from './logger';
16 |
17 | // DefaultLogger is the implementation for a Logger
18 | export class DefaultLogger implements Logger {
19 | private enable = false;
20 |
21 | public enableLog(enable: boolean): void {
22 | this.enable = enable;
23 | }
24 |
25 | public isEnable(): boolean {
26 | return this.enable;
27 | }
28 |
29 | public print(...v: any[]): void {
30 | if (this.enable) {
31 | console.log(...v);
32 | }
33 | }
34 |
35 | public printf(format: string, ...v: any[]): void {
36 | if (this.enable) {
37 | console.log(format, ...v);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/log/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export * from './defaultLogger';
16 | export * from './logger';
17 | export * from './logUtil';
18 |
--------------------------------------------------------------------------------
/src/log/logUtil.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { DefaultLogger } from './defaultLogger';
16 | import { Logger } from './logger';
17 |
18 | let logger: Logger = new DefaultLogger();
19 |
20 | // setLogger sets the current logger.
21 | function setLogger(l: Logger): void {
22 | logger = l;
23 | }
24 |
25 | // getLogger returns the current logger.
26 | function getLogger(): Logger {
27 | return logger;
28 | }
29 |
30 | // logPrint prints the log.
31 | function logPrint(...v: any[]): void {
32 | logger.print(...v);
33 | }
34 |
35 | // logPrintf prints the log with the format.
36 | function logPrintf(format: string, ...v: any[]): void {
37 | logger.printf(format, ...v);
38 | }
39 |
40 | export { setLogger, getLogger, logPrint, logPrintf };
41 |
--------------------------------------------------------------------------------
/src/log/logger.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // Logger is the logging interface implementation.
16 | export interface Logger {
17 | // enableLog controls whether print the message.
18 | enableLog(enable: boolean): void;
19 |
20 | // isEnable returns if logger is enabled.
21 | isEnable(): boolean;
22 |
23 | // print formats using the default formats for its operands and logs the message.
24 | print(...v: any[]): void;
25 |
26 | // printf formats according to a format specifier and logs the message.
27 | printf(format: string, ...v: any[]): void;
28 | }
29 |
--------------------------------------------------------------------------------
/src/model/assertion.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as rbac from '../rbac';
16 | import { logPrint } from '../log';
17 | import { PolicyOp } from './model';
18 |
19 | // Assertion represents an expression in a section of the model.
20 | // For example: r = sub, obj, act
21 | export class Assertion {
22 | public key: string;
23 | public value: string;
24 | public tokens: string[];
25 | public policy: string[][];
26 | public rm: rbac.RoleManager;
27 | public fieldIndexMap: Map;
28 |
29 | /**
30 | * constructor is the constructor for Assertion.
31 | */
32 | constructor() {
33 | this.key = '';
34 | this.value = '';
35 | this.tokens = [];
36 | this.policy = [];
37 | this.rm = new rbac.DefaultRoleManager(10);
38 | this.fieldIndexMap = new Map();
39 | }
40 |
41 | public async buildIncrementalRoleLinks(rm: rbac.RoleManager, op: PolicyOp, rules: string[][]): Promise {
42 | this.rm = rm;
43 | const count = (this.value.match(/_/g) || []).length;
44 | if (count < 2) {
45 | throw new Error('the number of "_" in role definition should be at least 2');
46 | }
47 | for (let rule of rules) {
48 | if (rule.length < count) {
49 | throw new Error('grouping policy elements do not meet role definition');
50 | }
51 | if (rule.length > count) {
52 | rule = rule.slice(0, count);
53 | }
54 | switch (op) {
55 | case PolicyOp.PolicyAdd:
56 | await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
57 | break;
58 | case PolicyOp.PolicyRemove:
59 | await this.rm.deleteLink(rule[0], rule[1], ...rule.slice(2));
60 | break;
61 | default:
62 | throw new Error('unsupported operation');
63 | }
64 | }
65 | }
66 |
67 | public async buildRoleLinks(rm: rbac.RoleManager): Promise {
68 | this.rm = rm;
69 | const count = (this.value.match(/_/g) || []).length;
70 | if (count < 2) {
71 | throw new Error('the number of "_" in role definition should be at least 2');
72 | }
73 | for (let rule of this.policy) {
74 | if (rule.length > count) {
75 | rule = rule.slice(0, count);
76 | }
77 | await this.rm.addLink(rule[0], rule[1], ...rule.slice(2));
78 | }
79 | logPrint(`Role links for: ${this.key}`);
80 | await this.rm.printRoles();
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/model/functionMap.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as util from '../util';
16 |
17 | export type MatchingFunction = (...arg: any[]) => boolean | number | string | Promise | Promise | Promise;
18 |
19 | // FunctionMap represents the collection of Function.
20 | export class FunctionMap {
21 | private functions: Map;
22 |
23 | /**
24 | * constructor is the constructor for FunctionMap.
25 | */
26 | constructor() {
27 | this.functions = new Map();
28 | }
29 |
30 | // loadFunctionMap loads an initial function map.
31 | public static loadFunctionMap(): FunctionMap {
32 | const fm = new FunctionMap();
33 |
34 | fm.addFunction('keyMatch', util.keyMatchFunc);
35 | fm.addFunction('keyGet', util.keyGetFunc);
36 | fm.addFunction('keyMatch2', util.keyMatch2Func);
37 | fm.addFunction('keyGet2', util.keyGet2Func);
38 | fm.addFunction('keyMatch3', util.keyMatch3Func);
39 | fm.addFunction('keyMatch4', util.keyMatch4Func);
40 | fm.addFunction('keyMatch5', util.keyMatch5Func);
41 | fm.addFunction('regexMatch', util.regexMatchFunc);
42 | fm.addFunction('ipMatch', util.ipMatchFunc);
43 | fm.addFunction('globMatch', util.globMatch);
44 |
45 | return fm;
46 | }
47 |
48 | // addFunction adds an expression function.
49 | public addFunction(name: string, func: MatchingFunction): void {
50 | if (!this.functions.get(name)) {
51 | this.functions.set(name, func);
52 | }
53 | }
54 |
55 | // getFunctions return all functions.
56 | public getFunctions(): any {
57 | return this.functions;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/model/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export * from './assertion';
16 | export * from './functionMap';
17 | export * from './model';
18 |
--------------------------------------------------------------------------------
/src/persist/adapter.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Model } from '../model';
16 |
17 | export interface Adapter {
18 | loadPolicy(model: Model): Promise;
19 |
20 | savePolicy(model: Model): Promise;
21 |
22 | addPolicy(sec: string, ptype: string, rule: string[]): Promise;
23 |
24 | removePolicy(sec: string, ptype: string, rule: string[]): Promise;
25 |
26 | removeFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise;
27 | }
28 |
--------------------------------------------------------------------------------
/src/persist/batchAdapter.ts:
--------------------------------------------------------------------------------
1 | import { Adapter } from './adapter';
2 |
3 | // BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.
4 | export interface BatchAdapter extends Adapter {
5 | // addPolicies adds policy rules to the storage.
6 | // This is part of the Auto-Save feature.
7 | addPolicies(sec: string, ptype: string, rules: string[][]): Promise;
8 | // removePolicies removes policy rules from the storage.
9 | // This is part of the Auto-Save feature.
10 | removePolicies(sec: string, ptype: string, rules: string[][]): Promise;
11 | }
12 |
--------------------------------------------------------------------------------
/src/persist/batchFileAdapter.ts:
--------------------------------------------------------------------------------
1 | import { FileAdapter } from './fileAdapter';
2 | import { BatchAdapter } from './batchAdapter';
3 |
4 | /**
5 | * BatchFileAdapter is the file adapter for Casbin.
6 | * It can add policies and remove policies.
7 | * @deprecated The class should not be used, you should use FileAdapter.
8 | */
9 | export class BatchFileAdapter extends FileAdapter implements BatchAdapter {
10 | /**
11 | * FileAdapter is the constructor for FileAdapter.
12 | * @param {string} filePath filePath the path of the policy file.
13 | */
14 | constructor(filePath: string) {
15 | super(filePath);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/persist/defaultFilteredAdapter.ts:
--------------------------------------------------------------------------------
1 | import { FilteredAdapter } from './filteredAdapter';
2 | import { Model } from '../model';
3 | import { FileAdapter } from './fileAdapter';
4 | import { Helper } from './helper';
5 | import { readFile } from '../util';
6 |
7 | export class Filter {
8 | public g: string[] = [];
9 | public p: string[] = [];
10 | }
11 |
12 | export class DefaultFilteredAdapter extends FileAdapter implements FilteredAdapter {
13 | private filtered: boolean;
14 |
15 | constructor(filePath: string) {
16 | super(filePath);
17 | this.filtered = false;
18 | }
19 |
20 | // loadPolicy loads all policy rules from the storage.
21 | public async loadPolicy(model: Model): Promise {
22 | this.filtered = false;
23 | await super.loadPolicy(model);
24 | }
25 |
26 | public async loadFilteredPolicy(model: Model, filter: Filter): Promise {
27 | if (!filter) {
28 | await this.loadPolicy(model);
29 | return;
30 | }
31 |
32 | if (!this.filePath) {
33 | throw new Error('invalid file path, file path cannot be empty');
34 | }
35 |
36 | await this.loadFilteredPolicyFile(model, filter, Helper.loadPolicyLine);
37 | this.filtered = true;
38 | }
39 |
40 | private async loadFilteredPolicyFile(model: Model, filter: Filter, handler: (line: string, model: Model) => void): Promise {
41 | const bodyBuf = await readFile(this.filePath);
42 | const lines = bodyBuf.toString().split('\n');
43 | lines.forEach((n: string, index: number) => {
44 | const line = n;
45 | if (!line || DefaultFilteredAdapter.filterLine(line, filter)) {
46 | return;
47 | }
48 | handler(line, model);
49 | });
50 | }
51 |
52 | public isFiltered(): boolean {
53 | return this.filtered;
54 | }
55 |
56 | public async savePolicy(model: Model): Promise {
57 | if (this.filtered) {
58 | throw new Error('cannot save a filtered policy');
59 | }
60 | await super.savePolicy(model);
61 | return true;
62 | }
63 |
64 | private static filterLine(line: string, filter: Filter): boolean {
65 | if (!filter) {
66 | return false;
67 | }
68 | const p = line.split(',');
69 | if (p.length === 0) {
70 | return true;
71 | }
72 | let filterSlice: string[] = [];
73 | switch (p[0].trim()) {
74 | case 'p':
75 | filterSlice = filter.p;
76 | break;
77 | case 'g':
78 | filterSlice = filter.g;
79 | break;
80 | }
81 |
82 | return DefaultFilteredAdapter.filterWords(p, filterSlice);
83 | }
84 |
85 | private static filterWords(line: string[], filter: string[]): boolean {
86 | if (line.length < filter.length + 1) {
87 | return true;
88 | }
89 | let skipLine = false;
90 | for (let i = 0; i < filter.length; i++) {
91 | if (filter[i] && filter[i] !== filter[i + 1]) {
92 | skipLine = true;
93 | break;
94 | }
95 | }
96 | return skipLine;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/persist/fileAdapter.ts:
--------------------------------------------------------------------------------
1 | import { Adapter } from './adapter';
2 | import { Model } from '../model';
3 | import { Helper } from './helper';
4 | import { arrayToString } from '../util';
5 | import { FileSystem, mustGetDefaultFileSystem } from './fileSystem';
6 |
7 | /**
8 | * FileAdapter is the file adapter for Casbin.
9 | * It can load policy from file or save policy to file.
10 | */
11 | export class FileAdapter implements Adapter {
12 | public readonly filePath: string;
13 | protected readonly fs?: FileSystem;
14 |
15 | /**
16 | * FileAdapter is the constructor for FileAdapter.
17 | *
18 | * @param filePath filePath the path of the policy file.
19 | * @param fs {@link FileSystem}
20 | */
21 | constructor(filePath: string, fs?: FileSystem) {
22 | this.filePath = filePath;
23 | this.fs = fs;
24 | }
25 |
26 | public async loadPolicy(model: Model): Promise {
27 | if (!this.filePath) {
28 | // throw new Error('invalid file path, file path cannot be empty');
29 | return;
30 | }
31 | await this.loadPolicyFile(model, Helper.loadPolicyLine);
32 | }
33 |
34 | private async loadPolicyFile(model: Model, handler: (line: string, model: Model) => void): Promise {
35 | const bodyBuf = await (this.fs ? this.fs : mustGetDefaultFileSystem()).readFileSync(this.filePath);
36 | const lines = bodyBuf.toString().split('\n');
37 | lines.forEach((line: string) => {
38 | if (!line || line.trim().startsWith('#')) {
39 | return;
40 | }
41 | handler(line, model);
42 | });
43 | }
44 |
45 | /**
46 | * savePolicy saves all policy rules to the storage.
47 | */
48 | public async savePolicy(model: Model): Promise {
49 | if (!this.filePath) {
50 | // throw new Error('invalid file path, file path cannot be empty');
51 | return false;
52 | }
53 | let result = '';
54 |
55 | const pList = model.model.get('p');
56 | if (!pList) {
57 | return false;
58 | }
59 | pList.forEach((n) => {
60 | n.policy.forEach((m) => {
61 | result += n.key + ', ';
62 | result += arrayToString(m);
63 | result += '\n';
64 | });
65 | });
66 |
67 | const gList = model.model.get('g');
68 | if (!gList) {
69 | return false;
70 | }
71 | gList.forEach((n) => {
72 | n.policy.forEach((m) => {
73 | result += n.key + ', ';
74 | result += arrayToString(m.map((element) => this.escapeCsv(element)));
75 | result += '\n';
76 | });
77 | });
78 |
79 | await this.savePolicyFile(result.trim());
80 | return true;
81 | }
82 |
83 | private escapeCsv(value: string): string {
84 | // If the value contains a comma, wrap it in double quotes and escape any existing double quotes
85 | if (value.includes(',')) {
86 | return `"${value.replace(/"/g, '""')}"`;
87 | }
88 | return value;
89 | }
90 |
91 | private async savePolicyFile(text: string): Promise {
92 | (this.fs ? this.fs : mustGetDefaultFileSystem()).writeFileSync(this.filePath, text);
93 | }
94 |
95 | /**
96 | * addPolicy adds a policy rule to the storage.
97 | */
98 | public async addPolicy(sec: string, ptype: string, rule: string[]): Promise {
99 | throw new Error('not implemented');
100 | }
101 | /**
102 | * addPolicies adds policy rules to the storage.
103 | This is part of the Auto-Save feature.
104 | */
105 | public async addPolicies(sec: string, ptype: string, rules: string[][]): Promise {
106 | throw new Error('not implemented');
107 | }
108 |
109 | /**
110 | * UpdatePolicy updates a policy rule from storage.
111 | * This is part of the Auto-Save feature.
112 | */
113 | updatePolicy(sec: string, ptype: string, oldRule: string[], newRule: string[]): Promise {
114 | throw new Error('not implemented');
115 | }
116 |
117 | /**
118 | * removePolicy removes a policy rule from the storage.
119 | */
120 | public async removePolicy(sec: string, ptype: string, rule: string[]): Promise {
121 | throw new Error('not implemented');
122 | }
123 |
124 | /**
125 | * removePolicies removes policy rules from the storage.
126 | * This is part of the Auto-Save feature.
127 | */
128 | public async removePolicies(sec: string, ptype: string, rules: string[][]): Promise {
129 | throw new Error('not implemented');
130 | }
131 |
132 | /**
133 | * removeFilteredPolicy removes policy rules that match the filter from the storage.
134 | */
135 | public async removeFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise {
136 | throw new Error('not implemented');
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/persist/fileSystem.ts:
--------------------------------------------------------------------------------
1 | export interface FileSystem {
2 | readFileSync(path: string, encoding?: string): Buffer | string;
3 | writeFileSync(path: string, text: string, encoding?: string): void;
4 | }
5 |
6 | let defaultFileSystem: FileSystem | undefined = undefined;
7 | const ErrorNoFileSystem = new Error('please set the default FileSystem by call the setDefaultFileSystem');
8 | export const setDefaultFileSystem = (fs?: FileSystem): void => {
9 | defaultFileSystem = fs;
10 | };
11 | export const getDefaultFileSystem = (): FileSystem | undefined => defaultFileSystem;
12 | export const mustGetDefaultFileSystem = (): FileSystem => {
13 | if (defaultFileSystem) {
14 | return defaultFileSystem;
15 | }
16 | throw ErrorNoFileSystem;
17 | };
18 |
--------------------------------------------------------------------------------
/src/persist/filteredAdapter.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '../model';
2 | import { Adapter } from './adapter';
3 |
4 | export interface FilteredAdapter extends Adapter {
5 | // loadFilteredPolicy loads only policy rules that match the filter.
6 | loadFilteredPolicy(model: Model, filter: any): Promise;
7 | // isFiltered returns true if the loaded policy has been filtered.
8 | isFiltered(): boolean;
9 | }
10 |
--------------------------------------------------------------------------------
/src/persist/helper.ts:
--------------------------------------------------------------------------------
1 | import { Model } from '../model';
2 | import { parse } from 'csv-parse/sync';
3 |
4 | export interface IPolicyParser {
5 | parse(line: string): string[][] | null;
6 | }
7 |
8 | export class BasicCsvParser implements IPolicyParser {
9 | parse(line: string): string[][] | null {
10 | if (!line || line.trimStart().charAt(0) === '#') {
11 | return null;
12 | }
13 |
14 | return parse(line, {
15 | delimiter: ',',
16 | skip_empty_lines: true,
17 | trim: true,
18 | relax_quotes: true,
19 | });
20 | }
21 | }
22 |
23 | export class BracketAwareCsvParser implements IPolicyParser {
24 | private readonly baseParser: IPolicyParser;
25 |
26 | constructor(baseParser: IPolicyParser = new BasicCsvParser()) {
27 | this.baseParser = baseParser;
28 | }
29 |
30 | parse(line: string): string[][] | null {
31 | const rawTokens = this.baseParser.parse(line);
32 | if (!rawTokens || !rawTokens[0]) {
33 | return null;
34 | }
35 |
36 | const tokens = rawTokens[0];
37 | const processedTokens: string[] = [];
38 | let currentToken = '';
39 | let bracketCount = 0;
40 |
41 | for (const token of tokens) {
42 | for (const char of token) {
43 | if (char === '(') bracketCount++;
44 | else if (char === ')') bracketCount--;
45 | }
46 |
47 | currentToken += (currentToken ? ',' : '') + token;
48 |
49 | if (bracketCount === 0) {
50 | processedTokens.push(currentToken);
51 | currentToken = '';
52 | }
53 | }
54 |
55 | if (bracketCount !== 0) {
56 | throw new Error(`Unmatched brackets in policy line: ${line}`);
57 | }
58 |
59 | return processedTokens.length > 0 ? [processedTokens] : null;
60 | }
61 | }
62 |
63 | export class PolicyLoader {
64 | private readonly parser: IPolicyParser;
65 |
66 | constructor(parser: IPolicyParser = new BracketAwareCsvParser()) {
67 | this.parser = parser;
68 | }
69 |
70 | loadPolicyLine(line: string, model: Model): void {
71 | const tokens = this.parser.parse(line);
72 | if (!tokens || !tokens[0]) {
73 | return;
74 | }
75 |
76 | let key = tokens[0][0].trim();
77 | if (key.startsWith('"') && key.endsWith('"')) {
78 | key = key.slice(1, -1);
79 | }
80 |
81 | const sec = key.substring(0, 1);
82 | const item = model.model.get(sec);
83 | if (!item) {
84 | return;
85 | }
86 |
87 | const policy = item.get(key);
88 | if (!policy) {
89 | return;
90 | }
91 |
92 | const values = tokens[0].slice(1).map((v) => {
93 | if (v.startsWith('"') && v.endsWith('"')) {
94 | v = v.slice(1, -1);
95 | }
96 | return v.replace(/""/g, '"').trim();
97 | });
98 |
99 | policy.policy.push(values);
100 | }
101 | }
102 |
103 | export class Helper {
104 | private static readonly policyLoader = new PolicyLoader();
105 |
106 | public static loadPolicyLine(line: string, model: Model): void {
107 | Helper.policyLoader.loadPolicyLine(line, model);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/persist/index.ts:
--------------------------------------------------------------------------------
1 | export * from './adapter';
2 | export * from './fileAdapter';
3 | export * from './stringAdapter';
4 | export * from './helper';
5 | export * from './watcher';
6 | export * from './watcherEx';
7 | export * from './filteredAdapter';
8 | export * from './defaultFilteredAdapter';
9 | export * from './batchAdapter';
10 | export * from './batchFileAdapter';
11 | export * from './updatableAdapter';
12 | export * from './fileSystem';
13 |
--------------------------------------------------------------------------------
/src/persist/stringAdapter.ts:
--------------------------------------------------------------------------------
1 | import { Adapter } from './adapter';
2 | import { Model } from '../model';
3 | import { Helper } from './helper';
4 |
5 | /**
6 | * StringAdapter is the string adapter for Casbin.
7 | * It can load policy from a string.
8 | */
9 | export class StringAdapter implements Adapter {
10 | public readonly policy: string;
11 |
12 | /**
13 | * StringAdapter is the constructor for StringAdapter.
14 | * @param {string} policy policy formatted as a CSV string.
15 | */
16 |
17 | constructor(policy: string) {
18 | this.policy = policy;
19 | }
20 |
21 | public async loadPolicy(model: Model): Promise {
22 | if (!this.policy) {
23 | throw new Error('Invalid policy, policy document cannot be false-y');
24 | }
25 | await this.loadRules(model, Helper.loadPolicyLine);
26 | }
27 |
28 | private async loadRules(model: Model, handler: (line: string, model: Model) => void): Promise {
29 | const rules = this.policy.split('\n');
30 | rules.forEach((n: string, index: number) => {
31 | if (!n) {
32 | return;
33 | }
34 | handler(n, model);
35 | });
36 | }
37 |
38 | /**
39 | * savePolicy saves all policy rules to the storage.
40 | */
41 | public async savePolicy(model: Model): Promise {
42 | throw new Error('not implemented');
43 | }
44 |
45 | /**
46 | * addPolicy adds a policy rule to the storage.
47 | */
48 | public async addPolicy(sec: string, ptype: string, rule: string[]): Promise {
49 | throw new Error('not implemented');
50 | }
51 |
52 | /**
53 | * removePolicy removes a policy rule from the storage.
54 | */
55 | public async removePolicy(sec: string, ptype: string, rule: string[]): Promise {
56 | throw new Error('not implemented');
57 | }
58 |
59 | /**
60 | * removeFilteredPolicy removes policy rules that match the filter from the storage.
61 | */
62 | public async removeFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise {
63 | throw new Error('not implemented');
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/persist/updatableAdapter.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Adapter } from './adapter';
16 |
17 | // UpdatableAdapter is the interface for Casbin adapters with update policy functions.
18 | export interface UpdatableAdapter extends Adapter {
19 | // UpdatePolicy updates a policy rule from storage.
20 | // This is part of the Auto-Save feature.
21 | updatePolicy(sec: string, ptype: string, oldRule: string[], newRule: string[]): Promise;
22 | }
23 |
--------------------------------------------------------------------------------
/src/persist/watcher.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export interface Watcher {
16 | setUpdateCallback(cb: () => void): void;
17 |
18 | update(): Promise;
19 | }
20 |
--------------------------------------------------------------------------------
/src/persist/watcherEx.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2022 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Model } from '../model';
16 |
17 | export interface WatcherEx {
18 | updateForAddPolicy(sec: string, ptype: string, ...params: string[]): Promise;
19 |
20 | updateForRemovePolicy(sec: string, ptype: string, ...params: string[]): Promise;
21 |
22 | updateForRemoveFilteredPolicy(sec: string, ptype: string, fieldIndex: number, ...fieldValues: string[]): Promise;
23 |
24 | updateForSavePolicy(model: Model): Promise;
25 |
26 | updateForAddPolicies(sec: string, ptype: string, ...rules: string[][]): Promise;
27 |
28 | updateForRemovePolicies(sec: string, ptype: string, ...rules: string[][]): Promise;
29 | }
30 |
--------------------------------------------------------------------------------
/src/rbac/defaultRoleManager.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { RoleManager } from './roleManager';
16 | import { getLogger, logPrint } from '../log';
17 |
18 | export type MatchingFunc = (arg1: string, arg2: string) => boolean;
19 |
20 | // DEFAULT_DOMAIN defines the default domain space.
21 | const DEFAULT_DOMAIN = 'casbin::default';
22 |
23 | // loadOrDefault returns the existing value for the key if present.
24 | // Otherwise, it stores and returns the given value.
25 | function loadOrDefault(map: Map, key: K, value: V): V {
26 | const read = map.get(key);
27 | if (read === undefined) {
28 | map.set(key, value);
29 | return value;
30 | }
31 | return read;
32 | }
33 |
34 | /**
35 | * Role represents the data structure for a role in RBAC.
36 | */
37 | class Role {
38 | public name: string;
39 | private roles: Role[];
40 |
41 | constructor(name: string) {
42 | this.name = name;
43 | this.roles = [];
44 | }
45 |
46 | public addRole(role: Role): void {
47 | if (this.roles.some((n) => n.name === role.name)) {
48 | return;
49 | }
50 | this.roles.push(role);
51 | }
52 |
53 | public deleteRole(role: Role): void {
54 | this.roles = this.roles.filter((n) => n.name !== role.name);
55 | }
56 |
57 | public hasRole(name: string, hierarchyLevel: number): boolean {
58 | if (this.name === name) {
59 | return true;
60 | }
61 | if (hierarchyLevel <= 0) {
62 | return false;
63 | }
64 | for (const role of this.roles) {
65 | if (role.hasRole(name, hierarchyLevel - 1)) {
66 | return true;
67 | }
68 | }
69 |
70 | return false;
71 | }
72 |
73 | public hasDirectRole(name: string): boolean {
74 | return this.roles.some((n) => n.name === name);
75 | }
76 |
77 | public toString(): string {
78 | return this.name + this.roles.join(', ');
79 | }
80 |
81 | public getRoles(): string[] {
82 | return this.roles.map((n) => n.name);
83 | }
84 | }
85 |
86 | class Roles extends Map {
87 | constructor() {
88 | super();
89 | }
90 |
91 | public hasRole(name: string, matchingFunc?: MatchingFunc): boolean {
92 | let ok = false;
93 | if (matchingFunc) {
94 | this.forEach((value, key) => {
95 | if (matchingFunc(name, key)) {
96 | ok = true;
97 | }
98 | });
99 | } else {
100 | return this.has(name);
101 | }
102 | return ok;
103 | }
104 |
105 | public createRole(name: string, matchingFunc?: MatchingFunc): Role {
106 | const role = loadOrDefault(this, name, new Role(name));
107 | if (matchingFunc) {
108 | this.forEach((value, key) => {
109 | if (matchingFunc(name, key) && name !== key) {
110 | // Add new role to matching role
111 | const role1 = loadOrDefault(this, key, new Role(key));
112 | role.addRole(role1);
113 | }
114 | });
115 | }
116 | return role;
117 | }
118 | }
119 |
120 | // RoleManager provides a default implementation for the RoleManager interface
121 | export class DefaultRoleManager implements RoleManager {
122 | private allDomains: Map;
123 | private maxHierarchyLevel: number;
124 | private hasPattern = false;
125 | private hasDomainPattern = false;
126 | private hasDomainHierarchy = false;
127 | private domainHierarchyManager: RoleManager;
128 | private matchingFunc: MatchingFunc;
129 | private domainMatchingFunc: MatchingFunc;
130 |
131 | /**
132 | * DefaultRoleManager is the constructor for creating an instance of the
133 | * default RoleManager implementation.
134 | *
135 | * @param maxHierarchyLevel the maximized allowed RBAC hierarchy level.
136 | */
137 | constructor(maxHierarchyLevel: number) {
138 | this.allDomains = new Map();
139 | this.allDomains.set(DEFAULT_DOMAIN, new Roles());
140 | this.maxHierarchyLevel = maxHierarchyLevel;
141 | }
142 |
143 | /**
144 | * addMatchingFunc support use pattern in g
145 | * @param name name
146 | * @param fn matching function
147 | * @deprecated
148 | */
149 | public async addMatchingFunc(name: string, fn: MatchingFunc): Promise;
150 |
151 | /**
152 | * addMatchingFunc support use pattern in g
153 | * @param fn matching function
154 | */
155 | public async addMatchingFunc(fn: MatchingFunc): Promise;
156 |
157 | /**
158 | * addMatchingFunc support use pattern in g
159 | * @param name name
160 | * @param fn matching function
161 | * @deprecated
162 | */
163 | public async addMatchingFunc(name: string | MatchingFunc, fn?: MatchingFunc): Promise {
164 | this.hasPattern = true;
165 | if (typeof name === 'string' && fn) {
166 | this.matchingFunc = fn;
167 | } else if (typeof name === 'function') {
168 | this.matchingFunc = name;
169 | } else {
170 | throw new Error('error: domain should be 1 parameter');
171 | }
172 | }
173 |
174 | /**
175 | * addDomainMatchingFunc support use domain pattern in g
176 | * @param fn domain matching function
177 | * ```
178 | */
179 | public async addDomainMatchingFunc(fn: MatchingFunc): Promise {
180 | this.hasDomainPattern = true;
181 | this.domainMatchingFunc = fn;
182 | }
183 |
184 | /**
185 | * addDomainHierarchy sets a rolemanager to define role inheritance between domains
186 | * @param rm RoleManager to define domain hierarchy
187 | */
188 | public async addDomainHierarchy(rm: RoleManager): Promise {
189 | if (!rm?.syncedHasLink) throw Error('Domain hierarchy must be syncronous.');
190 | this.hasDomainHierarchy = true;
191 | this.domainHierarchyManager = rm;
192 | }
193 |
194 | private generateTempRoles(domain: string): Roles {
195 | if (!this.hasPattern && !this.hasDomainPattern && !this.hasDomainHierarchy) {
196 | return loadOrDefault(this.allDomains, domain, new Roles());
197 | }
198 |
199 | const extraDomain = new Set([domain]);
200 | if (this.hasDomainPattern || this.hasDomainHierarchy) {
201 | this.allDomains.forEach((value, key) => {
202 | if (
203 | (this.hasDomainPattern && this.domainMatchingFunc(domain, key)) ||
204 | (this.domainHierarchyManager?.syncedHasLink && this.domainHierarchyManager.syncedHasLink(key, domain))
205 | ) {
206 | extraDomain.add(key);
207 | }
208 | });
209 | }
210 |
211 | const allRoles = new Roles();
212 | extraDomain.forEach((dom) => {
213 | loadOrDefault(this.allDomains, dom, new Roles()).forEach((value, key) => {
214 | const role1 = allRoles.createRole(value.name, this.matchingFunc);
215 | value.getRoles().forEach((n) => {
216 | role1.addRole(allRoles.createRole(n, this.matchingFunc));
217 | });
218 | });
219 | });
220 | return allRoles;
221 | }
222 |
223 | /**
224 | * addLink adds the inheritance link between role: name1 and role: name2.
225 | * aka role: name1 inherits role: name2.
226 | * domain is a prefix to the roles.
227 | */
228 | public async addLink(name1: string, name2: string, ...domain: string[]): Promise {
229 | if (domain.length === 0) {
230 | domain = [DEFAULT_DOMAIN];
231 | } else if (domain.length > 1) {
232 | throw new Error('error: domain should be 1 parameter');
233 | }
234 |
235 | const allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
236 |
237 | const role1 = loadOrDefault(allRoles, name1, new Role(name1));
238 | const role2 = loadOrDefault(allRoles, name2, new Role(name2));
239 | role1.addRole(role2);
240 | }
241 |
242 | /**
243 | * clear clears all stored data and resets the role manager to the initial state.
244 | */
245 | public async clear(): Promise {
246 | this.allDomains = new Map();
247 | this.allDomains.set(DEFAULT_DOMAIN, new Roles());
248 | }
249 |
250 | /**
251 | * deleteLink deletes the inheritance link between role: name1 and role: name2.
252 | * aka role: name1 does not inherit role: name2 any more.
253 | * domain is a prefix to the roles.
254 | */
255 | public async deleteLink(name1: string, name2: string, ...domain: string[]): Promise {
256 | if (domain.length === 0) {
257 | domain = [DEFAULT_DOMAIN];
258 | } else if (domain.length > 1) {
259 | throw new Error('error: domain should be 1 parameter');
260 | }
261 |
262 | const allRoles = loadOrDefault(this.allDomains, domain[0], new Roles());
263 |
264 | if (!allRoles.has(name1) || !allRoles.has(name2)) {
265 | return;
266 | }
267 |
268 | const role1 = loadOrDefault(allRoles, name1, new Role(name1));
269 | const role2 = loadOrDefault(allRoles, name2, new Role(name2));
270 | role1.deleteRole(role2);
271 | }
272 |
273 | /**
274 | * hasLink determines whether role: name1 inherits role: name2.
275 | * domain is a prefix to the roles.
276 | */
277 | public syncedHasLink(name1: string, name2: string, ...domain: string[]): boolean {
278 | if (domain.length === 0) {
279 | domain = [DEFAULT_DOMAIN];
280 | } else if (domain.length > 1) {
281 | throw new Error('error: domain should be 1 parameter');
282 | }
283 |
284 | if (name1 === name2) {
285 | return true;
286 | }
287 |
288 | const allRoles = this.generateTempRoles(domain[0]);
289 |
290 | if (!allRoles.hasRole(name1, this.matchingFunc) || !allRoles.hasRole(name2, this.matchingFunc)) {
291 | return false;
292 | }
293 |
294 | const role1 = allRoles.createRole(name1, this.matchingFunc);
295 | return role1.hasRole(name2, this.maxHierarchyLevel);
296 | }
297 |
298 | public async hasLink(name1: string, name2: string, ...domain: string[]): Promise {
299 | return new Promise((resolve) => resolve(this.syncedHasLink(name1, name2, ...domain)));
300 | }
301 |
302 | /**
303 | * getRoles gets the roles that a subject inherits.
304 | * domain is a prefix to the roles.
305 | */
306 | public async getRoles(name: string, ...domain: string[]): Promise {
307 | if (domain.length === 0) {
308 | domain = [DEFAULT_DOMAIN];
309 | } else if (domain.length > 1) {
310 | throw new Error('error: domain should be 1 parameter');
311 | }
312 |
313 | const allRoles = this.generateTempRoles(domain[0]);
314 |
315 | if (!allRoles.hasRole(name, this.matchingFunc)) {
316 | return [];
317 | }
318 |
319 | return allRoles.createRole(name, this.matchingFunc).getRoles();
320 | }
321 |
322 | /**
323 | * getUsers gets the users that inherits a subject.
324 | * domain is an unreferenced parameter here, may be used in other implementations.
325 | */
326 | public async getUsers(name: string, ...domain: string[]): Promise {
327 | if (domain.length === 0) {
328 | domain = [DEFAULT_DOMAIN];
329 | } else if (domain.length > 1) {
330 | throw new Error('error: domain should be 1 parameter');
331 | }
332 |
333 | const allRoles = this.generateTempRoles(domain[0]);
334 |
335 | if (!allRoles.hasRole(name, this.matchingFunc)) {
336 | return [];
337 | }
338 | const users = [];
339 | for (const user of allRoles.values()) {
340 | if (user.hasDirectRole(name)) users.push(user.name);
341 | }
342 | return users;
343 | }
344 |
345 | /**
346 | * printRoles prints all the roles to log.
347 | */
348 | public async printRoles(): Promise {
349 | if (getLogger().isEnable()) {
350 | [...this.allDomains.values()].forEach((n) => {
351 | logPrint(n.toString());
352 | });
353 | }
354 | }
355 | }
356 |
--------------------------------------------------------------------------------
/src/rbac/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export * from './defaultRoleManager';
16 | export * from './roleManager';
17 |
--------------------------------------------------------------------------------
/src/rbac/roleManager.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // RoleManager provides interface to define the operations for managing roles.
16 | export interface RoleManager {
17 | // Clear clears all stored data and resets the role manager to the initial state.
18 | clear(): Promise;
19 | // AddLink adds the inheritance link between two roles. role: name1 and role: name2.
20 | // domain is a prefix to the roles (can be used for other purposes).
21 | addLink(name1: string, name2: string, ...domain: string[]): Promise;
22 | // DeleteLink deletes the inheritance link between two roles. role: name1 and role: name2.
23 | // domain is a prefix to the roles (can be used for other purposes).
24 | deleteLink(name1: string, name2: string, ...domain: string[]): Promise;
25 | // HasLink determines whether a link exists between two roles. role: name1 inherits role: name2.
26 | // domain is a prefix to the roles (can be used for other purposes).
27 | hasLink(name1: string, name2: string, ...domain: string[]): Promise;
28 | // syncedHasLink is same as hasLink, but not wrapped in promise. Should not be called
29 | // if the matchers contain an asynchronous method. Can increase performance.
30 | syncedHasLink?(name1: string, name2: string, ...domain: string[]): boolean;
31 | // GetRoles gets the roles that a user inherits.
32 | // domain is a prefix to the roles (can be used for other purposes).
33 | getRoles(name: string, ...domain: string[]): Promise;
34 | // GetUsers gets the users that inherits a role.
35 | // domain is a prefix to the users (can be used for other purposes).
36 | getUsers(name: string, ...domain: string[]): Promise;
37 | // PrintRoles prints all the roles to log.
38 | printRoles(): Promise;
39 | }
40 |
--------------------------------------------------------------------------------
/src/util/builtinOperators.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the 'License');
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an 'AS IS' BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import * as rbac from '../rbac';
16 | import { ip } from './ip';
17 | import { minimatch } from 'minimatch';
18 |
19 | // regexMatch determines whether key1 matches the pattern of key2 in regular expression.
20 | function regexMatch(key1: string, key2: string): boolean {
21 | return new RegExp(key2).test(key1);
22 | }
23 |
24 | // keyMatch determines whether key1 matches the pattern of key2 (similar to RESTful path),
25 | // key2 can contain a *.
26 | // For example, '/foo/bar' matches '/foo/*'
27 | function keyMatch(key1: string, key2: string): boolean {
28 | const pos: number = key2.indexOf('*');
29 | if (pos === -1) {
30 | return key1 === key2;
31 | }
32 |
33 | if (key1.length > pos) {
34 | return key1.slice(0, pos) === key2.slice(0, pos);
35 | }
36 |
37 | return key1 === key2.slice(0, pos);
38 | }
39 |
40 | // keyMatchFunc is the wrapper for keyMatch.
41 | function keyMatchFunc(...args: any[]): boolean {
42 | const [arg0, arg1] = args;
43 | const name1: string = (arg0 || '').toString();
44 | const name2: string = (arg1 || '').toString();
45 |
46 | return keyMatch(name1, name2);
47 | }
48 |
49 | // KeyGet returns the matched part
50 | // For example, "/foo/bar/foo" matches "/foo/*"
51 | // "bar/foo" will been returned
52 | function keyGet(key1: string, key2: string): string {
53 | const pos: number = key2.indexOf('*');
54 | if (pos === -1) {
55 | return '';
56 | }
57 | if (key1.length > pos) {
58 | if (key1.slice(0, pos) === key2.slice(0, pos)) {
59 | return key1.slice(pos, key1.length);
60 | }
61 | }
62 | return '';
63 | }
64 |
65 | // keyGetFunc is the wrapper for keyGet.
66 | function keyGetFunc(...args: any[]): string {
67 | const [arg0, arg1] = args;
68 | const name1: string = (arg0 || '').toString();
69 | const name2: string = (arg1 || '').toString();
70 |
71 | return keyGet(name1, name2);
72 | }
73 |
74 | // keyMatch2 determines whether key1 matches the pattern of key2 (similar to RESTful path),
75 | // key2 can contain a *.
76 | // For example, '/foo/bar' matches '/foo/*', '/resource1' matches '/:resource'
77 | function keyMatch2(key1: string, key2: string): boolean {
78 | key2 = key2.replace(/\/\*/g, '/.*');
79 |
80 | const regexp = new RegExp(/(.*):[^/]+(.*)/g);
81 | for (;;) {
82 | if (!key2.includes('/:')) {
83 | break;
84 | }
85 | key2 = key2.replace(regexp, '$1[^/]+$2');
86 | }
87 |
88 | if (key2 === '*') {
89 | key2 = '(.*)';
90 | }
91 | return regexMatch(key1, '^' + key2 + '$');
92 | }
93 |
94 | // keyMatch2Func is the wrapper for keyMatch2.
95 | function keyMatch2Func(...args: any[]): boolean {
96 | const [arg0, arg1] = args;
97 | const name1: string = (arg0 || '').toString();
98 | const name2: string = (arg1 || '').toString();
99 |
100 | return keyMatch2(name1, name2);
101 | }
102 |
103 | // KeyGet2 returns value matched pattern
104 | // For example, "/resource1" matches "/:resource"
105 | // if the pathVar == "resource", then "resource1" will be returned
106 | function keyGet2(key1: string, key2: string, pathVar: string): string {
107 | if (keyMatch2(key1, key2)) {
108 | const re = new RegExp('[^/]+', 'g');
109 | const keys = key2.match(re);
110 | const values = key1.match(re);
111 | if (!keys || !values) {
112 | return '';
113 | }
114 | const index = keys.indexOf(`:${pathVar}`);
115 | if (index === -1) {
116 | return '';
117 | }
118 | return values[index];
119 | } else {
120 | return '';
121 | }
122 | }
123 |
124 | function keyGet2Func(...args: any[]): string {
125 | const [arg0, arg1, arg2] = args;
126 | const name1: string = (arg0 || '').toString();
127 | const name2: string = (arg1 || '').toString();
128 | const name3: string = (arg2 || '').toString();
129 |
130 | return keyGet2(name1, name2, name3);
131 | }
132 |
133 | // keyMatch3 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
134 | // For example, '/foo/bar' matches '/foo/*', '/resource1' matches '/{resource}'
135 | function keyMatch3(key1: string, key2: string): boolean {
136 | key2 = key2.replace(/\/\*/g, '/.*');
137 |
138 | const regexp = new RegExp(/(.*){[^/]+}(.*)/g);
139 | for (;;) {
140 | if (!key2.includes('/{')) {
141 | break;
142 | }
143 | key2 = key2.replace(regexp, '$1[^/]+$2');
144 | }
145 |
146 | return regexMatch(key1, '^' + key2 + '$');
147 | }
148 |
149 | // keyMatch3Func is the wrapper for keyMatch3.
150 | function keyMatch3Func(...args: any[]): boolean {
151 | const [arg0, arg1] = args;
152 | const name1: string = (arg0 || '').toString();
153 | const name2: string = (arg1 || '').toString();
154 |
155 | return keyMatch3(name1, name2);
156 | }
157 |
158 | // keyMatch4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
159 | // Besides what keyMatch3 does, keyMatch4 can also match repeated patterns:
160 | // "/parent/123/child/123" matches "/parent/{id}/child/{id}"
161 | // "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
162 | // But keyMatch3 will match both.
163 | function keyMatch4(key1: string, key2: string): boolean {
164 | key2 = key2.replace(/\/\*/g, '/.*');
165 |
166 | const tokens: string[] = [];
167 | let j = -1;
168 | for (let i = 0; i < key2.length; i++) {
169 | const c = key2.charAt(i);
170 | if (c === '{') {
171 | j = i;
172 | } else if (c === '}') {
173 | tokens.push(key2.substring(j, i + 1));
174 | }
175 | }
176 |
177 | let regexp = new RegExp(/(.*){[^/]+}(.*)/g);
178 |
179 | for (;;) {
180 | if (!key2.includes('/{')) {
181 | break;
182 | }
183 | key2 = key2.replace(regexp, '$1([^/]+)$2');
184 | }
185 |
186 | regexp = new RegExp('^' + key2 + '$');
187 |
188 | let values: RegExpExecArray | null | string[] = regexp.exec(key1);
189 |
190 | if (!values) {
191 | return false;
192 | }
193 |
194 | values = values.slice(1);
195 |
196 | if (tokens.length !== values.length) {
197 | throw new Error('KeyMatch4: number of tokens is not equal to number of values');
198 | }
199 |
200 | const m = new Map();
201 | tokens.forEach((n, index) => {
202 | const key = tokens[index];
203 | let v = m.get(key);
204 | if (!v) {
205 | v = [];
206 | }
207 |
208 | if (values) {
209 | v.push(values[index]);
210 | }
211 | m.set(key, v);
212 | });
213 |
214 | for (const value of m.values()) {
215 | if (value.length > 1) {
216 | for (let i = 1; i < values.length; i++) {
217 | if (values[i] !== values[0]) {
218 | return false;
219 | }
220 | }
221 | }
222 | }
223 |
224 | return true;
225 | }
226 |
227 | // keyMatch4Func is the wrapper for keyMatch4.
228 | function keyMatch4Func(...args: any[]): boolean {
229 | const [arg0, arg1] = args;
230 | const name1: string = (arg0 || '').toString();
231 | const name2: string = (arg1 || '').toString();
232 |
233 | return keyMatch4(name1, name2);
234 | }
235 |
236 | // KeyMatch determines whether key1 matches the pattern of key2 and ignores the parameters in key2.
237 | // For example, "/foo/bar?status=1&type=2" matches "/foo/bar"
238 | function KeyMatch5(key1: string, key2: string): boolean {
239 | const i: number = key1.indexOf('?');
240 | if (i !== -1) {
241 | key1 = key1.slice(0, i);
242 | }
243 |
244 | key2 = key2.replace(/\/\*/g, '/.*');
245 |
246 | const regexp = new RegExp(/(.*){[^/]+}(.*)/g);
247 | for (;;) {
248 | if (!key2.includes('/{')) {
249 | break;
250 | }
251 | key2 = key2.replace(regexp, '$1[^/]+$2');
252 | }
253 |
254 | return regexMatch(key1, '^' + key2 + '$');
255 | }
256 |
257 | // keyMatch5Func is the wrapper for KeyMatch5.
258 | function keyMatch5Func(...args: any[]): boolean {
259 | const [arg0, arg1] = args;
260 | const name1: string = (arg0 || '').toString();
261 | const name2: string = (arg1 || '').toString();
262 |
263 | return KeyMatch5(name1, name2);
264 | }
265 |
266 | // regexMatchFunc is the wrapper for regexMatch.
267 | function regexMatchFunc(...args: any[]): boolean {
268 | const [arg0, arg1] = args;
269 | const name1: string = (arg0 || '').toString();
270 | const name2: string = (arg1 || '').toString();
271 |
272 | return regexMatch(name1, name2);
273 | }
274 |
275 | // ipMatch determines whether IP address ip1 matches the pattern of IP address ip2,
276 | // ip2 can be an IP address or a CIDR pattern.
277 | // For example, '192.168.2.123' matches '192.168.2.0/24'
278 | function ipMatch(ip1: string, ip2: string): boolean {
279 | // check ip1
280 | if (!(ip.isV4Format(ip1) || ip.isV6Format(ip1))) {
281 | throw new Error('invalid argument: ip1 in ipMatch() function is not an IP address.');
282 | }
283 | // check ip2
284 | const cidrParts: string[] = ip2.split('/');
285 | if (cidrParts.length === 2) {
286 | return ip.cidrSubnet(ip2).contains(ip1);
287 | } else {
288 | if (!(ip.isV4Format(ip2) || ip.isV6Format(ip2))) {
289 | console.log(ip2);
290 | throw new Error('invalid argument: ip2 in ipMatch() function is not an IP address.');
291 | }
292 | return ip.isEqual(ip1, ip2);
293 | }
294 | }
295 |
296 | // ipMatchFunc is the wrapper for ipMatch.
297 | function ipMatchFunc(...args: any[]): boolean {
298 | const [arg0, arg1] = args;
299 | const ip1: string = (arg0 || '').toString();
300 | const ip2: string = (arg1 || '').toString();
301 |
302 | return ipMatch(ip1, ip2);
303 | }
304 |
305 | /**
306 | * Returns true if the specified `string` matches the given glob `pattern`.
307 | *
308 | * @param string String to match
309 | * @param pattern Glob pattern to use for matching.
310 | * @returns Returns true if the string matches the glob pattern.
311 | *
312 | * @example
313 | * ```javascript
314 | * globMatch("abc.conf", "*.conf") => true
315 | * ```
316 | */
317 | function globMatch(string: string, pattern: string): boolean {
318 | // The minimatch doesn't support the pattern starts with *
319 | // See https://github.com/isaacs/minimatch/issues/195
320 | if (pattern[0] === '*' && pattern[1] === '/') {
321 | pattern = pattern.substring(1);
322 | }
323 | return minimatch(string, pattern);
324 | }
325 |
326 | // generateGFunction is the factory method of the g(_, _) function.
327 | function generateGFunction(rm: rbac.RoleManager): any {
328 | const memorized = new Map();
329 | return async function (...args: any[]): Promise {
330 | const key = args.toString();
331 | let value = memorized.get(key);
332 | if (value) {
333 | return value;
334 | }
335 |
336 | const [arg0, arg1] = args;
337 | const name1: string = (arg0 || '').toString();
338 | const name2: string = (arg1 || '').toString();
339 |
340 | if (!rm) {
341 | value = name1 === name2;
342 | } else if (args.length === 2) {
343 | value = await rm.hasLink(name1, name2);
344 | } else {
345 | const domain: string = args[2].toString();
346 | value = await rm.hasLink(name1, name2, domain);
347 | }
348 |
349 | memorized.set(key, value);
350 | return value;
351 | };
352 | }
353 |
354 | // generateSyncedGFunction is the synchronous factory method of the g(_, _) function.
355 | function generateSyncedGFunction(rm: rbac.RoleManager): any {
356 | const memorized = new Map();
357 | return function (...args: any[]): boolean {
358 | const key = args.toString();
359 | let value = memorized.get(key);
360 | if (value) {
361 | return value;
362 | }
363 |
364 | const [arg0, arg1] = args;
365 | const name1: string = (arg0 || '').toString();
366 | const name2: string = (arg1 || '').toString();
367 |
368 | if (!rm) {
369 | value = name1 === name2;
370 | } else if (!rm?.syncedHasLink) {
371 | throw new Error('RoleManager requires syncedHasLink for synchronous execution');
372 | } else if (args.length === 2) {
373 | value = rm.syncedHasLink(name1, name2);
374 | } else {
375 | const domain: string = args[2].toString();
376 | value = rm.syncedHasLink(name1, name2, domain);
377 | }
378 |
379 | memorized.set(key, value);
380 | return value;
381 | };
382 | }
383 |
384 | export {
385 | keyMatchFunc,
386 | keyGetFunc,
387 | keyMatch2Func,
388 | keyGet2Func,
389 | keyMatch3Func,
390 | regexMatchFunc,
391 | ipMatchFunc,
392 | generateSyncedGFunction,
393 | generateGFunction,
394 | keyMatch4Func,
395 | keyMatch5Func,
396 | globMatch,
397 | };
398 |
--------------------------------------------------------------------------------
/src/util/index.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | export * from './builtinOperators';
16 | export * from './util';
17 |
--------------------------------------------------------------------------------
/src/util/ip.ts:
--------------------------------------------------------------------------------
1 | // This is a minimal subset of node-ip for handling IPMatch
2 | // https://github.com/indutny/node-ip/blob/master/lib/ip.js
3 | //
4 | // ### License
5 | //
6 | // This software is licensed under the MIT License.
7 | //
8 | // Copyright Fedor Indutny, 2012.
9 | //
10 | // Permission is hereby granted, free of charge, to any person obtaining a
11 | // copy of this software and associated documentation files (the
12 | // "Software"), to deal in the Software without restriction, including
13 | // without limitation the rights to use, copy, modify, merge, publish,
14 | // distribute, sublicense, and/or sell copies of the Software, and to permit
15 | // persons to whom the Software is furnished to do so, subject to the
16 | // following conditions:
17 | //
18 | // The above copyright notice and this permission notice shall be included
19 | // in all copies or substantial portions of the Software.
20 | //
21 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
24 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
25 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
26 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
27 | // USE OR OTHER DEALINGS IN THE SOFTWARE.
28 |
29 | import { Buffer } from 'buffer/';
30 |
31 | const ipv4Regex = /^(\d{1,3}\.){3,3}\d{1,3}$/;
32 | const ipv6Regex = /^(::)?(((\d{1,3}\.){3}(\d{1,3}){1})?([0-9a-f]){0,4}:{0,2}){1,8}(::)?$/i;
33 |
34 | export const ip = {
35 | toBuffer: function (ip: string, buff?: Buffer, offset?: number): Buffer {
36 | offset = offset ? offset : 0;
37 |
38 | let result;
39 |
40 | if (this.isV4Format(ip)) {
41 | result = buff || new Buffer(offset + 4);
42 | ip.split(/\./g).map(function (byte) {
43 | offset = offset ? offset : 0;
44 | result[offset++] = parseInt(byte, 10) & 0xff;
45 | });
46 | } else if (this.isV6Format(ip)) {
47 | const sections = ip.split(':', 8);
48 |
49 | let i;
50 | for (i = 0; i < sections.length; i++) {
51 | const isv4 = this.isV4Format(sections[i]);
52 |
53 | let v4Buffer;
54 |
55 | if (isv4) {
56 | v4Buffer = this.toBuffer(sections[i]);
57 | sections[i] = v4Buffer.slice(0, 2).toString('hex');
58 | }
59 |
60 | if (v4Buffer && ++i < 8) {
61 | sections.splice(i, 0, v4Buffer.slice(2, 4).toString('hex'));
62 | }
63 | }
64 |
65 | if (sections[0] === '') {
66 | while (sections.length < 8) sections.unshift('0');
67 | } else if (sections[sections.length - 1] === '') {
68 | while (sections.length < 8) sections.push('0');
69 | } else if (sections.length < 8) {
70 | for (i = 0; i < sections.length && sections[i] !== ''; i++) {}
71 | const argv = [i, 1];
72 | for (i = 9 - sections.length; i > 0; i--) {
73 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
74 | // @ts-ignore
75 | argv.push('0');
76 | }
77 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
78 | // @ts-ignore
79 | // eslint-disable-next-line prefer-spread
80 | sections.splice.apply(sections, argv);
81 | }
82 |
83 | result = buff || new Buffer(offset + 16);
84 | for (i = 0; i < sections.length; i++) {
85 | const word = parseInt(sections[i], 16);
86 | result[offset++] = (word >> 8) & 0xff;
87 | result[offset++] = word & 0xff;
88 | }
89 | }
90 |
91 | if (!result) {
92 | throw Error('Invalid ip address: ' + ip);
93 | }
94 |
95 | return result;
96 | },
97 | toString: function (buff: Buffer, offset?: number, length?: number): string {
98 | offset = offset ? offset : 0;
99 | length = length || buff.length - offset;
100 |
101 | let result = [];
102 | if (length === 4) {
103 | // IPv4
104 | for (let i = 0; i < length; i++) {
105 | result.push(buff[offset + i]);
106 | }
107 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
108 | // @ts-ignore
109 | result = result.join('.');
110 | } else if (length === 16) {
111 | // IPv6
112 | for (let i = 0; i < length; i += 2) {
113 | result.push(buff.readUInt16BE(offset + i).toString(16));
114 | }
115 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
116 | // @ts-ignore
117 | result = result.join(':');
118 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
119 | // @ts-ignore
120 | result = (result as string).replace(/(^|:)0(:0)*:0(:|$)/, '$1::$3');
121 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
122 | // @ts-ignore
123 | result = (result as string).replace(/:{3,4}/, '::');
124 | }
125 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment
126 | // @ts-ignore
127 | return result as string;
128 | },
129 | isV4Format: function (ip: string): boolean {
130 | return ipv4Regex.test(ip);
131 | },
132 |
133 | isV6Format: function (ip: string): boolean {
134 | return ipv6Regex.test(ip);
135 | },
136 |
137 | fromPrefixLen: function (prefixlen: number, family?: string): string {
138 | if (prefixlen > 32) {
139 | family = 'ipv6';
140 | } else {
141 | family = _normalizeFamily(typeof family === 'string' ? family : '');
142 | }
143 |
144 | let len = 4;
145 | if (family === 'ipv6') {
146 | len = 16;
147 | }
148 | const buff = new Buffer(len);
149 |
150 | for (let i = 0, n = buff.length; i < n; ++i) {
151 | let bits = 8;
152 | if (prefixlen < 8) {
153 | bits = prefixlen;
154 | }
155 | prefixlen -= bits;
156 |
157 | buff[i] = ~(0xff >> bits) & 0xff;
158 | }
159 |
160 | return ip.toString(buff);
161 | },
162 |
163 | mask: function (addr: string, mask: string): string {
164 | const addrBuffer = ip.toBuffer(addr);
165 | const maskBuffer = ip.toBuffer(mask);
166 |
167 | const result = new Buffer(Math.max(addrBuffer.length, maskBuffer.length));
168 |
169 | let i;
170 | // Same protocol - do bitwise and
171 | if (addrBuffer.length === maskBuffer.length) {
172 | for (i = 0; i < addrBuffer.length; i++) {
173 | result[i] = addrBuffer[i] & maskBuffer[i];
174 | }
175 | } else if (maskBuffer.length === 4) {
176 | // IPv6 address and IPv4 mask
177 | // (Mask low bits)
178 | for (i = 0; i < maskBuffer.length; i++) {
179 | result[i] = addrBuffer[addrBuffer.length - 4 + i] & maskBuffer[i];
180 | }
181 | } else {
182 | // IPv6 mask and IPv4 addr
183 | for (let i = 0; i < result.length - 6; i++) {
184 | result[i] = 0;
185 | }
186 |
187 | // ::ffff:ipv4
188 | result[10] = 0xff;
189 | result[11] = 0xff;
190 | for (i = 0; i < addrBuffer.length; i++) {
191 | result[i + 12] = addrBuffer[i] & maskBuffer[i + 12];
192 | }
193 | i = i + 12;
194 | }
195 | for (; i < result.length; i++) result[i] = 0;
196 |
197 | return ip.toString(result);
198 | },
199 |
200 | subnet: function (addr: string, mask: string): any {
201 | const networkAddress = ip.toLong(ip.mask(addr, mask));
202 |
203 | // Calculate the mask's length.
204 | const maskBuffer = ip.toBuffer(mask);
205 | let maskLength = 0;
206 |
207 | for (let i = 0; i < maskBuffer.length; i++) {
208 | if (maskBuffer[i] === 0xff) {
209 | maskLength += 8;
210 | } else {
211 | let octet = maskBuffer[i] & 0xff;
212 | while (octet) {
213 | octet = (octet << 1) & 0xff;
214 | maskLength++;
215 | }
216 | }
217 | }
218 |
219 | return {
220 | contains: function (other: string) {
221 | return networkAddress === ip.toLong(ip.mask(other, mask));
222 | },
223 | };
224 | },
225 | cidrSubnet: function (cidrString: string): any {
226 | const cidrParts = cidrString.split('/');
227 |
228 | const addr = cidrParts[0];
229 | if (cidrParts.length !== 2) throw new Error('invalid CIDR subnet: ' + addr);
230 |
231 | const mask = ip.fromPrefixLen(parseInt(cidrParts[1], 10));
232 |
233 | return ip.subnet(addr, mask);
234 | },
235 | isEqual: function (a: string, b: string): boolean {
236 | let aBuffer = ip.toBuffer(a);
237 | let bBuffer = ip.toBuffer(b);
238 |
239 | // Same protocol
240 | if (aBuffer.length === bBuffer.length) {
241 | for (let i = 0; i < aBuffer.length; i++) {
242 | if (aBuffer[i] !== bBuffer[i]) return false;
243 | }
244 | return true;
245 | }
246 |
247 | // Swap
248 | if (bBuffer.length === 4) {
249 | const t = bBuffer;
250 | bBuffer = aBuffer;
251 | aBuffer = t;
252 | }
253 |
254 | // a - IPv4, b - IPv6
255 | for (let i = 0; i < 10; i++) {
256 | if (bBuffer[i] !== 0) return false;
257 | }
258 |
259 | const word = bBuffer.readUInt16BE(10);
260 | if (word !== 0 && word !== 0xffff) return false;
261 |
262 | for (let i = 0; i < 4; i++) {
263 | if (aBuffer[i] !== bBuffer[i + 12]) return false;
264 | }
265 |
266 | return true;
267 | },
268 | toLong: function (ip: string): number {
269 | let ipl = 0;
270 | ip.split('.').forEach(function (octet) {
271 | ipl <<= 8;
272 | ipl += parseInt(octet);
273 | });
274 | return ipl >>> 0;
275 | },
276 | fromLong: function (ipl: number): string {
277 | return (ipl >>> 24) + '.' + ((ipl >> 16) & 255) + '.' + ((ipl >> 8) & 255) + '.' + (ipl & 255);
278 | },
279 | };
280 |
281 | function _normalizeFamily(family: string): string {
282 | return family ? family.toLowerCase() : 'ipv4';
283 | }
284 |
--------------------------------------------------------------------------------
/src/util/util.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the 'License');
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an 'AS IS' BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | // escapeAssertion escapes the dots in the assertion,
16 | // because the expression evaluation doesn't support such variable names.
17 |
18 | import { mustGetDefaultFileSystem } from '../persist';
19 |
20 | const escapeAssertionReg = new RegExp(/(^|[^A-Za-z0-9_])([rp])[0-9]*\./g);
21 |
22 | function escapeAssertion(s: string): string {
23 | s = s.replace(escapeAssertionReg, (match, p1, p2) => {
24 | return p1 + p2 + match.substring(p1.length + p2.length).replace('.', '_');
25 | });
26 | return s;
27 | }
28 |
29 | // removeComments removes the comments starting with # in the text.
30 | function removeComments(s: string): string {
31 | const pos = s.indexOf('#');
32 | return pos > -1 ? s.slice(0, pos).trim() : s;
33 | }
34 |
35 | // arrayEquals determines whether two string arrays are identical.
36 | function arrayEquals(a: string[] = [], b: string[] = []): boolean {
37 | const aLen = a.length;
38 | const bLen = b.length;
39 | if (aLen !== bLen) {
40 | return false;
41 | }
42 |
43 | for (let i = 0; i < aLen; i++) {
44 | if (a[i] !== b[i]) {
45 | return false;
46 | }
47 | }
48 | return true;
49 | }
50 |
51 | // array2DEquals determines whether two 2-dimensional string arrays are identical.
52 | function array2DEquals(a: string[][] = [], b: string[][] = []): boolean {
53 | const aLen = a.length;
54 | const bLen = a.length;
55 | if (aLen !== bLen) {
56 | return false;
57 | }
58 |
59 | for (let i = 0; i < aLen; i++) {
60 | if (!arrayEquals(a[i], b[i])) {
61 | return false;
62 | }
63 | }
64 | return true;
65 | }
66 |
67 | // arrayRemoveDuplicates removes any duplicated elements in a string array.
68 | function arrayRemoveDuplicates(s: string[]): string[] {
69 | return [...new Set(s)];
70 | }
71 |
72 | // arrayToString gets a printable string for a string array.
73 | function arrayToString(a: string[]): string {
74 | return a.join(', ');
75 | }
76 |
77 | // paramsToString gets a printable string for variable number of parameters.
78 | function paramsToString(...v: string[]): string {
79 | return v.join(', ');
80 | }
81 |
82 | // setEquals determines whether two string sets are identical.
83 | function setEquals(a: string[], b: string[]): boolean {
84 | return arrayEquals(a.sort(), b.sort());
85 | }
86 |
87 | // readFile return a promise for readFile.
88 | function readFile(path: string, encoding?: string): any {
89 | const fs = mustGetDefaultFileSystem();
90 | return new Promise((resolve, reject) => {
91 | try {
92 | fs.readFileSync(path, encoding || 'utf8');
93 | resolve();
94 | } catch (e) {
95 | reject(e);
96 | }
97 | });
98 | }
99 |
100 | // writeFile return a promise for writeFile.
101 | function writeFile(path: string, file: string, encoding?: string): any {
102 | const fs = mustGetDefaultFileSystem();
103 | return new Promise((resolve, reject) => {
104 | try {
105 | fs.writeFileSync(path, file, encoding || 'utf-8');
106 | resolve();
107 | } catch (e) {
108 | reject(e);
109 | }
110 | });
111 | }
112 |
113 | const evalRegG = new RegExp(/\beval\(([^),]*)\)/g);
114 | const evalReg = new RegExp(/\beval\(([^),]*)\)/);
115 |
116 | // hasEval determine whether matcher contains function eval
117 | function hasEval(s: string): boolean {
118 | return evalReg.test(s);
119 | }
120 |
121 | // replaceEval replace function eval with the value of its parameters
122 | function replaceEval(s: string, ruleName: string, rule: string): string {
123 | return s.replace(`eval(${ruleName})`, '(' + rule + ')');
124 | }
125 |
126 | // getEvalValue returns the parameters of function eval
127 | function getEvalValue(s: string): string[] {
128 | const subMatch = s.match(evalRegG);
129 | const rules: string[] = [];
130 | if (!subMatch) {
131 | return [];
132 | }
133 | for (const rule of subMatch) {
134 | const index: number = rule.indexOf('(');
135 | rules.push(rule.slice(index + 1, -1));
136 | }
137 | return rules;
138 | }
139 |
140 | // generatorRunSync handle generator function in Sync model and return value which is not Promise
141 | function generatorRunSync(iterator: Generator): any {
142 | let { value, done } = iterator.next();
143 | while (true) {
144 | if (value instanceof Promise) {
145 | throw new Error('cannot handle Promise in generatorRunSync, Please use generatorRunAsync');
146 | }
147 | if (!done) {
148 | const temp = value;
149 | ({ value, done } = iterator.next(temp));
150 | } else {
151 | return value;
152 | }
153 | }
154 | }
155 |
156 | // generatorRunAsync handle generator function in Async model and return Promise
157 | async function generatorRunAsync(iterator: Generator): Promise {
158 | let { value, done } = iterator.next();
159 | while (true) {
160 | if (!done) {
161 | const temp = await value;
162 | ({ value, done } = iterator.next(temp));
163 | } else {
164 | return value;
165 | }
166 | }
167 | }
168 |
169 | function deepCopy(obj: Array | any): any {
170 | if (typeof obj !== 'object') return;
171 | const newObj: any = obj instanceof Array ? [] : {};
172 | for (const key in obj) {
173 | if (obj.hasOwnProperty(key)) {
174 | newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
175 | }
176 | }
177 | return newObj;
178 | }
179 |
180 | function customIn(a: number | string, b: number | string): number {
181 | if ((b as any) instanceof Array) {
182 | return (((b as any) as Array).includes(a) as unknown) as number;
183 | }
184 | return ((a in (b as any)) as unknown) as number;
185 | }
186 |
187 | function bracketCompatible(exp: string): string {
188 | // TODO: This function didn't support nested bracket.
189 | if (!(exp.includes(' in ') && exp.includes(' ('))) {
190 | return exp;
191 | }
192 |
193 | const re = / \([^)]*\)/g;
194 | const array = exp.split('');
195 |
196 | let reResult: RegExpExecArray | null;
197 | while ((reResult = re.exec(exp)) !== null) {
198 | if (!(reResult[0] as string).includes(',')) {
199 | continue;
200 | }
201 | array[reResult.index + 1] = '[';
202 | array[re.lastIndex - 1] = ']';
203 | }
204 | exp = array.join('');
205 | return exp;
206 | }
207 |
208 | export {
209 | escapeAssertion,
210 | removeComments,
211 | arrayEquals,
212 | array2DEquals,
213 | arrayRemoveDuplicates,
214 | arrayToString,
215 | paramsToString,
216 | setEquals,
217 | readFile,
218 | writeFile,
219 | hasEval,
220 | replaceEval,
221 | getEvalValue,
222 | generatorRunSync,
223 | generatorRunAsync,
224 | deepCopy,
225 | customIn,
226 | bracketCompatible,
227 | };
228 |
--------------------------------------------------------------------------------
/test/cachedEnforcer.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Enforcer, newCachedEnforcer } from '../src';
16 |
17 | async function testEnforce(e: Enforcer, sub: string, obj: string, act: string, res: boolean): Promise {
18 | await expect(e.enforce(sub, obj, act)).resolves.toBe(res);
19 | }
20 |
21 | test('TestRBACModel', async () => {
22 | const e = await newCachedEnforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');
23 |
24 | await testEnforce(e, 'alice', 'data1', 'read', true);
25 | });
26 |
--------------------------------------------------------------------------------
/test/config/config.test.ts:
--------------------------------------------------------------------------------
1 | import { Config } from '../../src';
2 | import { readFileSync } from 'fs';
3 |
4 | const config = Config.newConfigFromText(readFileSync('test/config/testini.ini').toString());
5 |
6 | describe('multi-line test', () => {
7 | it('should config.get("multi1::name") to equal r.sub==p.sub&&r.obj==p.obj', function () {
8 | expect(config.get('multi1::name')).toEqual('r.sub==p.sub&&r.obj==p.obj');
9 | });
10 |
11 | it('should config.get("multi2::name") to equal r.sub==p.sub&&r.obj==p.obj', function () {
12 | expect(config.get('multi2::name')).toEqual('r.sub==p.sub&&r.obj==p.obj');
13 | });
14 |
15 | it('should config.get("multi3::name") to equal r.sub==p.sub&&r.obj==p.obj', function () {
16 | expect(config.get('multi3::name')).toEqual('r.sub==p.sub&&r.obj==p.obj');
17 | });
18 |
19 | it('should config.get("multi4::name") to equal r.sub==p.sub&&r.obj==p.obj', function () {
20 | expect(config.get('multi4::name')).toEqual('');
21 | });
22 |
23 | it('should config.get("multi5::name") to equal r.sub==p.sub&&r.obj==p.obj', function () {
24 | expect(config.get('multi5::name')).toEqual('r.sub==p.sub&&r.obj==p.obj');
25 | });
26 |
27 | it('should config.get("multi6::name") to equal r.sub==p.sub&&r.obj==p.obj&&r.tex==p.tex', function () {
28 | expect(config.get('multi6::name')).toEqual('r.sub==p.sub&&r.obj==p.obj&&r.tex==p.tex');
29 | });
30 |
31 | it('should config.get("mysql::mysql.master.host") to equal 10.0.0.1', function () {
32 | expect(config.get('mysql::mysql.master.host')).toEqual('10.0.0.1');
33 | });
34 | it('should config.get("mysql::mysql.master.user") to equal root', function () {
35 | expect(config.get('mysql::mysql.master.user')).toEqual('root');
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/test/config/testini.ini:
--------------------------------------------------------------------------------
1 | # test config
2 | debug = true
3 | url = act.wiki
4 |
5 | ; redis config
6 | [redis]
7 | redis.key = push1,push2
8 |
9 | ; mysql config
10 | [mysql]
11 | mysql.dev.host = 127.0.0.1
12 | mysql.dev.user = root
13 | mysql.dev.pass = 123456
14 | mysql.dev.db = test
15 |
16 | mysql.master.host = 10.0.0.1 # host # 10.0.0.1
17 | mysql.master.user = root ; user name
18 | mysql.master.pass = 89dds)2$#d
19 | mysql.master.db = act
20 |
21 | ; math config
22 | [math]
23 | math.i64 = 64
24 | math.f64 = 64.1
25 |
26 | # multi-line test
27 | [multi1]
28 | name = r.sub==p.sub \
29 | &&r.obj==p.obj\
30 | \
31 | [multi2]
32 | name = r.sub==p.sub \
33 | &&r.obj==p.obj
34 |
35 | [multi3]
36 | name = r.sub==p.sub \
37 | &&r.obj==p.obj
38 |
39 | [multi4]
40 | name = \
41 | \
42 | \
43 |
44 | [multi5]
45 | name = r.sub==p.sub \
46 | &&r.obj==p.obj \
47 | \
48 |
49 | [multi6]
50 | name = r.sub==p.sub \
51 | &&r.obj==p.obj \
52 | &&r.tex==p.tex
--------------------------------------------------------------------------------
/test/frontend.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2020 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { readFileSync } from 'fs';
16 | import { newEnforcer } from '../src';
17 | import { casbinJsGetPermissionForUser } from '../src';
18 |
19 | test('TestCasbinJsGetPermissionForUser', async () => {
20 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
21 | const a = await casbinJsGetPermissionForUser(e, 'alice');
22 | const b = await casbinJsGetPermissionForUser(e, 'alice');
23 | if (a !== b) {
24 | throw new Error('Unexpected side affect.');
25 | }
26 | const received = JSON.parse(await casbinJsGetPermissionForUser(e, 'alice'));
27 | let expectedModelStr = readFileSync('examples/rbac_model.conf').toString();
28 |
29 | // avoid the impact of line breaks changing to CRLF, when automatic conversion is enabled
30 | expectedModelStr = expectedModelStr.replace(/\r\n/g, '\n');
31 |
32 | expect(received['m']).toBe(expectedModelStr.replace(/\n\n/g, '\n'));
33 | const expectedPoliciesStr = readFileSync('examples/rbac_with_hierarchy_policy.csv').toString();
34 |
35 | let expectedPolicyItem = expectedPoliciesStr.split(RegExp(',|\n'));
36 | expectedPolicyItem = expectedPolicyItem.filter((item) => item !== null && item.trim() !== '');
37 |
38 | let i = 0;
39 | for (const sArr of received['p']) {
40 | for (const s of sArr) {
41 | expect(s.trim()).toEqual(expectedPolicyItem[i].trim());
42 | i = i + 1;
43 | }
44 | }
45 | });
46 |
--------------------------------------------------------------------------------
/test/managementAPI.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { newEnforcer, Enforcer, Util } from '../src';
16 | import { FileAdapter } from '../src';
17 |
18 | let e = {} as Enforcer;
19 |
20 | beforeEach(async () => {
21 | e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_policy.csv');
22 | });
23 |
24 | function testArrayEquals(value: string[], other: string[]): void {
25 | expect(Util.arrayEquals(value, other)).toBe(true);
26 | }
27 |
28 | function testArray2DEquals(value: string[][], other: string[][]): void {
29 | expect(Util.array2DEquals(value, other)).toBe(true);
30 | }
31 |
32 | test('getAllSubjects', async () => {
33 | const allSubjects = await e.getAllSubjects();
34 | expect(Util.arrayEquals(allSubjects, ['alice', 'bob', 'data2_admin']));
35 | });
36 |
37 | test('getAllNamedSubjects', async () => {
38 | const allNamedSubjects = await e.getAllNamedSubjects('p');
39 | expect(Util.arrayEquals(allNamedSubjects, ['alice', 'bob', 'data2_admin']));
40 | });
41 |
42 | test('getAllObjects', async () => {
43 | const allObjects = await e.getAllObjects();
44 | testArrayEquals(allObjects, ['data1', 'data2']);
45 | });
46 |
47 | test('getAllNamedObjects', async () => {
48 | let allNamedObjects = await e.getAllNamedObjects('p');
49 | testArrayEquals(allNamedObjects, ['data1', 'data2']);
50 | allNamedObjects = await e.getAllNamedObjects('p1');
51 | testArrayEquals(allNamedObjects, []);
52 | });
53 |
54 | test('getAllActions', async () => {
55 | const allActions = await e.getAllActions();
56 | testArrayEquals(allActions, ['read', 'write']);
57 | });
58 |
59 | test('getAllNamedActions', async () => {
60 | let allNamedActions = await e.getAllNamedActions('p');
61 | testArrayEquals(allNamedActions, ['read', 'write']);
62 | allNamedActions = await e.getAllNamedActions('p1');
63 | testArrayEquals(allNamedActions, []);
64 | });
65 |
66 | test('getAllRoles', async () => {
67 | const allRoles = await e.getAllRoles();
68 | testArrayEquals(allRoles, ['data2_admin']);
69 | });
70 |
71 | test('getAllNamedRoles', async () => {
72 | let allNamedRoles = await e.getAllNamedRoles('g');
73 | testArrayEquals(allNamedRoles, ['data2_admin']);
74 | allNamedRoles = await e.getAllNamedRoles('g1');
75 | testArrayEquals(allNamedRoles, []);
76 | });
77 |
78 | test('getPolicy', async () => {
79 | const policy = await e.getPolicy();
80 | testArray2DEquals(policy, [
81 | ['alice', 'data1', 'read'],
82 | ['bob', 'data2', 'write'],
83 | ['data2_admin', 'data2', 'read'],
84 | ['data2_admin', 'data2', 'write'],
85 | ]);
86 | });
87 |
88 | test('getFilteredPolicy', async () => {
89 | let filteredPolicy = await e.getFilteredPolicy(0, 'alice');
90 | testArray2DEquals(filteredPolicy, [['alice', 'data1', 'read']]);
91 | filteredPolicy = await e.getFilteredPolicy(0, 'bob');
92 | testArray2DEquals(filteredPolicy, [['bob', 'data2', 'write']]);
93 | });
94 |
95 | test('getNamedPolicy', async () => {
96 | let namedPolicy = await e.getNamedPolicy('p');
97 | testArray2DEquals(namedPolicy, [
98 | ['alice', 'data1', 'read'],
99 | ['bob', 'data2', 'write'],
100 | ['data2_admin', 'data2', 'read'],
101 | ['data2_admin', 'data2', 'write'],
102 | ]);
103 | namedPolicy = await e.getNamedPolicy('p1');
104 | testArray2DEquals(namedPolicy, []);
105 | });
106 |
107 | test('getFilteredNamedPolicy', async () => {
108 | const filteredNamedPolicy = await e.getFilteredNamedPolicy('p', 0, 'bob');
109 | testArray2DEquals(filteredNamedPolicy, [['bob', 'data2', 'write']]);
110 | });
111 |
112 | test('getGroupingPolicy', async () => {
113 | const groupingPolicy = await e.getGroupingPolicy();
114 | testArray2DEquals(groupingPolicy, [['alice', 'data2_admin']]);
115 | });
116 |
117 | test('getFilteredGroupingPolicy', async () => {
118 | const filteredGroupingPolicy = await e.getFilteredGroupingPolicy(0, 'alice');
119 | testArray2DEquals(filteredGroupingPolicy, [['alice', 'data2_admin']]);
120 | });
121 |
122 | test('getNamedGroupingPolicy', async () => {
123 | const namedGroupingPolicy = await e.getNamedGroupingPolicy('g');
124 | testArray2DEquals(namedGroupingPolicy, [['alice', 'data2_admin']]);
125 | });
126 |
127 | test('getFilteredNamedGroupingPolicy', async () => {
128 | const namedGroupingPolicy = await e.getFilteredNamedGroupingPolicy('g', 0, 'alice');
129 | testArray2DEquals(namedGroupingPolicy, [['alice', 'data2_admin']]);
130 | });
131 |
132 | test('hasPolicy', async () => {
133 | const hasPolicy = await e.hasPolicy('data2_admin', 'data2', 'read');
134 | expect(hasPolicy).toBe(true);
135 | });
136 |
137 | test('hasNamedPolicy', async () => {
138 | const hasNamedPolicy = await e.hasNamedPolicy('p', 'data2_admin', 'data2', 'read');
139 | expect(hasNamedPolicy).toBe(true);
140 | });
141 |
142 | test('addPolicy', async () => {
143 | const p = ['eve', 'data3', 'read'];
144 | const added = await e.addPolicy(...p);
145 | expect(added).toBe(true);
146 | expect(await e.hasPolicy(...p)).toBe(true);
147 | });
148 |
149 | test('addPolicies', async () => {
150 | const a = new FileAdapter('examples/rbac_policy.csv');
151 | e.setAdapter(a);
152 | const rules = [
153 | ['jack', 'data4', 'read'],
154 | ['katy', 'data4', 'write'],
155 | ['leyo', 'data4', 'read'],
156 | ['ham', 'data4', 'write'],
157 | ];
158 | const added = await e.addPolicies(rules);
159 | expect(added).toBe(true);
160 | for (const rule of rules) {
161 | expect(await e.hasPolicy(...rule)).toBe(true);
162 | }
163 | });
164 |
165 | test('addNamedPolicy', async () => {
166 | const p = ['eve', 'data3', 'read'];
167 | const added = await e.addNamedPolicy('p', ...p);
168 | expect(added).toBe(true);
169 | expect(await e.hasPolicy(...p)).toBe(true);
170 | });
171 |
172 | test('addNamedPolicies', async () => {
173 | const a = new FileAdapter('examples/rbac_policy.csv');
174 | e.setAdapter(a);
175 | const rules = [
176 | ['jack', 'data4', 'read'],
177 | ['katy', 'data4', 'write'],
178 | ['leyo', 'data4', 'read'],
179 | ['ham', 'data4', 'write'],
180 | ];
181 | const added = await e.addNamedPolicies('p', rules);
182 | expect(added).toBe(true);
183 | for (const rule of rules) {
184 | expect(await e.hasPolicy(...rule)).toBe(true);
185 | }
186 | });
187 |
188 | test('updatePolicy', async () => {
189 | const a = new FileAdapter('examples/rbac_policy.csv');
190 | e.setAdapter(a);
191 | const p = ['alice', 'data1', 'read'];
192 | const q = ['alice', 'data2', 'read'];
193 | const updated = await e.updatePolicy(p, q);
194 | expect(updated).toBe(true);
195 | expect(await e.hasPolicy(...p)).toBe(false);
196 | expect(await e.hasPolicy(...q)).toBe(true);
197 | });
198 |
199 | test('updateNamedPolicy', async () => {
200 | const a = new FileAdapter('examples/rbac_policy.csv');
201 | e.setAdapter(a);
202 | const p = ['alice', 'data1', 'read'];
203 | const q = ['alice', 'data2', 'read'];
204 | const updated = await e.updateNamedPolicy('p', p, q);
205 | expect(updated).toBe(true);
206 | expect(await e.hasPolicy(...p)).toBe(false);
207 | expect(await e.hasPolicy(...q)).toBe(true);
208 | });
209 |
210 | test('removePolicy', async () => {
211 | const p = ['alice', 'data1', 'read'];
212 | const removed = await e.removePolicy(...p);
213 | expect(removed).toBe(true);
214 | expect(await e.hasPolicy(...p)).toBe(false);
215 | });
216 |
217 | test('removePolicies', async () => {
218 | const a = new FileAdapter('examples/rbac_policy.csv');
219 | e.setAdapter(a);
220 | const rules = [
221 | ['jack', 'data4', 'read'],
222 | ['katy', 'data4', 'write'],
223 | ['leyo', 'data4', 'read'],
224 | ['ham', 'data4', 'write'],
225 | ];
226 | const added = await e.addPolicies(rules);
227 | expect(added).toBe(true);
228 | const removed = await e.removePolicies(rules);
229 | expect(removed).toBe(true);
230 | for (const rule of rules) {
231 | expect(await e.hasPolicy(...rule)).toBe(false);
232 | }
233 | });
234 |
235 | test('removeFilteredPolicy', async () => {
236 | const p = ['alice', 'data1', 'read'];
237 | const removed = await e.removeFilteredPolicy(0, ...p);
238 | expect(removed).toBe(true);
239 | expect(await e.hasPolicy(...p)).toBe(false);
240 | });
241 |
242 | test('removeNamedPolicy', async () => {
243 | const p = ['alice', 'data1', 'read'];
244 | const removed = await e.removeNamedPolicy('p', ...p);
245 | expect(removed).toBe(true);
246 | expect(await e.hasPolicy(...p)).toBe(false);
247 | });
248 |
249 | test('removeNamedPolicies', async () => {
250 | const a = new FileAdapter('examples/rbac_policy.csv');
251 | e.setAdapter(a);
252 | const rules = [
253 | ['jack', 'data4', 'read'],
254 | ['katy', 'data4', 'write'],
255 | ['leyo', 'data4', 'read'],
256 | ['ham', 'data4', 'write'],
257 | ];
258 | const added = await e.addPolicies(rules);
259 | expect(added).toBe(true);
260 | const removed = await e.removeNamedPolicies('p', rules);
261 | expect(removed).toBe(true);
262 | for (const rule of rules) {
263 | expect(await e.hasPolicy(...rule)).toBe(false);
264 | }
265 | });
266 |
267 | test('removeFilteredNamedPolicy', async () => {
268 | const p = ['alice', 'data1', 'read'];
269 | const removed = await e.removeFilteredNamedPolicy('p', 0, ...p);
270 | expect(removed).toBe(true);
271 | expect(await e.hasPolicy(...p)).toBe(false);
272 | });
273 |
274 | test('hasGroupingPolicy', async () => {
275 | const has = await e.hasGroupingPolicy('alice', 'data2_admin');
276 | expect(has).toBe(true);
277 | });
278 |
279 | test('hasNamedGroupingPolicy', async () => {
280 | const has = await e.hasNamedGroupingPolicy('g', 'alice', 'data2_admin');
281 | expect(has).toBe(true);
282 | });
283 |
284 | test('addGroupingPolicy', async () => {
285 | const added = await e.addGroupingPolicy('group1', 'data2_admin');
286 | expect(added).toBe(true);
287 | });
288 |
289 | test('addGroupingPolicies', async () => {
290 | const a = new FileAdapter('examples/rbac_policy.csv');
291 | e.setAdapter(a);
292 | const groupingRules = [
293 | ['ham', 'data4_admin'],
294 | ['jack', 'data5_admin'],
295 | ];
296 | const added = await e.addGroupingPolicies(groupingRules);
297 | expect(added).toBe(true);
298 | });
299 |
300 | test('addNamedGroupingPolicy', async () => {
301 | const added = await e.addNamedGroupingPolicy('g', 'group1', 'data2_admin');
302 | expect(added).toBe(true);
303 | });
304 |
305 | test('addNamedGroupingPolicies', async () => {
306 | const a = new FileAdapter('examples/rbac_policy.csv');
307 | e.setAdapter(a);
308 | const groupingRules = [
309 | ['ham', 'data4_admin'],
310 | ['jack', 'data5_admin'],
311 | ];
312 | const added = await e.addNamedGroupingPolicies('g', groupingRules);
313 | expect(added).toBe(true);
314 | });
315 |
316 | test('removeGroupingPolicy', async () => {
317 | const removed = await e.removeGroupingPolicy('alice', 'data2_admin');
318 | expect(removed).toBe(true);
319 | });
320 |
321 | test('removeGroupingPolicies', async () => {
322 | const a = new FileAdapter('examples/rbac_policy.csv');
323 | e.setAdapter(a);
324 | const groupingRules = [
325 | ['ham', 'data4_admin'],
326 | ['jack', 'data5_admin'],
327 | ];
328 | const added = await e.addGroupingPolicies(groupingRules);
329 | expect(added).toBe(true);
330 | const removed = await e.removeGroupingPolicies(groupingRules);
331 | expect(removed).toBe(true);
332 | });
333 |
334 | test('removeFilteredGroupingPolicy', async () => {
335 | const removed = await e.removeFilteredGroupingPolicy(0, 'alice');
336 | expect(removed).toBe(true);
337 | });
338 |
339 | test('removeFilteredNamedGroupingPolicy', async () => {
340 | const removed = await e.removeFilteredNamedGroupingPolicy('g', 0, 'alice');
341 | expect(removed).toBe(true);
342 | });
343 |
344 | test('removeNamedGroupingPolicies', async () => {
345 | const a = new FileAdapter('examples/rbac_policy.csv');
346 | e.setAdapter(a);
347 | const groupingRules = [
348 | ['ham', 'data4_admin'],
349 | ['jack', 'data5_admin'],
350 | ];
351 | const added = await e.addGroupingPolicies(groupingRules);
352 | expect(added).toBe(true);
353 | const removed = await e.removeNamedGroupingPolicies('g', groupingRules);
354 | expect(removed).toBe(true);
355 | });
356 |
357 | test('updateGroupingPolicy', async () => {
358 | const a = new FileAdapter('examples/rbac_policy.csv');
359 | e.setAdapter(a);
360 |
361 | let groupingPolicy = await e.getGroupingPolicy();
362 | testArray2DEquals(groupingPolicy, [['alice', 'data2_admin']]);
363 |
364 | const updated = e.updateGroupingPolicy(['alice', 'data2_admin'], ['alice', 'update_test']);
365 | groupingPolicy = await e.getGroupingPolicy();
366 | testArray2DEquals(groupingPolicy, [['alice', 'update_test']]);
367 | });
368 |
369 | test('updateNamedGroupingPolicy', async () => {
370 | const a = new FileAdapter('examples/rbac_policy.csv');
371 | e.setAdapter(a);
372 |
373 | let groupingPolicy = await e.getGroupingPolicy();
374 | testArray2DEquals(groupingPolicy, [['alice', 'data2_admin']]);
375 |
376 | const updated = e.updateNamedGroupingPolicy('g', ['alice', 'data2_admin'], ['alice', 'update_test']);
377 | groupingPolicy = await e.getGroupingPolicy();
378 | testArray2DEquals(groupingPolicy, [['alice', 'update_test']]);
379 | });
380 |
--------------------------------------------------------------------------------
/test/model/model.test.ts:
--------------------------------------------------------------------------------
1 | import { ConfigInterface, newModel, newModelFromFile, newModelFromString, requiredSections, sectionNameMap } from '../../src';
2 | import { readFileSync } from 'fs';
3 |
4 | class MockConfig implements ConfigInterface {
5 | public data = new Map();
6 |
7 | getBool(key: string): boolean {
8 | return false;
9 | }
10 |
11 | getFloat(key: string): number {
12 | return 0;
13 | }
14 |
15 | getInt(key: string): number {
16 | return 0;
17 | }
18 |
19 | getString(key: string): string {
20 | return this.data.get(key) || '';
21 | }
22 |
23 | getStrings(key: string): string[] {
24 | return [];
25 | }
26 |
27 | set(key: string, value: string): void {
28 | // not implementation
29 | }
30 | }
31 |
32 | const basicExample = 'examples/basic_model.conf';
33 |
34 | const basicConfig = new MockConfig();
35 | basicConfig.data.set('request_definition::r', 'sub, obj, act');
36 | basicConfig.data.set('policy_definition::p', 'sub, obj, act');
37 | basicConfig.data.set('policy_effect::e', 'some(where (p.eft == allow))');
38 | basicConfig.data.set('matchers::m', 'r.sub == p.sub && r.obj == p.obj && r.act == p.act');
39 |
40 | test('TestNewModel', () => {
41 | const m = newModel();
42 |
43 | expect(m !== null).toBe(true);
44 | });
45 |
46 | test('TestNewModelFromString', () => {
47 | const m = newModelFromString(readFileSync(basicExample).toString());
48 |
49 | expect(m !== null).toBe(true);
50 | });
51 |
52 | test('TestLoadModelFromConfig', (done) => {
53 | let m = newModel();
54 | m.loadModelFromConfig(basicConfig);
55 |
56 | m = newModel();
57 |
58 | try {
59 | m.loadModelFromConfig(new MockConfig());
60 |
61 | done.fail('empty config should return error');
62 | } catch (e) {
63 | if (e instanceof TypeError) {
64 | throw e;
65 | }
66 |
67 | if (e instanceof Error) {
68 | requiredSections.forEach((n) => {
69 | if (!e.message.includes(n)) {
70 | throw new Error(`section name: ${sectionNameMap[n]} should be in message`);
71 | }
72 | });
73 | }
74 | }
75 |
76 | done();
77 | });
78 |
--------------------------------------------------------------------------------
/test/persist/fileSystem.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2023 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { FileSystem, getDefaultFileSystem, setDefaultFileSystem } from '../../src';
16 |
17 | test('get an set the default system', async () => {
18 | let fs = getDefaultFileSystem();
19 | expect(fs).toBeUndefined();
20 | const defaultFileSystem: FileSystem = {
21 | readFileSync(path: string, encoding?: string): Buffer {
22 | // noop
23 | return Buffer.of();
24 | },
25 | writeFileSync(path: string, text: string, encoding?: string) {
26 | // noop
27 | return;
28 | },
29 | };
30 | setDefaultFileSystem(defaultFileSystem);
31 |
32 | fs = getDefaultFileSystem();
33 | expect(fs).toEqual(defaultFileSystem);
34 | });
35 |
--------------------------------------------------------------------------------
/test/persist/helper.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2021 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { Helper, newModel } from '../../src';
16 |
17 | test('test Helper.loadPolicyLine', async () => {
18 | const m = newModel();
19 | m.loadModelFromText(`
20 | [request_definition]
21 | r = sub, obj, act
22 |
23 | [policy_definition]
24 | p = sub, obj, act
25 |
26 | [policy_effect]
27 | e = some(where (p.eft == allow))
28 |
29 | [matchers]
30 | m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
31 | `);
32 |
33 | const testdata = [
34 | 'p, admin, /, GET',
35 | '# test comment 1',
36 | ' # test comment 2',
37 | `p, "admin", /, POST`,
38 | `"p", "admin", "/", "PUT"`,
39 | `"p","admin",/, "DELETE"`,
40 | `p, " admin","/ ", "PATCH"`,
41 | ];
42 |
43 | const expectedPolicy = [
44 | ['admin', '/', 'GET'],
45 | ['admin', '/', 'POST'],
46 | ['admin', '/', 'PUT'],
47 | ['admin', '/', 'DELETE'],
48 | ['admin', '/', 'PATCH'],
49 | ];
50 |
51 | testdata.forEach((n) => {
52 | Helper.loadPolicyLine(n, m);
53 | });
54 |
55 | const ast = m.model.get('p')?.get('p');
56 | expect(ast !== null && ast !== undefined).toBe(true);
57 | expect(ast?.policy?.length === expectedPolicy.length).toBe(true);
58 | expect(ast?.policy).toEqual(expectedPolicy);
59 | });
60 |
--------------------------------------------------------------------------------
/test/rbac/defaultRoleManager.test.ts:
--------------------------------------------------------------------------------
1 | import { DefaultRoleManager } from '../../src';
2 | import { keyMatch2Func } from '../../src/util';
3 |
4 | test('TestAllMatchingFunc', async () => {
5 | const rm = new DefaultRoleManager(10);
6 | await rm.addMatchingFunc(keyMatch2Func);
7 | await rm.addDomainMatchingFunc(keyMatch2Func);
8 | await rm.addLink('/book/:id', 'book_group', '*');
9 | // Current role inheritance tree after deleting the links:
10 | // *:book_group
11 | // |
12 | // *:/book/:id
13 | expect(await rm.hasLink('/book/1', 'book_group', 'domain1')).toBe(true);
14 | });
15 |
--------------------------------------------------------------------------------
/test/rbacAPI.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { newEnforcer } from '../src';
16 |
17 | test('test getRolesForUser', async () => {
18 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
19 | expect(await e.getRolesForUser('alice')).toEqual(['admin']);
20 | });
21 |
22 | test('test getRolesForUser with domain', async () => {
23 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_hierarchy_with_domains_policy.csv');
24 | expect(await e.getRolesForUser('alice', 'domain1')).toEqual(['role:global_admin']);
25 | });
26 |
27 | test('test add/deleteRoleForUSer with domain', async () => {
28 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
29 | expect(await e.getRolesForUser('bob')).toEqual([]);
30 | expect(await e.addRoleForUser('bob', 'data1_admin')).toEqual(true);
31 | expect(await e.hasRoleForUser('bob', 'data1_admin')).toEqual(true);
32 | expect(await e.getUsersForRole('data1_admin')).toEqual(['admin', 'bob']);
33 | expect(await e.deleteRoleForUser('bob', 'data1_admin')).toEqual(true);
34 | expect(await e.hasRoleForUser('bob', 'role:global_admin')).toEqual(false);
35 | expect(await e.getUsersForRole('data1_admin')).toEqual(['admin']);
36 | });
37 |
38 | test('test add/deleteRoleForUSer with domain', async () => {
39 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_hierarchy_with_domains_policy.csv');
40 | expect(await e.getRolesForUser('bob', 'domain1')).toEqual([]);
41 | expect(await e.addRoleForUser('bob', 'role:global_admin', 'domain1')).toEqual(true);
42 | expect(await e.hasRoleForUser('bob', 'role:global_admin', 'domain1')).toEqual(true);
43 | expect(await e.getUsersForRole('role:global_admin', 'domain1')).toEqual(['alice', 'bob']);
44 | expect(await e.deleteRoleForUser('bob', 'role:global_admin', 'domain1')).toEqual(true);
45 | expect(await e.hasRoleForUser('bob', 'role:global_admin', 'domain1')).toEqual(false);
46 | expect(await e.getUsersForRole('role:global_admin', 'domain1')).toEqual(['alice']);
47 | });
48 |
49 | test('test getImplicitRolesForUser', async () => {
50 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
51 | expect(await e.getImplicitRolesForUser('bob')).toEqual([]);
52 | expect(await e.getImplicitRolesForUser('alice')).toEqual(['admin', 'data1_admin', 'data2_admin']);
53 | });
54 |
55 | test('test getImplicitRolesForUser with domain', async () => {
56 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_hierarchy_with_domains_policy.csv');
57 | expect(await e.getImplicitRolesForUser('alice', 'domain1')).toEqual(['role:global_admin', 'role:reader', 'role:writer']);
58 | });
59 |
60 | test('test getImplicitPermissionsForUser', async () => {
61 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
62 | expect(await e.hasPermissionForUser('bob', 'data2', 'write')).toEqual(true);
63 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
64 | expect(await e.hasPermissionForUser('alice', 'data1', 'read')).toEqual(true);
65 | expect(await e.hasPermissionForUser('data1_admin', 'data1', 'read')).toEqual(true);
66 | expect(await e.hasPermissionForUser('data1_admin', 'data1', 'write')).toEqual(true);
67 | expect(await e.hasPermissionForUser('data2_admin', 'data2', 'read')).toEqual(true);
68 | expect(await e.hasPermissionForUser('data2_admin', 'data2', 'write')).toEqual(true);
69 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([
70 | ['alice', 'data1', 'read'],
71 | ['data1_admin', 'data1', 'read'],
72 | ['data1_admin', 'data1', 'write'],
73 | ['data2_admin', 'data2', 'read'],
74 | ['data2_admin', 'data2', 'write'],
75 | ]);
76 | });
77 |
78 | test('test getImplicitResourcesForUser', async () => {
79 | const e = await newEnforcer('examples/rbac_with_pattern_model.conf', 'examples/rbac_with_pattern_policy.csv');
80 | expect(await e.getImplicitResourcesForUser('alice')).toEqual([
81 | ['alice', '/pen/1', 'GET'],
82 | ['alice', '/pen2/1', 'GET'],
83 | ['alice', '/book/*', 'GET'],
84 | ['alice', '/book/:id', 'GET'],
85 | ['alice', '/book2/{id}', 'GET'],
86 | ['alice', 'book_group', 'GET'],
87 | ]);
88 | expect(await e.getImplicitResourcesForUser('bob')).toEqual([
89 | ['bob', '/pen/:id', 'GET'],
90 | ['bob', '/pen2/{id}', 'GET'],
91 | ['bob', 'pen_group', 'GET'],
92 | ]);
93 | expect(await e.getImplicitResourcesForUser('cathy')).toEqual([
94 | ['cathy', '/pen/:id', 'GET'],
95 | ['cathy', '/pen2/{id}', 'GET'],
96 | ['cathy', 'pen_group', 'GET'],
97 | ]);
98 | });
99 |
100 | test('test deleteRolesForUser', async () => {
101 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
102 | expect(await e.hasPermissionForUser('bob', 'data2', 'write')).toEqual(true);
103 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
104 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([
105 | ['alice', 'data1', 'read'],
106 | ['data1_admin', 'data1', 'read'],
107 | ['data1_admin', 'data1', 'write'],
108 | ['data2_admin', 'data2', 'read'],
109 | ['data2_admin', 'data2', 'write'],
110 | ]);
111 | expect(await e.deleteRolesForUser('alice')).toEqual(true);
112 | expect(await e.hasPermissionForUser('alice', 'data1', 'read')).toEqual(true);
113 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([['alice', 'data1', 'read']]);
114 | expect(await e.hasPermissionForUser('bob', 'data2', 'write')).toEqual(true);
115 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
116 | expect(await e.deleteRolesForUser('bob')).toEqual(false);
117 | expect(await e.hasPermissionForUser('alice', 'data1', 'read')).toEqual(true);
118 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([['alice', 'data1', 'read']]);
119 | expect(await e.hasPermissionForUser('bob', 'data2', 'write')).toEqual(true);
120 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
121 | });
122 |
123 | test('test deleteRolesForUser with domain', async () => {
124 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_domains_policy.csv');
125 | expect(await e.getImplicitRolesForUser('alice', 'domain1')).toEqual(['admin']);
126 | expect(await e.getImplicitPermissionsForUser('alice', 'domain1')).toEqual([
127 | ['admin', 'domain1', 'data1', 'read'],
128 | ['admin', 'domain1', 'data1', 'write'],
129 | ]);
130 | expect(await e.getImplicitPermissionsForUser('bob', 'domain2')).toEqual([
131 | ['admin', 'domain2', 'data2', 'read'],
132 | ['admin', 'domain2', 'data2', 'write'],
133 | ]);
134 | expect(await e.deleteRolesForUser('alice', 'domain1')).toEqual(true);
135 | expect(await e.getImplicitRolesForUser('alice', 'domain1')).toEqual([]);
136 | expect(await e.getImplicitPermissionsForUser('alice', 'domain2')).toEqual([]);
137 | expect(await e.getImplicitPermissionsForUser('bob', 'domain2')).toEqual([
138 | ['admin', 'domain2', 'data2', 'read'],
139 | ['admin', 'domain2', 'data2', 'write'],
140 | ]);
141 | expect(await e.deleteRolesForUser('bob', 'domain1')).toEqual(false);
142 | expect(await e.getImplicitPermissionsForUser('alice', 'domain2')).toEqual([]);
143 | expect(await e.getImplicitPermissionsForUser('bob', 'domain1')).toEqual([]);
144 | });
145 |
146 | test('test deleteRole', async () => {
147 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
148 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
149 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([
150 | ['alice', 'data1', 'read'],
151 | ['data1_admin', 'data1', 'read'],
152 | ['data1_admin', 'data1', 'write'],
153 | ['data2_admin', 'data2', 'read'],
154 | ['data2_admin', 'data2', 'write'],
155 | ]);
156 | expect(await e.deleteRole('data1_admin')).toEqual(true);
157 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([
158 | ['alice', 'data1', 'read'],
159 | ['data2_admin', 'data2', 'read'],
160 | ['data2_admin', 'data2', 'write'],
161 | ]);
162 | await e.deleteRole('data2_admin');
163 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([['alice', 'data1', 'read']]);
164 | });
165 |
166 | test('test deleteUser', async () => {
167 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
168 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
169 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([
170 | ['alice', 'data1', 'read'],
171 | ['data1_admin', 'data1', 'read'],
172 | ['data1_admin', 'data1', 'write'],
173 | ['data2_admin', 'data2', 'read'],
174 | ['data2_admin', 'data2', 'write'],
175 | ]);
176 | await e.deleteUser('alice');
177 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([]);
178 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([['bob', 'data2', 'write']]);
179 | await e.deleteRole('bob');
180 | expect(await e.getImplicitPermissionsForUser('alice')).toEqual([]);
181 | expect(await e.getImplicitPermissionsForUser('bob')).toEqual([]);
182 | });
183 |
184 | test('test getImplicitUsersForPermission', async () => {
185 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
186 | expect(await e.getImplicitUsersForPermission('data1', 'read')).toEqual(['alice']);
187 | expect(await e.getImplicitUsersForPermission('data1', 'write')).toEqual(['alice']);
188 | expect(await e.getImplicitUsersForPermission('data2', 'read')).toEqual(['alice']);
189 | expect(await e.getImplicitUsersForPermission('data2', 'write')).toEqual(['alice', 'bob']);
190 |
191 | e.clearPolicy();
192 |
193 | await e.addPolicy('admin', 'data1', 'read');
194 | await e.addPolicy('bob', 'data1', 'read');
195 | await e.addGroupingPolicy('alice', 'admin');
196 |
197 | expect(await e.getImplicitUsersForPermission('data1', 'read')).toEqual(['bob', 'alice']);
198 | });
199 |
200 | test('test getImplicitUsersForRole', async () => {
201 | const e = await newEnforcer('examples/rbac_model.conf', 'examples/rbac_with_hierarchy_policy.csv');
202 | expect(await e.getImplicitUsersForRole('admin')).toEqual(['alice']);
203 | expect(await e.getImplicitUsersForRole('data1_admin')).toEqual(['admin', 'alice']);
204 | });
205 |
--------------------------------------------------------------------------------
/test/rbacHierarchicalDomain.test.ts:
--------------------------------------------------------------------------------
1 | import { DefaultRoleManager, newSyncedEnforcer, RoleManager, SyncedEnforcer } from '../src';
2 |
3 | describe('Test Hierarchical Domain RBAC', () => {
4 | let e: SyncedEnforcer;
5 | beforeAll(async () => {
6 | e = await newSyncedEnforcer('examples/rbac_with_hierarchical_domains_model.conf', 'examples/rbac_with_hierarchical_domains_policy.csv');
7 | await (e.getRoleManager() as DefaultRoleManager).addDomainHierarchy(e.getNamedRoleManager('g2') as RoleManager);
8 | });
9 |
10 | test('Authorization to lower domain should pass', async () => {
11 | expect(e.enforceSync('alice', 'domain1', 'data1', 'read')).toBe(true);
12 | expect(e.enforceSync('alice', 'domain1', 'data2', 'read')).toBe(true);
13 | expect(e.enforceSync('alice', 'domain2', 'data2', 'read')).toBe(true);
14 | expect(e.enforceSync('alice', 'domain1', 'sdata2', 'read')).toBe(true);
15 | expect(e.enforceSync('alice', 'sibling2', 'sdata2', 'read')).toBe(true);
16 | expect(e.enforceSync('alice', 'domain3', 'data3', 'write')).toBe(true);
17 | expect(e.enforceSync('alice', 'domain1', 'data3', 'write')).toBe(true);
18 | expect(e.enforceSync('alice', 'domain2', 'data3', 'write')).toBe(true);
19 | expect(e.enforceSync('bob', 'domain2', 'data2', 'read')).toBe(true);
20 | expect(e.enforceSync('bob', 'domain2', 'data3', 'write')).toBe(true);
21 | expect(e.enforceSync('bob', 'domain3', 'data3', 'write')).toBe(true);
22 | });
23 |
24 | test('Authorization to lower domain should faill if no role', async () => {
25 | expect(e.enforceSync('bob', 'domain1', 'data3', 'write')).toBe(false);
26 | expect(e.enforceSync('bob', 'domain1', 'data2', 'read')).toBe(false);
27 | });
28 |
29 | test('Authorization to higher domain without permissions should faill', async () => {
30 | expect(e.enforceSync('bob', 'domain1', 'data1', 'read')).toBe(false);
31 | expect(e.enforceSync('bob', 'domain2', 'data1', 'read')).toBe(false);
32 | expect(e.enforceSync('bob', 'domain3', 'data1', 'write')).toBe(false);
33 | });
34 |
35 | test('Authorization to higher domain with permissions should faill', async () => {
36 | // alice data1
37 | expect(e.enforceSync('alice', 'domain2', 'data1', 'read')).toBe(false);
38 | expect(e.enforceSync('alice', 'domain3', 'data1', 'read')).toBe(false);
39 | expect(e.enforceSync('alice', 'sibling2', 'data1', 'read')).toBe(false);
40 | // alice data2
41 | expect(e.enforceSync('alice', 'domain3', 'data2', 'read')).toBe(false);
42 | expect(e.enforceSync('alice', 'sibling2', 'data2', 'read')).toBe(false);
43 | // alice data3
44 | expect(e.enforceSync('alice', 'sibling2', 'data3', 'write')).toBe(false);
45 | // alice sdata2
46 | expect(e.enforceSync('alice', 'domain2', 'sdata2', 'read')).toBe(false);
47 | // bob data2
48 | expect(e.enforceSync('bob', 'domain3', 'data2', 'read')).toBe(false);
49 | });
50 |
51 | test('Authorization to sibling without permissions should faill', async () => {
52 | expect(e.enforceSync('bob', 'domain1', 'sdata2', 'read')).toBe(false);
53 | expect(e.enforceSync('bob', 'domain2', 'sdata2', 'read')).toBe(false);
54 | expect(e.enforceSync('bob', 'sibling2', 'sdata2', 'read')).toBe(false);
55 | expect(e.enforceSync('bob', 'domain3', 'sdata2', 'read')).toBe(false);
56 | });
57 |
58 | test('User should not have a permission if there is none in the lower domain', async () => {
59 | expect(e.enforceSync('alice', 'domain3', 'data3', 'read')).toBe(false);
60 | expect(e.enforceSync('bob', 'domain3', 'data3', 'read')).toBe(false);
61 | });
62 |
63 | test('test getRolesForUserInDomain', async () => {
64 | expect(await e.getRolesForUserInDomain('alice', 'domain1')).toEqual(['admin']);
65 | expect(await e.getRolesForUserInDomain('alice', 'domain2')).toEqual(['admin']);
66 | expect(await e.getRolesForUserInDomain('alice', 'domain3')).toEqual(['admin']);
67 | expect(await e.getRolesForUserInDomain('alice', 'sibling2')).toEqual(['admin']);
68 | expect(await e.getRolesForUserInDomain('bob', 'domain1')).toEqual([]);
69 | expect(await e.getRolesForUserInDomain('bob', 'domain2')).toEqual(['admin']);
70 | expect(await e.getRolesForUserInDomain('bob', 'domain3')).toEqual(['admin']);
71 | });
72 |
73 | test('test getUsersForRoleInDomain', async () => {
74 | expect(await e.getUsersForRoleInDomain('admin', 'domain1')).toEqual(['alice']);
75 | expect(await e.getUsersForRoleInDomain('admin', 'domain2')).toEqual(['bob', 'alice']);
76 | expect(await e.getUsersForRoleInDomain('admin', 'sibling2')).toEqual(['alice']);
77 | expect(await e.getUsersForRoleInDomain('admin', 'domain3')).toEqual(['alice', 'bob']);
78 | });
79 | });
80 |
--------------------------------------------------------------------------------
/test/rbacwDomainAPI.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2019 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { newEnforcer } from '../src';
16 |
17 | test('test getRolesForUserInDomain', async () => {
18 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_domains_policy.csv');
19 | expect(await e.getRolesForUserInDomain('alice', 'domain1')).toEqual(['admin']);
20 | expect(await e.getRolesForUserInDomain('alice', 'domain2')).toEqual([]);
21 | expect(await e.getRolesForUserInDomain('bob', 'domain1')).toEqual([]);
22 | expect(await e.getRolesForUserInDomain('bob', 'domain2')).toEqual(['admin']);
23 | });
24 |
25 | test('test getUsersForRoleInDomain', async () => {
26 | const e = await newEnforcer('examples/rbac_with_domains_model.conf', 'examples/rbac_with_domains_policy.csv');
27 | expect(await e.getUsersForRoleInDomain('admin', 'domain1')).toEqual(['alice']);
28 | expect(await e.getUsersForRoleInDomain('admin', 'domain2')).toEqual(['bob']);
29 | expect(await e.getUsersForRoleInDomain('superadmin', 'domain1')).toEqual([]);
30 | expect(await e.getUsersForRoleInDomain('superadmin', 'domain2')).toEqual([]);
31 | });
32 |
--------------------------------------------------------------------------------
/test/syncedEnforcer.test.ts:
--------------------------------------------------------------------------------
1 | import { newModel, newSyncedEnforcer } from '../src';
2 |
3 | test('TestGFunctionInSyncedEnforcerWithRoles', async () => {
4 | const m = newModel();
5 | m.addDef('r', 'r', 'sub, obj, act');
6 | m.addDef('p', 'p', 'sub, obj, act');
7 | m.addDef('g', 'g', '_, _');
8 | m.addDef('e', 'e', 'some(where (p.eft == allow))');
9 | m.addDef('m', 'm', 'g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act');
10 |
11 | const e = await newSyncedEnforcer(m);
12 |
13 | await e.addPermissionForUser('alice', 'data1', 'read');
14 | await e.addPermissionForUser('bob', 'data2', 'write');
15 | await e.addPermissionForUser('data2_admin', 'data2', 'read');
16 | await e.addPermissionForUser('data2_admin', 'data2', 'write');
17 |
18 | // Synced enforcer should not fail to recognize subject
19 | expect(e.enforceSync('alice', 'data1', 'read')).toBe(true);
20 | expect(e.enforceSync('alice', 'data2', 'read')).toBe(false);
21 | expect(e.enforceSync('alice', 'data2', 'write')).toBe(false);
22 | expect(e.enforceSync('bob', 'data1', 'read')).toBe(false);
23 | expect(e.enforceSync('bob', 'data2', 'write')).toBe(true);
24 | expect(e.enforceSync('data2_admin', 'data2', 'read')).toBe(true);
25 | expect(e.enforceSync('data2_admin', 'data2', 'write')).toBe(true);
26 | expect(e.enforceSync('data2_admin', 'data1', 'read')).toBe(false);
27 |
28 | await e.addRoleForUser('alice', 'data2_admin');
29 |
30 | // Synced enforcer should not fail to recognize subject
31 | expect(e.enforceSync('alice', 'data1', 'read')).toBe(true);
32 | expect(e.enforceSync('alice', 'data2', 'read')).toBe(true);
33 | expect(e.enforceSync('alice', 'data2', 'write')).toBe(true);
34 | expect(e.enforceSync('bob', 'data1', 'read')).toBe(false);
35 | expect(e.enforceSync('bob', 'data2', 'write')).toBe(true);
36 | expect(e.enforceSync('data2_admin', 'data2', 'read')).toBe(true);
37 | expect(e.enforceSync('data2_admin', 'data2', 'write')).toBe(true);
38 | expect(e.enforceSync('data2_admin', 'data1', 'read')).toBe(false);
39 | });
40 |
41 | test('TestGFunctionInSyncedEnforcerWithDomains', async () => {
42 | const m = newModel();
43 | m.addDef('r', 'r', 'sub, dom, obj, act');
44 | m.addDef('p', 'p', 'sub, dom, obj, act');
45 | m.addDef('g', 'g', '_, _, _');
46 | m.addDef('e', 'e', 'some(where (p.eft == allow))');
47 | m.addDef('m', 'm', 'g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.act');
48 |
49 | const e = await newSyncedEnforcer(m);
50 |
51 | await e.addPermissionForUser('admin', 'domain1', 'data1', 'read');
52 | await e.addPermissionForUser('admin', 'domain1', 'data1', 'write');
53 | await e.addPermissionForUser('admin', 'domain2', 'data2', 'read');
54 | await e.addPermissionForUser('admin', 'domain2', 'data2', 'write');
55 |
56 | // Alice and Bob should not have rights
57 | expect(e.enforceSync('alice', 'domain1', 'data1', 'read')).toBe(false);
58 | expect(e.enforceSync('alice', 'domain1', 'data1', 'write')).toBe(false);
59 | expect(e.enforceSync('bob', 'domain2', 'data2', 'read')).toBe(false);
60 | expect(e.enforceSync('bob', 'domain2', 'data2', 'write')).toBe(false);
61 |
62 | await e.addRoleForUser('alice', 'admin', 'domain1');
63 | await e.addRoleForUser('bob', 'admin', 'domain2');
64 |
65 | // Alice and Bob should have rights
66 | expect(e.enforceSync('alice', 'domain1', 'data1', 'read')).toBe(true);
67 | expect(e.enforceSync('alice', 'domain1', 'data1', 'write')).toBe(true);
68 | expect(e.enforceSync('bob', 'domain2', 'data2', 'read')).toBe(true);
69 | expect(e.enforceSync('bob', 'domain2', 'data2', 'write')).toBe(true);
70 |
71 | // Alice and Bob should not have rights
72 | expect(e.enforceSync('alice', 'domain2', 'data2', 'read')).toBe(false);
73 | expect(e.enforceSync('alice', 'domain2', 'data2', 'write')).toBe(false);
74 | expect(e.enforceSync('bob', 'domain1', 'data2', 'read')).toBe(false);
75 | expect(e.enforceSync('bob', 'domain1', 'data2', 'write')).toBe(false);
76 |
77 | // Domains should be respected
78 | expect(e.enforceSync('alice', 'domain2', 'data1', 'read')).toBe(false);
79 | expect(e.enforceSync('alice', 'domain2', 'data1', 'write')).toBe(false);
80 | expect(e.enforceSync('bob', 'domain1', 'data2', 'read')).toBe(false);
81 | expect(e.enforceSync('bob', 'domain1', 'data2', 'write')).toBe(false);
82 | });
83 |
--------------------------------------------------------------------------------
/test/util.test.ts:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Casbin Authors. All Rights Reserved.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | import { getLogger, logPrint, Util as util } from '../src';
16 | import { compile } from '@casbin/expression-eval';
17 |
18 | test('test enableLog success', () => {
19 | getLogger().enableLog(true);
20 | expect(getLogger().isEnable()).toEqual(true);
21 | getLogger().enableLog(false);
22 | expect(getLogger().isEnable()).toEqual(false);
23 | });
24 |
25 | test('test enableLog failed', () => {
26 | getLogger().enableLog(true);
27 | expect(getLogger().isEnable()).not.toEqual(false);
28 | getLogger().enableLog(false);
29 | expect(getLogger().isEnable()).not.toEqual(true);
30 | });
31 |
32 | test('test logPrint', () => {
33 | getLogger().enableLog(true);
34 | expect(logPrint('test log')).toBeUndefined();
35 | });
36 |
37 | test('test Valuate', () => {
38 | expect(compile('1 + 1 === 2')({})).toEqual(true);
39 | expect(compile('1 + 1 !== 2')({})).toEqual(false);
40 | });
41 |
42 | test('test regexMatchFunc', () => {
43 | expect(util.regexMatchFunc('foobar', '^foo*')).toEqual(true);
44 | expect(util.regexMatchFunc('barfoo', '^foo*')).toEqual(false);
45 | });
46 |
47 | test('test keyMatchFunc', () => {
48 | expect(util.keyMatchFunc('/foo/bar', '/foo/*')).toEqual(true);
49 | expect(util.keyMatchFunc('/bar/foo', '/foo/*')).toEqual(false);
50 | });
51 |
52 | test('test keyGetFunc', () => {
53 | expect(util.keyGetFunc('/foo/bar', '/foo/*')).toEqual('bar');
54 | expect(util.keyGetFunc('/bar/foo', '/foo/*')).toEqual('');
55 | });
56 |
57 | test('test keyMatch2Func', () => {
58 | expect(util.keyMatch2Func('/foo/bar', '/foo/*')).toEqual(true);
59 | expect(util.keyMatch2Func('/foo/baz', '/foo/:bar')).toEqual(true);
60 | expect(util.keyMatch2Func('/foo/baz/foo', '/foo/:bar/foo')).toEqual(true);
61 | expect(util.keyMatch2Func('/baz', '/foo')).toEqual(false);
62 | expect(util.keyMatch2Func('/foo/baz', '/foo')).toEqual(false);
63 | });
64 |
65 | test('test keyGet2Func', () => {
66 | expect(util.keyGet2Func('/foo/bar', '/foo/*', 'bar')).toEqual('');
67 | expect(util.keyGet2Func('/foo/baz', '/foo/:bar', 'bar')).toEqual('baz');
68 | expect(util.keyGet2Func('/foo/baz/foo', '/foo/:bar/foo', 'bar')).toEqual('baz');
69 | expect(util.keyGet2Func('/baz', '/foo', 'bar')).toEqual('');
70 | expect(util.keyGet2Func('/foo/baz', '/foo', 'bar')).toEqual('');
71 | });
72 |
73 | test('test keyMatch3Func', () => {
74 | expect(util.keyMatch3Func('/foo/bar', '/foo/*')).toEqual(true);
75 | expect(util.keyMatch3Func('/foo/baz', '/foo/{bar}')).toEqual(true);
76 | expect(util.keyMatch3Func('/foo/baz/foo', '/foo/{bar}/foo')).toEqual(true);
77 | expect(util.keyMatch3Func('/baz', '/foo')).toEqual(false);
78 | expect(util.keyMatch3Func('/foo/baz', '/foo')).toEqual(false);
79 | });
80 |
81 | test('test keyMatch4Func', () => {
82 | expect(util.keyMatch4Func('/parent/123/child/123', '/parent/{id}/child/{id}')).toEqual(true);
83 | expect(util.keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{id}')).toEqual(false);
84 |
85 | expect(util.keyMatch4Func('/parent/123/child/123', '/parent/{id}/child/{another_id}')).toEqual(true);
86 | expect(util.keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{another_id}')).toEqual(true);
87 |
88 | expect(util.keyMatch4Func('/parent/123/child/123/book/123', '/parent/{id}/child/{id}/book/{id}')).toEqual(true);
89 | expect(util.keyMatch4Func('/parent/123/child/123/book/456', '/parent/{id}/child/{id}/book/{id}')).toEqual(false);
90 | expect(util.keyMatch4Func('/parent/123/child/456/book/123', '/parent/{id}/child/{id}/book/{id}')).toEqual(false);
91 | expect(util.keyMatch4Func('/parent/123/child/456/book/', '/parent/{id}/child/{id}/book/{id}')).toEqual(false);
92 | expect(util.keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{id}/book/{id}')).toEqual(false);
93 | });
94 |
95 | test('test keyMatch5Func', () => {
96 | expect(util.keyMatch5Func('/parent/child?status=1&type=2', '/parent/child')).toEqual(true);
97 | expect(util.keyMatch5Func('/parent?status=1&type=2', '/parent/child')).toEqual(false);
98 |
99 | expect(util.keyMatch5Func('/parent/child/?status=1&type=2', '/parent/child/')).toEqual(true);
100 | expect(util.keyMatch5Func('/parent/child/?status=1&type=2', '/parent/child')).toEqual(false);
101 | expect(util.keyMatch5Func('/parent/child?status=1&type=2', '/parent/child/')).toEqual(false);
102 |
103 | expect(util.keyMatch5Func('keyMatch5: expected 2 arguments, but got 1', '/foo')).toEqual(false);
104 | expect(util.keyMatch5Func('keyMatch5: expected 2 arguments, but got 3', '/foo/create/123', '/foo/*', '/foo/update/123')).toEqual(false);
105 | expect(util.keyMatch5Func('keyMatch5: argument must be a string', '/parent/123', true)).toEqual(false);
106 |
107 | expect(util.keyMatch5Func('/foo', '/foo')).toEqual(true);
108 | expect(util.keyMatch5Func('/foo', '/foo*')).toEqual(true);
109 | expect(util.keyMatch5Func('/foo', '/foo/*')).toEqual(false);
110 | expect(util.keyMatch5Func('/foo/bar', '/foo')).toEqual(false);
111 | expect(util.keyMatch5Func('/foo/bar', '/foo*')).toEqual(false);
112 | expect(util.keyMatch5Func('/foo/bar', '/foo/*')).toEqual(true);
113 | expect(util.keyMatch5Func('/foobar', '/foo')).toEqual(false);
114 | expect(util.keyMatch5Func('/foobar', '/foo*')).toEqual(false);
115 | expect(util.keyMatch5Func('/foobar', '/foo/*')).toEqual(false);
116 |
117 | expect(util.keyMatch5Func('/', '/{resource}')).toEqual(false);
118 | expect(util.keyMatch5Func('/resource1', '/{resource}')).toEqual(true);
119 | expect(util.keyMatch5Func('/myid', '/{id}/using/{resId}')).toEqual(false);
120 | expect(util.keyMatch5Func('/myid/using/myresid', '/{id}/using/{resId}')).toEqual(true);
121 |
122 | expect(util.keyMatch5Func('/proxy/myid', '/proxy/{id}/*')).toEqual(false);
123 | expect(util.keyMatch5Func('/proxy/myid/', '/proxy/{id}/*')).toEqual(true);
124 | expect(util.keyMatch5Func('/proxy/myid/res', '/proxy/{id}/*')).toEqual(true);
125 | expect(util.keyMatch5Func('/proxy/myid/res/res2', '/proxy/{id}/*')).toEqual(true);
126 | expect(util.keyMatch5Func('/proxy/myid/res/res2/res3', '/proxy/{id}/*')).toEqual(true);
127 | expect(util.keyMatch5Func('/proxy/', '/proxy/{id}/*')).toEqual(false);
128 |
129 | expect(util.keyMatch5Func('/proxy/myid?status=1&type=2', '/proxy/{id}/*')).toEqual(false);
130 | expect(util.keyMatch5Func('/proxy/myid/', '/proxy/{id}/*')).toEqual(true);
131 | expect(util.keyMatch5Func('/proxy/myid/res?status=1&type=2', '/proxy/{id}/*')).toEqual(true);
132 | expect(util.keyMatch5Func('/proxy/myid/res/res2?status=1&type=2', '/proxy/{id}/*')).toEqual(true);
133 | expect(util.keyMatch5Func('/proxy/myid/res/res2/res3?status=1&type=2', '/proxy/{id}/*')).toEqual(true);
134 | expect(util.keyMatch5Func('/proxy/', '/proxy/{id}/*')).toEqual(false);
135 | });
136 |
137 | test('test ipMatchFunc', () => {
138 | expect(util.ipMatchFunc('::1', '::0:1')).toEqual(true);
139 | expect(util.ipMatchFunc('192.168.1.1', '192.168.1.1')).toEqual(true);
140 | expect(util.ipMatchFunc('127.0.0.1', '::ffff:127.0.0.1')).toEqual(true);
141 | expect(util.ipMatchFunc('192.168.2.123', '192.168.2.0/24')).toEqual(true);
142 | expect(util.ipMatchFunc('::1', '127.0.0.2')).toEqual(false);
143 | expect(() => util.ipMatchFunc('I am alice', '127.0.0.1')).toThrow(Error);
144 | expect(() => util.ipMatchFunc('127.0.0.1', 'I am alice')).toThrow(/invalid/g);
145 | expect(util.ipMatchFunc('192.168.2.189', '192.168.1.134/26')).toEqual(false);
146 | });
147 |
148 | test('test globMatch', () => {
149 | expect(util.globMatch('/foo', '/foo')).toEqual(true);
150 | expect(util.globMatch('/foo', '/foo*')).toEqual(true);
151 | expect(util.globMatch('/foo', '/foo/*')).toEqual(false);
152 |
153 | expect(util.globMatch('/foo', '/foo')).toEqual(true);
154 | expect(util.globMatch('/foo', '/foo*')).toEqual(true);
155 | expect(util.globMatch('/foo', '/foo/*')).toEqual(false);
156 | expect(util.globMatch('/foo/bar', '/foo')).toEqual(false);
157 | expect(util.globMatch('/foo/bar', '/foo*')).toEqual(false);
158 | expect(util.globMatch('/foo/bar', '/foo/*')).toEqual(true);
159 | expect(util.globMatch('/foobar', '/foo')).toEqual(false);
160 | expect(util.globMatch('/foobar', '/foo*')).toEqual(true);
161 | expect(util.globMatch('/foobar', '/foo/*')).toEqual(false);
162 |
163 | expect(util.globMatch('/foo', '*/foo')).toEqual(true);
164 | expect(util.globMatch('/foo', '*/foo*')).toEqual(true);
165 | expect(util.globMatch('/foo', '*/foo/*')).toEqual(false);
166 | expect(util.globMatch('/foo/bar', '*/foo')).toEqual(false);
167 | expect(util.globMatch('/foo/bar', '*/foo*')).toEqual(false);
168 | expect(util.globMatch('/foo/bar', '*/foo/*')).toEqual(true);
169 | expect(util.globMatch('/foobar', '*/foo')).toEqual(false);
170 | expect(util.globMatch('/foobar', '*/foo*')).toEqual(true);
171 | expect(util.globMatch('/foobar', '*/foo/*')).toEqual(false);
172 |
173 | expect(util.globMatch('/prefix/foo', '*/foo')).toEqual(false);
174 | expect(util.globMatch('/prefix/foo', '*/foo*')).toEqual(false);
175 | expect(util.globMatch('/prefix/foo', '*/foo/*')).toEqual(false);
176 | expect(util.globMatch('/prefix/foo/bar', '*/foo')).toEqual(false);
177 | expect(util.globMatch('/prefix/foo/bar', '*/foo*')).toEqual(false);
178 | expect(util.globMatch('/prefix/foo/bar', '*/foo/*')).toEqual(false);
179 | expect(util.globMatch('/prefix/foobar', '*/foo')).toEqual(false);
180 | expect(util.globMatch('/prefix/foobar', '*/foo*')).toEqual(false);
181 | expect(util.globMatch('/prefix/foobar', '*/foo/*')).toEqual(false);
182 |
183 | expect(util.globMatch('/prefix/subprefix/foo', '*/foo')).toEqual(false);
184 | expect(util.globMatch('/prefix/subprefix/foo', '*/foo*')).toEqual(false);
185 | expect(util.globMatch('/prefix/subprefix/foo', '*/foo/*')).toEqual(false);
186 | expect(util.globMatch('/prefix/subprefix/foo/bar', '*/foo')).toEqual(false);
187 | expect(util.globMatch('/prefix/subprefix/foo/bar', '*/foo*')).toEqual(false);
188 | expect(util.globMatch('/prefix/subprefix/foo/bar', '*/foo/*')).toEqual(false);
189 | expect(util.globMatch('/prefix/subprefix/foobar', '*/foo')).toEqual(false);
190 | expect(util.globMatch('/prefix/subprefix/foobar', '*/foo*')).toEqual(false);
191 | expect(util.globMatch('/prefix/subprefix/foobar', '*/foo/*')).toEqual(false);
192 |
193 | expect(util.globMatch('a.conf', '*.conf')).toEqual(true);
194 | });
195 |
196 | test('test hasEval', () => {
197 | expect(util.hasEval('eval() && a && b && c')).toEqual(true);
198 | expect(util.hasEval('eval() && a && b && c')).toEqual(true);
199 | expect(util.hasEval('eval) && a && b && c')).toEqual(false);
200 | expect(util.hasEval('eval)( && a && b && c')).toEqual(false);
201 | expect(util.hasEval('xeval() && a && b && c')).toEqual(false);
202 | expect(util.hasEval('eval(c * (a + b)) && a && b && c')).toEqual(true);
203 | });
204 |
205 | test('test replaceEval', () => {
206 | expect(util.replaceEval('eval() && a && b && c', '', 'a')).toEqual('(a) && a && b && c');
207 | expect(util.replaceEval('eval() && a && b && c', '', '(a)')).toEqual('((a)) && a && b && c');
208 | expect(util.replaceEval('eval(p_some_rule) && c', 'p_some_rule', '(a)')).toEqual('((a)) && c');
209 | expect(util.replaceEval('eval(p_some_rule) && eval(p_some_other_rule) && c', 'p_some_rule', '(a)')).toEqual(
210 | '((a)) && eval(p_some_other_rule) && c'
211 | );
212 | });
213 |
214 | test('test getEvalValue', () => {
215 | expect(util.arrayEquals(util.getEvalValue('eval(a) && a && b && c'), ['a']));
216 | expect(util.arrayEquals(util.getEvalValue('a && eval(a) && b && c'), ['a']));
217 | expect(util.arrayEquals(util.getEvalValue('eval(a) && eval(b) && a && b && c'), ['a', 'b']));
218 | expect(util.arrayEquals(util.getEvalValue('a && eval(a) && eval(b) && b && c'), ['a', 'b']));
219 | });
220 |
221 | test('bracketCompatible', () => {
222 | expect(util.bracketCompatible("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')")).toEqual(
223 | "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3']"
224 | );
225 | expect(
226 | util.bracketCompatible(
227 | "g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3') || r.obj in ('data4', 'data5')"
228 | )
229 | ).toEqual("g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ['data2', 'data3'] || r.obj in ['data4', 'data5']");
230 | });
231 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "target": "ES2017",
5 | "module": "CommonJS",
6 | "outDir": "lib/cjs"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.esm.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "compilerOptions": {
4 | "target": "ES2017",
5 | "module": "ESNext",
6 | "outDir": "lib/esm"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES6",
4 | "module": "CommonJS",
5 | "moduleResolution": "Node",
6 | "strict": true,
7 | "strictPropertyInitialization": false,
8 | "declaration": true,
9 | "downlevelIteration": true,
10 | "allowSyntheticDefaultImports": true,
11 | "esModuleInterop": true
12 | },
13 | "include": ["src/**/*"]
14 | }
15 |
--------------------------------------------------------------------------------