├── .github └── workflows │ └── phpunit.yml ├── .gitignore ├── .releaserc.yml ├── LICENSE ├── README.md ├── composer.json ├── config ├── lauthz-rbac-model.conf └── lauthz.php ├── database └── migrations │ └── 2019_03_01_000000_create_rules_table.php ├── phpunit.10.xml ├── phpunit.xml ├── src ├── Adapters │ └── DatabaseAdapter.php ├── Commands │ ├── GroupAdd.php │ ├── PolicyAdd.php │ └── RoleAssign.php ├── Contracts │ ├── BatchDatabaseAdapter.php │ ├── DatabaseAdapter.php │ ├── Factory.php │ ├── FilteredDatabaseAdapter.php │ ├── ModelLoader.php │ └── UpdatableDatabaseAdapter.php ├── EnforcerLocalizer.php ├── EnforcerManager.php ├── Exceptions │ └── UnauthorizedException.php ├── Facades │ └── Enforcer.php ├── LauthzServiceProvider.php ├── Loaders │ ├── FileLoader.php │ ├── ModelLoaderManager.php │ ├── TextLoader.php │ └── UrlLoader.php ├── Middlewares │ ├── EnforcerMiddleware.php │ └── RequestMiddleware.php ├── Models │ └── Rule.php └── Observers │ └── RuleObserver.php └── tests ├── Commands ├── GroupAddTest.php ├── PolicyAddTest.php └── RoleAssignTest.php ├── DatabaseAdapterForCacheTest.php ├── DatabaseAdapterTest.php ├── EnforcerCustomLocalizerTest.php ├── EnforcerLocalizerTest.php ├── EnforcerMiddlewareTest.php ├── ModelLoaderTest.php ├── Models └── User.php ├── RequestMiddlewareTest.php ├── RuleCacheTest.php └── TestCase.php /.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: lauthz 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: [ ] 27 | # laravel: [ ] 28 | # stability: [ prefer-lowest, prefer-stable ] 29 | include: 30 | # Laravel 8.x 31 | - php: 8.0 32 | laravel: 8.* 33 | phpunit: ~9.0 34 | # Laravel 9.x 35 | - php: 8.0 36 | laravel: 9.* 37 | phpunit: ~9.0 38 | # Laravel 10.x 39 | - php: 8.1 40 | laravel: 10.* 41 | phpunit: ~9.0 42 | # Laravel 11.x 43 | - php: 8.2 44 | laravel: 11.* 45 | phpunit: ~10.5 46 | - php: 8.3 47 | laravel: 11.* 48 | phpunit: ~10.5 49 | - php: 8.4 50 | laravel: 11.* 51 | phpunit: ~10.5 52 | # Laravel 12.x 53 | - php: 8.2 54 | laravel: 12.* 55 | phpunit: ~11.5 56 | - php: 8.3 57 | laravel: 12.* 58 | phpunit: ~11.5 59 | - php: 8.4 60 | laravel: 12.* 61 | phpunit: ~11.5 62 | 63 | name: Laravel${{ matrix.laravel }}-PHP${{ matrix.php }} 64 | 65 | steps: 66 | - name: Checkout code 67 | uses: actions/checkout@v2 68 | 69 | - name: Setup PHP 70 | uses: shivammathur/setup-php@v2 71 | with: 72 | php-version: ${{ matrix.php }} 73 | tools: composer:v2 74 | coverage: xdebug 75 | 76 | - name: Validate composer.json and composer.lock 77 | run: composer validate 78 | 79 | - name: Install dependencies 80 | if: steps.composer-cache.outputs.cache-hit != 'true' 81 | run: | 82 | composer require laravel/framework:${{ matrix.laravel }} --no-update --no-interaction 83 | composer require laravel/laravel:${{ matrix.laravel }} phpunit/phpunit:${{ matrix.phpunit }} --no-update --no-interaction --dev 84 | composer install --prefer-dist --no-progress --no-suggest 85 | 86 | - name: Run test suite 87 | if: matrix.laravel != '11.*' && matrix.laravel != '12.*' 88 | run: ./vendor/bin/phpunit 89 | 90 | - name: Run test suite laravel 11 91 | if: matrix.laravel == '11.*' || matrix.laravel == '12.*' 92 | run: ./vendor/bin/phpunit -c phpunit.10.xml 93 | 94 | - name: Run Coveralls 95 | env: 96 | COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} 97 | COVERALLS_PARALLEL: true 98 | COVERALLS_FLAG_NAME: ${{ runner.os }} - ${{ matrix.php }} - Laravel${{ matrix.laravel }} 99 | run: | 100 | composer global require php-coveralls/php-coveralls:^2.4 101 | php-coveralls --coverage_clover=build/logs/clover.xml -v 102 | 103 | upload-coverage: 104 | runs-on: ubuntu-latest 105 | needs: [ test ] 106 | steps: 107 | - name: Coveralls Finished 108 | uses: coverallsapp/github-action@master 109 | with: 110 | github-token: ${{ secrets.GITHUB_TOKEN }} 111 | parallel-finished: true 112 | 113 | semantic-release: 114 | runs-on: ubuntu-latest 115 | needs: [ test, upload-coverage ] 116 | steps: 117 | - uses: actions/checkout@v2 118 | - uses: actions/setup-node@v2 119 | with: 120 | node-version: 'lts/*' 121 | 122 | - name: Run semantic-release 123 | env: 124 | GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} 125 | run: npx semantic-release -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | .idea 4 | .vscode 5 | .phpunit* 6 | composer.lock -------------------------------------------------------------------------------- /.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 | Laravel Authorization 3 |

4 | 5 |

6 | Laravel-authz is an authorization library for the laravel framework. 7 |

8 | 9 |

