├── .github ├── semantic.yml └── workflows │ └── phpunit.yml ├── .gitignore ├── .releaserc.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml.dist ├── src ├── Adapters │ └── DatabaseAdapter.php ├── Config │ ├── Enforcer.php │ ├── Services.php │ └── rbac-model.conf ├── Database │ └── Migrations │ │ └── 20190901100537_add_rule.php ├── EnforcerManager.php └── Models │ └── RuleModel.php └── tests ├── Database └── Seeds │ └── CITestSeeder.php ├── EnforcerManagerTest.php └── _bootstrap.php /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | # Always validate the PR title AND all the commits 2 | titleAndCommits: true 3 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | services: 14 | mysql: 15 | image: mysql:5.7 16 | env: 17 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 18 | MYSQL_DATABASE: casbin 19 | ports: 20 | - 3306:3306 21 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 22 | 23 | strategy: 24 | fail-fast: true 25 | matrix: 26 | php: [8.1, 8.2, 8.3] 27 | 28 | name: PHP ${{ matrix.php }} 29 | 30 | steps: 31 | - name: Checkout code 32 | uses: actions/checkout@v3 33 | 34 | - name: Setup PHP 35 | uses: shivammathur/setup-php@v2 36 | with: 37 | php-version: ${{ matrix.php }} 38 | tools: composer:v2 39 | coverage: xdebug 40 | 41 | - name: Validate composer.json and composer.lock 42 | run: composer validate 43 | 44 | - name: Install dependencies 45 | if: steps.composer-cache.outputs.cache-hit != 'true' 46 | run: | 47 | composer install --prefer-dist --no-progress --no-suggest 48 | 49 | - name: Run test suite 50 | run: ./vendor/bin/phpunit 51 | 52 | - name: Run Coveralls 53 | env: 54 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 55 | COVERALLS_PARALLEL: true 56 | COVERALLS_FLAG_NAME: ${{ runner.os }} - ${{ matrix.php }} 57 | run: | 58 | composer global require php-coveralls/php-coveralls:^2.7 59 | php-coveralls --coverage_clover=build/logs/clover.xml -v 60 | 61 | upload-coverage: 62 | runs-on: ubuntu-latest 63 | needs: [ test ] 64 | steps: 65 | - name: Coveralls Finished 66 | uses: coverallsapp/github-action@master 67 | with: 68 | github-token: ${{ secrets.GITHUB_TOKEN }} 69 | parallel-finished: true 70 | 71 | semantic-release: 72 | runs-on: ubuntu-latest 73 | needs: [ test, upload-coverage ] 74 | steps: 75 | - uses: actions/checkout@v3 76 | - uses: actions/setup-node@v4 77 | with: 78 | node-version: 'lts/*' 79 | 80 | - name: Run semantic-release 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 83 | run: npx semantic-release 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | 3 | composer.lock 4 | 5 | .idea/ 6 | *.iml 7 | 8 | # coverage report 9 | /build 10 | 11 | phpunit.xml -------------------------------------------------------------------------------- /.releaserc.yml: -------------------------------------------------------------------------------- 1 | plugins: 2 | - "@semantic-release/commit-analyzer" 3 | - "@semantic-release/release-notes-generator" 4 | - "@semantic-release/github" -------------------------------------------------------------------------------- /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 |

2 | CodeIgniter Permission 3 |

4 | 5 |

6 | CodeIgniter Permission is an authorization library for the CodeIgniter4 framework. 7 |

8 | 9 |

10 | 11 | Build Status 12 | 13 | 14 | Coverage Status 15 | 16 | 17 | Latest Stable Version 18 | 19 | 20 | Total Downloads 21 | 22 | 23 | License 24 | 25 |

