.gitignore000064400000000077152475272420006552 0ustar00/vendor/ composer.lock .idea/ *.iml # coverage report /build.travis.yml000064400000000334152475272420006667 0ustar00language: php php: - 5.6 - 7.0 - 7.1 - 7.2 install: - composer install --prefer-dist --dev --no-interaction script: - mkdir -p build/logs - phpunit after_script: - travis_retry vendor/bin/php-coveralls -vLICENSE000064400000026135152475272420005572 0ustar00 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. README.md000064400000032044152475272420006040 0ustar00PHP-Casbin ==== [![StyleCI](https://github.styleci.io/repos/153135401/shield?branch=master)](https://github.styleci.io/repos/153135401) [![Build Status](https://travis-ci.org/php-casbin/php-casbin.svg?branch=master)](https://travis-ci.org/php-casbin/php-casbin) [![Coverage Status](https://coveralls.io/repos/github/php-casbin/php-casbin/badge.svg)](https://coveralls.io/github/php-casbin/php-casbin) [![Latest Stable Version](https://poser.pugx.org/casbin/casbin/v/stable)](https://packagist.org/packages/casbin/casbin) [![Total Downloads](https://poser.pugx.org/casbin/casbin/downloads)](https://packagist.org/packages/casbin/casbin) [![License](https://poser.pugx.org/casbin/casbin/license)](https://packagist.org/packages/casbin/casbin) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) [中文文档](https://github.com/php-casbin/php-casbin/blob/master/README_CN.md) **PHP-Casbin** is a powerful and efficient open-source access control library for PHP projects. It provides support for enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model). ## All the languages supported by Casbin: [![golang](https://casbin.org/docs/assets/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/docs/assets/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/docs/assets/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/docs/assets/langs/php.png)](https://github.com/php-casbin/php-casbin) ----|----|----|---- [Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin) | [PHP-Casbin](https://github.com/php-casbin/php-casbin) production-ready | production-ready | production-ready | production-ready ## Installation Require this package in the `composer.json` of your project. This will download the package: ``` composer require casbin/casbin ``` ## Get started 1. New a Casbin enforcer with a model file and a policy file: ```php require_once './vendor/autoload.php'; use Casbin\Enforcer; $e = new Enforcer("path/to/model.conf", "path/to/policy.csv"); ``` 2. Add an enforcement hook into your code right before the access happens: ```php $sub = "alice"; // the user that wants to access a resource. $obj = "data1"; // the resource that is going to be accessed. $act = "read"; // the operation that the user performs on the resource. if ($e->enforce($sub, $obj, $act) === true) { // permit alice to read data1 } else { // deny the request, show an error } ``` ## Table of contents - [Supported models](#supported-models) - [How it works?](#how-it-works) - [Features](#features) - [Documentation](#documentation) - [Online editor](#online-editor) - [Tutorials](#tutorials) - [Policy management](#policy-management) - [Policy persistence](#policy-persistence) - [Role manager](#role-manager) - [Examples](#examples) - [Our adopters](#our-adopters) ## Supported models 1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list) 2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)** 3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins. 3. **ACL without resources**: some scenarios may target for a type of resources instead of an individual resource by using permissions like ``write-article``, ``read-log``. It doesn't control the access to a specific article or log. 4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)** 5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time. 6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants. 7. **[ABAC (Attribute-Based Access Control)](https://en.wikipedia.org/wiki/Attribute-Based_Access_Control)**: syntax sugar like ``resource.Owner`` can be used to get the attribute for a resource. 8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``. 9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow. 10. **Priority**: the policy rules can be prioritized like firewall rules. ## How it works? In php-casbin, an access control model is abstracted into a CONF file based on the **PERM metamodel (Policy, Effect, Request, Matchers)**. So switching or upgrading the authorization mechanism for a project is just as simple as modifying a configuration. You can customize your own access control model by combining the available models. For example, you can get RBAC roles and ABAC attributes together inside one model and share one set of policy rules. The most basic and simplest model in php-casbin is ACL. ACL's model CONF is: ```ini # Request definition [request_definition] r = sub, obj, act # Policy definition [policy_definition] p = sub, obj, act # Policy effect [policy_effect] e = some(where (p.eft == allow)) # Matchers [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act ``` An example policy for ACL model is like: ``` p, alice, data1, read p, bob, data2, write ``` It means: - alice can read data1 - bob can write data2 ## Features What php-casbin does: 1. enforce the policy in the classic ``{subject, object, action}`` form or a customized form as you defined, both allow and deny authorizations are supported. 2. handle the storage of the access control model and its policy. 3. manage the role-user mappings and role-role mappings (aka role hierarchy in RBAC). 4. support built-in superuser like ``root`` or ``administrator``. A superuser can do anything without explict permissions. 5. multiple built-in operators to support the rule matching. For example, ``keyMatch`` can map a resource key ``/foo/bar`` to the pattern ``/foo*``. What php-casbin does NOT do: 1. authentication (aka verify ``username`` and ``password`` when a user logs in) 2. manage the list of users or roles. I believe it's more convenient for the project itself to manage these entities. Users usually have their passwords, and php-casbin is not designed as a password container. However, php-casbin stores the user-role mapping for the RBAC scenario. ## Documentation https://casbin.org/docs/en/overview ## Online editor You can also use the online editor (http://casbin.org/editor/) to write your php-casbin model and policy in your web browser. It provides functionality such as ``syntax highlighting`` and ``code completion``, just like an IDE for a programming language. ## Tutorials https://casbin.org/docs/en/tutorials ## Policy management php-casbin provides two sets of APIs to manage permissions: - [Management API](https://github.com/php-casbin/php-casbin/blob/master/src/ManagementApi.php): the primitive API that provides full support for php-casbin policy management. See [here](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/ManagementApiTest.php) for examples. - [RBAC API](https://github.com/php-casbin/php-casbin/blob/master/src/RbacApi.php): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code. See [here](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/RbacApiTest.php) for examples. We also provide a web-based UI for model management and policy management: ![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png) ![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png) ## Policy persistence In php-casbin, the policy storage is implemented as an adapter (aka middleware for php-casbin). To keep light-weight, we don't put adapter code in the main library (except the default file adapter). A complete list of php-casbin adapters is provided as below. Any 3rd-party contribution on a new adapter is welcomed, please inform us and I will put it in this list:) Adapter | Type | Author | Description ----|------|----|---- [File Adapter (built-in)](https://casbin.org/docs/en/policy-storage#file-adapter-built-in) | File | php-casbin | Persistence for [.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) files [Database Adapter](https://github.com/php-casbin/database-adapter) | Database | php-casbin | MySQL, PostgreSQL, SQLite, Microsoft SQL Server are supported by Database Adapter For details of adapters, please refer to the documentation: https://casbin.org/docs/en/policy-storage ## Role manager The role manager is used to manage the RBAC role hierarchy (user-role mapping) in php-casbin. A role manager can retrieve the role data from php-casbin policy rules or external sources such as LDAP, Okta, Auth0, Azure AD, etc. We support different implementations of a role manager. To keep light-weight, we don't put role manager code in the main library (except the default role manager). A complete list of php-casbin role managers is provided as below. Any 3rd-party contribution on a new role manager is welcomed, please inform us and I will put it in this list:) Role manager | Author | Description ----|----|---- [Default Role Manager (built-in)](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) | php-casbin | Supports role hierarchy stored in php-casbin policy For developers: all role managers must implement the [RoleManager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/RoleManager.php) interface. [Default Role Manager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) can be used as a reference implementation. ## Examples Model | Model file | Policy file ----|------|---- ACL | [basic_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL with superuser | [basic_model_with_root.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL without users | [basic_model_without_users.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_policy.csv) ACL without resources | [basic_model_without_resources.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_policy.csv) RBAC | [rbac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_policy.csv) RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_policy.csv) ABAC | [abac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/abac_model.conf) | N/A RESTful | [keymatch_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_policy.csv) Deny-override | [rbac_model_with_deny.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_policy.csv) Priority | [priority_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_policy.csv) ## Our adopters ### Web frameworks - [Laravel](https://laravel.com/): The PHP framework for web artisans, via plugin: [laravel-casbin](https://github.com/php-casbin/laravel-casbin) - [Yii PHP Framework](https://www.yiiframework.com/): A fast, secure, and efficient PHP framework, via plugin: [yii-casbin](https://github.com/php-casbin/yii-casbin) - [ThinkPHP](http://www.thinkphp.cn/): The ThinkPHP framework, via plugin: [think-casbin](https://github.com/php-casbin/think-casbin) ## License This project is licensed under the [Apache 2.0 license](LICENSE). ## Contact If you have any issues or feature requests, please contact us. PR is welcomed. - https://github.com/php-casbin/php-casbin/issues - techlee@qq.com - Tencent QQ group: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885) README_CN.md000064400000031563152475272420006425 0ustar00PHP-Casbin ==== [![StyleCI](https://github.styleci.io/repos/153135401/shield?branch=master)](https://github.styleci.io/repos/153135401) [![Build Status](https://travis-ci.org/php-casbin/php-casbin.svg?branch=master)](https://travis-ci.org/php-casbin/php-casbin) [![Coverage Status](https://coveralls.io/repos/github/php-casbin/php-casbin/badge.svg)](https://coveralls.io/github/php-casbin/php-casbin) [![Latest Stable Version](https://poser.pugx.org/casbin/casbin/v/stable)](https://packagist.org/packages/casbin/casbin) [![Total Downloads](https://poser.pugx.org/casbin/casbin/downloads)](https://packagist.org/packages/casbin/casbin) [![License](https://poser.pugx.org/casbin/casbin/license)](https://packagist.org/packages/casbin/casbin) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) **PHP-Casbin** 是一个强大的、高效的开源访问控制框架,它支持基于各种[访问控制模型](https://en.wikipedia.org/wiki/Computer_security_model)的权限管理。 ## Casbin支持的编程语言: [![golang](https://casbin.org/docs/assets/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/docs/assets/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/docs/assets/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/docs/assets/langs/php.png)](https://github.com/php-casbin/php-casbin) ----|----|----|---- [Casbin](https://github.com/casbin/casbin) | [jCasbin](https://github.com/casbin/jcasbin) | [node-Casbin](https://github.com/casbin/node-casbin) | [PHP-Casbin](https://github.com/php-casbin/php-casbin) 可用于生产环境 | 可用于生产环境 | 可用于生产环境 | 可用于生产环境 ## 安装 通过`Composer`安装: ``` composer require casbin/casbin ``` ## 快速开始 1. 通过`model`和`policy`文件初始化一个`Enforcer`实例: ```php require_once './vendor/autoload.php'; use Casbin\Enforcer; $e = new Enforcer("path/to/model.conf", "path/to/policy.csv"); ``` 2. 在需要进行访问控制的位置,通过以下代码进行权限验证: ```php $sub = "alice"; // the user that wants to access a resource. $obj = "data1"; // the resource that is going to be accessed. $act = "read"; // the operation that the user performs on the resource. if ($e->enforce($sub, $obj, $act) === true) { // permit alice to read data1 } else { // deny the request, show an error } ``` ## 目录 - [支持的Models](#支持的Models) - [工作原理](#工作原理) - [特性](#特性) - [文档](#文档) - [在线编辑器](#在线编辑器) - [教程](#教程) - [Policy管理](#Policy管理) - [Policy持久化](#Policy持久化) - [Role管理](#Role管理) - [例子](#例子) - [支持的适配器](#支持的适配器) - [协议](#协议) - [联系](#联系) ## 支持的Models 1. [**ACL (Access Control List)**](https://en.wikipedia.org/wiki/Access_control_list) 2. **ACL with [superuser](https://en.wikipedia.org/wiki/Superuser)** 3. **ACL without users**: especially useful for systems that don't have authentication or user log-ins. 3. **ACL without resources**: some scenarios may target for a type of resources instead of an individual resource by using permissions like ``write-article``, ``read-log``. It doesn't control the access to a specific article or log. 4. **[RBAC (Role-Based Access Control)](https://en.wikipedia.org/wiki/Role-based_access_control)** 5. **RBAC with resource roles**: both users and resources can have roles (or groups) at the same time. 6. **RBAC with domains/tenants**: users can have different role sets for different domains/tenants. 7. **[ABAC (Attribute-Based Access Control)](https://en.wikipedia.org/wiki/Attribute-Based_Access_Control)**: syntax sugar like ``resource.Owner`` can be used to get the attribute for a resource. 8. **[RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer)**: supports paths like ``/res/*``, ``/res/:id`` and HTTP methods like ``GET``, ``POST``, ``PUT``, ``DELETE``. 9. **Deny-override**: both allow and deny authorizations are supported, deny overrides the allow. 10. **Priority**: the policy rules can be prioritized like firewall rules. ## 工作原理 在 Casbin 中, 访问控制模型被抽象为基于 **PERM (Policy, Effect, Request, Matcher)** 的一个文件。 因此,切换或升级项目的授权机制与修改配置一样简单。 您可以通过组合可用的模型来定制您自己的访问控制模型。 例如,您可以在一个model中获得RBAC角色和ABAC属性,并共享一组policy规则。 Casbin中最基本、最简单的`model`是ACL。ACL中的`Model` CONF为: ```ini # Request definition [request_definition] r = sub, obj, act # Policy definition [policy_definition] p = sub, obj, act # Policy effect [policy_effect] e = some(where (p.eft == allow)) # Matchers [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act ``` ACL `Model`的示例`Policy`如下: ``` p, alice, data1, read p, bob, data2, write ``` 这表示: - alice对data1有读权限 - bob对data2有写权限 ## 特性 Casbin 做了什么: 1. 自定义请求的格式,默认的请求格式为``{subject, object, action}``。 2. 访问控制模型及其策略的存储。 3. 支持RBAC中的多层角色继承,不止主体可以有角色,资源也可以具有角色。 4. 支持超级用户,如 ``root`` 或 ``Administrator``,超级用户可以不受授权策略的约束访问任意资源。 5. 支持多种内置的操作符,如 ``keyMatch``,方便对路径式的资源进行管理,如 ``/foo/bar`` 可以映射到 ``/foo*``。 Casbin 不做的事情: 1. 身份认证 `authentication`(即验证用户的用户名、密码),`casbin`只负责访问控制。应该有其他专门的组件负责身份认证,然后由`casbin`进行访问控制,二者是相互配合的关系。 2. 管理用户列表或角色列表。 `Casbin` 认为由项目自身来管理用户、角色列表更为合适, 用户通常有他们的密码,但是 `Casbin`的设计思想并不是把它作为一个存储密码的容器。 而是存储RBAC方案中用户和角色之间的映射关系。 ## 文档 https://casbin.org/docs/zh-CN/overview ## 在线编辑器 你也可以使用在线编辑器(https://casbin.org/editor/) 在你的浏览器里编写Casbin模型和策略。 它提供了一些比如 `语法高亮`以及`代码补全`这样的功能,就像编程语言的IDE一样。 ## 教程 https://casbin.org/docs/zh-CN/tutorials ## Policy管理 Casbin 提供两组 API 来管理权限: - [管理API](https://github.com/php-casbin/php-casbin/blob/master/src/ManagementApi.php): Casbin的底层原生API,支持全部的策略管理功能。点击 [这里](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/ManagementApiTest.php) 查看更多例子。 - [RBAC API](https://github.com/php-casbin/php-casbin/blob/master/src/RbacApi.php): 对于RBAC, 是一个更加友好的 API。 此 API 是管理 API 中的一个子集。 RBAC 用户可以使用此 API 来简化代码。 点击 [这里](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/RbacApiTest.php) 查看更多例子。 同时也提供了一个简单的前端页面来管理`Model`和`Policy`: ![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png) ![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png) ## Policy持久化 在`Casbin`中,适配器(`adapter`,`Casbin`的中间件)实现了`policy`规则写入持久层的细节。 `Casbin`的用户可以调用`adapter`的`loadPolicy()`方法从持久层中加载`policy`规则, 同样也可以调用`savePolicy()`方法将`Policy`规则保存到持久层中。 为了保持代码轻量, 我们没有将`adapter`的代码放在主库中。 以下是`PHP-Casbin`支持的适配器:(欢迎更多新的第三方贡献的适配器,可以联系我们添加在下面) Adapter | Type | Author | Description ----|------|----|---- [File Adapter (内置)](https://casbin.org/docs/zh-CN/policy-storage#file-adapter-built-in) | File | php-casbin | 存储到[.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) 文件中 [Database Adapter](https://github.com/php-casbin/database-adapter) | Database | php-casbin | 支持存储到MySQL, PostgreSQL, SQLite, Microsoft SQL Server数据库的适配器 更多适配器的内容,请参考文档: https://casbin.org/docs/zh-CN/policy-storage ## Role管理 角色管理器用于在`Casbin`中管理`RBAC`多层角色继承(用户-角色的关系)。角色管理器可以从Casbin的`Policy`规则或者外部数据源(如LDAP, Okta, Auth0, Azure AD等)获取角色数据。我们支持多种角色管理器,为了保持代码轻量,我们没有将除了内置的默认的角色管理器以外的角色管理器放在主库中。以下是支持的角色管理器:(欢迎更多新的第三方贡献的角色管理器,可以联系我们添加在下面) Role manager | Author | Description ----|----|---- [Default Role Manager (内置)](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) | php-casbin | 支持多层角色继承 提示: 所有的角色管理器必须实现[RoleManager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/RoleManager.php) 接口。 可以参考[Default Role Manager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) 。 ## 例子 Model | Model file | Policy file ----|------|---- ACL | [basic_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL with superuser | [basic_model_with_root.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL without users | [basic_model_without_users.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_policy.csv) ACL without resources | [basic_model_without_resources.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_policy.csv) RBAC | [rbac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_policy.csv) RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_policy.csv) ABAC | [abac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/abac_model.conf) | N/A RESTful | [keymatch_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_policy.csv) Deny-override | [rbac_model_with_deny.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_policy.csv) Priority | [priority_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_policy.csv) ## 支持的适配器 ### Web框架 - [Laravel](https://laravel.com/): 为WEB艺术家创造的PHP框架, 通过这个扩展: [laravel-casbin](https://github.com/php-casbin/laravel-casbin) - [Yii PHP Framework](https://www.yiiframework.com/): 一个高性能的,适用于开发WEB2.0应用的PHP框架, 通过这个扩展: [yii-casbin](https://github.com/php-casbin/yii-casbin) - [ThinkPHP](http://www.thinkphp.cn/): 一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架, 通过这个扩展: [think-casbin](https://github.com/php-casbin/think-casbin) ## 协议 `PHP-Casbin` 采用 [Apache 2.0 license](LICENSE) 开源协议发布。 ## 联系 有问题或者功能建议,请联系我们或者提交PR: - https://github.com/php-casbin/php-casbin/issues - techlee@qq.com - QQ群: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885) composer.json000064400000001446152475272420007305 0ustar00{ "name": "casbin/casbin", "description": "a powerful and efficient open-source access control library for php projects.", "authors": [ { "name": "TechLee", "email": "techlee@qq.com" } ], "license": "Apache-2.0", "keywords": [ "casbin", "rbac", "access control", "acl" ], "require": { "php": ">=5.6.0", "symfony/expression-language": "^2.8", "s1lentium/iptools": "^1.1" }, "autoload": { "psr-4": { "Casbin\\": "src/" } }, "require-dev": { "phpunit/phpunit": "~5.7", "php-coveralls/php-coveralls": "^2.1" }, "autoload-dev": { "psr-4": { "Casbin\\Tests\\": "tests/" } } } examples/abac_model.conf000064400000000244152475272420011311 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == r.obj.Ownerexamples/basic_inverse_policy.csv000064400000000052152475272420013301 0ustar00p, alice, data1, write p, bob, data2, readexamples/basic_model.conf000064400000000302152475272420011477 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.actexamples/basic_policy.csv000064400000000052152475272420011546 0ustar00p, alice, data1, read p, bob, data2, writeexamples/basic_with_root_model.conf000064400000000325152475272420013602 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.act || r.sub == "root"examples/basic_without_resources_model.conf000064400000000246152475272420015363 0ustar00[request_definition] r = sub, act [policy_definition] p = sub, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.act == p.actexamples/basic_without_resources_policy.csv000064400000000034152475272420015423 0ustar00p, alice, read p, bob, writeexamples/basic_without_users_model.conf000064400000000246152475272420014512 0ustar00[request_definition] r = obj, act [policy_definition] p = obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.obj == p.obj && r.act == p.actexamples/basic_without_users_policy.csv000064400000000036152475272420014554 0ustar00p, data1, read p, data2, writeexamples/error/error_model.conf000064400000000301152475272420012677 0ustar00[request_definition] r = sub, obj, act [policy_definition p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && r.obj == p.obj && r.act == p.actexamples/error/error_policy.csv000064400000000047152475272420012753 0ustar00p, alice, data1, read bob, data2, writeexamples/ipmatch_model.conf000064400000000311152475272420012043 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = ipMatch(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/ipmatch_policy.csv000064400000000073152475272420012115 0ustar00p, 192.168.2.0/24, data1, read p, 10.0.0.0/16, data2, writeexamples/keymatch2_model.conf000064400000000325152475272420012312 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && keyMatch2(r.obj, p.obj) && regexMatch(r.act, p.act)examples/keymatch2_policy.csv000064400000000121152475272420012351 0ustar00p, alice, /alice_data/:resource, GET p, alice, /alice_data2/:id/using/:resId, GETexamples/keymatch_custom_model.conf000064400000000332152475272420013620 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && keyMatchCustom(r.obj, p.obj) && regexMatch(r.act, p.act)examples/keymatch_model.conf000064400000000324152475272420012227 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)examples/keymatch_policy.csv000064400000000245152475272420012276 0ustar00p, alice, /alice_data/*, GET p, alice, /alice_data/resource1, POST p, bob, /alice_data/resource2, GET p, bob, /bob_data/*, POST p, cathy, /cathy_data, (GET)|(POST)examples/priority_indeterminate_policy.csv000064400000000045152475272420015260 0ustar00p, alice, data1, read, intdeterminateexamples/priority_model.conf000064400000000337152475272420012307 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = priority(p.eft) || deny [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/priority_policy.csv000064400000000437152475272420012355 0ustar00p, alice, data1, read, allow p, data1_deny_group, data1, read, deny p, data1_deny_group, data1, write, deny p, alice, data1, write, allow g, alice, data1_deny_group p, data2_allow_group, data2, read, allow p, bob, data2, read, deny p, bob, data2, write, deny g, bob, data2_allow_groupexamples/rbac_model.conf000064400000000337152475272420011335 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/rbac_policy.csv000064400000000172152475272420011377 0ustar00p, alice, data1, read p, bob, data2, write p, data2_admin, data2, read p, data2_admin, data2, write g, alice, data2_adminexamples/rbac_with_deny_model.conf000064400000000404152475272420013402 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/rbac_with_deny_policy.csv000064400000000263152475272420013452 0ustar00p, alice, data1, read, allow p, bob, data2, write, allow p, data2_admin, data2, read, allow p, data2_admin, data2, write, allow p, alice, data2, write, deny g, alice, data2_adminexamples/rbac_with_domains_model.conf000064400000000405152475272420014076 0ustar00[request_definition] r = sub, dom, obj, act [policy_definition] p = sub, dom, obj, act [role_definition] g = _, _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.actexamples/rbac_with_domains_policy.csv000064400000000256152475272420014147 0ustar00p, admin, domain1, data1, read p, admin, domain1, data1, write p, admin, domain2, data2, read p, admin, domain2, data2, write g, alice, admin, domain1 g, bob, admin, domain2examples/rbac_with_hierarchy_policy.csv000064400000000331152475272420014465 0ustar00p, alice, data1, read p, bob, data2, write p, data1_admin, data1, read p, data1_admin, data1, write p, data2_admin, data2, read p, data2_admin, data2, write g, alice, admin g, admin, data1_admin g, admin, data2_adminexamples/rbac_with_not_deny_model.conf000064400000000344152475272420014265 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = !some(where (p_eft == deny)) [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/rbac_with_resource_roles_model.conf000064400000000353152475272420015501 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act [role_definition] g = _, _ g2 = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && g2(r.obj, p.obj) && r.act == p.actexamples/rbac_with_resource_roles_policy.csv000064400000000231152475272420015541 0ustar00p, alice, data1, read p, bob, data2, write p, data_group_admin, data_group, write g, alice, data_group_admin g2, data1, data_group g2, data2, data_groupphpunit.xml000064400000001524152475272420006771 0ustar00 ./tests/ ./src src/CachedEnforcer.php000064400000003033152475272420010710 0ustar00enableCache = true; } // EnableCache determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions. public function enableCache($enableCache) { $this->enableCache = $enableCache; } // Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). // if rvals is not string , ingore the cache public function enforce(...$rvals) { if (!$this->enableCache) { return parent::enforce(...$rvals); } $key = ''; foreach ($rvals as $rval) { if (is_string($rval)) { $key .= $rval.'$$'; } else { return parent::enforce(...$rvals); } } if (isset(self::$m[$key])) { return self::$m[$key]; } else { $res = parent::enforce(...$rvals); self::$m[$key] = $res; return $res; } } // InvalidateCache deletes all the existing cached decisions. public function invalidateCache() { self::$m = []; } } src/Config/Config.php000064400000010246152475272420010473 0ustar00parse($confName); return $c; } public static function newConfigFromText($text) { $c = new static(); $c->parseBuffer($text); return $c; } public function addConfig($section, $option, $value) { if (empty($section)) { $section = self::DEFAULT_SECTION; } if (!isset($this->data[$section])) { $this->data[$section] = []; } $this->data[$section][$option] = $value; return true; } private function parse($fname) { $buf = file_get_contents($fname); $res = $this->parseBuffer($buf); return $res; } private function parseBuffer($buf) { $section = null; $lineNum = 0; $buffer = ''; $canWrite = null; $buf = preg_replace('/[\r\n]+/', PHP_EOL, $buf); $buf = explode(PHP_EOL, $buf); for ($i = 0; $i <= \count($buf); ++$i) { if ($canWrite) { $this->write($section, $lineNum, $buffer); $canWrite = false; } ++$lineNum; $line = isset($buf[$i]) ? $buf[$i] : ''; if ($i == \count($buf)) { if (\strlen($buffer) > 0) { $this->write($section, $lineNum, $buffer); } break; } $line = trim($line); if ('' == $line || self::DEFAULT_COMMENT == substr($line, 0, 1) || self::DEFAULT_COMMENT_SEM == substr($line, 0, 1)) { $canWrite = true; continue; } elseif ('[' == substr($line, 0, 1) && ']' == substr($line, -1)) { if (\strlen($buffer) > 0) { $this->write($section, $lineNum, $buffer); $canWrite = false; } $section = substr($line, 1, -1); } else { $p = ''; if (self::DEFAULT_MULTI_LINE_SEPARATOR == substr($line, -1)) { $p = trim(substr($line, 0, -1)); } else { $p = $line; $canWrite = true; } $buffer .= $p; } } return true; } private function write($section, $lineNum, &$b) { if (\strlen($b) <= 0) { return; } $optionVal = explode('=', $b, 2); if (2 != \count($optionVal)) { throw new CasbinException(sprintf('parse the content error : line %d , %s = ?', $lineNum, current($optionVal))); } $option = trim($optionVal[0]); $value = trim($optionVal[1]); $this->addConfig($section, $option, $value); $b = ''; } public function getString($key) { return $this->get($key); } public function getStrings($key) { $v = $this->get($key); if ('' == $v) { return []; } return explode(',', $v); } public function set($key, $value) { if (0 == \strlen($key)) { throw new CasbinException('key is empty'); } $keys = explode('::', strtolower($key)); if (\strlen($key) >= 2) { $section = $keys[0]; $option = $keys[1]; } else { $option = $keys[0]; } $this->addConfig($section, $option, $value); } public function get($key) { $keys = explode('::', $key); if (\count($keys) >= 2) { $section = $keys[0]; $option = $keys[1]; } else { $section = self::DEFAULT_SECTION; $option = $keys[0]; } return isset($this->data[$section][$option]) ? $this->data[$section][$option] : ''; } } src/Config/ConfigContract.php000064400000000166152475272420012171 0ustar00 'mysql', // mysql,pgsql,sqlite,sqlsrv * 'hostname' => '127.0.0.1', * 'database' => 'test', * 'username' => 'root', * 'password' => '123456', * 'hostport' => '3306', * ]); * $e = new Enforcer("path/to/basic_model.conf", $a). * * @author techlee@qq.com * * @param array|mixed ...$params */ public function __construct(...$params) { $parsedParamLen = 0; if (\count($params) >= 1) { if (\is_bool($params[\count($params) - 1])) { $enableLog = $params[\count($params) - 1]; $this->enableLog($enableLog); ++$parsedParamLen; } } if (2 == \count($params) - $parsedParamLen) { $p0 = $params[0]; if (\is_string($p0)) { $p1 = $params[1]; if (\is_string($p1)) { $this->initWithFile($p0, $p1); } else { $this->initWithAdapter($p0, $p1); } } else { if (\is_string($params[1])) { throw new CasbinException('Invalid parameters for enforcer.'); } else { $this->initWithModelAndAdapter($p0, $params[1]); } } } elseif (1 == \count($params) - $parsedParamLen) { $p0 = $params[0]; if (\is_string($p0)) { $this->initWithFile($p0, ''); } else { $this->initWithModelAndAdapter($p0, null); } } elseif (0 == \count($params) - $parsedParamLen) { $this->initWithFile('', ''); } else { throw new CasbinException('Invalid parameters for enforcer.'); } } /** * initializes an enforcer with a model file and a policy file. * * @param string $modelPath * @param string $policyPath */ public function initWithFile($modelPath, $policyPath) { $adapter = new FileAdapter($policyPath); $this->initWithAdapter($modelPath, $adapter); } /** * initWithAdapter initializes an enforcer with a database adapter. * * @param string $modelPath * @param Adapter $adapter */ public function initWithAdapter($modelPath, Adapter $adapter) { $m = self::newModel($modelPath, ''); $this->initWithModelAndAdapter($m, $adapter); $this->modelPath = $modelPath; } /** * initWithModelAndAdapter initializes an enforcer with a model and a database adapter. * * @param Model $m * @param Adapter|null $adapter */ public function initWithModelAndAdapter(Model $m, $adapter) { $this->adapter = $adapter; $this->model = $m; $this->model->printModel(); $this->fm = Model::loadFunctionMap(); $this->initialize(); if (null !== $this->adapter) { try { $this->loadPolicy(); } catch (\Exception $e) { // error intentionally ignored } } } protected function initialize() { $this->rm = new DefaultRoleManager(10); $this->eft = new DefaultEffector(); $this->watcher = null; $this->enabled = true; $this->autoSave = true; $this->autoBuildRoleLinks = true; } public static function newModel(...$text) { $model = new Model(); if (2 == \count($text)) { if ('' != $text[0]) { $model->loadModel($text[0]); } } elseif (1 == \count($text)) { $model->loadModelFromText($text[0]); } elseif (0 != \count($text)) { throw new CasbinException('Invalid parameters for model.'); } return $model; } public function loadModel() { $this->model = self::newModel(); $this->model->loadModel($this->modelPath); $this->model->printModel(); $this->fm = Model::LoadFunctionMap(); } public function getModel() { return $this->model; } public function setModel(Model $model) { $this->model = $model; $this->fm = $this->model->loadFunctionMap(); } public function getAdapter() { return $this->adapter; } public function setAdapter(Adapter $adapter) { $this->adapter = $adapter; } public function setWatcher(Watcher $watcher) { $this->watcher = $watcher; $this->watcher->setUpdateCallback(function () { $this->loadPolicy(); }); } public function setRoleManager(RoleManager $rm) { $this->rm = $rm; } public function setEffector(Effector $eft) { $this->eft = $eft; } public function clearPolicy() { $this->model->clearPolicy(); } public function loadPolicy() { $this->model->clearPolicy(); $this->adapter->loadPolicy($this->model); $this->model->printPolicy(); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } } public function loadFilteredPolicy($filter) { $this->model->clearPolicy(); if ($this->adapter instanceof FilteredAdapter) { $filteredAdapter = $this->adapter; } else { throw new CasbinException('filtered policies are not supported by this adapter'); } $filteredAdapter->loadFilteredPolicy($this->model, $filter); $this->model->printPolicy(); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } } public function isFiltered() { if (!$this->adapter instanceof FilteredAdapter) { return false; } $filteredAdapter = $this->adapter; $filteredAdapter->isFiltered(); } public function savePolicy() { if ($this->isFiltered()) { throw new CasbinException('cannot save a filtered policy'); } $this->adapter->savePolicy($this->model); if (null !== $this->watcher) { return $this->watcher->update(); } } public function enableEnforce($enabled = true) { $this->enabled = $enabled; } public function enableLog($enabled = true) { Log::getLogger()->enableLog($enabled); } public function enableAutoSave($autoSave = true) { $this->autoSave = $autoSave; } public function enableAutoBuildRoleLinks($autoBuildRoleLinks = true) { $this->autoBuildRoleLinks = $autoBuildRoleLinks; } public function buildRoleLinks() { $this->rm->clear(); $this->model->buildRoleLinks($this->rm); } public function enforce(...$rvals) { if (!$this->enabled) { return true; } $functions = []; foreach ($this->fm->getFunctions() as $key => $func) { $functions[$key] = $func; } if (isset($this->model->model['g'])) { foreach ($this->model->model['g'] as $key => $ast) { $rm = $ast->rM; $functions[$key] = BuiltinOperations::GenerateGFunction($rm); } } if (!isset($this->model->model['m']['m'])) { throw new CasbinException('model is undefined'); } $expString = $this->model->model['m']['m']->value; $policyEffects = []; $matcherResults = []; $policyLen = \count($this->model->model['p']['p']->policy); if (0 != $policyLen) { foreach ($this->model->model['p']['p']->policy as $i => $pvals) { $parameters = []; foreach ($this->model->model['r']['r']->tokens as $j => $token) { $parameters[$token] = $rvals[$j]; } foreach ($this->model->model['p']['p']->tokens as $j => $token) { $parameters[$token] = $pvals[$j]; } $result = $this->expressionEvaluate($expString, $parameters, $functions); if (\is_bool($result)) { if (!$result) { $policyEffects[$i] = Effector::INDETERMINATE; continue; } } elseif (\is_float($result)) { if (0 == $result) { $policyEffects[$i] = Effector::INDETERMINATE; continue; } else { $matcherResults[$i] = $result; } } else { throw new CasbinException('matcher result should be bool, int or float'); } if (isset($parameters['p_eft'])) { $eft = $parameters['p_eft']; if ('allow' == $eft) { $policyEffects[$i] = Effector::ALLOW; } elseif ('deny' == $eft) { $policyEffects[$i] = Effector::DENY; } else { $policyEffects[$i] = Effector::INDETERMINATE; } } else { $policyEffects[$i] = Effector::ALLOW; } if (isset($this->model->model['e']['e']) && 'priority(p_eft) || deny' == $this->model->model['e']['e']->value) { break; } } } else { $parameters = []; foreach ($this->model->model['r']['r']->tokens as $j => $token) { $parameters[$token] = $rvals[$j]; } foreach ($this->model->model['p']['p']->tokens as $token) { $parameters[$token] = ''; } $result = $this->expressionEvaluate($expString, $parameters, $functions); if ($result) { $policyEffects[0] = Effector::ALLOW; } else { $policyEffects[0] = Effector::INDETERMINATE; } } $result = $this->eft->mergeEffects($this->model->model['e']['e']->value, $policyEffects, $matcherResults); if (Log::getLogger()->isEnabled()) { $reqStr = 'Request: '; $reqStr .= implode(', ', array_values($rvals)); $reqStr .= sprintf(' ---> %s', (string) $result); Log::logPrint($reqStr); } return $result; } protected function expressionEvaluate($expString, $parameters, $functions) { $expString = preg_replace_callback( '/([\s\S]*in\s+)\(([\s\S]+)\)([\s\S]*)/', function ($m) { return $m[1].'['.$m[2].']'.$m[3]; }, $expString ); $expressionLanguage = new ExpressionLanguage(); foreach ($functions as $key => $func) { $expressionLanguage->register($key, function (...$args) use ($key) { return sprintf($key.'(%1$s)', implode(',', $args)); }, function ($arguments, ...$args) use ($func) { return $func(...$args); }); } $expressionLanguage->evaluate($expString, $parameters); // $expressionLanguage->compile($expString, array_keys($parameters)); return $expressionLanguage->evaluate($expString, $parameters); } } src/Exceptions/CasbinException.php000064400000000213152475272420013251 0ustar00model->addPolicy($sec, $ptype, $rule); if (!$ruleAdded) { return $ruleAdded; } if (!is_null($this->adapter) && $this->autoSave) { try { $this->adapter->addPolicy($sec, $ptype, $rule); } catch (NotImplementedException $e) { } if (!is_null($this->watcher)) { // error intentionally ignored $this->watcher->update(); } } return $ruleAdded; } // removePolicy removes a rule from the current policy. protected function removePolicyInternal($sec, $ptype, array $rule) { $ruleRemoved = $this->model->removePolicy($sec, $ptype, $rule); if (!$ruleRemoved) { return $ruleRemoved; } if (!is_null($this->adapter) && $this->autoSave) { try { $this->adapter->removePolicy($sec, $ptype, $rule); } catch (NotImplementedException $e) { } if (!is_null($this->watcher)) { // error intentionally ignored $this->watcher->update(); } } return $ruleRemoved; } // removeFilteredPolicy removes rules based on field filters from the current policy. protected function removeFilteredPolicyInternal($sec, $ptype, $fieldIndex, ...$fieldValues) { $ruleRemoved = $this->model->removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); if (!$ruleRemoved) { return $ruleRemoved; } if (!is_null($this->adapter) && $this->autoSave) { try { $this->adapter->removeFilteredPolicy($sec, $ptype, $fieldIndex, $fieldValues); } catch (NotImplementedException $e) { } if (!is_null($this->watcher)) { // error intentionally ignored $this->watcher->update(); } } return $ruleRemoved; } } src/Log/Log.php000064400000001722152475272420007322 0ustar00write(...$v); } /** * logPrintf prints the log with the format. * * @param string $format * @param mix $v */ public static function logPrintf($format, ...$v) { self::$logger->writef($format, ...$v); } } Log::setLogger(new DefaultLogger()); src/Log/Logger.php000064400000000754152475272420010024 0ustar00path = sys_get_temp_dir(); } /** * enableLog. * * @param bool $enable */ public function enableLog($enable) { $this->enable = $enable; } public function isEnabled() { return $this->enable; } public function write(...$v) { if ($this->enable) { $content = date('Y-m-d H:i:s '); foreach ($v as $value) { if (\is_array($value)) { $value = json_encode($value); } elseif (\is_object($value)) { $value = json_encode($value); } $content .= $value; } $content .= PHP_EOL; $this->save($content); } } public function writef($format, ...$v) { if ($this->enable) { $content = date('Y-m-d H:i:s '); $content .= sprintf($format, ...$v); $content .= PHP_EOL; $this->save($content); } } public function save($content) { $file = $this->path.DIRECTORY_SEPARATOR.$this->name; file_put_contents($file, $content, FILE_APPEND | LOCK_EX); } } src/ManagementApi.php000064400000024407152475272420010573 0ustar00getAllNamedSubjects('p'); } // GetAllNamedSubjects gets the list of subjects that show up in the current named policy. public function getAllNamedSubjects($ptype) { return $this->model->getValuesForFieldInPolicy('p', $ptype, 0); } // GetAllObjects gets the list of objects that show up in the current policy. public function getAllObjects() { return $this->getAllNamedObjects('p'); } // GetAllNamedObjects gets the list of objects that show up in the current named policy. public function getAllNamedObjects($ptype) { return $this->model->getValuesForFieldInPolicy('p', $ptype, 1); } // GetAllActions gets the list of actions that show up in the current policy. public function getAllActions() { return $this->getAllNamedActions('p'); } // GetAllNamedActions gets the list of actions that show up in the current named policy. public function getAllNamedActions($ptype) { return $this->model->getValuesForFieldInPolicy('p', $ptype, 2); } // GetAllRoles gets the list of roles that show up in the current policy. public function getAllRoles() { return $this->getAllNamedRoles('g'); } // GetAllNamedRoles gets the list of roles that show up in the current named policy. public function getAllNamedRoles($ptype) { return $this->model->getValuesForFieldInPolicy('g', $ptype, 1); } // GetPolicy gets all the authorization rules in the policy. public function getPolicy() { return $this->getNamedPolicy('p'); } // GetFilteredPolicy gets all the authorization rules in the policy, field filters can be specified. public function getFilteredPolicy($fieldIndex, ...$fieldValues) { return $this->getFilteredNamedPolicy('p', $fieldIndex, ...$fieldValues); } // GetNamedPolicy gets all the authorization rules in the named policy. public function getNamedPolicy($ptype) { return $this->model->getPolicy('p', $ptype); } // GetFilteredNamedPolicy gets all the authorization rules in the named policy, field filters can be specified. public function getFilteredNamedPolicy($ptype, $fieldIndex, ...$fieldValues) { return $this->model->getFilteredPolicy('p', $ptype, $fieldIndex, ...$fieldValues); } // GetGroupingPolicy gets all the role inheritance rules in the policy. public function getGroupingPolicy() { return $this->getNamedGroupingPolicy('g'); } // GetFilteredGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. public function getFilteredGroupingPolicy($fieldIndex, ...$fieldValues) { return $this->getFilteredNamedGroupingPolicy('g', $fieldIndex, ...$fieldValues); } // GetNamedGroupingPolicy gets all the role inheritance rules in the policy. public function getNamedGroupingPolicy($ptype) { return $this->model->getPolicy('g', $ptype); } // GetFilteredNamedGroupingPolicy gets all the role inheritance rules in the policy, field filters can be specified. public function getFilteredNamedGroupingPolicy($ptype, $fieldIndex, ...$fieldValues) { return $this->model->getFilteredPolicy('g', $ptype, $fieldIndex, ...$fieldValues); } // HasPolicy determines whether an authorization rule exists. public function hasPolicy(...$params) { return $this->hasNamedPolicy('p', ...$params); } // HasNamedPolicy determines whether a named authorization rule exists. public function hasNamedPolicy($ptype, ...$params) { if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; return $this->model->hasPolicy('p', $ptype, $strSlice); } $policy = []; foreach ($params as $param) { $policy[] = $param; } return $this->model->hasPolicy('p', $ptype, $policy); } // AddPolicy adds an authorization rule to the current policy. // If the rule already exists, the function returns false and the rule will not be added. // Otherwise the function returns true by adding the new rule. public function addPolicy(...$params) { return $this->addNamedPolicy('p', ...$params); } // AddNamedPolicy adds an authorization rule to the current named policy. // If the rule already exists, the function returns false and the rule will not be added. // Otherwise the function returns true by adding the new rule. public function addNamedPolicy($ptype, ...$params) { $ruleAdded = false; if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; $ruleAdded = $this->addPolicyInternal('p', $ptype, $strSlice); } else { $policy = []; foreach ($params as $param) { $policy[] = $param; } $ruleAdded = $this->addPolicyInternal('p', $ptype, $policy); } return $ruleAdded; } // RemovePolicy removes an authorization rule from the current policy. public function removePolicy(...$params) { return $this->removeNamedPolicy('p', ...$params); } // RemoveFilteredPolicy removes an authorization rule from the current policy, field filters can be specified. public function removeFilteredPolicy($fieldIndex, ...$fieldValues) { return $this->removeFilteredNamedPolicy('p', $fieldIndex, ...$fieldValues); } // RemoveNamedPolicy removes an authorization rule from the current named policy. public function removeNamedPolicy($ptype, ...$params) { $ruleRemoved = false; if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; $ruleAdded = $this->removePolicyInternal('p', $ptype, $strSlice); } else { $policy = []; foreach ($params as $param) { $policy[] = $param; } $ruleRemoved = $this->removePolicyInternal('p', $ptype, $policy); } return $ruleRemoved; } // RemoveFilteredNamedPolicy removes an authorization rule from the current named policy, field filters can be specified. public function removeFilteredNamedPolicy($ptype, $fieldIndex, ...$fieldValues) { return $this->removeFilteredPolicyInternal('p', $ptype, $fieldIndex, ...$fieldValues); } // HasGroupingPolicy determines whether a role inheritance rule exists. public function hasGroupingPolicy(...$params) { return $this->hasNamedGroupingPolicy('g', ...$params); } // HasNamedGroupingPolicy determines whether a named role inheritance rule exists. public function hasNamedGroupingPolicy($ptype, ...$params) { if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; return $this->model->hasPolicy('g', $ptype, $strSlice); } $policy = []; foreach ($params as $param) { $policy[] = $param; } return $this->model->hasPolicy('g', $ptype, $policy); } // AddGroupingPolicy adds a role inheritance rule to the current policy. // If the rule already exists, the function returns false and the rule will not be added. // Otherwise the function returns true by adding the new rule. public function addGroupingPolicy(...$params) { return $this->addNamedGroupingPolicy('g', ...$params); } // AddNamedGroupingPolicy adds a named role inheritance rule to the current policy. // If the rule already exists, the function returns false and the rule will not be added. // Otherwise the function returns true by adding the new rule. public function addNamedGroupingPolicy($ptype, ...$params) { $ruleAdded = false; if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; $ruleAdded = $this->addPolicyInternal('g', $ptype, $strSlice); } else { $policy = []; foreach ($params as $param) { $policy[] = $param; } $ruleAdded = $this->addPolicyInternal('g', $ptype, $policy); } if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleAdded; } // RemoveGroupingPolicy removes a role inheritance rule from the current policy. public function removeGroupingPolicy(...$params) { return $this->removeNamedGroupingPolicy('g', ...$params); } // RemoveFilteredGroupingPolicy removes a role inheritance rule from the current policy, field filters can be specified. public function removeFilteredGroupingPolicy($fieldIndex, ...$fieldValues) { return $this->removeFilteredNamedGroupingPolicy('g', $fieldIndex, ...$fieldValues); } // RemoveNamedGroupingPolicy removes a role inheritance rule from the current named policy. public function removeNamedGroupingPolicy($ptype, ...$params) { $ruleRemoved = false; if (1 == count($params) && is_array($params[0])) { $strSlice = $params[0]; $ruleRemoved = $this->removePolicyInternal('g', $ptype, $strSlice); } else { $policy = []; foreach ($params as $param) { $policy[] = $param; } $ruleRemoved = $this->removePolicyInternal('g', $ptype, $policy); } if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleRemoved; } // RemoveFilteredNamedGroupingPolicy removes a role inheritance rule from the current named policy, field filters can be specified. public function removeFilteredNamedGroupingPolicy($ptype, $fieldIndex, ...$fieldValues) { $ruleRemoved = $this->removeFilteredPolicyInternal('g', $ptype, $fieldIndex, ...$fieldValues); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleRemoved; } // AddFunction adds a customized function. public function addFunction($name, \Closure $func) { $this->fm->addFunction($name, $func); } } src/Model/Assertion.php000064400000002730152475272420011067 0ustar00rM = $rm; $count = substr_count($this->value, '_'); foreach ($this->policy as $rule) { if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } if (\count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (2 == $count) { $this->rM->addLink($rule[0], $rule[1]); } elseif (3 == $count) { $this->rM->addLink($rule[0], $rule[1], $rule[2]); } elseif (4 == $count) { $this->rM->addLink($rule[0], $rule[1], $rule[2], $rule[3]); } } Log::logPrint('Role links for: '.$this->key); $this->rM->printRoles(); } } src/Model/FunctionMap.php000064400000001754152475272420011350 0ustar00functions[$name] = $func; } public static function loadFunctionMap() { $fm = new self(); $fm->addFunction('keyMatch', function (...$args) { return BuiltinOperations::keyMatchFunc(...$args); }); $fm->addFunction('keyMatch2', function (...$args) { return BuiltinOperations::keyMatch2Func(...$args); }); $fm->addFunction('regexMatch', function (...$args) { return BuiltinOperations::regexMatchFunc(...$args); }); $fm->addFunction('ipMatch', function (...$args) { return BuiltinOperations::iPMatchFunc(...$args); }); return $fm; } public function getFunctions() { return $this->functions; } } src/Model/Model.php000064400000005047152475272420010164 0ustar00 'request_definition', 'p' => 'policy_definition', 'g' => 'role_definition', 'e' => 'policy_effect', 'm' => 'matchers', ]; public function __construct() { } private function loadAssertion($cfg, $sec, $key) { $value = $cfg->getString($this->sectionNameMap[$sec].'::'.$key); return $this->addDef($sec, $key, $value); } public function addDef($sec, $key, $value) { if ('' == $value) { return; } $ast = new Assertion(); $ast->key = $key; $ast->value = $value; if ('r' == $sec || 'p' == $sec) { $ast->tokens = explode(', ', $ast->value); foreach ($ast->tokens as $i => $token) { $ast->tokens[$i] = $key.'_'.$token; } } else { $ast->value = Util::removeComments(Util::escapeAssertion($ast->value)); } $this->model[$sec][$key] = $ast; return true; } private function getKeySuffix($i) { if (1 == $i) { return ''; } return (string) $i; } private function loadSection($cfg, $sec) { $i = 1; for (; ;) { if (!$this->loadAssertion($cfg, $sec, $sec.$this->getKeySuffix($i))) { break; } else { ++$i; } } } public function loadModel($path) { $cfg = Config::newConfig($path); $this->loadSection($cfg, 'r'); $this->loadSection($cfg, 'p'); $this->loadSection($cfg, 'e'); $this->loadSection($cfg, 'm'); $this->loadSection($cfg, 'g'); } public function loadModelFromText($text) { $cfg = Config::newConfigFromText($text); $this->loadSection($cfg, 'r'); $this->loadSection($cfg, 'p'); $this->loadSection($cfg, 'e'); $this->loadSection($cfg, 'm'); $this->loadSection($cfg, 'g'); } public function printModel() { Log::logPrint('Model:'); foreach ($this->model as $k => $v) { foreach ($v as $i => $j) { Log::logPrintf('%s.%s: %s', $k, $i, $j->value); } } } public static function loadFunctionMap() { return FunctionMap::loadFunctionMap(); } } src/Model/Policy.php000064400000007373152475272420010367 0ustar00model['g'])) { return; } foreach ($this->model['g'] as $ast) { $ast->buildRoleLinks($rm); } } public function printPolicy() { Log::logPrint('Policy:'); foreach (['p', 'g'] as $sec) { if (!isset($this->model[$sec])) { return; } foreach ($this->model[$sec] as $key => $ast) { Log::logPrint($key, ': ', $ast->value, ': ', $ast->policy); } } } public function clearPolicy() { foreach (['p', 'g'] as $sec) { if (!isset($this->model[$sec])) { return; } foreach ($this->model[$sec] as $key => $ast) { $this->model[$sec][$key]->policy = []; } } } public function getPolicy($sec, $ptype) { return $this->model[$sec][$ptype]->policy; } // GetFilteredPolicy gets rules based on field filters from a policy. public function getFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues) { $res = []; foreach ($this->model[$sec][$ptype]->policy as $rule) { $matched = true; foreach ($fieldValues as $i => $fieldValue) { if ('' != $fieldValue && $rule[$fieldIndex + $i] != $fieldValue) { $matched = false; break; } } if ($matched) { $res[] = $rule; } } return $res; } public function hasPolicy($sec, $ptype, $rule) { if (!isset($this->model[$sec][$ptype])) { return false; } foreach ($this->model[$sec][$ptype]->policy as $r) { if (empty(array_diff($rule, $r))) { return true; } } return false; } public function addPolicy($sec, $ptype, array $rule) { if (!$this->hasPolicy($sec, $ptype, $rule)) { $this->model[$sec][$ptype]->policy[] = $rule; return true; } return false; } public function removePolicy($sec, $ptype, array $rule) { foreach ($this->model[$sec][$ptype]->policy as $i => $r) { if (empty(array_diff($rule, $r))) { array_splice($this->model[$sec][$ptype]->policy, $i, 1); return true; } } return false; } public function removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues) { $tmp = []; $res = false; if (!isset($this->model[$sec][$ptype])) { return $res; } foreach ($this->model[$sec][$ptype]->policy as $rule) { $matched = true; foreach ($fieldValues as $i => $fieldValue) { if ('' != $fieldValue && $rule[$fieldIndex + $i] != $fieldValue) { $matched = false; break; } } if ($matched) { $res = true; } else { $tmp[] = $rule; } } $this->model[$sec][$ptype]->policy = $tmp; return $res; } public function getValuesForFieldInPolicy($sec, $ptype, $fieldIndex) { $values = []; if (!isset($this->model[$sec][$ptype])) { return $values; } foreach ($this->model[$sec][$ptype]->policy as $rule) { $values[] = $rule[$fieldIndex]; } Util::arrayRemoveDuplicates($values); return $values; } } src/Persist/Adapter.php000064400000000610152475272420011064 0ustar00model[$sec][$key])) { return; } $model->model[$sec][$key]->policy[] = \array_slice($tokens, 1); } } src/Persist/Adapters/FileAdapter.php000064400000004610152475272420013433 0ustar00filePath = $filePath; } public function loadPolicy($model) { if (!file_exists($this->filePath)) { throw new CasbinException('invalid file path, file path cannot be empty'); } $this->loadPolicyFile($model); } public function savePolicy($model) { if ('' == $this->filePath) { throw new CasbinException('invalid file path, file path cannot be empty'); } $writeString = ''; if (isset($model->model['p'])) { foreach ($model->model['p'] as $ptype => $ast) { foreach ($ast->policy as $rule) { $writeString .= $ptype.', '; $writeString .= Util::arrayToString($rule); $writeString .= PHP_EOL; } } } if (isset($model->model['g'])) { foreach ($model->model['g'] as $ptype => $ast) { foreach ($ast->policy as $rule) { $writeString .= $ptype.', '; $writeString .= Util::arrayToString($rule); $writeString .= PHP_EOL; } } } return $this->savePolicyFile(rtrim($writeString, PHP_EOL)); } public function loadPolicyFile($model) { $file = fopen($this->filePath, 'rb'); while ($line = fgets($file)) { $this->loadPolicyLine(trim($line), $model); } fclose($file); } public function savePolicyFile($text) { return file_put_contents($this->filePath, $text, LOCK_EX); } public function addPolicy($sec, $ptype, $rule) { throw new NotImplementedException('not implemented'); } public function removePolicy($sec, $ptype, $rule) { throw new NotImplementedException('not implemented'); } public function removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues) { throw new NotImplementedException('not implemented'); } } src/Persist/FilteredAdapter.php000064400000000416152475272420012547 0ustar00allRoles = []; $this->maxHierarchyLevel = $maxHierarchyLevel; } protected function hasRole($name) { return isset($this->allRoles[$name]); } protected function createRole($name) { if (!isset($this->allRoles[$name])) { $this->allRoles[$name] = new Role($name); } return $this->allRoles[$name]; } public function clear() { $this->allRoles = []; } public function addLink($name1, $name2, ...$domain) { if (1 == \count($domain)) { $name1 = $domain[0].'::'.$name1; $name2 = $domain[0].'::'.$name2; } elseif (\count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $role1 = $this->createRole($name1); $role2 = $this->createRole($name2); $role1->addRole($role2); } public function deleteLink($name1, $name2, ...$domain) { if (1 == \count($domain)) { $name1 = $domain[0].'::'.$name1; $name2 = $domain[0].'::'.$name2; } elseif (\count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } if (!$this->hasRole($name1) || !$this->hasRole($name2)) { throw new CasbinException('error: name1 or name2 does not exist'); } $role1 = $this->createRole($name1); $role2 = $this->createRole($name2); $role1->deleteRole($role2); } public function hasLink($name1, $name2, ...$domain) { if (1 == \count($domain)) { $name1 = $domain[0].'::'.$name1; $name2 = $domain[0].'::'.$name2; } elseif (\count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } if ($name1 == $name2) { return true; } if (!$this->hasRole($name1) || !$this->hasRole($name2)) { return false; } $role1 = $this->createRole($name1); return $role1->hasRole($name2, $this->maxHierarchyLevel); } public function getRoles($name, ...$domain) { if (1 == \count($domain)) { $name = $domain[0].'::'.$name; } elseif (\count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } if (!$this->hasRole($name)) { return []; } $roles = $this->createRole($name)->getRoles(); if (1 == \count($domain)) { foreach ($roles as $key => $value) { $roles[$key] = \array_slice($roles[$key], \strlen($domain[0]) + 2); } } return $roles; } public function getUsers($name, ...$domain) { if (!$this->hasRole($name)) { throw new CasbinException('error: name does not exist'); } $names = []; array_map(function ($role) use (&$names, $name) { if ($role->hasDirectRole($name)) { $names[] = $role->name; } }, $this->allRoles); return $names; } public function printRoles() { $line = []; array_map(function ($role) use (&$line) { if ($text = $role->toString()) { $line[] = $text; } }, $this->allRoles); Log::logPrint(implode(', ', $line)); } } src/Rbac/Role.php000064400000003340152475272420007626 0ustar00name = $name; } public function addRole(self $role) { foreach ($this->roles as $rr) { if ($rr->name == $role->name) { return; } } $this->roles[] = $role; } public function deleteRole(self $role) { foreach ($this->roles as $key => $rr) { if ($rr->name == $role->name) { unset($this->roles[$key]); return; } } } public function hasRole($name, $hierarchyLevel) { if ($name == $this->name) { return true; } if ($hierarchyLevel <= 0) { return false; } foreach ($this->roles as $role) { if ($role->hasRole($name, $hierarchyLevel - 1)) { return true; } } return false; } public function hasDirectRole($name) { foreach ($this->roles as $role) { if ($role->name == $name) { return true; } } return false; } public function toString() { if (0 == \count($this->roles)) { return ''; } $names = implode(', ', $this->getRoles()); if (1 == \count($this->roles)) { return $this->name.' < '.$names; } else { return $this->name.' < ('.$names.')'; } } public function getRoles() { return array_map(function ($role) { return $role->name; }, $this->roles); } } src/Rbac/RoleManager.php000064400000000703152475272420011121 0ustar00model->model['g']['g']->rM->getRoles($name); } // getUsersForRole gets the users that has a role. public function getUsersForRole($name) { return $this->model->model['g']['g']->rM->getUsers($name); } // hasRoleForUser determines whether a user has a role. public function hasRoleForUser($name, $role) { $roles = $this->getRolesForUser($name); $hasRole = false; foreach ($roles as $r) { if ($r == $role) { $hasRole = true; break; } } return $hasRole; } // AddRoleForUser adds a role for a user. // Returns false if the user already has the role (aka not affected). public function addRoleForUser($user, $role) { return $this->addGroupingPolicy($user, $role); } // DeleteRoleForUser deletes a role for a user. // Returns false if the user does not have the role (aka not affected). public function deleteRoleForUser($user, $role) { return $this->removeGroupingPolicy($user, $role); } // DeleteRolesForUser deletes all roles for a user. // Returns false if the user does not have any roles (aka not affected). public function deleteRolesForUser($user) { return $this->removeFilteredGroupingPolicy(0, $user); } // DeleteUser deletes a user. // Returns false if the user does not exist (aka not affected). public function deleteUser($user) { return $this->removeFilteredGroupingPolicy(0, $user); } // DeleteRole deletes a role. public function deleteRole($role) { $this->removeFilteredGroupingPolicy(1, $role); $this->removeFilteredPolicy(0, $role); } // DeletePermission deletes a permission. // Returns false if the permission does not exist (aka not affected). public function deletePermission(...$permission) { return $this->removeFilteredPolicy(1, ...$permission); } // AddPermissionForUser adds a permission for a user or role. // Returns false if the user or role already has the permission (aka not affected). public function addPermissionForUser($user, ...$permission) { $params = []; $params[] = $user; foreach ($permission as $perm) { $params[] = $perm; } return $this->addPolicy(...$params); } // DeletePermissionForUser deletes a permission for a user or role. // Returns false if the user or role does not have the permission (aka not affected). public function deletePermissionForUser($user, ...$permission) { $params = []; $params[] = $user; foreach ($permission as $perm) { $params[] = $perm; } return $this->removePolicy(...$params); } // DeletePermissionsForUser deletes permissions for a user or role. // Returns false if the user or role does not have any permissions (aka not affected). public function deletePermissionsForUser($user) { return $this->removeFilteredPolicy(0, $user); } // GetPermissionsForUser gets permissions for a user or role. public function getPermissionsForUser($user) { return $this->getFilteredPolicy(0, $user); } // HasPermissionForUser determines whether a user has a permission. public function hasPermissionForUser($user, ...$permission) { $params = []; $params[] = $user; foreach ($permission as $perm) { $params[] = $perm; } return $this->hasPolicy($params); } // getImplicitRolesForUser gets implicit roles that a user has. // Compared to getRolesForUser(), this function retrieves indirect roles besides direct roles. // For example: // g, alice, role:admin // g, role:admin, role:user // // getRolesForUser("alice") can only get: ["role:admin"]. // But getImplicitRolesForUser("alice") will get: ["role:admin", "role:user"]. public function getImplicitRolesForUser($name) { $res = []; $roleSet = []; $roleSet[$name] = true; $q = []; $q[] = $name; for (; count($q) > 0;) { $name = $q[0]; $q = array_slice($q, 1); $roles = $this->rm->getRoles($name); foreach ($roles as $r) { if (!isset($roleSet[$r])) { $res[] = $r; $q[] = $r; $roleSet[$r] = true; } } } return $res; } // getImplicitPermissionsForUser gets implicit permissions for a user or role. // Compared to getPermissionsForUser(), this function retrieves permissions for inherited roles. // For example: // p, admin, data1, read // p, alice, data2, read // g, alice, admin // // getPermissionsForUser("alice") can only get: [["alice", "data2", "read"]]. // But getImplicitPermissionsForUser("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. public function getImplicitPermissionsForUser($user) { $roles[] = $user; $roles = array_merge( $roles, $this->getImplicitRolesForUser($user) ); $res = []; foreach ($roles as $role) { $permissions = $this->getPermissionsForUser($role); $res = array_merge($res, $permissions); } return $res; } } src/Util/BuiltinOperations.php000064400000006226152475272420012453 0ustar00contains($objIP1); } public static function iPMatchFunc(...$args) { $ip1 = $args[0]; $ip2 = $args[1]; return self::iPMatch($ip1, $ip2); } public static function generateGFunction(RoleManager $rm) { return function (...$args) use ($rm) { $name1 = $args[0]; $name2 = $args[1]; if (null === $rm) { return $name1 == $name2; } elseif (2 == \count($args)) { $res = $rm->hasLink($name1, $name2); return $res; } else { $domain = (string) $args[2]; $res = $rm->hasLink($name1, $name2, $domain); return $res; } }; } } src/Util/Util.php000064400000001500152475272420007704 0ustar00 $x) { if (!isset($found[$x])) { $found[$x] = true; $s[$j] = $s[$i]; ++$j; } } $s = \array_slice($s, 0, $j); } } tests/EnforcerTest.php000064400000016121152475272420011035 0ustar00modelAndPolicyPath.'/basic_model.conf', $this->modelAndPolicyPath.'/basic_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('bob', 'data2', 'write'), true); $this->assertEquals($e->enforce('bob', 'data1', 'write'), false); } public function testEnforceBasicWithRoot() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_with_root_model.conf', $this->modelAndPolicyPath.'/basic_policy.csv'); $this->assertEquals($e->enforce('root', 'any', 'any'), true); } public function testEnforceBasicWithoutResources() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $this->assertEquals($e->enforce('alice', 'read'), true); $this->assertEquals($e->enforce('alice', 'write'), false); $this->assertEquals($e->enforce('bob', 'write'), true); $this->assertEquals($e->enforce('bob', 'read'), false); } public function testEnforceBasicWithoutUsers() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_users_model.conf', $this->modelAndPolicyPath.'/basic_without_users_policy.csv'); $this->assertEquals($e->enforce('data1', 'read'), true); $this->assertEquals($e->enforce('data1', 'write'), false); $this->assertEquals($e->enforce('data2', 'write'), true); $this->assertEquals($e->enforce('data2', 'read'), false); } public function testEnforceIpMatch() { $e = new Enforcer($this->modelAndPolicyPath.'/ipmatch_model.conf', $this->modelAndPolicyPath.'/ipmatch_policy.csv'); $this->assertEquals($e->enforce('192.168.2.1', 'data1', 'read'), true); $this->assertEquals($e->enforce('192.168.3.1', 'data1', 'read'), false); } public function testEnforceKeyMatch() { $e = new Enforcer($this->modelAndPolicyPath.'/keymatch_model.conf', $this->modelAndPolicyPath.'/keymatch_policy.csv'); $this->assertEquals($e->enforce('alice', '/alice_data/test', 'GET'), true); $this->assertEquals($e->enforce('alice', '/bob_data/test', 'GET'), false); $this->assertEquals($e->enforce('cathy', '/cathy_data', 'GET'), true); $this->assertEquals($e->enforce('cathy', '/cathy_data', 'POST'), true); $this->assertEquals($e->enforce('cathy', '/cathy_data/12', 'POST'), false); } public function testEnforceKeyMatch2() { $e = new Enforcer($this->modelAndPolicyPath.'/keymatch2_model.conf', $this->modelAndPolicyPath.'/keymatch2_policy.csv'); $this->assertEquals($e->enforce('alice', '/alice_data/resource', 'GET'), true); $this->assertEquals($e->enforce('alice', '/alice_data2/123/using/456', 'GET'), true); } public function testEnforcePriority() { $e = new Enforcer($this->modelAndPolicyPath.'/priority_model.conf', $this->modelAndPolicyPath.'/priority_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e->enforce('alice', 'data1', 'write'), false); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('bob', 'data1', 'read'), false); $this->assertEquals($e->enforce('bob', 'data1', 'write'), false); $this->assertEquals($e->enforce('bob', 'data2', 'read'), true); $this->assertEquals($e->enforce('bob', 'data2', 'write'), false); } public function testEnforcePriorityIndeterminate() { $e = new Enforcer($this->modelAndPolicyPath.'/priority_model.conf', $this->modelAndPolicyPath.'/priority_indeterminate_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), false); } public function testEnforceRbac() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e->enforce('bob', 'data2', 'write'), true); $this->assertEquals($e->enforce('alice', 'data2', 'read'), true); $this->assertEquals($e->enforce('alice', 'data2', 'write'), true); } public function testEnforceRbacWithDeny() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_with_deny_model.conf', $this->modelAndPolicyPath.'/rbac_with_deny_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e->enforce('bob', 'data2', 'write'), true); $this->assertEquals($e->enforce('alice', 'data2', 'read'), true); $this->assertEquals($e->enforce('alice', 'data2', 'write'), false); } public function testEnforceRbacWithDomains() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_with_domains_model.conf', $this->modelAndPolicyPath.'/rbac_with_domains_policy.csv'); $this->assertEquals($e->enforce('alice', 'domain1', 'data1', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain1', 'data1', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain1', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain1', 'data2', 'write'), false); $this->assertEquals($e->enforce('bob', 'domain2', 'data1', 'read'), false); $this->assertEquals($e->enforce('bob', 'domain2', 'data1', 'write'), false); $this->assertEquals($e->enforce('bob', 'domain2', 'data2', 'read'), true); $this->assertEquals($e->enforce('bob', 'domain2', 'data2', 'write'), true); } public function testEnforceRbacWithNotDeny() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_with_not_deny_model.conf', $this->modelAndPolicyPath.'/rbac_with_deny_policy.csv'); $this->assertEquals($e->enforce('alice', 'data2', 'write'), false); } public function testEnforceRbacWithResourceRoles() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_with_resource_roles_model.conf', $this->modelAndPolicyPath.'/rbac_with_resource_roles_policy.csv'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e->enforce('alice', 'data1', 'write'), true); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'data2', 'write'), true); $this->assertEquals($e->enforce('bob', 'data1', 'read'), false); $this->assertEquals($e->enforce('bob', 'data1', 'write'), false); $this->assertEquals($e->enforce('bob', 'data2', 'read'), false); $this->assertEquals($e->enforce('bob', 'data2', 'write'), true); } } tests/Unit/CachedEnforcerTest.php000064400000003305152475272420013044 0ustar00modelAndPolicyPath.'/basic_model.conf', $this->modelAndPolicyPath.'/basic_policy.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $e->removePolicy('alice', 'data1', 'read'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $e->invalidateCache(); $this->assertFalse($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); } public function testEnableCache() { $e = new CachedEnforcer($this->modelAndPolicyPath.'/basic_model.conf', $this->modelAndPolicyPath.'/basic_policy.csv'); $e->enableCache(false); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $e->removePolicy('alice', 'data1', 'read'); $this->assertFalse($e->enforce('alice', 'data1', 'read')); } } tests/Unit/Config/ConfigTest.php000064400000003303152475272420012621 0ustar00modelAndPolicyPath.'/basic_model.conf'); $this->assertTrue($cfg instanceof Config); } public function testNewConfigFromText() { $cfg = Config::newConfigFromText(file_get_contents(__DIR__.'/test.ini')); $this->assertTrue($cfg instanceof Config); } public function testGetString() { $cfg = Config::newConfigFromText(file_get_contents(__DIR__.'/test.ini')); $this->assertEquals('act.wiki', $cfg->getString('url')); $v = $cfg->getStrings('redis::redis.key'); $this->assertTrue(2 == \count($v) && 'push1' == $v[0] && 'push2' == $v[1]); $v = $cfg->getString('mysql::mysql.dev.host'); $this->assertEquals('127.0.0.1', $v); $cfg->set('other::key1', 'new test key'); $v = $cfg->getString('other::key1'); $this->assertEquals('new test key', $v); $v = $cfg->getString('multi1::name'); $this->assertEquals('r.sub==p.sub&&r.obj==p.obj', $v); $v = $cfg->getString('multi2::name'); $this->assertEquals('r.sub==p.sub&&r.obj==p.obj', $v); $v = $cfg->getString('multi3::name'); $this->assertEquals('r.sub==p.sub&&r.obj==p.obj', $v); $v = $cfg->getString('multi4::name'); $this->assertEquals('', $v); $v = $cfg->getString('multi5::name'); $this->assertEquals('r.sub==p.sub&&r.obj==p.obj', $v); } } tests/Unit/Config/test.ini000064400000001273152475272420011527 0ustar00# test config debug = true url = act.wiki ; redis config [redis] redis.key = push1,push2 ; mysql config [mysql] mysql.dev.host = 127.0.0.1 mysql.dev.user = root mysql.dev.pass = 123456 mysql.dev.db = test mysql.master.host = 10.0.0.1 mysql.master.user = root mysql.master.pass = 89dds)2$#d mysql.master.db = act ; math config [math] math.i64 = 64 math.f64 = 64.1 ; other config [other] name = ATC自动化测试^-^&($#……# key1 = test key # multi-line test [multi1] name = r.sub==p.sub \ &&r.obj==p.obj\ \ [multi2] name = r.sub==p.sub \ &&r.obj==p.obj [multi3] name = r.sub==p.sub \ &&r.obj==p.obj [multi4] name = \ \ \ [multi5] name = r.sub==p.sub \ &&r.obj==p.obj\ \tests/Unit/EnforcerTest.php000064400000002471152475272420011757 0ustar00setModel($m); $adapter = new FileAdapter($this->modelAndPolicyPath.'/basic_policy.csv'); $e->setAdapter($adapter); $e->loadPolicy(); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); } public function testSetAdapter() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_model.conf'); $adapter = new FileAdapter($this->modelAndPolicyPath.'/basic_policy.csv'); $e->setAdapter($adapter); $e->loadPolicy(); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); } } tests/Unit/Log/LogTest.php000064400000001110152475272420011443 0ustar00enableLog(true); $enable = Log::getLogger()->isEnabled(); $this->assertTrue($enable); Log::getLogger()->enableLog(false); $enable = Log::getLogger()->isEnabled(); $this->assertFalse($enable); } } tests/Unit/Log/Logger/DefaultLoggerTest.php000064400000001464152475272420014701 0ustar00enableLog(true); $enable = $logger->isEnabled(); $this->assertTrue($enable); $path = $logger->path; $name = $logger->name; $logfile = $logger->path.DIRECTORY_SEPARATOR.$logger->name; if (file_exists($logfile)) { unlink($logfile); } $logger->write('testing logger'); $logger->write(['testing', 'logger']); $logger->writef('testing %s', 'DefaultLogger'); $this->assertTrue(file_exists($logfile)); } } tests/Unit/ManagementApiTest.php000064400000011041152475272420012713 0ustar00modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->getAllSubjects(), ['alice', 'bob', 'data2_admin']); $this->assertEquals($e->getAllObjects(), ['data1', 'data2']); $this->assertEquals($e->getAllActions(), ['read', 'write']); $this->assertEquals($e->getAllRoles(), ['data2_admin']); } public function testGetPolicyAPI() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->getPolicy(), [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ]); $this->assertEquals($e->getFilteredPolicy(0, 'alice'), [['alice', 'data1', 'read']]); $this->assertEquals($e->getFilteredPolicy(0, 'bob'), [['bob', 'data2', 'write']]); $this->assertEquals($e->getFilteredPolicy(0, 'data2_admin'), [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]); $this->assertEquals($e->getFilteredPolicy(1, 'data1'), [['alice', 'data1', 'read']]); $this->assertEquals($e->getFilteredPolicy(1, 'data2'), [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]); $this->assertEquals($e->getFilteredPolicy(2, 'read'), [['alice', 'data1', 'read'], ['data2_admin', 'data2', 'read']]); $this->assertEquals($e->getFilteredPolicy(2, 'write'), [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]); $this->assertEquals($e->getFilteredPolicy(0, 'data2_admin', 'data2'), [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]); // Note: "" (empty string) in fieldValues means matching all values. $this->assertEquals($e->getFilteredPolicy(0, 'data2_admin', '', 'read'), [['data2_admin', 'data2', 'read']]); $this->assertEquals($e->getFilteredPolicy(1, 'data2', 'write'), [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]); $this->assertTrue($e->hasPolicy(['alice', 'data1', 'read'])); $this->assertTrue($e->hasPolicy(['bob', 'data2', 'write'])); $this->assertFalse($e->hasPolicy(['alice', 'data2', 'read'])); $this->assertFalse($e->hasPolicy(['bob', 'data3', 'write'])); $this->assertEquals($e->getGroupingPolicy(), [['alice', 'data2_admin']]); $this->assertEquals($e->getFilteredGroupingPolicy(0, 'alice'), [['alice', 'data2_admin']]); $this->assertEquals($e->getFilteredGroupingPolicy(0, 'bob'), []); $this->assertEquals($e->getFilteredGroupingPolicy(1, 'data1_admin'), []); $this->assertEquals($e->getFilteredGroupingPolicy(1, 'data2_admin'), [['alice', 'data2_admin']]); // Note: "" (empty string) in fieldValues means matching all values. $this->assertEquals($e->getFilteredGroupingPolicy(0, '', 'data2_admin'), [['alice', 'data2_admin']]); $this->assertTrue($e->hasGroupingPolicy(['alice', 'data2_admin'])); $this->assertFalse($e->hasGroupingPolicy(['bob', 'data2_admin'])); } public function testModifyPolicyAPI() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->getPolicy(), [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ]); $e->removePolicy('alice', 'data1', 'read'); $e->removePolicy('bob', 'data2', 'write'); $e->removePolicy('alice', 'data1', 'read'); $e->addPolicy('eve', 'data3', 'read'); $e->addPolicy('eve', 'data3', 'read'); $namedPolicy = ['eve', 'data3', 'read']; $e->removeNamedPolicy('p', $namedPolicy); $e->addNamedPolicy('p', $namedPolicy); $this->assertEquals($e->getPolicy(), [ ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ['eve', 'data3', 'read'], ]); $e->removeFilteredPolicy(1, 'data2'); $this->assertEquals($e->getPolicy(), [ ['eve', 'data3', 'read'], ]); } } tests/Unit/Model/ModelTest.php000064400000002200152475272420012302 0ustar00loadModelFromText($text); $rule = ['alice', 'data1', 'read']; $m->addPolicy('p', 'p', $rule); $rule = ['bob', 'data2', 'write']; $m->addPolicy('p', 'p', $rule); $e = new Enforcer($m); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $this->assertFalse($e->enforce('bob', 'data1', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); } } tests/Unit/Model/PolicyTest.php000064400000005007152475272420012511 0ustar00loadModel($this->modelAndPolicyPath.'/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p', $rule); $this->assertTrue($m->getPolicy('p', 'p') == [$rule]); } public function testHasPolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath.'/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p', $rule); $this->assertTrue($m->hasPolicy('p', 'p', $rule)); } public function testAddPolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath.'/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p1', $rule); $this->assertTrue($m->hasPolicy('p', 'p1', $rule)); $this->assertFalse($m->hasPolicy('p', 'p', $rule)); } public function testRemovePolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath.'/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p1', $rule); $this->assertTrue($m->hasPolicy('p', 'p1', $rule)); $m->removePolicy('p', 'p1', $rule); $this->assertFalse($m->hasPolicy('p', 'p1', $rule)); $this->assertFalse($m->removePolicy('p', 'p1', $rule)); } public function testRemoveFilteredPolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath.'/rbac_with_domains_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p', $rule); $res = $m->removeFilteredPolicy('p', 'p', 1, 'domain1', 'data1'); $this->assertTrue($res); $res = $m->removeFilteredPolicy('p', 'p', 1, 'domain1', 'read'); $this->assertFalse($res); } public function testGetValuesForFieldInPolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath.'/rbac_with_domains_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $m->addPolicy('p', 'p', $rule); $res = $m->getValuesForFieldInPolicy('p', 'p', 1); $this->assertTrue(['domain1'] == $res); } } tests/Unit/Persist/Adapters/FileAdapterTest.php000064400000001443152475272420015566 0ustar00loadModel($this->modelAndPolicyPath.'/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read2']; $m->addPolicy('p', 'p', $rule); $rule = ['alice', 'data2_admin']; $m->addPolicy('g', 'g', $rule); $res = $adapter->savePolicy($m); $this->assertFalse(false === $res); } } tests/Unit/Persist/Adapters/basic_policy_test.csv000064400000000065152475272420016250 0ustar00p, admin, domain1, data1, read2 g, alice, data2_admintests/Unit/Rbac/DefaultRoleManager/RoleManagerTest.php000064400000004145152475272420016760 0ustar00addLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertTrue($res); } public function testDeleteLink() { $rm = new RoleManager(3); try { $rm->deleteLink('u1', 'g1'); } catch (\Exception $e) { $this->assertTrue($e instanceof CasbinException); } $rm->addLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertTrue($res); $rm->deleteLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertFalse($res); } public function testGetRoles() { $rm = new RoleManager(3); $rm->addLink('u1', 'g1'); $rm->addLink('u2', 'g1'); $rm->addLink('u3', 'g2'); $rm->addLink('u4', 'g2'); $rm->addLink('u4', 'g3'); $rm->addLink('g1', 'g3'); // Current role inheritance tree: // g3 g2 // / \ / \ // g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->getRoles('u1'), ['g1']); $this->assertEquals($rm->getRoles('u4'), ['g2', 'g3']); } public function testGetUsers() { $rm = new RoleManager(3); $rm->addLink('u1', 'g1'); $rm->addLink('u2', 'g1'); $rm->addLink('u3', 'g2'); $rm->addLink('u4', 'g2'); $rm->addLink('u4', 'g3'); $rm->addLink('g1', 'g3'); // Current role inheritance tree: // g3 g2 // / \ / \ // g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->getUsers('g1'), ['u1', 'u2']); $this->assertEquals($rm->getUsers('g3'), ['g1', 'u4']); } } tests/Unit/RbacApiTest.php000064400000015462152475272420011521 0ustar00modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin']); $this->assertEquals($e->getRolesForUser('bob'), []); $this->assertEquals($e->getRolesForUser('data2_admin'), []); $this->assertEquals($e->getRolesForUser('non_exist'), []); } public function testGetUsersForRole() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertEquals($e->getUsersForRole('data2_admin'), ['alice']); } public function testHasRoleForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $this->assertTrue($e->hasRoleForUser('alice', 'data2_admin')); $this->assertFalse($e->hasRoleForUser('alice', 'data1_admin')); } public function testAddRoleForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $e->addRoleForUser('alice', 'data1_admin'); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin', 'data1_admin']); } public function testDeleteRoleForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $e->addRoleForUser('alice', 'data1_admin'); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin', 'data1_admin']); $e->deleteRoleForUser('alice', 'data1_admin'); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin']); } public function testDeleteRolesForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $e->deleteRolesForUser('alice'); $this->assertEquals($e->getRolesForUser('alice'), []); } public function testDeleteUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $e->deleteUser('alice'); $this->assertEquals($e->getRolesForUser('alice'), []); } public function testDeleteRole() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_policy.csv'); $e->deleteRole('data2_admin'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $this->assertFalse($e->enforce('bob', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data1', 'write')); $this->assertFalse($e->enforce('bob', 'data2', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); } public function testDeletePermission() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $e->deletePermission('read'); $this->assertFalse($e->enforce('alice', 'read')); $this->assertFalse($e->enforce('alice', 'write')); $this->assertFalse($e->enforce('bob', 'read')); $this->assertTrue($e->enforce('bob', 'write')); } public function testAddPermissionForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $e->deletePermission('read'); $e->addPermissionForUser('bob', 'read'); $this->assertTrue($e->enforce('bob', 'read')); $this->assertTrue($e->enforce('bob', 'write')); } public function testDeletePermissionForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $e->addPermissionForUser('bob', 'read'); $this->assertTrue($e->enforce('bob', 'read')); $e->deletePermissionForUser('bob', 'read'); $this->assertFalse($e->enforce('bob', 'read')); $this->assertTrue($e->enforce('bob', 'write')); } public function testDeletePermissionsForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $e->deletePermissionsForUser('bob'); $this->assertTrue($e->enforce('alice', 'read')); $this->assertFalse($e->enforce('bob', 'read')); $this->assertFalse($e->enforce('bob', 'write')); } public function testGetPermissionsForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $this->assertEquals($e->getPermissionsForUser('alice'), [['alice', 'read']]); } public function testHasPermissionForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/basic_without_resources_model.conf', $this->modelAndPolicyPath.'/basic_without_resources_policy.csv'); $this->assertTrue($e->hasPermissionForUser('alice', ...['read'])); $this->assertFalse($e->hasPermissionForUser('alice', ...['write'])); $this->assertFalse($e->hasPermissionForUser('bob', ...['read'])); $this->assertTrue($e->hasPermissionForUser('bob', ...['write'])); } public function testGetImplicitRolesForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_with_hierarchy_policy.csv'); $this->assertEquals($e->getImplicitRolesForUser('alice'), ['admin', 'data1_admin', 'data2_admin']); $this->assertEquals($e->getImplicitRolesForUser('bob'), []); } public function testGetImplicitPermissionsForUser() { $e = new Enforcer($this->modelAndPolicyPath.'/rbac_model.conf', $this->modelAndPolicyPath.'/rbac_with_hierarchy_policy.csv'); $this->assertEquals($e->getImplicitPermissionsForUser('alice'), [ ['alice', 'data1', 'read'], ['data1_admin', 'data1', 'read'], ['data1_admin', 'data1', 'write'], ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ]); $this->assertEquals($e->getImplicitPermissionsForUser('bob'), [ ['bob', 'data2', 'write'], ]); } } tests/Unit/Util/BuiltinOperationsTest.php000064400000011120152475272420014572 0ustar00assertTrue($this->keyMatchFunc('/foo', '/foo')); $this->assertTrue($this->keyMatchFunc('/foo', '/foo*')); $this->assertFalse($this->keyMatchFunc('/foo', '/foo/*')); $this->assertFalse($this->keyMatchFunc('/foo/bar', '/foo')); $this->assertTrue($this->keyMatchFunc('/foo/bar', '/foo*')); $this->assertTrue($this->keyMatchFunc('/foo/bar', '/foo/*')); $this->assertFalse($this->keyMatchFunc('/foobar', '/foo')); $this->assertTrue($this->keyMatchFunc('/foobar', '/foo*')); $this->assertFalse($this->keyMatchFunc('/foobar', '/foo/*')); } public function testKeyMatch2Func() { $this->assertTrue($this->keyMatch2Func('/foo', '/foo')); $this->assertTrue($this->keyMatch2Func('/foo', '/foo*')); $this->assertFalse($this->keyMatch2Func('/foo', '/foo/*')); $this->assertTrue($this->keyMatch2Func('/foo/bar', '/foo')); // different with KeyMatch. $this->assertTrue($this->keyMatch2Func('/foo/bar', '/foo*')); $this->assertTrue($this->keyMatch2Func('/foo/bar', '/foo/*')); $this->assertTrue($this->keyMatch2Func('/foobar', '/foo')); // different with KeyMatch. $this->assertTrue($this->keyMatch2Func('/foobar', '/foo*')); $this->assertFalse($this->keyMatch2Func('/foobar', '/foo/*')); $this->assertFalse($this->keyMatch2Func('/', '/:resource')); $this->assertTrue($this->keyMatch2Func('/resource1', '/:resource')); $this->assertFalse($this->keyMatch2Func('/myid', '/:id/using/:resId')); $this->assertTrue($this->keyMatch2Func('/myid/using/myresid', '/:id/using/:resId')); $this->assertFalse($this->keyMatch2Func('/proxy/myid', '/proxy/:id/*')); $this->assertTrue($this->keyMatch2Func('/proxy/myid/', '/proxy/:id/*')); $this->assertTrue($this->keyMatch2Func('/proxy/myid/res', '/proxy/:id/*')); $this->assertTrue($this->keyMatch2Func('/proxy/myid/res/res2', '/proxy/:id/*')); $this->assertTrue($this->keyMatch2Func('/proxy/myid/res/res2/res3', '/proxy/:id/*')); $this->assertFalse($this->keyMatch2Func('/proxy/', '/proxy/:id/*')); $this->assertTrue($this->keyMatch2Func('/alice', '/:id')); $this->assertTrue($this->keyMatch2Func('/alice/all', '/:id/all')); $this->assertFalse($this->keyMatch2Func('/alice', '/:id/all')); $this->assertFalse($this->keyMatch2Func('/alice/all', '/:id')); } public function testKeyMatch3Func() { $this->assertTrue($this->keyMatch3Func('/foo', '/foo')); $this->assertTrue($this->keyMatch3Func('/foo', '/foo*')); $this->assertFalse($this->keyMatch3Func('/foo', '/foo/*')); $this->assertTrue($this->keyMatch3Func('/foo/bar', '/foo')); // different with KeyMatch. $this->assertTrue($this->keyMatch3Func('/foo/bar', '/foo*')); $this->assertTrue($this->keyMatch3Func('/foo/bar', '/foo/*')); $this->assertTrue($this->keyMatch3Func('/foobar', '/foo')); // different with KeyMatch. $this->assertTrue($this->keyMatch3Func('/foobar', '/foo*')); $this->assertFalse($this->keyMatch3Func('/foobar', '/foo/*')); $this->assertFalse($this->keyMatch3Func('/', '/{resource}')); $this->assertTrue($this->keyMatch3Func('/resource1', '/{resource}')); $this->assertFalse($this->keyMatch3Func('/myid', '/{id}/using/{resId}')); $this->assertTrue($this->keyMatch3Func('/myid/using/myresid', '/{id}/using/{resId}')); $this->assertFalse($this->keyMatch3Func('/proxy/myid', '/proxy/{id}/*')); $this->assertTrue($this->keyMatch3Func('/proxy/myid/', '/proxy/{id}/*')); $this->assertTrue($this->keyMatch3Func('/proxy/myid/res', '/proxy/{id}/*')); $this->assertTrue($this->keyMatch3Func('/proxy/myid/res/res2', '/proxy/{id}/*')); $this->assertTrue($this->keyMatch3Func('/proxy/myid/res/res2/res3', '/proxy/{id}/*')); $this->assertFalse($this->keyMatch3Func('/proxy/', '/proxy/{id}/*')); } }