10 | 11 | PHPUnit 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 | * [Using a middleware](#using-a-middleware) 36 | * [basic Enforcer Middleware](#basic-enforcer-middleware) 37 | * [HTTP Request Middleware ( RESTful is also supported )](#http-request-middleware--restful-is-also-supported-) 38 | * [Using Gates](#using-gates) 39 | * [Multiple enforcers](#multiple-enforcers) 40 | * [Using artisan commands](#using-artisan-commands) 41 | * [Cache](#using-cache) 42 | * [Thinks](#thinks) 43 | * [License](#license) 44 | 45 | ## Installation 46 | 47 | Require this package in the `composer.json` of your Laravel project. This will download the package. 48 | 49 | ``` 50 | composer require casbin/laravel-authz 51 | ``` 52 | 53 | The `Lauthz\LauthzServiceProvider` is `auto-discovered` and registered by default, but if you want to register it yourself: 54 | 55 | Add the ServiceProvider in `config/app.php` 56 | 57 | ```php 58 | 'providers' => [ 59 | /* 60 | * Package Service Providers... 61 | */ 62 | Lauthz\LauthzServiceProvider::class, 63 | ] 64 | ``` 65 | 66 | The Enforcer facade is also `auto-discovered`, but if you want to add it manually: 67 | 68 | Add the Facade in `config/app.php` 69 | 70 | ```php 71 | 'aliases' => [ 72 | // ... 73 | 'Enforcer' => Lauthz\Facades\Enforcer::class, 74 | ] 75 | ``` 76 | 77 | To publish the config, run the vendor publish command: 78 | 79 | ``` 80 | php artisan vendor:publish 81 | ``` 82 | 83 | This will create a new model config file named `config/lauthz-rbac-model.conf` and a new lauthz config file named `config/lauthz.php`. 84 | 85 | 86 | To migrate the migrations, run the migrate command: 87 | 88 | ``` 89 | php artisan migrate 90 | ``` 91 | 92 | This will create a new table named `rules` 93 | 94 | 95 | ## Usage 96 | 97 | ### Quick start 98 | 99 | Once installed you can do stuff like this: 100 | 101 | ```php 102 | 103 | use Enforcer; 104 | 105 | // adds permissions to a user 106 | Enforcer::addPermissionForUser('eve', 'articles', 'read'); 107 | // adds a role for a user. 108 | Enforcer::addRoleForUser('eve', 'writer'); 109 | // adds permissions to a role 110 | Enforcer::addPolicy('writer', 'articles','edit'); 111 | 112 | ``` 113 | 114 | You can check if a user has a permission like this: 115 | 116 | ```php 117 | // to check if a user has permission 118 | if (Enforcer::enforce("eve", "articles", "edit")) { 119 | // permit eve to edit articles 120 | } else { 121 | // deny the request, show an error 122 | } 123 | 124 | ``` 125 | 126 | By default, [Gate](https://laravel.com/docs/11.x/authorization#gates) checks will be automatically intercepted 127 | . You can check if a user has a permission with Laravel's default `can` function: 128 | 129 | ```php 130 | $user->can('articles,read'); 131 | ``` 132 | 133 | ### Using Enforcer Api 134 | 135 | It provides a very rich api to facilitate various operations on the Policy: 136 | 137 | Gets all roles: 138 | 139 | ```php 140 | Enforcer::getAllRoles(); // ['writer', 'reader'] 141 | ``` 142 | 143 | Gets all the authorization rules in the policy.: 144 | 145 | ```php 146 | Enforcer::getPolicy(); 147 | ``` 148 | 149 | Gets the roles that a user has. 150 | 151 | ```php 152 | Enforcer::getRolesForUser('eve'); // ['writer'] 153 | ``` 154 | 155 | Gets the users that has a role. 156 | 157 | ```php 158 | Enforcer::getUsersForRole('writer'); // ['eve'] 159 | ``` 160 | 161 | Determines whether a user has a role. 162 | 163 | ```php 164 | Enforcer::hasRoleForUser('eve', 'writer'); // true or false 165 | ``` 166 | 167 | Adds a role for a user. 168 | 169 | ```php 170 | Enforcer::addRoleForUser('eve', 'writer'); 171 | ``` 172 | 173 | Adds a permission for a user or role. 174 | 175 | ```php 176 | // to user 177 | Enforcer::addPermissionForUser('eve', 'articles', 'read'); 178 | // to role 179 | Enforcer::addPermissionForUser('writer', 'articles','edit'); 180 | ``` 181 | 182 | Deletes a role for a user. 183 | 184 | ```php 185 | Enforcer::deleteRoleForUser('eve', 'writer'); 186 | ``` 187 | 188 | Deletes all roles for a user. 189 | 190 | ```php 191 | Enforcer::deleteRolesForUser('eve'); 192 | ``` 193 | 194 | Deletes a role. 195 | 196 | ```php 197 | Enforcer::deleteRole('writer'); 198 | ``` 199 | 200 | Deletes a permission. 201 | 202 | ```php 203 | Enforcer::deletePermission('articles', 'read'); // returns false if the permission does not exist (aka not affected). 204 | ``` 205 | 206 | Deletes a permission for a user or role. 207 | 208 | ```php 209 | Enforcer::deletePermissionForUser('eve', 'articles', 'read'); 210 | ``` 211 | 212 | Deletes permissions for a user or role. 213 | 214 | ```php 215 | // to user 216 | Enforcer::deletePermissionsForUser('eve'); 217 | // to role 218 | Enforcer::deletePermissionsForUser('writer'); 219 | ``` 220 | 221 | Gets permissions for a user or role. 222 | 223 | ```php 224 | Enforcer::getPermissionsForUser('eve'); // return array 225 | ``` 226 | 227 | Determines whether a user has a permission. 228 | 229 | ```php 230 | Enforcer::hasPermissionForUser('eve', 'articles', 'read'); // true or false 231 | ``` 232 | 233 | See [Casbin API](https://casbin.org/docs/management-api#reference) for more APIs. 234 | 235 | ### Using a middleware 236 | 237 | This package comes with `EnforcerMiddleware`, `RequestMiddleware` middlewares. You can add them inside your `app/Http/Kernel.php` file. 238 | 239 | ```php 240 | protected $routeMiddleware = [ 241 | // ... 242 | // a basic Enforcer Middleware 243 | 'enforcer' => \Lauthz\Middlewares\EnforcerMiddleware::class, 244 | // an HTTP Request Middleware 245 | 'http_request' => \Lauthz\Middlewares\RequestMiddleware::class, 246 | ]; 247 | ``` 248 | 249 | #### basic Enforcer Middleware 250 | 251 | Then you can protect your routes using middleware rules: 252 | 253 | ```php 254 | Route::group(['middleware' => ['enforcer:articles,read']], function () { 255 | // pass 256 | }); 257 | ``` 258 | 259 | #### HTTP Request Middleware ( RESTful is also supported ) 260 | 261 | If you need to authorize a Request,you need to define the model configuration first in `config/lauthz-rbac-model.conf`: 262 | 263 | ```ini 264 | [request_definition] 265 | r = sub, obj, act 266 | 267 | [policy_definition] 268 | p = sub, obj, act 269 | 270 | [role_definition] 271 | g = _, _ 272 | 273 | [policy_effect] 274 | e = some(where (p.eft == allow)) 275 | 276 | [matchers] 277 | m = g(r.sub, p.sub) && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act) 278 | ``` 279 | 280 | Then, using middleware rules: 281 | 282 | ```php 283 | Route::group(['middleware' => ['http_request']], function () { 284 | Route::resource('photo', 'PhotoController'); 285 | }); 286 | ``` 287 | 288 | ### Using Gates 289 | 290 | You can use Laravel Gates to check if a user has a permission, provided that you have set an existing user instance as the currently authenticated user. 291 | 292 | ```php 293 | $user->can('articles,read'); 294 | // For multiple enforcers 295 | $user->can('articles,read', 'second'); 296 | // The methods cant, cannot, canAny, etc. also work 297 | ``` 298 | 299 | If you require custom Laravel Gates, you can disable the automatic registration by setting `enabled_register_at_gates` to `false` in the lauthz file. After that, you can use `Gates::before` or `Gates::after` in your ServiceProvider to register custom Gates. See [Gates](https://laravel.com/docs/11.x/authorization#gates) for more details. 300 | 301 | ### Multiple enforcers 302 | 303 | If you need multiple permission controls in your project, you can configure multiple enforcers. 304 | 305 | In the lauthz file, it should be like this: 306 | 307 | ```php 308 | return [ 309 | 'default' => 'basic', 310 | 311 | 'basic' => [ 312 | 'model' => [ 313 | // ... 314 | ], 315 | 316 | 'adapter' => Lauthz\Adapters\DatabaseAdapter::class, 317 | // ... 318 | ], 319 | 320 | 'second' => [ 321 | 'model' => [ 322 | // ... 323 | ], 324 | 325 | 'adapter' => Lauthz\Adapters\DatabaseAdapter::class, 326 | // ... 327 | ], 328 | ]; 329 | 330 | ``` 331 | 332 | Then you can choose which enforcers to use. 333 | 334 | ```php 335 | Enforcer::guard('second')->enforce("eve", "articles", "edit"); 336 | ``` 337 | 338 | 339 | ### Using artisan commands 340 | 341 | You can create a policy from a console with artisan commands. 342 | 343 | To user: 344 | 345 | ```bash 346 | php artisan policy:add eve,articles,read 347 | ``` 348 | 349 | To Role: 350 | 351 | ```bash 352 | php artisan policy:add writer,articles,edit 353 | ``` 354 | 355 | Adds a role for a user: 356 | 357 | ```bash 358 | php artisan role:assign eve writer 359 | # Specify the ptype of the role assignment by using the --ptype option. 360 | php artisan role:assign eve writer --ptype=g2 361 | ``` 362 | 363 | ### Using cache 364 | 365 | Authorization rules are cached to speed up performance. The default is off. 366 | 367 | Sets your own cache configs in Laravel's `config/lauthz.php`. 368 | 369 | ```php 370 | 'cache' => [ 371 | // changes whether Lauthz will cache the rules. 372 | 'enabled' => false, 373 | 374 | // cache store 375 | 'store' => 'default', 376 | 377 | // cache Key 378 | 'key' => 'rules', 379 | 380 | // ttl \DateTimeInterface|\DateInterval|int|null 381 | 'ttl' => 24 * 60, 382 | ], 383 | ``` 384 | 385 | ## Thinks 386 | 387 | [Casbin](https://github.com/php-casbin/php-casbin) in Laravel. You can find the full documentation of Casbin [on the website](https://casbin.org/). 388 | 389 | ## License 390 | 391 | This project is licensed under the [Apache 2.0 license](LICENSE). 392 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "casbin/laravel-authz", 3 | "keywords": [ 4 | "laravel", 5 | "casbin", 6 | "permission", 7 | "access-control", 8 | "authorization", 9 | "rbac", 10 | "acl", 11 | "abac", 12 | "authz" 13 | ], 14 | "description": "An authorization library that supports access control models like ACL, RBAC, ABAC in Laravel. ", 15 | "authors": [ 16 | { 17 | "name": "TechLee", 18 | "email": "techlee@qq.com" 19 | } 20 | ], 21 | "license": "Apache-2.0", 22 | "require": { 23 | "php": ">=8.0", 24 | "illuminate/support": "~8.0|~9.0|~10.0|~11.0|~12.0", 25 | "illuminate/database": "~8.0|~9.0|~10.0|~11.0|~12.0", 26 | "illuminate/console": "~8.0|~9.0|~10.0|~11.0|~12.0", 27 | "casbin/casbin": "~4.0" 28 | }, 29 | "require-dev": { 30 | "phpunit/phpunit": "~9.0|~10.5|^11.5.3", 31 | "php-coveralls/php-coveralls": "^2.7", 32 | "mockery/mockery": "^1.0", 33 | "laravel/laravel": "~9.0|~10.0|~11.0|~12.0" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Lauthz\\": "src/" 38 | } 39 | }, 40 | "autoload-dev": { 41 | "psr-4": { 42 | "Lauthz\\Tests\\": "tests/" 43 | } 44 | }, 45 | "extra": { 46 | "laravel": { 47 | "providers": [ 48 | "Lauthz\\LauthzServiceProvider" 49 | ], 50 | "aliases": { 51 | "Enforcer": "Lauthz\\Facades\\Enforcer" 52 | } 53 | } 54 | }, 55 | "config": { 56 | "allow-plugins": { 57 | "kylekatarnls/update-helper": true 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /config/lauthz-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 -------------------------------------------------------------------------------- /config/lauthz.php: -------------------------------------------------------------------------------- 1 | 'basic', 8 | 9 | /* 10 | * Lauthz Localizer 11 | */ 12 | 'localizer' => [ 13 | // changes whether enforcer will register at gates. 14 | 'enabled_register_at_gates' => true 15 | ], 16 | 17 | 'basic' => [ 18 | /* 19 | * Casbin model setting. 20 | */ 21 | 'model' => [ 22 | // Available Settings: "file", "text", "url" 23 | 'config_type' => 'file', 24 | 25 | 'config_file_path' => __DIR__ . DIRECTORY_SEPARATOR . 'lauthz-rbac-model.conf', 26 | 27 | 'config_text' => '', 28 | 29 | 'config_url' => '' 30 | ], 31 | 32 | /* 33 | * Casbin adapter . 34 | */ 35 | 'adapter' => Lauthz\Adapters\DatabaseAdapter::class, 36 | 37 | /* 38 | * Database setting. 39 | */ 40 | 'database' => [ 41 | // Database connection for following tables. 42 | 'connection' => '', 43 | 44 | // Rule table name. 45 | 'rules_table' => 'rules', 46 | ], 47 | 48 | 'log' => [ 49 | // changes whether Lauthz will log messages to the Logger. 50 | 'enabled' => false, 51 | 52 | // Casbin Logger, Supported: \Psr\Log\LoggerInterface|string 53 | 'logger' => 'log', 54 | ], 55 | 56 | 'cache' => [ 57 | // changes whether Lauthz will cache the rules. 58 | 'enabled' => false, 59 | 60 | // cache store 61 | 'store' => 'default', 62 | 63 | // cache Key 64 | 'key' => 'rules', 65 | 66 | // ttl \DateTimeInterface|\DateInterval|int|null 67 | 'ttl' => 24 * 60, 68 | ], 69 | ], 70 | ]; 71 | -------------------------------------------------------------------------------- /database/migrations/2019_03_01_000000_create_rules_table.php: -------------------------------------------------------------------------------- 1 | create(config('lauthz.basic.database.rules_table'), function (Blueprint $table) { 15 | $table->increments('id'); 16 | $table->string('ptype')->nullable(); 17 | $table->string('v0')->nullable(); 18 | $table->string('v1')->nullable(); 19 | $table->string('v2')->nullable(); 20 | $table->string('v3')->nullable(); 21 | $table->string('v4')->nullable(); 22 | $table->string('v5')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down() 31 | { 32 | $connection = config('lauthz.basic.database.connection') ?: config('database.default'); 33 | Schema::connection($connection)->dropIfExists(config('lauthz.basic.database.rules_table')); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.10.xml: -------------------------------------------------------------------------------- 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 | 42 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | 16 | ./src 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/Adapters/DatabaseAdapter.php: -------------------------------------------------------------------------------- 1 | eloquent = $eloquent; 47 | } 48 | 49 | /** 50 | * Filter the rule. 51 | * 52 | * @param array $rule 53 | * @return array 54 | */ 55 | public function filterRule(array $rule): array 56 | { 57 | $rule = array_values($rule); 58 | 59 | $i = count($rule) - 1; 60 | for (; $i >= 0; $i--) { 61 | if ($rule[$i] != '' && !is_null($rule[$i])) { 62 | break; 63 | } 64 | } 65 | 66 | return array_slice($rule, 0, $i + 1); 67 | } 68 | 69 | /** 70 | * savePolicyLine function. 71 | * 72 | * @param string $ptype 73 | * @param array $rule 74 | */ 75 | public function savePolicyLine(string $ptype, array $rule): void 76 | { 77 | $col['ptype'] = $ptype; 78 | foreach ($rule as $key => $value) { 79 | $col['v'.strval($key)] = $value; 80 | } 81 | 82 | $this->eloquent->create($col); 83 | } 84 | 85 | /** 86 | * loads all policy rules from the storage. 87 | * 88 | * @param Model $model 89 | */ 90 | public function loadPolicy(Model $model): void 91 | { 92 | $rows = $this->eloquent->getAllFromCache(); 93 | 94 | foreach ($rows as $row) { 95 | $this->loadPolicyArray($this->filterRule($row), $model); 96 | } 97 | } 98 | 99 | /** 100 | * saves all policy rules to the storage. 101 | * 102 | * @param Model $model 103 | */ 104 | public function savePolicy(Model $model): void 105 | { 106 | foreach ($model['p'] as $ptype => $ast) { 107 | foreach ($ast->policy as $rule) { 108 | $this->savePolicyLine($ptype, $rule); 109 | } 110 | } 111 | 112 | foreach ($model['g'] as $ptype => $ast) { 113 | foreach ($ast->policy as $rule) { 114 | $this->savePolicyLine($ptype, $rule); 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * adds a policy rule to the storage. 121 | * This is part of the Auto-Save feature. 122 | * 123 | * @param string $sec 124 | * @param string $ptype 125 | * @param array $rule 126 | */ 127 | public function addPolicy(string $sec, string $ptype, array $rule): void 128 | { 129 | $this->savePolicyLine($ptype, $rule); 130 | } 131 | 132 | /** 133 | * Adds a policy rules to the storage. 134 | * This is part of the Auto-Save feature. 135 | * 136 | * @param string $sec 137 | * @param string $ptype 138 | * @param string[][] $rules 139 | */ 140 | public function addPolicies(string $sec, string $ptype, array $rules): void 141 | { 142 | $cols = []; 143 | $i = 0; 144 | 145 | foreach($rules as $rule) { 146 | $temp['ptype'] = $ptype; 147 | $temp['created_at'] = new DateTime(); 148 | $temp['updated_at'] = $temp['created_at']; 149 | foreach ($rule as $key => $value) { 150 | $temp['v'.strval($key)] = $value; 151 | } 152 | $cols[$i++] = $temp ?? []; 153 | $temp = []; 154 | } 155 | $this->eloquent->insert($cols); 156 | Rule::fireModelEvent('saved'); 157 | } 158 | 159 | /** 160 | * This is part of the Auto-Save feature. 161 | * 162 | * @param string $sec 163 | * @param string $ptype 164 | * @param array $rule 165 | */ 166 | public function removePolicy(string $sec, string $ptype, array $rule): void 167 | { 168 | $instance = $this->eloquent->where('ptype', $ptype); 169 | 170 | foreach ($rule as $key => $value) { 171 | $instance->where('v'.strval($key), $value); 172 | } 173 | 174 | $instance->delete(); 175 | Rule::fireModelEvent('deleted'); 176 | } 177 | 178 | /** 179 | * Removes policy rules from the storage. 180 | * This is part of the Auto-Save feature. 181 | * 182 | * @param string $sec 183 | * @param string $ptype 184 | * @param string[][] $rules 185 | */ 186 | public function removePolicies(string $sec, string $ptype, array $rules): void 187 | { 188 | $this->eloquent->getConnection()->transaction(function () use ($sec, $rules, $ptype) { 189 | foreach ($rules as $rule) { 190 | $this->removePolicy($sec, $ptype, $rule); 191 | } 192 | }); 193 | } 194 | 195 | /** 196 | * @param string $sec 197 | * @param string $ptype 198 | * @param int $fieldIndex 199 | * @param string|null ...$fieldValues 200 | * @return array 201 | * @throws Throwable 202 | */ 203 | public function _removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, ?string ...$fieldValues): array 204 | { 205 | $removedRules = []; 206 | $instance = $this->eloquent->where('ptype', $ptype); 207 | 208 | foreach (range(0, 5) as $value) { 209 | if ($fieldIndex <= $value && $value < $fieldIndex + count($fieldValues)) { 210 | if ('' != $fieldValues[$value - $fieldIndex]) { 211 | $instance->where('v' . strval($value), $fieldValues[$value - $fieldIndex]); 212 | } 213 | } 214 | } 215 | 216 | $oldP = $instance->get()->makeHidden(['created_at','updated_at', 'id', 'ptype'])->toArray(); 217 | foreach ($oldP as &$item) { 218 | $item = $this->filterRule($item); 219 | $removedRules[] = $item; 220 | } 221 | 222 | $instance->delete(); 223 | Rule::fireModelEvent('deleted'); 224 | 225 | return $removedRules; 226 | } 227 | 228 | /** 229 | * RemoveFilteredPolicy removes policy rules that match the filter from the storage. 230 | * This is part of the Auto-Save feature. 231 | * 232 | * @param string $sec 233 | * @param string $ptype 234 | * @param int $fieldIndex 235 | * @param string|null ...$fieldValues 236 | * @return void 237 | */ 238 | public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, ?string ...$fieldValues): void 239 | { 240 | $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); 241 | } 242 | 243 | /** 244 | * Updates a policy rule from storage. 245 | * This is part of the Auto-Save feature. 246 | * 247 | * @param string $sec 248 | * @param string $ptype 249 | * @param string[] $oldRule 250 | * @param string[] $newPolicy 251 | */ 252 | public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newPolicy): void 253 | { 254 | $instance = $this->eloquent->where('ptype', $ptype); 255 | foreach($oldRule as $k => $v) { 256 | $instance->where('v' . $k, $v); 257 | } 258 | $instance = $instance->first(); 259 | if (!$instance) { 260 | return; 261 | } 262 | 263 | $update = []; 264 | foreach($newPolicy as $k => $v) { 265 | $update['v' . $k] = $v; 266 | } 267 | $instance->update($update); 268 | Rule::fireModelEvent('saved'); 269 | } 270 | 271 | /** 272 | * UpdatePolicies updates some policy rules to storage, like db, redis. 273 | * 274 | * @param string $sec 275 | * @param string $ptype 276 | * @param string[][] $oldRules 277 | * @param string[][] $newRules 278 | * @return void 279 | */ 280 | public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void 281 | { 282 | $this->eloquent->getConnection()->transaction(function () use ($sec, $ptype, $oldRules, $newRules) { 283 | foreach ($oldRules as $i => $oldRule) { 284 | $this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]); 285 | } 286 | }); 287 | } 288 | 289 | /** 290 | * UpdateFilteredPolicies deletes old rules and adds new rules. 291 | * 292 | * @param string $sec 293 | * @param string $ptype 294 | * @param array $newPolicies 295 | * @param integer $fieldIndex 296 | * @param string ...$fieldValues 297 | * @return array 298 | */ 299 | public function updateFilteredPolicies(string $sec, string $ptype, array $newPolicies, int $fieldIndex, string ...$fieldValues): array 300 | { 301 | $oldRules = []; 302 | $this->eloquent->getConnection()->transaction(function () use ($sec, $ptype, $fieldIndex, $fieldValues, $newPolicies, &$oldRules) { 303 | $oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); 304 | $this->addPolicies($sec, $ptype, $newPolicies); 305 | }); 306 | return $oldRules; 307 | } 308 | 309 | /** 310 | * Loads only policy rules that match the filter. 311 | * 312 | * @param Model $model 313 | * @param mixed $filter 314 | */ 315 | public function loadFilteredPolicy(Model $model, $filter): void 316 | { 317 | $instance = $this->eloquent; 318 | 319 | if (is_string($filter)) { 320 | $instance = $instance->whereRaw($filter); 321 | } else if ($filter instanceof Filter) { 322 | foreach($filter->p as $k => $v) { 323 | $where[$v] = $filter->g[$k]; 324 | $instance = $instance->where($v, $filter->g[$k]); 325 | } 326 | } else if ($filter instanceof \Closure) { 327 | $instance = $instance->where($filter); 328 | } else { 329 | throw new InvalidFilterTypeException('invalid filter type'); 330 | } 331 | $rows = $instance->get()->makeHidden(['created_at','updated_at', 'id'])->toArray(); 332 | foreach ($rows as $row) { 333 | $row = array_filter($row, static fn($value): bool => !is_null($value) && $value !== ''); 334 | $line = implode(', ', array_filter($row, static fn ($val): bool => '' != $val && !is_null($val))); 335 | $this->loadPolicyLine(trim($line), $model); 336 | } 337 | $this->setFiltered(true); 338 | } 339 | 340 | /** 341 | * Returns true if the loaded policy has been filtered. 342 | * 343 | * @return bool 344 | */ 345 | public function isFiltered(): bool 346 | { 347 | return $this->filtered; 348 | } 349 | 350 | /** 351 | * Sets filtered parameter. 352 | * 353 | * @param bool $filtered 354 | */ 355 | public function setFiltered(bool $filtered): void 356 | { 357 | $this->filtered = $filtered; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /src/Commands/GroupAdd.php: -------------------------------------------------------------------------------- 1 | argument('policy')); 36 | array_walk($params, static function (&$value): void { 37 | $value = trim($value); 38 | }); 39 | $ret = Enforcer::addGroupingPolicy(...$params); 40 | if ($ret) { 41 | $this->info('Grouping `' . implode(', ', $params) . '` created'); 42 | } else { 43 | $this->error('Grouping `' . implode(', ', $params) . '` creation failed'); 44 | } 45 | 46 | return $ret ? 0 : 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Commands/PolicyAdd.php: -------------------------------------------------------------------------------- 1 | argument('policy')); 36 | array_walk($params, static function (&$value): void { 37 | $value = trim($value); 38 | }); 39 | $ret = Enforcer::addPolicy(...$params); 40 | if ($ret) { 41 | $this->info('Policy `'.implode(', ', $params).'` created'); 42 | } else { 43 | $this->error('Policy `'.implode(', ', $params).'` creation failed'); 44 | } 45 | 46 | return $ret ? 0 : 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Commands/RoleAssign.php: -------------------------------------------------------------------------------- 1 | argument('user'); 38 | $role = $this->argument('role'); 39 | $ptype = $this->option('ptype') ?: 'g'; 40 | 41 | $ret = Enforcer::addNamedGroupingPolicy($ptype, $user, $role); 42 | if ($ret) { 43 | $this->info('Added `'.$role.'` role to `'.$user.'` successfully'); 44 | } else { 45 | $this->error('Added `'.$role.'` role to `'.$user.'` failed'); 46 | } 47 | 48 | return $ret ? 0 : 1; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Contracts/BatchDatabaseAdapter.php: -------------------------------------------------------------------------------- 1 | app = $app; 27 | } 28 | 29 | /** 30 | * Register the localizer based on the configuration. 31 | */ 32 | public function register() 33 | { 34 | if ($this->app->config->get('lauthz.localizer.enabled_register_at_gates')) { 35 | $this->registerAtGate(); 36 | } 37 | } 38 | 39 | /** 40 | * Register the localizer at the gate. 41 | */ 42 | protected function registerAtGate() 43 | { 44 | $this->app->make(Gate::class)->before(function (Authorizable $user, string $ability, array $guards) { 45 | /** @var \Illuminate\Contracts\Auth\Authenticatable $user */ 46 | $identifier = $user->getAuthIdentifier(); 47 | if (method_exists($user, 'getAuthzIdentifier')) { 48 | /** @var \Lauthz\Tests\Models\User $user */ 49 | $identifier = $user->getAuthzIdentifier(); 50 | } 51 | $identifier = strval($identifier); 52 | $ability = explode(',', $ability); 53 | if (empty($guards)) { 54 | return Enforcer::enforce($identifier, ...$ability); 55 | } 56 | 57 | foreach ($guards as $guard) { 58 | return Enforcer::guard($guard)->enforce($identifier, ...$ability); 59 | } 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/EnforcerManager.php: -------------------------------------------------------------------------------- 1 | app = $app; 43 | } 44 | 45 | /** 46 | * Attempt to get the enforcer from the local cache. 47 | * 48 | * @param string $name 49 | * 50 | * @return \Casbin\Enforcer 51 | * 52 | * @throws \InvalidArgumentException 53 | */ 54 | public function guard($name = null) 55 | { 56 | $name = $name ?: $this->getDefaultGuard(); 57 | 58 | if (!isset($this->guards[$name])) { 59 | $this->guards[$name] = $this->resolve($name); 60 | } 61 | 62 | return $this->guards[$name]; 63 | } 64 | 65 | /** 66 | * Resolve the given guard. 67 | * 68 | * @param string $name 69 | * 70 | * @return \Casbin\Enforcer 71 | * 72 | * @throws \InvalidArgumentException 73 | */ 74 | protected function resolve($name) 75 | { 76 | $config = $this->getConfig($name); 77 | 78 | if (is_null($config)) { 79 | throw new InvalidArgumentException("Enforcer [{$name}] is not defined."); 80 | } 81 | 82 | if ($logger = Arr::get($config, 'log.logger')) { 83 | if (is_string($logger)) { 84 | $logger = new DefaultLogger($this->app->make($logger)); 85 | } 86 | 87 | Log::setLogger($logger); 88 | } 89 | 90 | $model = new Model(); 91 | $loader = $this->app->make(ModelLoaderManager::class); 92 | $loader->initFromConfig($config); 93 | $loader->loadModel($model); 94 | 95 | $adapter = Arr::get($config, 'adapter'); 96 | if (!is_null($adapter)) { 97 | $adapter = $this->app->make($adapter, [ 98 | 'eloquent' => new Rule([], $name), 99 | ]); 100 | } 101 | 102 | return new Enforcer($model, $adapter, $logger, Arr::get($config, 'log.enabled', false)); 103 | } 104 | 105 | /** 106 | * Get the lauthz driver configuration. 107 | * 108 | * @param string $name 109 | * 110 | * @return array 111 | */ 112 | protected function getConfig($name) 113 | { 114 | return $this->app['config']["lauthz.{$name}"]; 115 | } 116 | 117 | /** 118 | * Get the default enforcer guard name. 119 | * 120 | * @return string 121 | */ 122 | public function getDefaultGuard() 123 | { 124 | return $this->app['config']['lauthz.default']; 125 | } 126 | 127 | /** 128 | * Set the default guard driver the factory should serve. 129 | * 130 | * @param string $name 131 | */ 132 | public function shouldUse($name) 133 | { 134 | $name = $name ?: $this->getDefaultGuard(); 135 | 136 | $this->setDefaultGuard($name); 137 | } 138 | 139 | /** 140 | * Set the default authorization guard name. 141 | * 142 | * @param string $name 143 | */ 144 | public function setDefaultGuard($name) 145 | { 146 | $this->app['config']['lauthz.default'] = $name; 147 | } 148 | 149 | /** 150 | * Dynamically call the default driver instance. 151 | * 152 | * @param string $method 153 | * @param array $parameters 154 | * 155 | * @return mixed 156 | */ 157 | public function __call($method, $parameters) 158 | { 159 | return $this->guard()->{$method}(...$parameters); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/Exceptions/UnauthorizedException.php: -------------------------------------------------------------------------------- 1 | app->runningInConsole()) { 19 | $this->publishes([__DIR__ . '/../database/migrations' => database_path('migrations')], 'laravel-lauthz-migrations'); 20 | $this->publishes([ 21 | __DIR__ . '/../config/lauthz-rbac-model.conf' => $this->app->basePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . ('lauthz-rbac-model.conf'), 22 | __DIR__ . '/../config/lauthz.php' => $this->app->basePath() . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . ('lauthz.php'), 23 | ], 'laravel-lauthz-config'); 24 | 25 | $this->commands([ 26 | Commands\GroupAdd::class, 27 | Commands\PolicyAdd::class, 28 | Commands\RoleAssign::class, 29 | ]); 30 | } 31 | 32 | $this->mergeConfigFrom(__DIR__ . '/../config/lauthz.php', 'lauthz'); 33 | 34 | $this->bootObserver(); 35 | 36 | $this->registerLocalizer(); 37 | } 38 | 39 | /** 40 | * Boot Observer. 41 | * 42 | * @return void 43 | */ 44 | protected function bootObserver() 45 | { 46 | Rule::observe(new RuleObserver()); 47 | } 48 | 49 | /** 50 | * Register bindings in the container. 51 | */ 52 | public function register() 53 | { 54 | $this->app->singleton('enforcer', fn ($app) => new EnforcerManager($app)); 55 | 56 | $this->app->singleton(ModelLoaderManager::class, fn ($app) => new ModelLoaderManager($app)); 57 | 58 | $this->app->singleton(EnforcerLocalizer::class, fn ($app) => new EnforcerLocalizer($app)); 59 | } 60 | 61 | /** 62 | * Register a gate that allows users to use Laravel's built-in Gate to call Enforcer. 63 | * 64 | * @return void 65 | */ 66 | protected function registerLocalizer() 67 | { 68 | $this->app->make(EnforcerLocalizer::class)->register(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Loaders/FileLoader.php: -------------------------------------------------------------------------------- 1 | filePath = Arr::get($config, 'model.config_file_path', ''); 26 | } 27 | 28 | /** 29 | * Loads model from file. 30 | * 31 | * @param Model $model 32 | * @return void 33 | * @throws \Casbin\Exceptions\CasbinException 34 | */ 35 | public function loadModel(Model $model): void 36 | { 37 | $model->loadModel($this->filePath); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Loaders/ModelLoaderManager.php: -------------------------------------------------------------------------------- 1 | extend()`. 16 | * 17 | * Built-in loader implementations include: 18 | * - FileLoader: For loading model from file. 19 | * - TextLoader: Suitable for model defined as a multi-line string. 20 | * - UrlLoader: Handles model loading from URL. 21 | * 22 | * To utilize a built-in or custom loader, set 'model.config_type' in the configuration to match one of the above types. 23 | */ 24 | class ModelLoaderManager extends Manager 25 | { 26 | 27 | /** 28 | * The array of the lauthz driver configuration. 29 | * 30 | * @var array 31 | */ 32 | protected $config; 33 | 34 | /** 35 | * Initialize configuration for the loader manager instance. 36 | * 37 | * @param array $config the lauthz driver configuration. 38 | */ 39 | public function initFromConfig(array $config) 40 | { 41 | $this->config = $config; 42 | } 43 | 44 | /** 45 | * Get the default driver from the configuration. 46 | * 47 | * @return string The default driver name. 48 | */ 49 | public function getDefaultDriver() 50 | { 51 | return Arr::get($this->config, 'model.config_type', ''); 52 | } 53 | 54 | /** 55 | * Create a new TextLoader instance. 56 | * 57 | * @return TextLoader 58 | */ 59 | public function createTextDriver() 60 | { 61 | return new TextLoader($this->config); 62 | } 63 | 64 | /** 65 | * Create a new UrlLoader instance. 66 | * 67 | * @return UrlLoader 68 | */ 69 | public function createUrlDriver() 70 | { 71 | return new UrlLoader($this->config); 72 | } 73 | 74 | /** 75 | * Create a new FileLoader instance. 76 | * 77 | * @return FileLoader 78 | */ 79 | public function createFileDriver() 80 | { 81 | return new FileLoader($this->config); 82 | } 83 | 84 | /** 85 | * Create a new driver instance. 86 | * 87 | * @param string $driver 88 | * @return mixed 89 | * 90 | * @throws \InvalidArgumentException 91 | */ 92 | protected function createDriver($driver) 93 | { 94 | if(empty($driver)) { 95 | throw new InvalidArgumentException('Unsupported empty model loader type.'); 96 | } 97 | 98 | if (isset($this->customCreators[$driver])) { 99 | return $this->callCustomCreator($driver); 100 | } 101 | $method = 'create' . Str::studly($driver) . 'Driver'; 102 | if (method_exists($this, $method)) { 103 | return $this->$method(); 104 | } 105 | 106 | throw new InvalidArgumentException("Unsupported model loader type: {$driver}."); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Loaders/TextLoader.php: -------------------------------------------------------------------------------- 1 | text = Arr::get($config, 'model.config_text', ''); 26 | } 27 | 28 | /** 29 | * Loads model from text. 30 | * 31 | * @param Model $model 32 | * @return void 33 | * @throws \Casbin\Exceptions\CasbinException 34 | */ 35 | public function loadModel(Model $model): void 36 | { 37 | $model->loadModelFromText($this->text); 38 | } 39 | } -------------------------------------------------------------------------------- /src/Loaders/UrlLoader.php: -------------------------------------------------------------------------------- 1 | url = Arr::get($config, 'model.config_url', ''); 27 | } 28 | 29 | /** 30 | * Loads model from remote url. 31 | * 32 | * @param Model $model 33 | * @return void 34 | * @throws \Casbin\Exceptions\CasbinException 35 | * @throws RuntimeException 36 | */ 37 | public function loadModel(Model $model): void 38 | { 39 | $contextOptions = [ 40 | 'http' => [ 41 | 'method' => 'GET', 42 | 'header' => "Accept: text/plain\r\n", 43 | 'timeout' => 3 44 | ] 45 | ]; 46 | 47 | $context = stream_context_create($contextOptions); 48 | $response = @file_get_contents($this->url, false, $context); 49 | if ($response === false) { 50 | $error = error_get_last(); 51 | throw new RuntimeException( 52 | "Failed to fetch remote model " . $this->url . ": " . $error['message'] 53 | ); 54 | } 55 | 56 | $model->loadModelFromText($response); 57 | } 58 | } -------------------------------------------------------------------------------- /src/Middlewares/EnforcerMiddleware.php: -------------------------------------------------------------------------------- 1 | getAuthIdentifier(); 33 | if (method_exists($user, 'getAuthzIdentifier')) { 34 | /** @var \Lauthz\Tests\Models\User $user */ 35 | $identifier = $user->getAuthzIdentifier(); 36 | } 37 | $identifier = strval($identifier); 38 | 39 | if (!Enforcer::enforce($identifier, ...$args)) { 40 | throw new UnauthorizedException(); 41 | } 42 | 43 | return $next($request); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Middlewares/RequestMiddleware.php: -------------------------------------------------------------------------------- 1 | authorize($request, $guards); 32 | 33 | return $next($request); 34 | } 35 | 36 | /** 37 | * Determine if the user is authorized in to any of the given guards. 38 | * 39 | * @param \Illuminate\Http\Request $request 40 | * @param array $guards 41 | * 42 | * @throws \Lauthz\Exceptions\UnauthorizedException 43 | */ 44 | protected function authorize(Request $request, array $guards) 45 | { 46 | $user = Auth::user(); 47 | $identifier = $user->getAuthIdentifier(); 48 | if (method_exists($user, 'getAuthzIdentifier')) { 49 | $identifier = $user->getAuthzIdentifier(); 50 | } 51 | $identifier = strval($identifier); 52 | 53 | if (empty($guards)) { 54 | if (Enforcer::enforce($identifier, $request->getPathInfo(), $request->method())) { 55 | return; 56 | } 57 | } 58 | 59 | foreach ($guards as $guard) { 60 | if (Enforcer::guard($guard)->enforce($identifier, $request->getPathInfo(), $request->method())) { 61 | return Enforcer::shouldUse($guard); 62 | } 63 | } 64 | 65 | throw new UnauthorizedException(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Models/Rule.php: -------------------------------------------------------------------------------- 1 | guard = $guard; 43 | if (!$guard) { 44 | $this->guard = config('lauthz.default'); 45 | } 46 | 47 | $connection = $this->config('database.connection') ?: config('database.default'); 48 | 49 | $this->setConnection($connection); 50 | $this->setTable($this->config('database.rules_table')); 51 | 52 | parent::__construct($attributes); 53 | 54 | $this->initCache(); 55 | } 56 | 57 | /** 58 | * Gets rules from caches. 59 | * 60 | * @return mixed 61 | */ 62 | public function getAllFromCache() 63 | { 64 | $get = fn () => $this->select('ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5')->get()->toArray(); 65 | if (!$this->config('cache.enabled', false)) { 66 | return $get(); 67 | } 68 | 69 | return $this->store->remember($this->config('cache.key'), $this->config('cache.ttl'), $get); 70 | } 71 | 72 | /** 73 | * Refresh Cache. 74 | */ 75 | public function refreshCache() 76 | { 77 | if (!$this->config('cache.enabled', false)) { 78 | return; 79 | } 80 | 81 | $this->forgetCache(); 82 | $this->getAllFromCache(); 83 | } 84 | 85 | /** 86 | * Forget Cache. 87 | */ 88 | public function forgetCache() 89 | { 90 | $this->store->forget($this->config('cache.key')); 91 | } 92 | 93 | /** 94 | * Init cache. 95 | */ 96 | protected function initCache() 97 | { 98 | $store = $this->config('cache.store', 'default'); 99 | $store = 'default' == $store ? null : $store; 100 | $this->store = Cache::store($store); 101 | } 102 | 103 | /** 104 | * Gets config value by key. 105 | * 106 | * @param string $key 107 | * @param string $default 108 | * 109 | * @return mixed 110 | */ 111 | protected function config($key = null, $default = null) 112 | { 113 | return config('lauthz.'.$this->guard.'.'.$key, $default); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Observers/RuleObserver.php: -------------------------------------------------------------------------------- 1 | refreshCache(); 12 | } 13 | 14 | public function deleted(Rule $rule) 15 | { 16 | $rule->refreshCache(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Commands/GroupAddTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(Enforcer::hasGroupingPolicy('eve', 'writer', 'domain')); 17 | 18 | $exitCode = Artisan::call('group:add', ['policy' => 'eve, writer, domain']); 19 | $this->assertTrue(0 === $exitCode); 20 | $this->assertTrue(Enforcer::hasGroupingPolicy('eve', 'writer', 'domain')); 21 | 22 | $exitCode = Artisan::call('group:add', ['policy' => 'eve, writer, domain']); 23 | $this->assertFalse(0 === $exitCode); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Commands/PolicyAddTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(Enforcer::enforce('eve', 'articles', 'read')); 17 | $exitCode = Artisan::call('policy:add', ['policy' => 'eve, articles, read']); 18 | $this->assertTrue(0 === $exitCode); 19 | $this->assertTrue(Enforcer::enforce('eve', 'articles', 'read')); 20 | 21 | $exitCode = Artisan::call('policy:add', ['policy' => 'eve, articles, read']); 22 | $this->assertFalse(0 === $exitCode); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/Commands/RoleAssignTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(Enforcer::hasRoleForUser('eve', 'writer')); 18 | $exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer']); 19 | $this->assertTrue(0 === $exitCode); 20 | $exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer']); 21 | $this->assertFalse(0 === $exitCode); 22 | $this->assertTrue(Enforcer::hasRoleForUser('eve', 'writer')); 23 | 24 | $model = Model::newModel(); 25 | $model->addDef('r', 'r', 'sub, obj, act'); 26 | $model->addDef('p', 'p', 'sub, obj, act'); 27 | $model->addDef('g', 'g', '_, _'); 28 | $model->addDef('g', 'g2', '_, _'); 29 | $model->addDef('e', 'e', 'some(where (p.eft == allow))'); 30 | $model->addDef('m', 'm', 'g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.act'); 31 | Enforcer::setModel($model); 32 | Enforcer::loadPolicy(); 33 | $this->assertFalse(Enforcer::hasNamedGroupingPolicy('g2', 'eve', 'writer')); 34 | $exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer', '--ptype' => 'g2']); 35 | $this->assertTrue(0 === $exitCode); 36 | $exitCode = Artisan::call('role:assign', ['user' => 'eve', 'role' => 'writer', '--ptype' => 'g2']); 37 | $this->assertFalse(0 === $exitCode); 38 | $this->assertTrue(Enforcer::hasNamedGroupingPolicy('g2', 'eve', 'writer')); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/DatabaseAdapterForCacheTest.php: -------------------------------------------------------------------------------- 1 | enableCache(); 17 | $this->assertFalse(Enforcer::enforce('eve', 'data3', 'read')); 18 | Enforcer::addPermissionForUser('eve', 'data3', 'read'); 19 | $this->refreshPolicies(); 20 | $this->assertTrue(Enforcer::enforce('eve', 'data3', 'read')); 21 | } 22 | 23 | public function testAddPolicies() 24 | { 25 | $this->enableCache(); 26 | $policies = [ 27 | ['u1', 'd1', 'read'], 28 | ['u2', 'd2', 'read'], 29 | ['u3', 'd3', 'read'], 30 | ]; 31 | $this->refreshPolicies(); 32 | Rule::truncate(); 33 | Enforcer::addPolicies($policies); 34 | $this->refreshPolicies(); 35 | $this->assertEquals($policies, Enforcer::getPolicy()); 36 | } 37 | 38 | public function testSavePolicy() 39 | { 40 | $this->enableCache(); 41 | $this->assertFalse(Enforcer::enforce('alice', 'data4', 'read')); 42 | 43 | $model = Enforcer::getModel(); 44 | $model->clearPolicy(); 45 | $model->addPolicy('p', 'p', ['alice', 'data4', 'read']); 46 | 47 | $adapter = Enforcer::getAdapter(); 48 | $adapter->savePolicy($model); 49 | $this->refreshPolicies(); 50 | $this->assertTrue(Enforcer::enforce('alice', 'data4', 'read')); 51 | } 52 | 53 | public function testRemovePolicy() 54 | { 55 | $this->enableCache(); 56 | $this->assertFalse(Enforcer::enforce('alice', 'data5', 'read')); 57 | 58 | Enforcer::addPermissionForUser('alice', 'data5', 'read'); 59 | $this->refreshPolicies(); 60 | $this->assertTrue(Enforcer::enforce('alice', 'data5', 'read')); 61 | 62 | Enforcer::deletePermissionForUser('alice', 'data5', 'read'); 63 | $this->refreshPolicies(); 64 | $this->assertFalse(Enforcer::enforce('alice', 'data5', 'read')); 65 | } 66 | 67 | public function testRemovePolicies() 68 | { 69 | $this->enableCache(); 70 | $this->assertEquals([ 71 | ['alice', 'data1', 'read'], 72 | ['bob', 'data2', 'write'], 73 | ['data2_admin', 'data2', 'read'], 74 | ['data2_admin', 'data2', 'write'], 75 | ], Enforcer::getPolicy()); 76 | 77 | Enforcer::removePolicies([ 78 | ['data2_admin', 'data2', 'read'], 79 | ['data2_admin', 'data2', 'write'], 80 | ]); 81 | $this->refreshPolicies(); 82 | $this->assertEquals([ 83 | ['alice', 'data1', 'read'], 84 | ['bob', 'data2', 'write'] 85 | ], Enforcer::getPolicy()); 86 | } 87 | 88 | public function testRemoveFilteredPolicy() 89 | { 90 | $this->enableCache(); 91 | $this->assertTrue(Enforcer::enforce('alice', 'data1', 'read')); 92 | Enforcer::removeFilteredPolicy(1, 'data1'); 93 | $this->refreshPolicies(); 94 | $this->assertFalse(Enforcer::enforce('alice', 'data1', 'read')); 95 | $this->assertTrue(Enforcer::enforce('bob', 'data2', 'write')); 96 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'read')); 97 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'write')); 98 | Enforcer::removeFilteredPolicy(1, 'data2', 'read'); 99 | $this->refreshPolicies(); 100 | $this->assertTrue(Enforcer::enforce('bob', 'data2', 'write')); 101 | $this->assertFalse(Enforcer::enforce('alice', 'data2', 'read')); 102 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'write')); 103 | Enforcer::removeFilteredPolicy(2, 'write'); 104 | $this->refreshPolicies(); 105 | $this->assertFalse(Enforcer::enforce('bob', 'data2', 'write')); 106 | $this->assertFalse(Enforcer::enforce('alice', 'data2', 'write')); 107 | } 108 | 109 | public function testUpdatePolicy() 110 | { 111 | $this->enableCache(); 112 | $this->assertEquals([ 113 | ['alice', 'data1', 'read'], 114 | ['bob', 'data2', 'write'], 115 | ['data2_admin', 'data2', 'read'], 116 | ['data2_admin', 'data2', 'write'], 117 | ], Enforcer::getPolicy()); 118 | 119 | Enforcer::updatePolicy( 120 | ['alice', 'data1', 'read'], 121 | ['alice', 'data1', 'write'] 122 | ); 123 | 124 | Enforcer::updatePolicy( 125 | ['bob', 'data2', 'write'], 126 | ['bob', 'data2', 'read'] 127 | ); 128 | $this->refreshPolicies(); 129 | $this->assertEquals([ 130 | ['alice', 'data1', 'write'], 131 | ['bob', 'data2', 'read'], 132 | ['data2_admin', 'data2', 'read'], 133 | ['data2_admin', 'data2', 'write'], 134 | ], Enforcer::getPolicy()); 135 | } 136 | 137 | protected function refreshPolicies() 138 | { 139 | Enforcer::loadPolicy(); 140 | } 141 | 142 | protected function enableCache() 143 | { 144 | $this->app['config']->set('lauthz.basic.cache.enabled', true); 145 | } 146 | 147 | } 148 | -------------------------------------------------------------------------------- /tests/DatabaseAdapterTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(Enforcer::enforce('alice', 'data1', 'read')); 17 | 18 | $this->assertFalse(Enforcer::enforce('bob', 'data1', 'read')); 19 | $this->assertTrue(Enforcer::enforce('bob', 'data2', 'write')); 20 | 21 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'read')); 22 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'write')); 23 | } 24 | 25 | public function testAddPolicy() 26 | { 27 | $this->assertFalse(Enforcer::enforce('eve', 'data3', 'read')); 28 | Enforcer::addPermissionForUser('eve', 'data3', 'read'); 29 | $this->assertTrue(Enforcer::enforce('eve', 'data3', 'read')); 30 | } 31 | 32 | public function testAddPolicies() 33 | { 34 | $policies = [ 35 | ['u1', 'd1', 'read'], 36 | ['u2', 'd2', 'read'], 37 | ['u3', 'd3', 'read'], 38 | ]; 39 | Enforcer::clearPolicy(); 40 | $this->initTable(); 41 | $this->assertEquals([], Enforcer::getPolicy()); 42 | Enforcer::addPolicies($policies); 43 | $this->assertEquals($policies, Enforcer::getPolicy()); 44 | } 45 | 46 | public function testSavePolicy() 47 | { 48 | $this->assertFalse(Enforcer::enforce('alice', 'data4', 'read')); 49 | 50 | $model = Enforcer::getModel(); 51 | $model->clearPolicy(); 52 | $model->addPolicy('p', 'p', ['alice', 'data4', 'read']); 53 | 54 | $adapter = Enforcer::getAdapter(); 55 | $adapter->savePolicy($model); 56 | $this->assertTrue(Enforcer::enforce('alice', 'data4', 'read')); 57 | } 58 | 59 | public function testRemovePolicy() 60 | { 61 | $this->assertFalse(Enforcer::enforce('alice', 'data5', 'read')); 62 | 63 | Enforcer::addPermissionForUser('alice', 'data5', 'read'); 64 | $this->assertTrue(Enforcer::enforce('alice', 'data5', 'read')); 65 | 66 | Enforcer::deletePermissionForUser('alice', 'data5', 'read'); 67 | $this->assertFalse(Enforcer::enforce('alice', 'data5', 'read')); 68 | } 69 | 70 | public function testRemovePolicies() 71 | { 72 | $this->assertEquals([ 73 | ['alice', 'data1', 'read'], 74 | ['bob', 'data2', 'write'], 75 | ['data2_admin', 'data2', 'read'], 76 | ['data2_admin', 'data2', 'write'], 77 | ], Enforcer::getPolicy()); 78 | 79 | Enforcer::removePolicies([ 80 | ['data2_admin', 'data2', 'read'], 81 | ['data2_admin', 'data2', 'write'], 82 | ]); 83 | 84 | $this->assertEquals([ 85 | ['alice', 'data1', 'read'], 86 | ['bob', 'data2', 'write'] 87 | ], Enforcer::getPolicy()); 88 | } 89 | 90 | public function testRemoveFilteredPolicy() 91 | { 92 | $this->assertTrue(Enforcer::enforce('alice', 'data1', 'read')); 93 | Enforcer::removeFilteredPolicy(1, 'data1'); 94 | $this->assertFalse(Enforcer::enforce('alice', 'data1', 'read')); 95 | $this->assertTrue(Enforcer::enforce('bob', 'data2', 'write')); 96 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'read')); 97 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'write')); 98 | Enforcer::removeFilteredPolicy(1, 'data2', 'read'); 99 | $this->assertTrue(Enforcer::enforce('bob', 'data2', 'write')); 100 | $this->assertFalse(Enforcer::enforce('alice', 'data2', 'read')); 101 | $this->assertTrue(Enforcer::enforce('alice', 'data2', 'write')); 102 | Enforcer::removeFilteredPolicy(2, 'write'); 103 | $this->assertFalse(Enforcer::enforce('bob', 'data2', 'write')); 104 | $this->assertFalse(Enforcer::enforce('alice', 'data2', 'write')); 105 | } 106 | 107 | public function testUpdatePolicy() 108 | { 109 | $this->assertEquals([ 110 | ['alice', 'data1', 'read'], 111 | ['bob', 'data2', 'write'], 112 | ['data2_admin', 'data2', 'read'], 113 | ['data2_admin', 'data2', 'write'], 114 | ], Enforcer::getPolicy()); 115 | 116 | Enforcer::updatePolicy( 117 | ['alice', 'data1', 'read'], 118 | ['alice', 'data1', 'write'] 119 | ); 120 | 121 | Enforcer::updatePolicy( 122 | ['bob', 'data2', 'write'], 123 | ['bob', 'data2', 'read'] 124 | ); 125 | 126 | $this->assertEquals([ 127 | ['alice', 'data1', 'write'], 128 | ['bob', 'data2', 'read'], 129 | ['data2_admin', 'data2', 'read'], 130 | ['data2_admin', 'data2', 'write'], 131 | ], Enforcer::getPolicy()); 132 | } 133 | 134 | public function testUpdatePolicies() 135 | { 136 | $this->assertEquals([ 137 | ['alice', 'data1', 'read'], 138 | ['bob', 'data2', 'write'], 139 | ['data2_admin', 'data2', 'read'], 140 | ['data2_admin', 'data2', 'write'], 141 | ], Enforcer::getPolicy()); 142 | 143 | $oldPolicies = [ 144 | ['alice', 'data1', 'read'], 145 | ['bob', 'data2', 'write'] 146 | ]; 147 | $newPolicies = [ 148 | ['alice', 'data1', 'write'], 149 | ['bob', 'data2', 'read'] 150 | ]; 151 | 152 | Enforcer::updatePolicies($oldPolicies, $newPolicies); 153 | 154 | $this->assertEquals([ 155 | ['alice', 'data1', 'write'], 156 | ['bob', 'data2', 'read'], 157 | ['data2_admin', 'data2', 'read'], 158 | ['data2_admin', 'data2', 'write'], 159 | ], Enforcer::getPolicy()); 160 | } 161 | 162 | public function arrayEqualsWithoutOrder(array $expected, array $actual) 163 | { 164 | if (method_exists($this, 'assertEqualsCanonicalizing')) { 165 | $this->assertEqualsCanonicalizing($expected, $actual); 166 | } else { 167 | array_multisort($expected); 168 | array_multisort($actual); 169 | $this->assertEquals($expected, $actual); 170 | } 171 | } 172 | 173 | public function testUpdateFilteredPolicies() 174 | { 175 | $this->assertEquals([ 176 | ['alice', 'data1', 'read'], 177 | ['bob', 'data2', 'write'], 178 | ['data2_admin', 'data2', 'read'], 179 | ['data2_admin', 'data2', 'write'], 180 | ], Enforcer::getPolicy()); 181 | 182 | Enforcer::updateFilteredPolicies([["alice", "data1", "write"]], 0, "alice", "data1", "read"); 183 | Enforcer::updateFilteredPolicies([["bob", "data2", "read"]], 0, "bob", "data2", "write"); 184 | 185 | $policies = [ 186 | ['alice', 'data1', 'write'], 187 | ['bob', 'data2', 'read'], 188 | ['data2_admin', 'data2', 'read'], 189 | ['data2_admin', 'data2', 'write'] 190 | ]; 191 | 192 | $this->arrayEqualsWithoutOrder($policies, Enforcer::getPolicy()); 193 | 194 | // test use updateFilteredPolicies to update all policies of a user 195 | $this->initTable(); 196 | Enforcer::loadPolicy(); 197 | $policies = [ 198 | ['alice', 'data2', 'write'], 199 | ['bob', 'data1', 'read'] 200 | ]; 201 | Enforcer::addPolicies($policies); 202 | 203 | $this->arrayEqualsWithoutOrder([ 204 | ['alice', 'data1', 'read'], 205 | ['bob', 'data2', 'write'], 206 | ['data2_admin', 'data2', 'read'], 207 | ['data2_admin', 'data2', 'write'], 208 | ['alice', 'data2', 'write'], 209 | ['bob', 'data1', 'read'] 210 | ], Enforcer::getPolicy()); 211 | 212 | Enforcer::updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice'); 213 | Enforcer::updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob'); 214 | 215 | $policies = [ 216 | ['alice', 'data1', 'write'], 217 | ['alice', 'data2', 'read'], 218 | ['bob', 'data1', 'write'], 219 | ['bob', 'data2', 'read'], 220 | ['data2_admin', 'data2', 'read'], 221 | ['data2_admin', 'data2', 'write'] 222 | ]; 223 | 224 | $this->arrayEqualsWithoutOrder($policies, Enforcer::getPolicy()); 225 | 226 | // test if $fieldValues contains empty string 227 | $this->initTable(); 228 | Enforcer::loadPolicy(); 229 | $policies = [ 230 | ['alice', 'data2', 'write'], 231 | ['bob', 'data1', 'read'] 232 | ]; 233 | Enforcer::addPolicies($policies); 234 | 235 | $this->assertEquals([ 236 | ['alice', 'data1', 'read'], 237 | ['bob', 'data2', 'write'], 238 | ['data2_admin', 'data2', 'read'], 239 | ['data2_admin', 'data2', 'write'], 240 | ['alice', 'data2', 'write'], 241 | ['bob', 'data1', 'read'] 242 | ], Enforcer::getPolicy()); 243 | 244 | Enforcer::updateFilteredPolicies([['alice', 'data1', 'write'], ['alice', 'data2', 'read']], 0, 'alice', '', ''); 245 | Enforcer::updateFilteredPolicies([['bob', 'data1', 'write'], ["bob", "data2", "read"]], 0, 'bob', '', ''); 246 | 247 | $policies = [ 248 | ['alice', 'data1', 'write'], 249 | ['alice', 'data2', 'read'], 250 | ['bob', 'data1', 'write'], 251 | ['bob', 'data2', 'read'], 252 | ['data2_admin', 'data2', 'read'], 253 | ['data2_admin', 'data2', 'write'] 254 | ]; 255 | 256 | $this->arrayEqualsWithoutOrder($policies, Enforcer::getPolicy()); 257 | 258 | // test if $fieldIndex is not zero 259 | $this->initTable(); 260 | Enforcer::loadPolicy(); 261 | $policies = [ 262 | ['alice', 'data2', 'write'], 263 | ['bob', 'data1', 'read'] 264 | ]; 265 | Enforcer::addPolicies($policies); 266 | 267 | $this->assertEquals([ 268 | ['alice', 'data1', 'read'], 269 | ['bob', 'data2', 'write'], 270 | ['data2_admin', 'data2', 'read'], 271 | ['data2_admin', 'data2', 'write'], 272 | ['alice', 'data2', 'write'], 273 | ['bob', 'data1', 'read'] 274 | ], Enforcer::getPolicy()); 275 | 276 | Enforcer::updateFilteredPolicies([['alice', 'data1', 'write'], ['bob', 'data1', 'write']], 2, 'read'); 277 | Enforcer::updateFilteredPolicies([['alice', 'data2', 'read'], ["bob", "data2", "read"]], 2, 'write'); 278 | 279 | $policies = [ 280 | ['alice', 'data2', 'read'], 281 | ['bob', 'data2', 'read'], 282 | ]; 283 | 284 | $this->arrayEqualsWithoutOrder($policies, Enforcer::getPolicy()); 285 | } 286 | 287 | public function testLoadFilteredPolicy() 288 | { 289 | $this->initTable(); 290 | Enforcer::clearPolicy(); 291 | $this->initConfig(); 292 | $adapter = Enforcer::getAdapter(); 293 | $adapter->setFiltered(true); 294 | $this->assertEquals([], Enforcer::getPolicy()); 295 | 296 | // invalid filter type 297 | try { 298 | $filter = ['alice', 'data1', 'read']; 299 | Enforcer::loadFilteredPolicy($filter); 300 | $e = InvalidFilterTypeException::class; 301 | $this->fail("Expected exception $e not thrown"); 302 | } catch (InvalidFilterTypeException $e) { 303 | $this->assertEquals("invalid filter type", $e->getMessage()); 304 | } 305 | 306 | // string 307 | $filter = "v0 = 'bob'"; 308 | Enforcer::loadFilteredPolicy($filter); 309 | $this->assertEquals([ 310 | ['bob', 'data2', 'write'] 311 | ], Enforcer::getPolicy()); 312 | 313 | // Filter 314 | $filter = new Filter(['v2'], ['read']); 315 | Enforcer::loadFilteredPolicy($filter); 316 | $this->assertEquals([ 317 | ['alice', 'data1', 'read'], 318 | ['data2_admin', 'data2', 'read'], 319 | ], Enforcer::getPolicy()); 320 | 321 | // Closure 322 | Enforcer::loadFilteredPolicy(function ($query) { 323 | $query->where('v1', 'data1'); 324 | }); 325 | 326 | $this->assertEquals([ 327 | ['alice', 'data1', 'read'], 328 | ], Enforcer::getPolicy()); 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /tests/EnforcerCustomLocalizerTest.php: -------------------------------------------------------------------------------- 1 | user("alice"); 14 | $this->assertFalse($user->can('data3,read')); 15 | 16 | app(Gate::class)->before(function () { 17 | return true; 18 | }); 19 | 20 | $this->assertTrue($user->can('data3,read')); 21 | } 22 | 23 | public function testCustomRegisterAtGatesDefine() 24 | { 25 | $user = $this->user("alice"); 26 | $this->assertFalse($user->can('data3,read')); 27 | 28 | app(Gate::class)->define('data3,read', function () { 29 | return true; 30 | }); 31 | 32 | $this->assertTrue($user->can('data3,read')); 33 | } 34 | 35 | public function initConfig() 36 | { 37 | parent::initConfig(); 38 | $this->app['config']->set('lauthz.localizer.enabled_register_at_gates', false); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/EnforcerLocalizerTest.php: -------------------------------------------------------------------------------- 1 | user('alice'); 15 | $this->assertTrue($user->can('data1,read')); 16 | $this->assertFalse($user->can('data1,write')); 17 | $this->assertFalse($user->cannot('data2,read')); 18 | 19 | Enforcer::guard('second')->addPolicy('alice', 'data1', 'read'); 20 | $this->assertTrue($user->can('data1,read', 'second')); 21 | $this->assertFalse($user->can('data3,read', 'second')); 22 | } 23 | 24 | public function testNotLogin() 25 | { 26 | $this->assertFalse(app(Gate::class)->allows('data1,read')); 27 | $this->assertTrue(app(Gate::class)->forUser($this->user('alice'))->allows('data1,read')); 28 | $this->assertFalse(app(Gate::class)->forUser($this->user('bob'))->allows('data1,read')); 29 | } 30 | 31 | public function testAfterLogin() 32 | { 33 | $this->login('alice'); 34 | $this->assertTrue(app(Gate::class)->allows('data1,read')); 35 | $this->assertTrue(app(Gate::class)->allows('data2,read')); 36 | $this->assertTrue(app(Gate::class)->allows('data2,write')); 37 | 38 | $this->login('bob'); 39 | $this->assertFalse(app(Gate::class)->allows('data1,read')); 40 | $this->assertTrue(app(Gate::class)->allows('data2,write')); 41 | } 42 | 43 | public function initConfig() 44 | { 45 | parent::initConfig(); 46 | $this->app['config']->set('lauthz.second.model.config_type', 'text'); 47 | $this->app['config']->set( 48 | 'lauthz.second.model.config_text', 49 | $this->getModelText() 50 | ); 51 | } 52 | 53 | protected function getModelText(): string 54 | { 55 | return <<assertEquals($this->middleware('data1', 'read'), 'Unauthorized Exception'); 16 | } 17 | 18 | public function testAfterLogin() 19 | { 20 | $this->login('alice'); 21 | $this->assertEquals($this->middleware('data1', 'read'), 200); 22 | $this->assertEquals($this->middleware('data2', 'read'), 200); 23 | $this->assertEquals($this->middleware('data2', 'write'), 200); 24 | 25 | $this->login('bob'); 26 | $this->assertEquals($this->middleware('data1', 'read'), 'Unauthorized Exception'); 27 | $this->assertEquals($this->middleware('data2', 'write'), 200); 28 | } 29 | 30 | protected function middleware(...$args) 31 | { 32 | return parent::runMiddleware(EnforcerMiddleware::class, new Request(), ...$args); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/ModelLoaderTest.php: -------------------------------------------------------------------------------- 1 | initUrlConfig(); 16 | 17 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 18 | 19 | Enforcer::addPolicy('data_admin', 'data', 'read'); 20 | Enforcer::addRoleForUser('alice', 'data_admin'); 21 | $this->assertTrue(Enforcer::enforce('alice', 'data', 'read')); 22 | } 23 | 24 | public function testTextLoader(): void 25 | { 26 | $this->initTextConfig(); 27 | 28 | Enforcer::addPolicy('data_admin', 'data', 'read'); 29 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 30 | $this->assertTrue(Enforcer::enforce('data_admin', 'data', 'read')); 31 | } 32 | 33 | public function testFileLoader(): void 34 | { 35 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 36 | 37 | Enforcer::addPolicy('data_admin', 'data', 'read'); 38 | Enforcer::addRoleForUser('alice', 'data_admin'); 39 | $this->assertTrue(Enforcer::enforce('alice', 'data', 'read')); 40 | } 41 | 42 | public function testCustomLoader(): void 43 | { 44 | $this->initCustomConfig(); 45 | Enforcer::guard('second')->addPolicy('data_admin', 'data', 'read'); 46 | $this->assertFalse(Enforcer::guard('second')->enforce('alice', 'data', 'read')); 47 | $this->assertTrue(Enforcer::guard('second')->enforce('data_admin', 'data', 'read')); 48 | } 49 | 50 | public function testMultipleLoader(): void 51 | { 52 | $this->testFileLoader(); 53 | $this->testCustomLoader(); 54 | } 55 | 56 | public function testEmptyModel(): void 57 | { 58 | Enforcer::shouldUse('third'); 59 | $this->expectException(InvalidArgumentException::class); 60 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 61 | } 62 | 63 | public function testEmptyLoaderType(): void 64 | { 65 | $this->app['config']->set('lauthz.basic.model.config_type', ''); 66 | $this->expectException(InvalidArgumentException::class); 67 | 68 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 69 | } 70 | 71 | public function testNotExistLoaderType(): void 72 | { 73 | $this->app['config']->set('lauthz.basic.model.config_type', 'not_exist'); 74 | $this->expectException(InvalidArgumentException::class); 75 | 76 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 77 | } 78 | 79 | public function testBadUrlConnection(): void 80 | { 81 | $this->initUrlConfig(); 82 | $this->app['config']->set('lauthz.basic.model.config_url', 'http://filenoexists'); 83 | $this->expectException(RuntimeException::class); 84 | 85 | $this->assertFalse(Enforcer::enforce('alice', 'data', 'read')); 86 | } 87 | 88 | protected function initUrlConfig(): void 89 | { 90 | $this->app['config']->set('lauthz.basic.model.config_type', 'url'); 91 | $this->app['config']->set( 92 | 'lauthz.basic.model.config_url', 93 | 'https://raw.githubusercontent.com/casbin/casbin/master/examples/rbac_model.conf' 94 | ); 95 | } 96 | 97 | protected function initTextConfig(): void 98 | { 99 | $this->app['config']->set('lauthz.basic.model.config_type', 'text'); 100 | $this->app['config']->set( 101 | 'lauthz.basic.model.config_text', 102 | $this->getModelText() 103 | ); 104 | } 105 | 106 | protected function initCustomConfig(): void 107 | { 108 | $this->app['config']->set('lauthz.second.model.config_type', 'custom'); 109 | $this->app['config']->set( 110 | 'lauthz.second.model.config_text', 111 | $this->getModelText() 112 | ); 113 | 114 | $config = $this->app['config']->get('lauthz.second'); 115 | $loader = $this->app->make(ModelLoaderManager::class); 116 | 117 | $loader->extend('custom', function () use ($config) { 118 | return new \Lauthz\Loaders\TextLoader($config); 119 | }); 120 | } 121 | 122 | protected function getModelText(): string 123 | { 124 | return <<name; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/RequestMiddlewareTest.php: -------------------------------------------------------------------------------- 1 | assertEquals($this->middleware('/foo', 'GET'), 'Unauthorized Exception'); 18 | } 19 | 20 | public function testAfterLogin() 21 | { 22 | $this->login('alice'); 23 | $this->assertEquals($this->middleware(Request::create('/foo', 'GET')), 200); 24 | $this->assertEquals($this->middleware(Request::create('/foo/1', 'GET')), 200); 25 | $this->assertEquals($this->middleware(Request::create('/foo', 'POST')), 200); 26 | $this->assertEquals($this->middleware(Request::create('/foo/1', 'PUT')), 200); 27 | $this->assertEquals($this->middleware(Request::create('/foo/1', 'DELETE')), 200); 28 | 29 | $this->assertEquals($this->middleware(Request::create('/foo/2', 'GET')), 200); 30 | $this->assertEquals($this->middleware(Request::create('/foo/2', 'PUT')), 200); 31 | $this->assertEquals($this->middleware(Request::create('/foo/2', 'DELETE')), 200); 32 | 33 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'GET')), 200); 34 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'POST')), 200); 35 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'PUT')), 'Unauthorized Exception'); 36 | 37 | $this->assertEquals($this->middleware(Request::create('/proxy', 'GET')), 'Unauthorized Exception'); 38 | 39 | Enforcer::guard('second')->addPolicy('alice', '/foo1/*', '(GET|POST)'); 40 | 41 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'GET'), 'second'), 200); 42 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'POST'), 'second'), 200); 43 | $this->assertEquals($this->middleware(Request::create('/foo1/123', 'PUT'), 'second'), 'Unauthorized Exception'); 44 | 45 | $this->assertEquals($this->middleware(Request::create('/proxy', 'GET'), 'second'), 'Unauthorized Exception'); 46 | } 47 | 48 | protected function middleware($request, ...$guards) 49 | { 50 | return parent::runMiddleware(RequestMiddleware::class, $request, ...$guards); 51 | } 52 | 53 | protected function initConfig() 54 | { 55 | parent::initConfig(); 56 | $this->app['config']->set('lauthz.basic.model.config_type', 'text'); 57 | $text = <<<'EOT' 58 | [request_definition] 59 | r = sub, obj, act 60 | 61 | [policy_definition] 62 | p = sub, obj, act 63 | 64 | [role_definition] 65 | g = _, _ 66 | 67 | [policy_effect] 68 | e = some(where (p.eft == allow)) 69 | 70 | [matchers] 71 | m = g(r.sub, p.sub) && r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act) 72 | EOT; 73 | $this->app['config']->set('lauthz.basic.model.config_text', $text); 74 | $this->app['config']->set('lauthz.second.model.config_type', 'text'); 75 | $this->app['config']->set('lauthz.second.model.config_text', $text); 76 | } 77 | 78 | protected function initTable() 79 | { 80 | Rule::truncate(); 81 | 82 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo', 'v2' => 'GET']); 83 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo/:id', 'v2' => 'GET']); 84 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo', 'v2' => 'POST']); 85 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo/:id', 'v2' => 'PUT']); 86 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo/:id', 'v2' => 'DELETE']); 87 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => '/foo1/*', 'v2' => '(GET)|(POST)']); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /tests/RuleCacheTest.php: -------------------------------------------------------------------------------- 1 | enableCache(); 16 | 17 | DB::connection()->enableQueryLog(); 18 | 19 | app(Rule::class)->forgetCache(); 20 | 21 | app(Rule::class)->getAllFromCache(); 22 | $this->assertCount(1, DB::getQueryLog()); 23 | 24 | app(Rule::class)->getAllFromCache(); 25 | $this->assertCount(1, DB::getQueryLog()); 26 | 27 | DB::flushQueryLog(); 28 | app(Rule::class)->getAllFromCache(); 29 | $this->assertCount(0, DB::getQueryLog()); 30 | 31 | $rule = Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => 'data1', 'v2' => 'read']); 32 | app(Rule::class)->getAllFromCache(); 33 | $this->assertCount(2, DB::getQueryLog()); 34 | 35 | $rule->delete(); 36 | app(Rule::class)->getAllFromCache(); 37 | app(Rule::class)->getAllFromCache(); 38 | $this->assertCount(4, DB::getQueryLog()); 39 | 40 | DB::flushQueryLog(); 41 | } 42 | 43 | public function testDisableCache() 44 | { 45 | $this->app['config']->set('lauthz.basic.cache.enabled', false); 46 | 47 | DB::connection()->enableQueryLog(); 48 | app(Rule::class)->getAllFromCache(); 49 | $this->assertCount(1, DB::getQueryLog()); 50 | 51 | $rule = Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => 'data1', 'v2' => 'read']); 52 | app(Rule::class)->getAllFromCache(); 53 | $this->assertCount(3, DB::getQueryLog()); 54 | 55 | $rule->delete(); 56 | app(Rule::class)->getAllFromCache(); 57 | app(Rule::class)->getAllFromCache(); 58 | $this->assertCount(6, DB::getQueryLog()); 59 | 60 | DB::flushQueryLog(); 61 | } 62 | 63 | protected function enableCache() 64 | { 65 | $this->app['config']->set('lauthz.basic.cache.enabled', true); 66 | } 67 | 68 | protected function initTable() 69 | { 70 | Rule::truncate(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | app = require __DIR__.'/../vendor/laravel/laravel/bootstrap/app.php'; 23 | 24 | $this->app->booting(function () { 25 | $loader = \Illuminate\Foundation\AliasLoader::getInstance(); 26 | $loader->alias('Enforcer', \Lauthz\Facades\Enforcer::class); 27 | }); 28 | 29 | $this->app->make(Kernel::class)->bootstrap(); 30 | $this->initConfig(); 31 | 32 | $this->app->register(\Lauthz\LauthzServiceProvider::class); 33 | 34 | $this->artisan('vendor:publish', ['--provider' => 'Lauthz\LauthzServiceProvider']); 35 | $this->artisan('migrate', ['--force' => true]); 36 | 37 | $this->afterApplicationCreated(function () { 38 | $this->initTable(); 39 | }); 40 | 41 | return $this->app; 42 | } 43 | 44 | protected function initConfig() 45 | { 46 | $this->app['config']->set('database.default', 'mysql'); 47 | $this->app['config']->set('database.connections.mysql.charset', 'utf8'); 48 | $this->app['config']->set('database.connections.mysql.collation', 'utf8_unicode_ci'); 49 | $this->app['config']->set('cache.default', 'array'); 50 | // $app['config']->set('lauthz.log.enabled', true); 51 | } 52 | 53 | protected function initTable() 54 | { 55 | Rule::truncate(); 56 | 57 | Rule::create(['ptype' => 'p', 'v0' => 'alice', 'v1' => 'data1', 'v2' => 'read']); 58 | Rule::create(['ptype' => 'p', 'v0' => 'bob', 'v1' => 'data2', 'v2' => 'write']); 59 | 60 | Rule::create(['ptype' => 'p', 'v0' => 'data2_admin', 'v1' => 'data2', 'v2' => 'read']); 61 | Rule::create(['ptype' => 'p', 'v0' => 'data2_admin', 'v1' => 'data2', 'v2' => 'write']); 62 | Rule::create(['ptype' => 'g', 'v0' => 'alice', 'v1' => 'data2_admin']); 63 | } 64 | 65 | protected function runMiddleware($middleware, $request, ...$args) 66 | { 67 | $middleware = $this->app->make($middleware); 68 | try { 69 | return $middleware->handle($request, function () { 70 | return (new Response())->setContent(''); 71 | }, ...$args)->status(); 72 | } catch (UnauthorizedException $e) { 73 | return 'Unauthorized Exception'; 74 | } 75 | 76 | return 'Exception'; 77 | } 78 | 79 | protected function login($name) 80 | { 81 | Auth::login($this->user($name)); 82 | } 83 | 84 | protected function user($name) 85 | { 86 | $user = new User(); 87 | $user->name = $name; 88 | 89 | return $user; 90 | } 91 | } 92 | --------------------------------------------------------------------------------