26 | 27 | It's based on [Casbin](https://github.com/php-casbin/php-casbin), an authorization library that supports access control models like ACL, RBAC, ABAC. 28 | 29 | All you need to learn to use `Casbin` first. 30 | 31 | * [Installation](#installation) 32 | * [Usage](#usage) 33 | * [Quick start](#quick-start) 34 | * [Using Enforcer Api](#using-enforcer-api) 35 | * [Multiple enforcers](#multiple-enforcers) 36 | * [Cache](#using-cache) 37 | * [Thinks](#thinks) 38 | * [License](#license) 39 | 40 | ## Installation 41 | 42 | Require this package in the `composer.json` of your `CodeIgniter 4` project. This will download the package. 43 | 44 | ``` 45 | composer require casbin/codeigniter-permission 46 | ``` 47 | 48 | To migrate the migrations, run the migrate command: 49 | 50 | ``` 51 | php spark migrate -n "Casbin\CodeIgniter" 52 | ``` 53 | 54 | This will create a new table named `rules` 55 | 56 | 57 | ## Usage 58 | 59 | ### Quick start 60 | 61 | Once installed you can do stuff like this: 62 | 63 | ```php 64 | 65 | $enforcer = \Config\Services::enforcer(); 66 | 67 | // adds permissions to a user 68 | $enforcer->addPermissionForUser('eve', 'articles', 'read'); 69 | // adds a role for a user. 70 | $enforcer->addRoleForUser('eve', 'writer'); 71 | // adds permissions to a rule 72 | $enforcer->addPolicy('writer', 'articles','edit'); 73 | 74 | ``` 75 | 76 | You can check if a user has a permission like this: 77 | 78 | ```php 79 | // to check if a user has permission 80 | if ($enforcer->enforce("eve", "articles", "edit")) { 81 | // permit eve to edit articles 82 | } else { 83 | // deny the request, show an error 84 | } 85 | 86 | ``` 87 | 88 | ### Using Enforcer Api 89 | 90 | It provides a very rich api to facilitate various operations on the Policy: 91 | 92 | Gets all roles: 93 | 94 | ```php 95 | $enforcer->getAllRoles(); // ['writer', 'reader'] 96 | ``` 97 | 98 | Gets all the authorization rules in the policy.: 99 | 100 | ```php 101 | $enforcer->getPolicy(); 102 | ``` 103 | 104 | Gets the roles that a user has. 105 | 106 | ```php 107 | $enforcer->getRolesForUser('eve'); // ['writer'] 108 | ``` 109 | 110 | Gets the users that has a role. 111 | 112 | ```php 113 | $enforcer->getUsersForRole('writer'); // ['eve'] 114 | ``` 115 | 116 | Determines whether a user has a role. 117 | 118 | ```php 119 | $enforcer->hasRoleForUser('eve', 'writer'); // true or false 120 | ``` 121 | 122 | Adds a role for a user. 123 | 124 | ```php 125 | $enforcer->addRoleForUser('eve', 'writer'); 126 | ``` 127 | 128 | Adds a permission for a user or role. 129 | 130 | ```php 131 | // to user 132 | $enforcer->addPermissionForUser('eve', 'articles', 'read'); 133 | // to role 134 | $enforcer->addPermissionForUser('writer', 'articles','edit'); 135 | ``` 136 | 137 | Deletes a role for a user. 138 | 139 | ```php 140 | $enforcer->deleteRoleForUser('eve', 'writer'); 141 | ``` 142 | 143 | Deletes all roles for a user. 144 | 145 | ```php 146 | $enforcer->deleteRolesForUser('eve'); 147 | ``` 148 | 149 | Deletes a role. 150 | 151 | ```php 152 | $enforcer->deleteRole('writer'); 153 | ``` 154 | 155 | Deletes a permission. 156 | 157 | ```php 158 | $enforcer->deletePermission('articles', 'read'); // returns false if the permission does not exist (aka not affected). 159 | ``` 160 | 161 | Deletes a permission for a user or role. 162 | 163 | ```php 164 | $enforcer->deletePermissionForUser('eve', 'articles', 'read'); 165 | ``` 166 | 167 | Deletes permissions for a user or role. 168 | 169 | ```php 170 | // to user 171 | $enforcer->deletePermissionsForUser('eve'); 172 | // to role 173 | $enforcer->deletePermissionsForUser('writer'); 174 | ``` 175 | 176 | Gets permissions for a user or role. 177 | 178 | ```php 179 | $enforcer->getPermissionsForUser('eve'); // return array 180 | ``` 181 | 182 | Determines whether a user has a permission. 183 | 184 | ```php 185 | $enforcer->hasPermissionForUser('eve', 'articles', 'read'); // true or false 186 | ``` 187 | 188 | See [Casbin API](https://casbin.org/docs/en/management-api) for more APIs. 189 | 190 | ### Multiple enforcers 191 | 192 | If you need multiple permission controls in your project, you can configure multiple enforcers. 193 | 194 | In the `Config\Enforcer.php` file, it should be like this: 195 | 196 | ```php 197 | 198 | namespace Config; 199 | 200 | use Casbin\CodeIgniter\Config\Enforcer as BaseConfig; 201 | use Casbin\CodeIgniter\Adapters\DatabaseAdapter; 202 | 203 | class Enforcer extends BaseConfig 204 | { 205 | /* 206 | * Default Enforcer driver 207 | * 208 | * @var string 209 | */ 210 | public $default = 'basic'; 211 | 212 | public $basic = [ 213 | /* 214 | * Casbin model setting. 215 | */ 216 | 'model' => [ 217 | // Available Settings: "file", "text" 218 | 'config_type' => 'file', 219 | 220 | 'config_file_path' => __DIR__.'/rbac-model.conf', 221 | 222 | 'config_text' => '', 223 | ], 224 | 225 | /* 226 | * Casbin adapter . 227 | */ 228 | 'adapter' => DatabaseAdapter::class, 229 | 230 | /* 231 | * Database setting. 232 | */ 233 | 'database' => [ 234 | // Database connection for following tables. 235 | 'connection' => '', 236 | 237 | // Rule table name. 238 | 'rules_table' => 'rules', 239 | ], 240 | 241 | 'log' => [ 242 | // changes whether Casbin will log messages to the Logger. 243 | 'enabled' => false, 244 | 245 | // Casbin Logger 246 | 'logger' => \Casbin\CodeIgniter\Logger::class, 247 | ], 248 | 249 | 'cache' => [ 250 | // changes whether Casbin will cache the rules. 251 | 'enabled' => false, 252 | 253 | // cache Key 254 | 'key' => 'rules', 255 | 256 | // ttl int|null 257 | 'ttl' => 24 * 60, 258 | ], 259 | ]; 260 | 261 | public $second = [ 262 | 'model' => [ 263 | // ... 264 | ], 265 | 266 | 'adapter' => DatabaseAdapter::class, 267 | // ... 268 | ]; 269 | } 270 | 271 | ``` 272 | 273 | Then you can choose which enforcers to use. 274 | 275 | ```php 276 | $enforcer->guard('second')->enforce("eve", "articles", "edit"); 277 | ``` 278 | 279 | ### Using cache 280 | 281 | Authorization rules are cached to speed up performance. The default is off. 282 | 283 | Sets your own cache configs in `Config\Enforcer.php`. 284 | 285 | ```php 286 | 'cache' => [ 287 | // changes whether Casbin will cache the rules. 288 | 'enabled' => false, 289 | // cache Key 290 | 'key' => 'rules', 291 | // ttl int|null 292 | 'ttl' => 24 * 60, 293 | ] 294 | ``` 295 | 296 | ## Thinks 297 | 298 | [PHP-Casbin](https://github.com/php-casbin/php-casbin). You can find the full documentation of Casbin [on the website](https://casbin.org/). 299 | 300 | ## License 301 | 302 | This project is licensed under the [Apache 2.0 license](LICENSE). 303 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "casbin/codeigniter-permission", 3 | "description": "Associate users with roles and permissions, use Casbin in CodeIgniter4 Web Framework.", 4 | "authors": [ 5 | { 6 | "name": "TechLee", 7 | "email": "techlee@qq.com" 8 | } 9 | ], 10 | "license": "Apache-2.0", 11 | "keywords": [ 12 | "casbin", 13 | "rbac", 14 | "acl", 15 | "authorization", 16 | "permission", 17 | "abac", 18 | "access control", 19 | "codeigniter" 20 | ], 21 | "scripts": { 22 | "test": "vendor/bin/phpunit" 23 | }, 24 | "require": { 25 | "php": "^8.1", 26 | "codeigniter4/framework": "^4.5", 27 | "casbin/casbin": "^4.0.2" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "Casbin\\CodeIgniter\\": "src/" 32 | } 33 | }, 34 | "require-dev": { 35 | "phpunit/phpunit": "^10.0|^11.0" 36 | }, 37 | "autoload-dev": { 38 | "psr-4": { 39 | "Casbin\\CodeIgniter\\Tests\\": "tests/" 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | ./tests 12 | 13 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ./src 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/Adapters/DatabaseAdapter.php: -------------------------------------------------------------------------------- 1 | model = new RuleModel($db); 42 | $this->model->setTable($config['database']['rules_table']); 43 | $this->model->setCacheConfig($config['cache']); 44 | } 45 | 46 | /** 47 | * savePolicyLine function. 48 | * 49 | * @param string $ptype 50 | * @param array $rule 51 | */ 52 | public function savePolicyLine(string $ptype, array $rule): void 53 | { 54 | $col['ptype'] = $ptype; 55 | foreach ($rule as $key => $value) { 56 | $col['v'.strval($key)] = $value; 57 | } 58 | 59 | $this->model->insert($col); 60 | } 61 | 62 | /** 63 | * loads all policy rules from the storage. 64 | * 65 | * @param Model $model 66 | */ 67 | public function loadPolicy(Model $model): void 68 | { 69 | $rows = $this->model->getAllFromCache(); 70 | 71 | foreach ($rows as $row) { 72 | $line = implode(', ', array_filter($row, function ($val) { 73 | return '' != $val && !is_null($val); 74 | })); 75 | $this->loadPolicyLine(trim($line), $model); 76 | } 77 | } 78 | 79 | /** 80 | * saves all policy rules to the storage. 81 | * 82 | * @param Model $model 83 | */ 84 | public function savePolicy(Model $model): void 85 | { 86 | foreach ($model['p'] as $ptype => $ast) { 87 | foreach ($ast->policy as $rule) { 88 | $this->savePolicyLine($ptype, $rule); 89 | } 90 | } 91 | 92 | foreach ($model['g'] as $ptype => $ast) { 93 | foreach ($ast->policy as $rule) { 94 | $this->savePolicyLine($ptype, $rule); 95 | } 96 | } 97 | } 98 | 99 | /** 100 | * adds a policy rule to the storage. 101 | * This is part of the Auto-Save feature. 102 | * 103 | * @param string $sec 104 | * @param string $ptype 105 | * @param array $rule 106 | */ 107 | public function addPolicy(string $sec, string $ptype, array $rule): void 108 | { 109 | $this->savePolicyLine($ptype, $rule); 110 | } 111 | 112 | /** 113 | * This is part of the Auto-Save feature. 114 | * 115 | * @param string $sec 116 | * @param string $ptype 117 | * @param array $rule 118 | */ 119 | public function removePolicy(string $sec, string $ptype, array $rule): void 120 | { 121 | $count = 0; 122 | 123 | $instance = $this->model->where('ptype', $ptype); 124 | 125 | foreach ($rule as $key => $value) { 126 | $instance->where('v'.strval($key), $value); 127 | } 128 | 129 | foreach ($instance->findAll() as $model) { 130 | if ($this->model->delete($model['id'])) { 131 | ++$count; 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * RemoveFilteredPolicy removes policy rules that match the filter from the storage. 138 | * This is part of the Auto-Save feature. 139 | * 140 | * @param string $sec 141 | * @param string $ptype 142 | * @param int $fieldIndex 143 | * @param string ...$fieldValues 144 | */ 145 | public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void 146 | { 147 | $count = 0; 148 | 149 | $instance = $this->model->where('ptype', $ptype); 150 | foreach (range(0, 5) as $value) { 151 | if ($fieldIndex <= $value && $value < $fieldIndex + count($fieldValues)) { 152 | if ('' != $fieldValues[$value - $fieldIndex]) { 153 | $instance->where('v'.strval($value), $fieldValues[$value - $fieldIndex]); 154 | } 155 | } 156 | } 157 | 158 | foreach ($instance->findAll() as $model) { 159 | if ($this->model->delete($model['id'])) { 160 | ++$count; 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/Config/Enforcer.php: -------------------------------------------------------------------------------- 1 | [ 22 | // Available Settings: "file", "text" 23 | 'config_type' => 'file', 24 | 25 | 'config_file_path' => __DIR__.'/rbac-model.conf', 26 | 27 | 'config_text' => '', 28 | ], 29 | 30 | /* 31 | * Casbin adapter . 32 | */ 33 | 'adapter' => DatabaseAdapter::class, 34 | 35 | /* 36 | * Database setting. 37 | */ 38 | 'database' => [ 39 | // Database connection for following tables. 40 | 'connection' => '', 41 | 42 | // Rule table name. 43 | 'rules_table' => 'rules', 44 | ], 45 | 46 | 'log' => [ 47 | // changes whether Casbin will log messages to the Logger. 48 | 'enabled' => false, 49 | 50 | // Casbin Logger, Supported: \Psr\Log\LoggerInterface|string 51 | 'logger' => 'logger', 52 | ], 53 | 54 | 'cache' => [ 55 | // changes whether Casbin will cache the rules. 56 | 'enabled' => false, 57 | 58 | // cache Key 59 | 'key' => 'rules', 60 | 61 | // ttl int|null 62 | 'ttl' => 24 * 60, 63 | ], 64 | ]; 65 | } 66 | -------------------------------------------------------------------------------- /src/Config/Services.php: -------------------------------------------------------------------------------- 1 | getDefaultConfig(); 12 | if ($config['database']['connection']){ 13 | $this->DBGroup = $config['database']['connection']; 14 | } 15 | $this->forge->addField([ 16 | 'id' => [ 17 | 'type' => 'INT', 18 | 'auto_increment' => true, 19 | ], 20 | 'ptype' => [ 21 | 'type' => 'VARCHAR', 22 | 'constraint' => '255', 23 | 'null' => true, 24 | ], 25 | 'v0' => [ 26 | 'type' => 'VARCHAR', 27 | 'constraint' => '255', 28 | 'null' => true, 29 | ], 30 | 'v1' => [ 31 | 'type' => 'VARCHAR', 32 | 'constraint' => '255', 33 | 'null' => true, 34 | ], 35 | 'v2' => [ 36 | 'type' => 'VARCHAR', 37 | 'constraint' => '255', 38 | 'null' => true, 39 | ], 40 | 'v3' => [ 41 | 'type' => 'VARCHAR', 42 | 'constraint' => '255', 43 | 'null' => true, 44 | ], 45 | 'v4' => [ 46 | 'type' => 'VARCHAR', 47 | 'constraint' => '255', 48 | 'null' => true, 49 | ], 50 | 'v5' => [ 51 | 'type' => 'VARCHAR', 52 | 'constraint' => '255', 53 | 'null' => true, 54 | ], 55 | ]); 56 | $this->forge->addKey('id', true); 57 | $this->forge->createTable($config['database']['rules_table']); 58 | } 59 | 60 | public function down() 61 | { 62 | $config = Services::enforcer()->getDefaultConfig(); 63 | if ($config['database']['connection']){ 64 | $this->DBGroup = $config['database']['connection']; 65 | } 66 | $this->forge->dropTable($config['database']['rules_table']); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/EnforcerManager.php: -------------------------------------------------------------------------------- 1 | config = $config; 40 | } 41 | 42 | /** 43 | * Attempt to get the enforcer from the local cache. 44 | * 45 | * @param string $name 46 | * 47 | * @return \Casbin\Enforcer 48 | * 49 | * @throws \InvalidArgumentException 50 | */ 51 | public function guard($name = null) 52 | { 53 | $name = $name ?: $this->getDefaultGuard(); 54 | 55 | if (!isset($this->guards[$name])) { 56 | $this->guards[$name] = $this->resolve($name); 57 | } 58 | 59 | return $this->guards[$name]; 60 | } 61 | 62 | /** 63 | * Resolve the given guard. 64 | * 65 | * @param string $name 66 | * 67 | * @return \Casbin\Enforcer 68 | * 69 | * @throws \InvalidArgumentException 70 | */ 71 | protected function resolve($name) 72 | { 73 | $config = $this->getConfig($name); 74 | 75 | if (is_null($config)) { 76 | throw new InvalidArgumentException("Enforcer [{$name}] is not defined."); 77 | } 78 | 79 | if ($logger = $config['log']['logger']) { 80 | if (is_string($logger)) { 81 | $logger = new DefaultLogger(Services::$logger()); 82 | } 83 | 84 | Log::setLogger($logger); 85 | } 86 | 87 | $model = new Model(); 88 | $configType = $config['model']['config_type']; 89 | if ('file' == $configType) { 90 | $model->loadModel($config['model']['config_file_path']); 91 | } elseif ('text' == $configType) { 92 | $model->loadModelFromText($config['model']['config_text']); 93 | } 94 | $adapter = $config['adapter']; 95 | if (!is_null($adapter)) { 96 | if (is_string($adapter)) { 97 | $adapter = new $adapter($config); 98 | } 99 | } 100 | 101 | return new Enforcer($model, $adapter, $logger, $config['log']['enabled']); 102 | } 103 | 104 | /** 105 | * Get the enforcer driver configuration. 106 | * 107 | * @param string $name 108 | * 109 | * @return array 110 | */ 111 | protected function getConfig($name): array 112 | { 113 | return $this->config->{$name}; 114 | } 115 | 116 | /** 117 | * Get the default enforcer guard name. 118 | * 119 | * @return string 120 | */ 121 | public function getDefaultGuard() 122 | { 123 | return $this->config->default; 124 | } 125 | 126 | /** 127 | * Set the default guard driver the factory should serve. 128 | * 129 | * @param string $name 130 | */ 131 | public function shouldUse($name) 132 | { 133 | $name = $name ?: $this->getDefaultGuard(); 134 | 135 | $this->setDefaultGuard($name); 136 | } 137 | 138 | /** 139 | * Set the default authorization guard name. 140 | * 141 | * @param string $name 142 | */ 143 | public function setDefaultGuard($name) 144 | { 145 | $this->config->default = $name; 146 | } 147 | 148 | /** 149 | * gets default config for enforcer. 150 | * 151 | * @param string $name 152 | * 153 | * @return void 154 | */ 155 | public function getDefaultConfig() 156 | { 157 | $name = $this->getDefaultGuard(); 158 | 159 | return $this->config->{$name}; 160 | } 161 | 162 | /** 163 | * Dynamically call the default driver instance. 164 | * 165 | * @param string $method 166 | * @param array $parameters 167 | * 168 | * @return mixed 169 | */ 170 | public function __call($method, $parameters) 171 | { 172 | return $this->guard()->{$method}(...$parameters); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/Models/RuleModel.php: -------------------------------------------------------------------------------- 1 | false, 17 | 'key' => 'rules', 18 | 'ttl' => 24 * 60, 19 | ]; 20 | 21 | protected $allowedFields = ['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5']; 22 | 23 | /** 24 | * Gets rules from caches. 25 | * 26 | * @return mixed 27 | */ 28 | public function getAllFromCache() 29 | { 30 | $get = function () { 31 | return $this->select('ptype, v0, v1, v2, v3, v4, v5')->findAll(); 32 | }; 33 | if (!($this->cacheConfig['enabled'] ?? false)) { 34 | return $get(); 35 | } 36 | 37 | if (!$rules = cache($this->cacheConfig['key'])) { 38 | $rules = $get(); 39 | cache()->save($this->cacheConfig['key'], $rules, $this->cacheConfig['ttl']); 40 | } 41 | 42 | return $rules; 43 | } 44 | 45 | /** 46 | * Refresh Cache. 47 | */ 48 | protected function refreshCache(...$args) 49 | { 50 | if (!($this->cacheConfig['enabled'] ?? false)) { 51 | return; 52 | } 53 | 54 | $this->forgetCache(); 55 | $this->getAllFromCache(); 56 | } 57 | 58 | /** 59 | * Forget Cache. 60 | */ 61 | public function forgetCache() 62 | { 63 | cache()->delete($this->cacheConfig['key']); 64 | } 65 | 66 | /** 67 | * sets cacheConfig. 68 | * 69 | * @param array $cacheConfig 70 | * 71 | * @return void 72 | */ 73 | public function setCacheConfig(array $cacheConfig = []) 74 | { 75 | if ($cacheConfig) { 76 | $this->cacheConfig = $cacheConfig; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/Database/Seeds/CITestSeeder.php: -------------------------------------------------------------------------------- 1 | 'p', 'v0' => 'alice', 'v1' => 'data1', 'v2' => 'read'], 14 | ['ptype' => 'p', 'v0' => 'bob', 'v1' => 'data2', 'v2' => 'write'], 15 | ['ptype' => 'p', 'v0' => 'data2_admin', 'v1' => 'data2', 'v2' => 'read'], 16 | ['ptype' => 'p', 'v0' => 'data2_admin', 'v1' => 'data2', 'v2' => 'write'], 17 | ['ptype' => 'g', 'v0' => 'alice', 'v1' => 'data2_admin'], 18 | ]; 19 | $config = Services::enforcer()->getDefaultConfig(); 20 | $table = $config['database']['rules_table']; 21 | 22 | $this->db->table($table)->truncate(); 23 | 24 | foreach ($data as $single_dummy_data) { 25 | $this->db->table($table)->insert($single_dummy_data); 26 | } 27 | } 28 | 29 | //-------------------------------------------------------------------- 30 | } 31 | -------------------------------------------------------------------------------- /tests/EnforcerManagerTest.php: -------------------------------------------------------------------------------- 1 | addNamespace('Casbin\CodeIgniter', dirname(__DIR__).'/src'); 21 | 22 | Services::cache()->clean(); 23 | 24 | return $app; 25 | } 26 | 27 | /** 28 | * The path to where we can find the seeds directory. 29 | * Allows overriding the default application directories. 30 | * 31 | * @var string 32 | */ 33 | protected $basePath = __DIR__.'/Database'; 34 | 35 | protected $seed = '\Casbin\CodeIgniter\Tests\Database\Seeds\CITestSeeder'; 36 | 37 | /** 38 | * The namespace to help us find the migration classes. 39 | * 40 | * @var string 41 | */ 42 | protected $namespace = 'Casbin\CodeIgniter'; 43 | 44 | public function testEnforce() 45 | { 46 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data1', 'read')); 47 | 48 | $this->assertFalse(Services::enforcer()->enforce('bob', 'data1', 'read')); 49 | $this->assertTrue(Services::enforcer()->enforce('bob', 'data2', 'write')); 50 | 51 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data2', 'read')); 52 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data2', 'write')); 53 | } 54 | 55 | public function testAddPolicy() 56 | { 57 | $this->assertFalse(Services::enforcer()->enforce('eve', 'data3', 'read')); 58 | Services::enforcer()->addPermissionForUser('eve', 'data3', 'read'); 59 | $this->assertTrue(Services::enforcer()->enforce('eve', 'data3', 'read')); 60 | } 61 | 62 | public function testSavePolicy() 63 | { 64 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data4', 'read')); 65 | 66 | $model = Services::enforcer()->getModel(); 67 | // $model->clearPolicy(); 68 | $model->addPolicy('p', 'p', ['alice', 'data4', 'read']); 69 | 70 | $adapter = Services::enforcer()->getAdapter(); 71 | $adapter->savePolicy($model); 72 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data4', 'read')); 73 | } 74 | 75 | public function testRemovePolicy() 76 | { 77 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data5', 'read')); 78 | 79 | Services::enforcer()->addPermissionForUser('alice', 'data5', 'read'); 80 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data5', 'read')); 81 | 82 | Services::enforcer()->deletePermissionForUser('alice', 'data5', 'read'); 83 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data5', 'read')); 84 | } 85 | 86 | public function testRemoveFilteredPolicy() 87 | { 88 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data1', 'read')); 89 | Services::enforcer()->removeFilteredPolicy(1, 'data1'); 90 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data1', 'read')); 91 | $this->assertTrue(Services::enforcer()->enforce('bob', 'data2', 'write')); 92 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data2', 'read')); 93 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data2', 'write')); 94 | Services::enforcer()->removeFilteredPolicy(1, 'data2', 'read'); 95 | $this->assertTrue(Services::enforcer()->enforce('bob', 'data2', 'write')); 96 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data2', 'read')); 97 | $this->assertTrue(Services::enforcer()->enforce('alice', 'data2', 'write')); 98 | Services::enforcer()->removeFilteredPolicy(2, 'write'); 99 | $this->assertFalse(Services::enforcer()->enforce('bob', 'data2', 'write')); 100 | $this->assertFalse(Services::enforcer()->enforce('alice', 'data2', 'write')); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /tests/_bootstrap.php: -------------------------------------------------------------------------------- 1 |