├── .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 | [![install size](https://packagephobia.now.sh/badge?p=casbin)](https://packagephobia.now.sh/result?p=casbin) 6 | [![codebeat badge](https://codebeat.co/badges/c17c9ee1-da42-4db3-8047-9574ad2b23b1)](https://codebeat.co/projects/github-com-casbin-node-casbin-master) 7 | [![GitHub Actions](https://github.com/casbin/node-casbin/workflows/main/badge.svg)](https://github.com/casbin/node-casbin/actions) 8 | [![Coverage Status](https://coveralls.io/repos/github/casbin/node-casbin/badge.svg?branch=master)](https://coveralls.io/github/casbin/node-casbin?branch=master) 9 | [![Release](https://img.shields.io/github/release/casbin/node-casbin.svg)](https://github.com/casbin/node-casbin/releases/latest) 10 | [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](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 | casdoor 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 | ![casbin Logo](casbin-logo.png) 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 | | [![golang](https://casbin.org/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/img/langs/php.png)](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 | | [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin-net/Casbin.NET) | [![c++](https://casbin.org/img/langs/cpp.png)](https://github.com/casbin/casbin-cpp) | [![rust](https://casbin.org/img/langs/rust.png)](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 | --------------------------------------------------------------------------------