.github/FUNDING.yml000064400000001235152475271650007740 0ustar00# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: casbin ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] .github/semantic.yml000064400000000000152475271650010436 0ustar00.github/workflows/build.yml000064400000005262152475271650012006 0ustar00name: build on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: fail-fast: true matrix: php: [8.0, 8.1, 8.2, 8.3, 8.4] stability: [ prefer-lowest, prefer-stable ] name: Test PHP ${{ matrix.php }} - ${{ matrix.stability }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} tools: composer:v2 coverage: xdebug - name: Validate composer.json and composer.lock run: composer validate - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' run: composer install --prefer-dist --no-progress --no-suggest - name: Run phpstan analyse run: composer analyse - name: Run test suite run: composer test - name: Run Coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_PARALLEL: true COVERALLS_FLAG_NAME: ${{ runner.os }} - ${{ matrix.php }} - ${{ matrix.stability }} run: ./vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v benchmark: runs-on: ubuntu-latest strategy: fail-fast: true matrix: php: [8.0, 8.1] name: Benchmark PHP ${{ matrix.php }} steps: - name: Checkout code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} tools: composer:v2 coverage: none - name: Validate composer.json and composer.lock run: composer validate - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' run: composer install --prefer-dist --no-progress --no-suggest - name: Run benchmark run: composer benchmark upload-coverage: runs-on: ubuntu-latest needs: [ test ] steps: - name: Coveralls Finished uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true semantic-release: runs-on: ubuntu-latest needs: [ test, upload-coverage ] steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 'lts/*' - name: Run semantic-release if: github.repository == 'php-casbin/php-casbin' && github.event_name == 'push' env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} run: npx semantic-release .gitignore000064400000000113152475271650006545 0ustar00/vendor/ composer.lock .idea/ *.iml # coverage report /build .phpunit.*.releaserc.yml000064400000000202152475271650007322 0ustar00plugins: - "@semantic-release/commit-analyzer" - "@semantic-release/release-notes-generator" - "@semantic-release/github"LICENSE000064400000026135152475271650005576 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.md000064400000033007152475271650006044 0ustar00PHP-Casbin ==== [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-casbin/php-casbin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-casbin/php-casbin/?branch=master) [![Default](https://github.com/php-casbin/php-casbin/workflows/build/badge.svg?branch=master)](https://github.com/php-casbin/php-casbin/actions) [![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) [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN) [Documentation](https://casbin.org/docs/overview) | [Tutorials](https://github.com/php-casbin/casbin-tutorials) | [Extensions](https://github.com/php-casbin) **Breaking News**: [Laravel-authz](https://github.com/php-casbin/laravel-authz) is now available, an authorization library for the Laravel framework. **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/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/img/langs/php.png)](https://github.com/php-casbin/php-casbin) ----|----|----|---- [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 [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin/Casbin.NET) | [![c++](https://casbin.org/img/langs/cpp.png)](https://github.com/casbin/casbin-cpp) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) ----|----|----|---- [PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin/Casbin.NET) | [Casbin-CPP](https://github.com/casbin/casbin-cpp) | [Casbin-RS](https://github.com/casbin/casbin-rs) 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) - [Middlewares](#middlewares) - [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/tutorials ## Policy management php-casbin provides two sets of APIs to manage permissions: - [Management API](https://casbin.org/docs/en/management-api): the primitive API that provides full support for php-casbin policy management. - [RBAC API](https://casbin.org/docs/en/rbac-api): a more friendly API for RBAC. This API is a subset of Management API. The RBAC users could use this API to simplify the code. ![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png) ![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png) ## Policy persistence https://casbin.org/docs/en/adapters ## Role manager https://casbin.org/docs/en/role-managers ## 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) ## Middlewares Authz middlewares for web frameworks: https://casbin.org/docs/middlewares ## Our adopters https://casbin.org/docs/adopters ## Contributors This project exists thanks to all the people who contribute. ## Backers Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/casbin#backer)] ## Sponsors Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/casbin#sponsor)] ## 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.md000064400000033475152475271650006435 0ustar00PHP-Casbin ==== [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/php-casbin/php-casbin/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/php-casbin/php-casbin/?branch=master) [![Default](https://github.com/php-casbin/php-casbin/workflows/build/badge.svg?branch=master)](https://github.com/php-casbin/php-casbin/actions) [![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) **好消息**: [Laravel-authz](https://github.com/php-casbin/laravel-authz) 现已发布,一个专为Laravel打造的授权库. **PHP-Casbin** 是一个强大的、高效的开源访问控制框架,它支持基于各种[访问控制模型](https://en.wikipedia.org/wiki/Computer_security_model)的权限管理。 ## Casbin支持的编程语言: [![golang](https://casbin.org/img/langs/golang.png)](https://github.com/casbin/casbin) | [![java](https://casbin.org/img/langs/java.png)](https://github.com/casbin/jcasbin) | [![nodejs](https://casbin.org/img/langs/nodejs.png)](https://github.com/casbin/node-casbin) | [![php](https://casbin.org/img/langs/php.png)](https://github.com/php-casbin/php-casbin) ----|----|----|---- [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 [![python](https://casbin.org/img/langs/python.png)](https://github.com/casbin/pycasbin) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/casbin/Casbin.NET) | [![c++](https://casbin.org/img/langs/cpp.png)](https://github.com/casbin/casbin-cpp) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) ----|----|----|---- [PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin/Casbin.NET) | [Casbin-CPP](https://github.com/casbin/casbin-cpp) | [Casbin-RS](https://github.com/casbin/casbin-rs) production-ready | production-ready | production-ready | production-ready ## 安装 通过`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/zh/docs/overview ## 在线编辑器 你也可以使用在线编辑器(https://casbin.org/editor/) 在你的浏览器里编写Casbin模型和策略。 它提供了一些比如 `语法高亮`以及`代码补全`这样的功能,就像编程语言的IDE一样。 ## 教程 https://casbin.org/zh/docs/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/zh/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/zh/docs/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-Authorization](https://github.com/php-casbin/laravel-authz) - [Yii PHP Framework](https://www.yiiframework.com/): 一个高性能的,适用于开发WEB2.0应用的PHP框架, 通过这个扩展: [Yii-Permission](https://github.com/php-casbin/yii-permission) - [CakePHP](https://cakephp.org/): 快速、稳定的PHP框架, 通过这个扩展: [Cake-Permission](https://github.com/php-casbin/cake-permission) - [ThinkPHP](http://www.thinkphp.cn/): 一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架, 通过这个扩展: [Think-Authorization](https://github.com/php-casbin/think-authz) ## 协议 `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.json000064400000002252152475271650007305 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", "acl", "authorization", "permission", "abac", "access control" ], "require": { "php": ">=8.0", "symfony/expression-language": "^6.0|^7.0", "symfony/cache": "^6.0|^7.0", "psr/log": "^2.0|^3.0" }, "autoload": { "psr-4": { "Casbin\\": "src/" } }, "require-dev": { "phpunit/phpunit": "~9.0", "php-coveralls/php-coveralls": "^2.4", "phpstan/phpstan": "^1.11", "mockery/mockery": "^1.6" }, "autoload-dev": { "psr-4": { "Casbin\\Tests\\": "tests/" } }, "scripts": { "test": "./vendor/bin/phpunit -v --testsuite test", "benchmark": "./vendor/bin/phpunit -v --no-coverage --testsuite benchmark", "analyse": "./vendor/bin/phpstan analyse" } } examples/abac_model.conf000064400000000244152475271650011315 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/abac_not_using_policy_model.conf000064400000000311152475271650014754 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [policy_effect] e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) [matchers] m = r.sub == r.obj.ownerexamples/abac_rule_effect_policy.csv000064400000000163152475271650013725 0ustar00p, alice, /data1, read, deny p, alice, /data1, write, allow p, bob, /data2, write, deny p, bob, /data2, read, allowexamples/abac_rule_model.conf000064400000000312152475271650012340 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub_rule, obj, act [policy_effect] e = some(where (p.eft == allow)) [matchers] m = eval(p.sub_rule) && r.obj == p.obj && r.act == p.act examples/abac_rule_policy.csv000064400000000101152475271650012401 0ustar00p, r.sub.Age > 18, /data1, read p, r.sub.Age < 60, /data2, write examples/basic_inverse_policy.csv000064400000000052152475271650013305 0ustar00p, alice, data1, write p, bob, data2, readexamples/basic_model.conf000064400000000302152475271650011503 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.csv000064400000000052152475271650011552 0ustar00p, alice, data1, read p, bob, data2, writeexamples/basic_with_root_model.conf000064400000000325152475271650013606 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.conf000064400000000246152475271650015367 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.csv000064400000000034152475271650015427 0ustar00p, alice, read p, bob, writeexamples/basic_without_users_model.conf000064400000000246152475271650014516 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.csv000064400000000036152475271650014560 0ustar00p, data1, read p, data2, writeexamples/error/error_model.conf000064400000000301152475271650012703 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.csv000064400000000047152475271650012757 0ustar00p, alice, data1, read bob, data2, writeexamples/ipmatch_model.conf000064400000000311152475271650012047 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.csv000064400000000073152475271650012121 0ustar00p, 192.168.2.0/24, data1, read p, 10.0.0.0/16, data2, writeexamples/keyget2_model.conf000064400000000362152475271650012002 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 && keyGet2(r.obj, p.obj, 'resource') in ('age', 'name') && regexMatch(r.act, p.act)examples/keyget_model.conf000064400000000371152475271650011720 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 || keyGet(r.obj, p.obj) in ('age','name')) && regexMatch(r.act, p.act) examples/keymatch2_model.conf000064400000000325152475271650012316 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.csv000064400000000121152475271650012355 0ustar00p, alice, /alice_data/:resource, GET p, alice, /alice_data2/:id/using/:resId, GETexamples/keymatch_custom_model.conf000064400000000332152475271650013624 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.conf000064400000000324152475271650012233 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.csv000064400000000245152475271650012302 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/multiple_policy_definitions_model.conf000064400000000532152475271650016234 0ustar00[request_definition] r = sub, obj, act r2 = sub, obj, act [policy_definition] p = sub, obj, act p2= sub_rule, obj, act, eft [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] #RABC m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act #ABAC m2 = eval(p2.sub_rule) && r2.obj == p2.obj && r2.act == p2.act examples/multiple_policy_definitions_policy.csv000064400000000252152475271650016300 0ustar00p, data2_admin, data2, read p2, r2.sub.Age > 18 && r2.sub.Age < 60, /data1, read, allow p2, r2.sub.Age > 60 && r2.sub.Age < 100, /data1, read, deny g, alice, data2_adminexamples/object_conditions_model.conf000064400000000346152475271650014131 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, sub_rule, act [role_definition] g = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub) && eval(p.sub_rule) && r.act == p.actexamples/object_conditions_policy.csv000064400000000171152475271650014172 0ustar00p, alice, r.obj.price < 25, read p, admin, r.obj.category_id = 2, read p, bob, r.obj.author = bob, write g, alice, adminexamples/performance/rbac_with_pattern_large_scale_model.conf000064400000000411152475271650020744 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) && keyMatch4(r.obj, p.obj) && regexMatch(r.act, p.act)examples/performance/rbac_with_pattern_large_scale_policy.csv000064400000531204152475271650021022 0ustar00# 132 policies / 3000 grouping policies / 300 subjuects / 6 roles # Policy - staff001 p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, staff001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, staff001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1006 p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1007 p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1008 p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1009 p, staff001, /orgs/{orgID}/sites, App001.Module002.Action1010 p, staff001, /orgs/{orgID}/sites, App003.*.Action3001 p, staff001, /orgs/{orgID}/sites, App003.*.Action3002 p, staff001, /orgs/{orgID}/sites, App003.*.Action3003 p, staff001, /orgs/{orgID}/sites, App003.*.Action3004 p, staff001, /orgs/{orgID}/sites, App003.*.Action3005 p, staff001, /orgs/{orgID}, App004.* p, staff001, /orgs, App005.* # Policy - staff002 p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, staff002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, staff002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1006 p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1007 p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1008 p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1009 p, staff002, /orgs/{orgID}/sites, App001.Module002.Action1010 p, staff002, /orgs/{orgID}/sites, App003.*.Action3001 p, staff002, /orgs/{orgID}/sites, App003.*.Action3002 p, staff002, /orgs/{orgID}/sites, App003.*.Action3003 p, staff002, /orgs/{orgID}/sites, App003.*.Action3004 p, staff002, /orgs/{orgID}/sites, App003.*.Action3005 p, staff002, /orgs/{orgID}, App004.* p, staff002, /orgs, App005.* # Policy - manager001 p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, manager001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, manager001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1006 p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1007 p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1008 p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1009 p, manager001, /orgs/{orgID}/sites, App001.Module002.Action1010 p, manager001, /orgs/{orgID}/sites, App003.*.Action3001 p, manager001, /orgs/{orgID}/sites, App003.*.Action3002 p, manager001, /orgs/{orgID}/sites, App003.*.Action3003 p, manager001, /orgs/{orgID}/sites, App003.*.Action3004 p, manager001, /orgs/{orgID}/sites, App003.*.Action3005 p, manager001, /orgs/{orgID}, App004.* p, manager001, /orgs, App005.* # Policy - manager002 p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, manager002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, manager002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1006 p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1007 p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1008 p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1009 p, manager002, /orgs/{orgID}/sites, App001.Module002.Action1010 p, manager002, /orgs/{orgID}/sites, App003.*.Action3001 p, manager002, /orgs/{orgID}/sites, App003.*.Action3002 p, manager002, /orgs/{orgID}/sites, App003.*.Action3003 p, manager002, /orgs/{orgID}/sites, App003.*.Action3004 p, manager002, /orgs/{orgID}/sites, App003.*.Action3005 p, manager002, /orgs/{orgID}, App004.* p, manager002, /orgs, App005.* # Policy - customer001 p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, customer001, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, customer001, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1006 p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1007 p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1008 p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1009 p, customer001, /orgs/{orgID}/sites, App001.Module002.Action1010 p, customer001, /orgs/{orgID}/sites, App003.*.Action3001 p, customer001, /orgs/{orgID}/sites, App003.*.Action3002 p, customer001, /orgs/{orgID}/sites, App003.*.Action3003 p, customer001, /orgs/{orgID}/sites, App003.*.Action3004 p, customer001, /orgs/{orgID}/sites, App003.*.Action3005 p, customer001, /orgs/{orgID}, App004.* p, customer001, /orgs, App005.* # Policy - customer002 p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1001 p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1002 p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1003 p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1004 p, customer002, /orgs/{orgID}/sites/{siteID}, App001.Module001.Action1005 p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2001 p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2002 p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2003 p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2004 p, customer002, /orgs/{orgID}/sites/{siteID}, App002.*.Action2005 p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1006 p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1007 p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1008 p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1009 p, customer002, /orgs/{orgID}/sites, App001.Module002.Action1010 p, customer002, /orgs/{orgID}/sites, App003.*.Action3001 p, customer002, /orgs/{orgID}/sites, App003.*.Action3002 p, customer002, /orgs/{orgID}/sites, App003.*.Action3003 p, customer002, /orgs/{orgID}/sites, App003.*.Action3004 p, customer002, /orgs/{orgID}/sites, App003.*.Action3005 p, customer002, /orgs/{orgID}, App004.* p, customer002, /orgs, App005.* # Group - staff001, / org1 g, staffUser1001, staff001, /orgs/1/sites/site001 g, staffUser1001, staff001, /orgs/1/sites/site002 g, staffUser1001, staff001, /orgs/1/sites/site003 g, staffUser1001, staff001, /orgs/1/sites/site004 g, staffUser1001, staff001, /orgs/1/sites/site005 g, staffUser1001, staff001, /orgs/1/sites/site001 g, staffUser1001, staff001, /orgs/1/sites/site002 g, staffUser1001, staff001, /orgs/1/sites/site003 g, staffUser1001, staff001, /orgs/1/sites/site004 g, staffUser1001, staff001, /orgs/1/sites/site005 g, staffUser1003, staff001, /orgs/1/sites/site001 g, staffUser1003, staff001, /orgs/1/sites/site002 g, staffUser1003, staff001, /orgs/1/sites/site003 g, staffUser1003, staff001, /orgs/1/sites/site004 g, staffUser1003, staff001, /orgs/1/sites/site005 g, staffUser1004, staff001, /orgs/1/sites/site001 g, staffUser1004, staff001, /orgs/1/sites/site002 g, staffUser1004, staff001, /orgs/1/sites/site003 g, staffUser1004, staff001, /orgs/1/sites/site004 g, staffUser1004, staff001, /orgs/1/sites/site005 g, staffUser1005, staff001, /orgs/1/sites/site001 g, staffUser1005, staff001, /orgs/1/sites/site002 g, staffUser1005, staff001, /orgs/1/sites/site003 g, staffUser1005, staff001, /orgs/1/sites/site004 g, staffUser1005, staff001, /orgs/1/sites/site005 g, staffUser1006, staff001, /orgs/1/sites/site001 g, staffUser1006, staff001, /orgs/1/sites/site002 g, staffUser1006, staff001, /orgs/1/sites/site003 g, staffUser1006, staff001, /orgs/1/sites/site004 g, staffUser1006, staff001, /orgs/1/sites/site005 g, staffUser1007, staff001, /orgs/1/sites/site001 g, staffUser1007, staff001, /orgs/1/sites/site002 g, staffUser1007, staff001, /orgs/1/sites/site003 g, staffUser1007, staff001, /orgs/1/sites/site004 g, staffUser1007, staff001, /orgs/1/sites/site005 g, staffUser1008, staff001, /orgs/1/sites/site001 g, staffUser1008, staff001, /orgs/1/sites/site002 g, staffUser1008, staff001, /orgs/1/sites/site003 g, staffUser1008, staff001, /orgs/1/sites/site004 g, staffUser1008, staff001, /orgs/1/sites/site005 g, staffUser1009, staff001, /orgs/1/sites/site001 g, staffUser1009, staff001, /orgs/1/sites/site002 g, staffUser1009, staff001, /orgs/1/sites/site003 g, staffUser1009, staff001, /orgs/1/sites/site004 g, staffUser1009, staff001, /orgs/1/sites/site005 g, staffUser1010, staff001, /orgs/1/sites/site001 g, staffUser1010, staff001, /orgs/1/sites/site002 g, staffUser1010, staff001, /orgs/1/sites/site003 g, staffUser1010, staff001, /orgs/1/sites/site004 g, staffUser1010, staff001, /orgs/1/sites/site005 g, staffUser1011, staff001, /orgs/1/sites/site001 g, staffUser1011, staff001, /orgs/1/sites/site002 g, staffUser1011, staff001, /orgs/1/sites/site003 g, staffUser1011, staff001, /orgs/1/sites/site004 g, staffUser1011, staff001, /orgs/1/sites/site005 g, staffUser1012, staff001, /orgs/1/sites/site001 g, staffUser1012, staff001, /orgs/1/sites/site002 g, staffUser1012, staff001, /orgs/1/sites/site003 g, staffUser1012, staff001, /orgs/1/sites/site004 g, staffUser1012, staff001, /orgs/1/sites/site005 g, staffUser1013, staff001, /orgs/1/sites/site001 g, staffUser1013, staff001, /orgs/1/sites/site002 g, staffUser1013, staff001, /orgs/1/sites/site003 g, staffUser1013, staff001, /orgs/1/sites/site004 g, staffUser1013, staff001, /orgs/1/sites/site005 g, staffUser1014, staff001, /orgs/1/sites/site001 g, staffUser1014, staff001, /orgs/1/sites/site002 g, staffUser1014, staff001, /orgs/1/sites/site003 g, staffUser1014, staff001, /orgs/1/sites/site004 g, staffUser1014, staff001, /orgs/1/sites/site005 g, staffUser1015, staff001, /orgs/1/sites/site001 g, staffUser1015, staff001, /orgs/1/sites/site002 g, staffUser1015, staff001, /orgs/1/sites/site003 g, staffUser1015, staff001, /orgs/1/sites/site004 g, staffUser1015, staff001, /orgs/1/sites/site005 g, staffUser1016, staff001, /orgs/1/sites/site001 g, staffUser1016, staff001, /orgs/1/sites/site002 g, staffUser1016, staff001, /orgs/1/sites/site003 g, staffUser1016, staff001, /orgs/1/sites/site004 g, staffUser1016, staff001, /orgs/1/sites/site005 g, staffUser1017, staff001, /orgs/1/sites/site001 g, staffUser1017, staff001, /orgs/1/sites/site002 g, staffUser1017, staff001, /orgs/1/sites/site003 g, staffUser1017, staff001, /orgs/1/sites/site004 g, staffUser1017, staff001, /orgs/1/sites/site005 g, staffUser1018, staff001, /orgs/1/sites/site001 g, staffUser1018, staff001, /orgs/1/sites/site002 g, staffUser1018, staff001, /orgs/1/sites/site003 g, staffUser1018, staff001, /orgs/1/sites/site004 g, staffUser1018, staff001, /orgs/1/sites/site005 g, staffUser1019, staff001, /orgs/1/sites/site001 g, staffUser1019, staff001, /orgs/1/sites/site002 g, staffUser1019, staff001, /orgs/1/sites/site003 g, staffUser1019, staff001, /orgs/1/sites/site004 g, staffUser1019, staff001, /orgs/1/sites/site005 g, staffUser1020, staff001, /orgs/1/sites/site001 g, staffUser1020, staff001, /orgs/1/sites/site002 g, staffUser1020, staff001, /orgs/1/sites/site003 g, staffUser1020, staff001, /orgs/1/sites/site004 g, staffUser1020, staff001, /orgs/1/sites/site005 g, staffUser1021, staff001, /orgs/1/sites/site001 g, staffUser1021, staff001, /orgs/1/sites/site002 g, staffUser1021, staff001, /orgs/1/sites/site003 g, staffUser1021, staff001, /orgs/1/sites/site004 g, staffUser1021, staff001, /orgs/1/sites/site005 g, staffUser1022, staff001, /orgs/1/sites/site001 g, staffUser1022, staff001, /orgs/1/sites/site002 g, staffUser1022, staff001, /orgs/1/sites/site003 g, staffUser1022, staff001, /orgs/1/sites/site004 g, staffUser1022, staff001, /orgs/1/sites/site005 g, staffUser1023, staff001, /orgs/1/sites/site001 g, staffUser1023, staff001, /orgs/1/sites/site002 g, staffUser1023, staff001, /orgs/1/sites/site003 g, staffUser1023, staff001, /orgs/1/sites/site004 g, staffUser1023, staff001, /orgs/1/sites/site005 g, staffUser1024, staff001, /orgs/1/sites/site001 g, staffUser1024, staff001, /orgs/1/sites/site002 g, staffUser1024, staff001, /orgs/1/sites/site003 g, staffUser1024, staff001, /orgs/1/sites/site004 g, staffUser1024, staff001, /orgs/1/sites/site005 g, staffUser1025, staff001, /orgs/1/sites/site001 g, staffUser1025, staff001, /orgs/1/sites/site002 g, staffUser1025, staff001, /orgs/1/sites/site003 g, staffUser1025, staff001, /orgs/1/sites/site004 g, staffUser1025, staff001, /orgs/1/sites/site005 g, staffUser1026, staff001, /orgs/1/sites/site001 g, staffUser1026, staff001, /orgs/1/sites/site002 g, staffUser1026, staff001, /orgs/1/sites/site003 g, staffUser1026, staff001, /orgs/1/sites/site004 g, staffUser1026, staff001, /orgs/1/sites/site005 g, staffUser1027, staff001, /orgs/1/sites/site001 g, staffUser1027, staff001, /orgs/1/sites/site002 g, staffUser1027, staff001, /orgs/1/sites/site003 g, staffUser1027, staff001, /orgs/1/sites/site004 g, staffUser1027, staff001, /orgs/1/sites/site005 g, staffUser1028, staff001, /orgs/1/sites/site001 g, staffUser1028, staff001, /orgs/1/sites/site002 g, staffUser1028, staff001, /orgs/1/sites/site003 g, staffUser1028, staff001, /orgs/1/sites/site004 g, staffUser1028, staff001, /orgs/1/sites/site005 g, staffUser1029, staff001, /orgs/1/sites/site001 g, staffUser1029, staff001, /orgs/1/sites/site002 g, staffUser1029, staff001, /orgs/1/sites/site003 g, staffUser1029, staff001, /orgs/1/sites/site004 g, staffUser1029, staff001, /orgs/1/sites/site005 g, staffUser1030, staff001, /orgs/1/sites/site001 g, staffUser1030, staff001, /orgs/1/sites/site002 g, staffUser1030, staff001, /orgs/1/sites/site003 g, staffUser1030, staff001, /orgs/1/sites/site004 g, staffUser1030, staff001, /orgs/1/sites/site005 g, staffUser1031, staff001, /orgs/1/sites/site001 g, staffUser1031, staff001, /orgs/1/sites/site002 g, staffUser1031, staff001, /orgs/1/sites/site003 g, staffUser1031, staff001, /orgs/1/sites/site004 g, staffUser1031, staff001, /orgs/1/sites/site005 g, staffUser1032, staff001, /orgs/1/sites/site001 g, staffUser1032, staff001, /orgs/1/sites/site002 g, staffUser1032, staff001, /orgs/1/sites/site003 g, staffUser1032, staff001, /orgs/1/sites/site004 g, staffUser1032, staff001, /orgs/1/sites/site005 g, staffUser1033, staff001, /orgs/1/sites/site001 g, staffUser1033, staff001, /orgs/1/sites/site002 g, staffUser1033, staff001, /orgs/1/sites/site003 g, staffUser1033, staff001, /orgs/1/sites/site004 g, staffUser1033, staff001, /orgs/1/sites/site005 g, staffUser1034, staff001, /orgs/1/sites/site001 g, staffUser1034, staff001, /orgs/1/sites/site002 g, staffUser1034, staff001, /orgs/1/sites/site003 g, staffUser1034, staff001, /orgs/1/sites/site004 g, staffUser1034, staff001, /orgs/1/sites/site005 g, staffUser1035, staff001, /orgs/1/sites/site001 g, staffUser1035, staff001, /orgs/1/sites/site002 g, staffUser1035, staff001, /orgs/1/sites/site003 g, staffUser1035, staff001, /orgs/1/sites/site004 g, staffUser1035, staff001, /orgs/1/sites/site005 g, staffUser1036, staff001, /orgs/1/sites/site001 g, staffUser1036, staff001, /orgs/1/sites/site002 g, staffUser1036, staff001, /orgs/1/sites/site003 g, staffUser1036, staff001, /orgs/1/sites/site004 g, staffUser1036, staff001, /orgs/1/sites/site005 g, staffUser1037, staff001, /orgs/1/sites/site001 g, staffUser1037, staff001, /orgs/1/sites/site002 g, staffUser1037, staff001, /orgs/1/sites/site003 g, staffUser1037, staff001, /orgs/1/sites/site004 g, staffUser1037, staff001, /orgs/1/sites/site005 g, staffUser1038, staff001, /orgs/1/sites/site001 g, staffUser1038, staff001, /orgs/1/sites/site002 g, staffUser1038, staff001, /orgs/1/sites/site003 g, staffUser1038, staff001, /orgs/1/sites/site004 g, staffUser1038, staff001, /orgs/1/sites/site005 g, staffUser1039, staff001, /orgs/1/sites/site001 g, staffUser1039, staff001, /orgs/1/sites/site002 g, staffUser1039, staff001, /orgs/1/sites/site003 g, staffUser1039, staff001, /orgs/1/sites/site004 g, staffUser1039, staff001, /orgs/1/sites/site005 g, staffUser1040, staff001, /orgs/1/sites/site001 g, staffUser1040, staff001, /orgs/1/sites/site002 g, staffUser1040, staff001, /orgs/1/sites/site003 g, staffUser1040, staff001, /orgs/1/sites/site004 g, staffUser1040, staff001, /orgs/1/sites/site005 g, staffUser1041, staff001, /orgs/1/sites/site001 g, staffUser1041, staff001, /orgs/1/sites/site002 g, staffUser1041, staff001, /orgs/1/sites/site003 g, staffUser1041, staff001, /orgs/1/sites/site004 g, staffUser1041, staff001, /orgs/1/sites/site005 g, staffUser1042, staff001, /orgs/1/sites/site001 g, staffUser1042, staff001, /orgs/1/sites/site002 g, staffUser1042, staff001, /orgs/1/sites/site003 g, staffUser1042, staff001, /orgs/1/sites/site004 g, staffUser1042, staff001, /orgs/1/sites/site005 g, staffUser1043, staff001, /orgs/1/sites/site001 g, staffUser1043, staff001, /orgs/1/sites/site002 g, staffUser1043, staff001, /orgs/1/sites/site003 g, staffUser1043, staff001, /orgs/1/sites/site004 g, staffUser1043, staff001, /orgs/1/sites/site005 g, staffUser1044, staff001, /orgs/1/sites/site001 g, staffUser1044, staff001, /orgs/1/sites/site002 g, staffUser1044, staff001, /orgs/1/sites/site003 g, staffUser1044, staff001, /orgs/1/sites/site004 g, staffUser1044, staff001, /orgs/1/sites/site005 g, staffUser1045, staff001, /orgs/1/sites/site001 g, staffUser1045, staff001, /orgs/1/sites/site002 g, staffUser1045, staff001, /orgs/1/sites/site003 g, staffUser1045, staff001, /orgs/1/sites/site004 g, staffUser1045, staff001, /orgs/1/sites/site005 g, staffUser1046, staff001, /orgs/1/sites/site001 g, staffUser1046, staff001, /orgs/1/sites/site002 g, staffUser1046, staff001, /orgs/1/sites/site003 g, staffUser1046, staff001, /orgs/1/sites/site004 g, staffUser1046, staff001, /orgs/1/sites/site005 g, staffUser1047, staff001, /orgs/1/sites/site001 g, staffUser1047, staff001, /orgs/1/sites/site002 g, staffUser1047, staff001, /orgs/1/sites/site003 g, staffUser1047, staff001, /orgs/1/sites/site004 g, staffUser1047, staff001, /orgs/1/sites/site005 g, staffUser1048, staff001, /orgs/1/sites/site001 g, staffUser1048, staff001, /orgs/1/sites/site002 g, staffUser1048, staff001, /orgs/1/sites/site003 g, staffUser1048, staff001, /orgs/1/sites/site004 g, staffUser1048, staff001, /orgs/1/sites/site005 g, staffUser1049, staff001, /orgs/1/sites/site001 g, staffUser1049, staff001, /orgs/1/sites/site002 g, staffUser1049, staff001, /orgs/1/sites/site003 g, staffUser1049, staff001, /orgs/1/sites/site004 g, staffUser1049, staff001, /orgs/1/sites/site005 g, staffUser1050, staff001, /orgs/1/sites/site001 g, staffUser1050, staff001, /orgs/1/sites/site002 g, staffUser1050, staff001, /orgs/1/sites/site003 g, staffUser1050, staff001, /orgs/1/sites/site004 g, staffUser1050, staff001, /orgs/1/sites/site005 # Group - staff001, / org1 g, staffUser2001, staff001, /orgs/1/sites/site001 g, staffUser2001, staff001, /orgs/1/sites/site002 g, staffUser2001, staff001, /orgs/1/sites/site003 g, staffUser2001, staff001, /orgs/1/sites/site004 g, staffUser2001, staff001, /orgs/1/sites/site005 g, staffUser2001, staff001, /orgs/1/sites/site001 g, staffUser2001, staff001, /orgs/1/sites/site002 g, staffUser2001, staff001, /orgs/1/sites/site003 g, staffUser2001, staff001, /orgs/1/sites/site004 g, staffUser2001, staff001, /orgs/1/sites/site005 g, staffUser2003, staff001, /orgs/1/sites/site001 g, staffUser2003, staff001, /orgs/1/sites/site002 g, staffUser2003, staff001, /orgs/1/sites/site003 g, staffUser2003, staff001, /orgs/1/sites/site004 g, staffUser2003, staff001, /orgs/1/sites/site005 g, staffUser2004, staff001, /orgs/1/sites/site001 g, staffUser2004, staff001, /orgs/1/sites/site002 g, staffUser2004, staff001, /orgs/1/sites/site003 g, staffUser2004, staff001, /orgs/1/sites/site004 g, staffUser2004, staff001, /orgs/1/sites/site005 g, staffUser2005, staff001, /orgs/1/sites/site001 g, staffUser2005, staff001, /orgs/1/sites/site002 g, staffUser2005, staff001, /orgs/1/sites/site003 g, staffUser2005, staff001, /orgs/1/sites/site004 g, staffUser2005, staff001, /orgs/1/sites/site005 g, staffUser2006, staff001, /orgs/1/sites/site001 g, staffUser2006, staff001, /orgs/1/sites/site002 g, staffUser2006, staff001, /orgs/1/sites/site003 g, staffUser2006, staff001, /orgs/1/sites/site004 g, staffUser2006, staff001, /orgs/1/sites/site005 g, staffUser2007, staff001, /orgs/1/sites/site001 g, staffUser2007, staff001, /orgs/1/sites/site002 g, staffUser2007, staff001, /orgs/1/sites/site003 g, staffUser2007, staff001, /orgs/1/sites/site004 g, staffUser2007, staff001, /orgs/1/sites/site005 g, staffUser2008, staff001, /orgs/1/sites/site001 g, staffUser2008, staff001, /orgs/1/sites/site002 g, staffUser2008, staff001, /orgs/1/sites/site003 g, staffUser2008, staff001, /orgs/1/sites/site004 g, staffUser2008, staff001, /orgs/1/sites/site005 g, staffUser2009, staff001, /orgs/1/sites/site001 g, staffUser2009, staff001, /orgs/1/sites/site002 g, staffUser2009, staff001, /orgs/1/sites/site003 g, staffUser2009, staff001, /orgs/1/sites/site004 g, staffUser2009, staff001, /orgs/1/sites/site005 g, staffUser2010, staff001, /orgs/1/sites/site001 g, staffUser2010, staff001, /orgs/1/sites/site002 g, staffUser2010, staff001, /orgs/1/sites/site003 g, staffUser2010, staff001, /orgs/1/sites/site004 g, staffUser2010, staff001, /orgs/1/sites/site005 g, staffUser2011, staff001, /orgs/1/sites/site001 g, staffUser2011, staff001, /orgs/1/sites/site002 g, staffUser2011, staff001, /orgs/1/sites/site003 g, staffUser2011, staff001, /orgs/1/sites/site004 g, staffUser2011, staff001, /orgs/1/sites/site005 g, staffUser2012, staff001, /orgs/1/sites/site001 g, staffUser2012, staff001, /orgs/1/sites/site002 g, staffUser2012, staff001, /orgs/1/sites/site003 g, staffUser2012, staff001, /orgs/1/sites/site004 g, staffUser2012, staff001, /orgs/1/sites/site005 g, staffUser2013, staff001, /orgs/1/sites/site001 g, staffUser2013, staff001, /orgs/1/sites/site002 g, staffUser2013, staff001, /orgs/1/sites/site003 g, staffUser2013, staff001, /orgs/1/sites/site004 g, staffUser2013, staff001, /orgs/1/sites/site005 g, staffUser2014, staff001, /orgs/1/sites/site001 g, staffUser2014, staff001, /orgs/1/sites/site002 g, staffUser2014, staff001, /orgs/1/sites/site003 g, staffUser2014, staff001, /orgs/1/sites/site004 g, staffUser2014, staff001, /orgs/1/sites/site005 g, staffUser2015, staff001, /orgs/1/sites/site001 g, staffUser2015, staff001, /orgs/1/sites/site002 g, staffUser2015, staff001, /orgs/1/sites/site003 g, staffUser2015, staff001, /orgs/1/sites/site004 g, staffUser2015, staff001, /orgs/1/sites/site005 g, staffUser2016, staff001, /orgs/1/sites/site001 g, staffUser2016, staff001, /orgs/1/sites/site002 g, staffUser2016, staff001, /orgs/1/sites/site003 g, staffUser2016, staff001, /orgs/1/sites/site004 g, staffUser2016, staff001, /orgs/1/sites/site005 g, staffUser2017, staff001, /orgs/1/sites/site001 g, staffUser2017, staff001, /orgs/1/sites/site002 g, staffUser2017, staff001, /orgs/1/sites/site003 g, staffUser2017, staff001, /orgs/1/sites/site004 g, staffUser2017, staff001, /orgs/1/sites/site005 g, staffUser2018, staff001, /orgs/1/sites/site001 g, staffUser2018, staff001, /orgs/1/sites/site002 g, staffUser2018, staff001, /orgs/1/sites/site003 g, staffUser2018, staff001, /orgs/1/sites/site004 g, staffUser2018, staff001, /orgs/1/sites/site005 g, staffUser2019, staff001, /orgs/1/sites/site001 g, staffUser2019, staff001, /orgs/1/sites/site002 g, staffUser2019, staff001, /orgs/1/sites/site003 g, staffUser2019, staff001, /orgs/1/sites/site004 g, staffUser2019, staff001, /orgs/1/sites/site005 g, staffUser2020, staff001, /orgs/1/sites/site001 g, staffUser2020, staff001, /orgs/1/sites/site002 g, staffUser2020, staff001, /orgs/1/sites/site003 g, staffUser2020, staff001, /orgs/1/sites/site004 g, staffUser2020, staff001, /orgs/1/sites/site005 g, staffUser2021, staff001, /orgs/1/sites/site001 g, staffUser2021, staff001, /orgs/1/sites/site002 g, staffUser2021, staff001, /orgs/1/sites/site003 g, staffUser2021, staff001, /orgs/1/sites/site004 g, staffUser2021, staff001, /orgs/1/sites/site005 g, staffUser2022, staff001, /orgs/1/sites/site001 g, staffUser2022, staff001, /orgs/1/sites/site002 g, staffUser2022, staff001, /orgs/1/sites/site003 g, staffUser2022, staff001, /orgs/1/sites/site004 g, staffUser2022, staff001, /orgs/1/sites/site005 g, staffUser2023, staff001, /orgs/1/sites/site001 g, staffUser2023, staff001, /orgs/1/sites/site002 g, staffUser2023, staff001, /orgs/1/sites/site003 g, staffUser2023, staff001, /orgs/1/sites/site004 g, staffUser2023, staff001, /orgs/1/sites/site005 g, staffUser2024, staff001, /orgs/1/sites/site001 g, staffUser2024, staff001, /orgs/1/sites/site002 g, staffUser2024, staff001, /orgs/1/sites/site003 g, staffUser2024, staff001, /orgs/1/sites/site004 g, staffUser2024, staff001, /orgs/1/sites/site005 g, staffUser2025, staff001, /orgs/1/sites/site001 g, staffUser2025, staff001, /orgs/1/sites/site002 g, staffUser2025, staff001, /orgs/1/sites/site003 g, staffUser2025, staff001, /orgs/1/sites/site004 g, staffUser2025, staff001, /orgs/1/sites/site005 g, staffUser2026, staff001, /orgs/1/sites/site001 g, staffUser2026, staff001, /orgs/1/sites/site002 g, staffUser2026, staff001, /orgs/1/sites/site003 g, staffUser2026, staff001, /orgs/1/sites/site004 g, staffUser2026, staff001, /orgs/1/sites/site005 g, staffUser2027, staff001, /orgs/1/sites/site001 g, staffUser2027, staff001, /orgs/1/sites/site002 g, staffUser2027, staff001, /orgs/1/sites/site003 g, staffUser2027, staff001, /orgs/1/sites/site004 g, staffUser2027, staff001, /orgs/1/sites/site005 g, staffUser2028, staff001, /orgs/1/sites/site001 g, staffUser2028, staff001, /orgs/1/sites/site002 g, staffUser2028, staff001, /orgs/1/sites/site003 g, staffUser2028, staff001, /orgs/1/sites/site004 g, staffUser2028, staff001, /orgs/1/sites/site005 g, staffUser2029, staff001, /orgs/1/sites/site001 g, staffUser2029, staff001, /orgs/1/sites/site002 g, staffUser2029, staff001, /orgs/1/sites/site003 g, staffUser2029, staff001, /orgs/1/sites/site004 g, staffUser2029, staff001, /orgs/1/sites/site005 g, staffUser2030, staff001, /orgs/1/sites/site001 g, staffUser2030, staff001, /orgs/1/sites/site002 g, staffUser2030, staff001, /orgs/1/sites/site003 g, staffUser2030, staff001, /orgs/1/sites/site004 g, staffUser2030, staff001, /orgs/1/sites/site005 g, staffUser2031, staff001, /orgs/1/sites/site001 g, staffUser2031, staff001, /orgs/1/sites/site002 g, staffUser2031, staff001, /orgs/1/sites/site003 g, staffUser2031, staff001, /orgs/1/sites/site004 g, staffUser2031, staff001, /orgs/1/sites/site005 g, staffUser2032, staff001, /orgs/1/sites/site001 g, staffUser2032, staff001, /orgs/1/sites/site002 g, staffUser2032, staff001, /orgs/1/sites/site003 g, staffUser2032, staff001, /orgs/1/sites/site004 g, staffUser2032, staff001, /orgs/1/sites/site005 g, staffUser2033, staff001, /orgs/1/sites/site001 g, staffUser2033, staff001, /orgs/1/sites/site002 g, staffUser2033, staff001, /orgs/1/sites/site003 g, staffUser2033, staff001, /orgs/1/sites/site004 g, staffUser2033, staff001, /orgs/1/sites/site005 g, staffUser2034, staff001, /orgs/1/sites/site001 g, staffUser2034, staff001, /orgs/1/sites/site002 g, staffUser2034, staff001, /orgs/1/sites/site003 g, staffUser2034, staff001, /orgs/1/sites/site004 g, staffUser2034, staff001, /orgs/1/sites/site005 g, staffUser2035, staff001, /orgs/1/sites/site001 g, staffUser2035, staff001, /orgs/1/sites/site002 g, staffUser2035, staff001, /orgs/1/sites/site003 g, staffUser2035, staff001, /orgs/1/sites/site004 g, staffUser2035, staff001, /orgs/1/sites/site005 g, staffUser2036, staff001, /orgs/1/sites/site001 g, staffUser2036, staff001, /orgs/1/sites/site002 g, staffUser2036, staff001, /orgs/1/sites/site003 g, staffUser2036, staff001, /orgs/1/sites/site004 g, staffUser2036, staff001, /orgs/1/sites/site005 g, staffUser2037, staff001, /orgs/1/sites/site001 g, staffUser2037, staff001, /orgs/1/sites/site002 g, staffUser2037, staff001, /orgs/1/sites/site003 g, staffUser2037, staff001, /orgs/1/sites/site004 g, staffUser2037, staff001, /orgs/1/sites/site005 g, staffUser2038, staff001, /orgs/1/sites/site001 g, staffUser2038, staff001, /orgs/1/sites/site002 g, staffUser2038, staff001, /orgs/1/sites/site003 g, staffUser2038, staff001, /orgs/1/sites/site004 g, staffUser2038, staff001, /orgs/1/sites/site005 g, staffUser2039, staff001, /orgs/1/sites/site001 g, staffUser2039, staff001, /orgs/1/sites/site002 g, staffUser2039, staff001, /orgs/1/sites/site003 g, staffUser2039, staff001, /orgs/1/sites/site004 g, staffUser2039, staff001, /orgs/1/sites/site005 g, staffUser2040, staff001, /orgs/1/sites/site001 g, staffUser2040, staff001, /orgs/1/sites/site002 g, staffUser2040, staff001, /orgs/1/sites/site003 g, staffUser2040, staff001, /orgs/1/sites/site004 g, staffUser2040, staff001, /orgs/1/sites/site005 g, staffUser2041, staff001, /orgs/1/sites/site001 g, staffUser2041, staff001, /orgs/1/sites/site002 g, staffUser2041, staff001, /orgs/1/sites/site003 g, staffUser2041, staff001, /orgs/1/sites/site004 g, staffUser2041, staff001, /orgs/1/sites/site005 g, staffUser2042, staff001, /orgs/1/sites/site001 g, staffUser2042, staff001, /orgs/1/sites/site002 g, staffUser2042, staff001, /orgs/1/sites/site003 g, staffUser2042, staff001, /orgs/1/sites/site004 g, staffUser2042, staff001, /orgs/1/sites/site005 g, staffUser2043, staff001, /orgs/1/sites/site001 g, staffUser2043, staff001, /orgs/1/sites/site002 g, staffUser2043, staff001, /orgs/1/sites/site003 g, staffUser2043, staff001, /orgs/1/sites/site004 g, staffUser2043, staff001, /orgs/1/sites/site005 g, staffUser2044, staff001, /orgs/1/sites/site001 g, staffUser2044, staff001, /orgs/1/sites/site002 g, staffUser2044, staff001, /orgs/1/sites/site003 g, staffUser2044, staff001, /orgs/1/sites/site004 g, staffUser2044, staff001, /orgs/1/sites/site005 g, staffUser2045, staff001, /orgs/1/sites/site001 g, staffUser2045, staff001, /orgs/1/sites/site002 g, staffUser2045, staff001, /orgs/1/sites/site003 g, staffUser2045, staff001, /orgs/1/sites/site004 g, staffUser2045, staff001, /orgs/1/sites/site005 g, staffUser2046, staff001, /orgs/1/sites/site001 g, staffUser2046, staff001, /orgs/1/sites/site002 g, staffUser2046, staff001, /orgs/1/sites/site003 g, staffUser2046, staff001, /orgs/1/sites/site004 g, staffUser2046, staff001, /orgs/1/sites/site005 g, staffUser2047, staff001, /orgs/1/sites/site001 g, staffUser2047, staff001, /orgs/1/sites/site002 g, staffUser2047, staff001, /orgs/1/sites/site003 g, staffUser2047, staff001, /orgs/1/sites/site004 g, staffUser2047, staff001, /orgs/1/sites/site005 g, staffUser2048, staff001, /orgs/1/sites/site001 g, staffUser2048, staff001, /orgs/1/sites/site002 g, staffUser2048, staff001, /orgs/1/sites/site003 g, staffUser2048, staff001, /orgs/1/sites/site004 g, staffUser2048, staff001, /orgs/1/sites/site005 g, staffUser2049, staff001, /orgs/1/sites/site001 g, staffUser2049, staff001, /orgs/1/sites/site002 g, staffUser2049, staff001, /orgs/1/sites/site003 g, staffUser2049, staff001, /orgs/1/sites/site004 g, staffUser2049, staff001, /orgs/1/sites/site005 g, staffUser2050, staff001, /orgs/1/sites/site001 g, staffUser2050, staff001, /orgs/1/sites/site002 g, staffUser2050, staff001, /orgs/1/sites/site003 g, staffUser2050, staff001, /orgs/1/sites/site004 g, staffUser2050, staff001, /orgs/1/sites/site005 # Group - manager001, / org1 g, managerUser1001, manager001, /orgs/1/sites/site001 g, managerUser1001, manager001, /orgs/1/sites/site002 g, managerUser1001, manager001, /orgs/1/sites/site003 g, managerUser1001, manager001, /orgs/1/sites/site004 g, managerUser1001, manager001, /orgs/1/sites/site005 g, managerUser1001, manager001, /orgs/1/sites/site001 g, managerUser1001, manager001, /orgs/1/sites/site002 g, managerUser1001, manager001, /orgs/1/sites/site003 g, managerUser1001, manager001, /orgs/1/sites/site004 g, managerUser1001, manager001, /orgs/1/sites/site005 g, managerUser1003, manager001, /orgs/1/sites/site001 g, managerUser1003, manager001, /orgs/1/sites/site002 g, managerUser1003, manager001, /orgs/1/sites/site003 g, managerUser1003, manager001, /orgs/1/sites/site004 g, managerUser1003, manager001, /orgs/1/sites/site005 g, managerUser1004, manager001, /orgs/1/sites/site001 g, managerUser1004, manager001, /orgs/1/sites/site002 g, managerUser1004, manager001, /orgs/1/sites/site003 g, managerUser1004, manager001, /orgs/1/sites/site004 g, managerUser1004, manager001, /orgs/1/sites/site005 g, managerUser1005, manager001, /orgs/1/sites/site001 g, managerUser1005, manager001, /orgs/1/sites/site002 g, managerUser1005, manager001, /orgs/1/sites/site003 g, managerUser1005, manager001, /orgs/1/sites/site004 g, managerUser1005, manager001, /orgs/1/sites/site005 g, managerUser1006, manager001, /orgs/1/sites/site001 g, managerUser1006, manager001, /orgs/1/sites/site002 g, managerUser1006, manager001, /orgs/1/sites/site003 g, managerUser1006, manager001, /orgs/1/sites/site004 g, managerUser1006, manager001, /orgs/1/sites/site005 g, managerUser1007, manager001, /orgs/1/sites/site001 g, managerUser1007, manager001, /orgs/1/sites/site002 g, managerUser1007, manager001, /orgs/1/sites/site003 g, managerUser1007, manager001, /orgs/1/sites/site004 g, managerUser1007, manager001, /orgs/1/sites/site005 g, managerUser1008, manager001, /orgs/1/sites/site001 g, managerUser1008, manager001, /orgs/1/sites/site002 g, managerUser1008, manager001, /orgs/1/sites/site003 g, managerUser1008, manager001, /orgs/1/sites/site004 g, managerUser1008, manager001, /orgs/1/sites/site005 g, managerUser1009, manager001, /orgs/1/sites/site001 g, managerUser1009, manager001, /orgs/1/sites/site002 g, managerUser1009, manager001, /orgs/1/sites/site003 g, managerUser1009, manager001, /orgs/1/sites/site004 g, managerUser1009, manager001, /orgs/1/sites/site005 g, managerUser1010, manager001, /orgs/1/sites/site001 g, managerUser1010, manager001, /orgs/1/sites/site002 g, managerUser1010, manager001, /orgs/1/sites/site003 g, managerUser1010, manager001, /orgs/1/sites/site004 g, managerUser1010, manager001, /orgs/1/sites/site005 g, managerUser1011, manager001, /orgs/1/sites/site001 g, managerUser1011, manager001, /orgs/1/sites/site002 g, managerUser1011, manager001, /orgs/1/sites/site003 g, managerUser1011, manager001, /orgs/1/sites/site004 g, managerUser1011, manager001, /orgs/1/sites/site005 g, managerUser1012, manager001, /orgs/1/sites/site001 g, managerUser1012, manager001, /orgs/1/sites/site002 g, managerUser1012, manager001, /orgs/1/sites/site003 g, managerUser1012, manager001, /orgs/1/sites/site004 g, managerUser1012, manager001, /orgs/1/sites/site005 g, managerUser1013, manager001, /orgs/1/sites/site001 g, managerUser1013, manager001, /orgs/1/sites/site002 g, managerUser1013, manager001, /orgs/1/sites/site003 g, managerUser1013, manager001, /orgs/1/sites/site004 g, managerUser1013, manager001, /orgs/1/sites/site005 g, managerUser1014, manager001, /orgs/1/sites/site001 g, managerUser1014, manager001, /orgs/1/sites/site002 g, managerUser1014, manager001, /orgs/1/sites/site003 g, managerUser1014, manager001, /orgs/1/sites/site004 g, managerUser1014, manager001, /orgs/1/sites/site005 g, managerUser1015, manager001, /orgs/1/sites/site001 g, managerUser1015, manager001, /orgs/1/sites/site002 g, managerUser1015, manager001, /orgs/1/sites/site003 g, managerUser1015, manager001, /orgs/1/sites/site004 g, managerUser1015, manager001, /orgs/1/sites/site005 g, managerUser1016, manager001, /orgs/1/sites/site001 g, managerUser1016, manager001, /orgs/1/sites/site002 g, managerUser1016, manager001, /orgs/1/sites/site003 g, managerUser1016, manager001, /orgs/1/sites/site004 g, managerUser1016, manager001, /orgs/1/sites/site005 g, managerUser1017, manager001, /orgs/1/sites/site001 g, managerUser1017, manager001, /orgs/1/sites/site002 g, managerUser1017, manager001, /orgs/1/sites/site003 g, managerUser1017, manager001, /orgs/1/sites/site004 g, managerUser1017, manager001, /orgs/1/sites/site005 g, managerUser1018, manager001, /orgs/1/sites/site001 g, managerUser1018, manager001, /orgs/1/sites/site002 g, managerUser1018, manager001, /orgs/1/sites/site003 g, managerUser1018, manager001, /orgs/1/sites/site004 g, managerUser1018, manager001, /orgs/1/sites/site005 g, managerUser1019, manager001, /orgs/1/sites/site001 g, managerUser1019, manager001, /orgs/1/sites/site002 g, managerUser1019, manager001, /orgs/1/sites/site003 g, managerUser1019, manager001, /orgs/1/sites/site004 g, managerUser1019, manager001, /orgs/1/sites/site005 g, managerUser1020, manager001, /orgs/1/sites/site001 g, managerUser1020, manager001, /orgs/1/sites/site002 g, managerUser1020, manager001, /orgs/1/sites/site003 g, managerUser1020, manager001, /orgs/1/sites/site004 g, managerUser1020, manager001, /orgs/1/sites/site005 g, managerUser1021, manager001, /orgs/1/sites/site001 g, managerUser1021, manager001, /orgs/1/sites/site002 g, managerUser1021, manager001, /orgs/1/sites/site003 g, managerUser1021, manager001, /orgs/1/sites/site004 g, managerUser1021, manager001, /orgs/1/sites/site005 g, managerUser1022, manager001, /orgs/1/sites/site001 g, managerUser1022, manager001, /orgs/1/sites/site002 g, managerUser1022, manager001, /orgs/1/sites/site003 g, managerUser1022, manager001, /orgs/1/sites/site004 g, managerUser1022, manager001, /orgs/1/sites/site005 g, managerUser1023, manager001, /orgs/1/sites/site001 g, managerUser1023, manager001, /orgs/1/sites/site002 g, managerUser1023, manager001, /orgs/1/sites/site003 g, managerUser1023, manager001, /orgs/1/sites/site004 g, managerUser1023, manager001, /orgs/1/sites/site005 g, managerUser1024, manager001, /orgs/1/sites/site001 g, managerUser1024, manager001, /orgs/1/sites/site002 g, managerUser1024, manager001, /orgs/1/sites/site003 g, managerUser1024, manager001, /orgs/1/sites/site004 g, managerUser1024, manager001, /orgs/1/sites/site005 g, managerUser1025, manager001, /orgs/1/sites/site001 g, managerUser1025, manager001, /orgs/1/sites/site002 g, managerUser1025, manager001, /orgs/1/sites/site003 g, managerUser1025, manager001, /orgs/1/sites/site004 g, managerUser1025, manager001, /orgs/1/sites/site005 g, managerUser1026, manager001, /orgs/1/sites/site001 g, managerUser1026, manager001, /orgs/1/sites/site002 g, managerUser1026, manager001, /orgs/1/sites/site003 g, managerUser1026, manager001, /orgs/1/sites/site004 g, managerUser1026, manager001, /orgs/1/sites/site005 g, managerUser1027, manager001, /orgs/1/sites/site001 g, managerUser1027, manager001, /orgs/1/sites/site002 g, managerUser1027, manager001, /orgs/1/sites/site003 g, managerUser1027, manager001, /orgs/1/sites/site004 g, managerUser1027, manager001, /orgs/1/sites/site005 g, managerUser1028, manager001, /orgs/1/sites/site001 g, managerUser1028, manager001, /orgs/1/sites/site002 g, managerUser1028, manager001, /orgs/1/sites/site003 g, managerUser1028, manager001, /orgs/1/sites/site004 g, managerUser1028, manager001, /orgs/1/sites/site005 g, managerUser1029, manager001, /orgs/1/sites/site001 g, managerUser1029, manager001, /orgs/1/sites/site002 g, managerUser1029, manager001, /orgs/1/sites/site003 g, managerUser1029, manager001, /orgs/1/sites/site004 g, managerUser1029, manager001, /orgs/1/sites/site005 g, managerUser1030, manager001, /orgs/1/sites/site001 g, managerUser1030, manager001, /orgs/1/sites/site002 g, managerUser1030, manager001, /orgs/1/sites/site003 g, managerUser1030, manager001, /orgs/1/sites/site004 g, managerUser1030, manager001, /orgs/1/sites/site005 g, managerUser1031, manager001, /orgs/1/sites/site001 g, managerUser1031, manager001, /orgs/1/sites/site002 g, managerUser1031, manager001, /orgs/1/sites/site003 g, managerUser1031, manager001, /orgs/1/sites/site004 g, managerUser1031, manager001, /orgs/1/sites/site005 g, managerUser1032, manager001, /orgs/1/sites/site001 g, managerUser1032, manager001, /orgs/1/sites/site002 g, managerUser1032, manager001, /orgs/1/sites/site003 g, managerUser1032, manager001, /orgs/1/sites/site004 g, managerUser1032, manager001, /orgs/1/sites/site005 g, managerUser1033, manager001, /orgs/1/sites/site001 g, managerUser1033, manager001, /orgs/1/sites/site002 g, managerUser1033, manager001, /orgs/1/sites/site003 g, managerUser1033, manager001, /orgs/1/sites/site004 g, managerUser1033, manager001, /orgs/1/sites/site005 g, managerUser1034, manager001, /orgs/1/sites/site001 g, managerUser1034, manager001, /orgs/1/sites/site002 g, managerUser1034, manager001, /orgs/1/sites/site003 g, managerUser1034, manager001, /orgs/1/sites/site004 g, managerUser1034, manager001, /orgs/1/sites/site005 g, managerUser1035, manager001, /orgs/1/sites/site001 g, managerUser1035, manager001, /orgs/1/sites/site002 g, managerUser1035, manager001, /orgs/1/sites/site003 g, managerUser1035, manager001, /orgs/1/sites/site004 g, managerUser1035, manager001, /orgs/1/sites/site005 g, managerUser1036, manager001, /orgs/1/sites/site001 g, managerUser1036, manager001, /orgs/1/sites/site002 g, managerUser1036, manager001, /orgs/1/sites/site003 g, managerUser1036, manager001, /orgs/1/sites/site004 g, managerUser1036, manager001, /orgs/1/sites/site005 g, managerUser1037, manager001, /orgs/1/sites/site001 g, managerUser1037, manager001, /orgs/1/sites/site002 g, managerUser1037, manager001, /orgs/1/sites/site003 g, managerUser1037, manager001, /orgs/1/sites/site004 g, managerUser1037, manager001, /orgs/1/sites/site005 g, managerUser1038, manager001, /orgs/1/sites/site001 g, managerUser1038, manager001, /orgs/1/sites/site002 g, managerUser1038, manager001, /orgs/1/sites/site003 g, managerUser1038, manager001, /orgs/1/sites/site004 g, managerUser1038, manager001, /orgs/1/sites/site005 g, managerUser1039, manager001, /orgs/1/sites/site001 g, managerUser1039, manager001, /orgs/1/sites/site002 g, managerUser1039, manager001, /orgs/1/sites/site003 g, managerUser1039, manager001, /orgs/1/sites/site004 g, managerUser1039, manager001, /orgs/1/sites/site005 g, managerUser1040, manager001, /orgs/1/sites/site001 g, managerUser1040, manager001, /orgs/1/sites/site002 g, managerUser1040, manager001, /orgs/1/sites/site003 g, managerUser1040, manager001, /orgs/1/sites/site004 g, managerUser1040, manager001, /orgs/1/sites/site005 g, managerUser1041, manager001, /orgs/1/sites/site001 g, managerUser1041, manager001, /orgs/1/sites/site002 g, managerUser1041, manager001, /orgs/1/sites/site003 g, managerUser1041, manager001, /orgs/1/sites/site004 g, managerUser1041, manager001, /orgs/1/sites/site005 g, managerUser1042, manager001, /orgs/1/sites/site001 g, managerUser1042, manager001, /orgs/1/sites/site002 g, managerUser1042, manager001, /orgs/1/sites/site003 g, managerUser1042, manager001, /orgs/1/sites/site004 g, managerUser1042, manager001, /orgs/1/sites/site005 g, managerUser1043, manager001, /orgs/1/sites/site001 g, managerUser1043, manager001, /orgs/1/sites/site002 g, managerUser1043, manager001, /orgs/1/sites/site003 g, managerUser1043, manager001, /orgs/1/sites/site004 g, managerUser1043, manager001, /orgs/1/sites/site005 g, managerUser1044, manager001, /orgs/1/sites/site001 g, managerUser1044, manager001, /orgs/1/sites/site002 g, managerUser1044, manager001, /orgs/1/sites/site003 g, managerUser1044, manager001, /orgs/1/sites/site004 g, managerUser1044, manager001, /orgs/1/sites/site005 g, managerUser1045, manager001, /orgs/1/sites/site001 g, managerUser1045, manager001, /orgs/1/sites/site002 g, managerUser1045, manager001, /orgs/1/sites/site003 g, managerUser1045, manager001, /orgs/1/sites/site004 g, managerUser1045, manager001, /orgs/1/sites/site005 g, managerUser1046, manager001, /orgs/1/sites/site001 g, managerUser1046, manager001, /orgs/1/sites/site002 g, managerUser1046, manager001, /orgs/1/sites/site003 g, managerUser1046, manager001, /orgs/1/sites/site004 g, managerUser1046, manager001, /orgs/1/sites/site005 g, managerUser1047, manager001, /orgs/1/sites/site001 g, managerUser1047, manager001, /orgs/1/sites/site002 g, managerUser1047, manager001, /orgs/1/sites/site003 g, managerUser1047, manager001, /orgs/1/sites/site004 g, managerUser1047, manager001, /orgs/1/sites/site005 g, managerUser1048, manager001, /orgs/1/sites/site001 g, managerUser1048, manager001, /orgs/1/sites/site002 g, managerUser1048, manager001, /orgs/1/sites/site003 g, managerUser1048, manager001, /orgs/1/sites/site004 g, managerUser1048, manager001, /orgs/1/sites/site005 g, managerUser1049, manager001, /orgs/1/sites/site001 g, managerUser1049, manager001, /orgs/1/sites/site002 g, managerUser1049, manager001, /orgs/1/sites/site003 g, managerUser1049, manager001, /orgs/1/sites/site004 g, managerUser1049, manager001, /orgs/1/sites/site005 g, managerUser1050, manager001, /orgs/1/sites/site001 g, managerUser1050, manager001, /orgs/1/sites/site002 g, managerUser1050, manager001, /orgs/1/sites/site003 g, managerUser1050, manager001, /orgs/1/sites/site004 g, managerUser1050, manager001, /orgs/1/sites/site005 # Group - manager001, / org1 g, managerUser2001, manager001, /orgs/1/sites/site001 g, managerUser2001, manager001, /orgs/1/sites/site002 g, managerUser2001, manager001, /orgs/1/sites/site003 g, managerUser2001, manager001, /orgs/1/sites/site004 g, managerUser2001, manager001, /orgs/1/sites/site005 g, managerUser2001, manager001, /orgs/1/sites/site001 g, managerUser2001, manager001, /orgs/1/sites/site002 g, managerUser2001, manager001, /orgs/1/sites/site003 g, managerUser2001, manager001, /orgs/1/sites/site004 g, managerUser2001, manager001, /orgs/1/sites/site005 g, managerUser2003, manager001, /orgs/1/sites/site001 g, managerUser2003, manager001, /orgs/1/sites/site002 g, managerUser2003, manager001, /orgs/1/sites/site003 g, managerUser2003, manager001, /orgs/1/sites/site004 g, managerUser2003, manager001, /orgs/1/sites/site005 g, managerUser2004, manager001, /orgs/1/sites/site001 g, managerUser2004, manager001, /orgs/1/sites/site002 g, managerUser2004, manager001, /orgs/1/sites/site003 g, managerUser2004, manager001, /orgs/1/sites/site004 g, managerUser2004, manager001, /orgs/1/sites/site005 g, managerUser2005, manager001, /orgs/1/sites/site001 g, managerUser2005, manager001, /orgs/1/sites/site002 g, managerUser2005, manager001, /orgs/1/sites/site003 g, managerUser2005, manager001, /orgs/1/sites/site004 g, managerUser2005, manager001, /orgs/1/sites/site005 g, managerUser2006, manager001, /orgs/1/sites/site001 g, managerUser2006, manager001, /orgs/1/sites/site002 g, managerUser2006, manager001, /orgs/1/sites/site003 g, managerUser2006, manager001, /orgs/1/sites/site004 g, managerUser2006, manager001, /orgs/1/sites/site005 g, managerUser2007, manager001, /orgs/1/sites/site001 g, managerUser2007, manager001, /orgs/1/sites/site002 g, managerUser2007, manager001, /orgs/1/sites/site003 g, managerUser2007, manager001, /orgs/1/sites/site004 g, managerUser2007, manager001, /orgs/1/sites/site005 g, managerUser2008, manager001, /orgs/1/sites/site001 g, managerUser2008, manager001, /orgs/1/sites/site002 g, managerUser2008, manager001, /orgs/1/sites/site003 g, managerUser2008, manager001, /orgs/1/sites/site004 g, managerUser2008, manager001, /orgs/1/sites/site005 g, managerUser2009, manager001, /orgs/1/sites/site001 g, managerUser2009, manager001, /orgs/1/sites/site002 g, managerUser2009, manager001, /orgs/1/sites/site003 g, managerUser2009, manager001, /orgs/1/sites/site004 g, managerUser2009, manager001, /orgs/1/sites/site005 g, managerUser2010, manager001, /orgs/1/sites/site001 g, managerUser2010, manager001, /orgs/1/sites/site002 g, managerUser2010, manager001, /orgs/1/sites/site003 g, managerUser2010, manager001, /orgs/1/sites/site004 g, managerUser2010, manager001, /orgs/1/sites/site005 g, managerUser2011, manager001, /orgs/1/sites/site001 g, managerUser2011, manager001, /orgs/1/sites/site002 g, managerUser2011, manager001, /orgs/1/sites/site003 g, managerUser2011, manager001, /orgs/1/sites/site004 g, managerUser2011, manager001, /orgs/1/sites/site005 g, managerUser2012, manager001, /orgs/1/sites/site001 g, managerUser2012, manager001, /orgs/1/sites/site002 g, managerUser2012, manager001, /orgs/1/sites/site003 g, managerUser2012, manager001, /orgs/1/sites/site004 g, managerUser2012, manager001, /orgs/1/sites/site005 g, managerUser2013, manager001, /orgs/1/sites/site001 g, managerUser2013, manager001, /orgs/1/sites/site002 g, managerUser2013, manager001, /orgs/1/sites/site003 g, managerUser2013, manager001, /orgs/1/sites/site004 g, managerUser2013, manager001, /orgs/1/sites/site005 g, managerUser2014, manager001, /orgs/1/sites/site001 g, managerUser2014, manager001, /orgs/1/sites/site002 g, managerUser2014, manager001, /orgs/1/sites/site003 g, managerUser2014, manager001, /orgs/1/sites/site004 g, managerUser2014, manager001, /orgs/1/sites/site005 g, managerUser2015, manager001, /orgs/1/sites/site001 g, managerUser2015, manager001, /orgs/1/sites/site002 g, managerUser2015, manager001, /orgs/1/sites/site003 g, managerUser2015, manager001, /orgs/1/sites/site004 g, managerUser2015, manager001, /orgs/1/sites/site005 g, managerUser2016, manager001, /orgs/1/sites/site001 g, managerUser2016, manager001, /orgs/1/sites/site002 g, managerUser2016, manager001, /orgs/1/sites/site003 g, managerUser2016, manager001, /orgs/1/sites/site004 g, managerUser2016, manager001, /orgs/1/sites/site005 g, managerUser2017, manager001, /orgs/1/sites/site001 g, managerUser2017, manager001, /orgs/1/sites/site002 g, managerUser2017, manager001, /orgs/1/sites/site003 g, managerUser2017, manager001, /orgs/1/sites/site004 g, managerUser2017, manager001, /orgs/1/sites/site005 g, managerUser2018, manager001, /orgs/1/sites/site001 g, managerUser2018, manager001, /orgs/1/sites/site002 g, managerUser2018, manager001, /orgs/1/sites/site003 g, managerUser2018, manager001, /orgs/1/sites/site004 g, managerUser2018, manager001, /orgs/1/sites/site005 g, managerUser2019, manager001, /orgs/1/sites/site001 g, managerUser2019, manager001, /orgs/1/sites/site002 g, managerUser2019, manager001, /orgs/1/sites/site003 g, managerUser2019, manager001, /orgs/1/sites/site004 g, managerUser2019, manager001, /orgs/1/sites/site005 g, managerUser2020, manager001, /orgs/1/sites/site001 g, managerUser2020, manager001, /orgs/1/sites/site002 g, managerUser2020, manager001, /orgs/1/sites/site003 g, managerUser2020, manager001, /orgs/1/sites/site004 g, managerUser2020, manager001, /orgs/1/sites/site005 g, managerUser2021, manager001, /orgs/1/sites/site001 g, managerUser2021, manager001, /orgs/1/sites/site002 g, managerUser2021, manager001, /orgs/1/sites/site003 g, managerUser2021, manager001, /orgs/1/sites/site004 g, managerUser2021, manager001, /orgs/1/sites/site005 g, managerUser2022, manager001, /orgs/1/sites/site001 g, managerUser2022, manager001, /orgs/1/sites/site002 g, managerUser2022, manager001, /orgs/1/sites/site003 g, managerUser2022, manager001, /orgs/1/sites/site004 g, managerUser2022, manager001, /orgs/1/sites/site005 g, managerUser2023, manager001, /orgs/1/sites/site001 g, managerUser2023, manager001, /orgs/1/sites/site002 g, managerUser2023, manager001, /orgs/1/sites/site003 g, managerUser2023, manager001, /orgs/1/sites/site004 g, managerUser2023, manager001, /orgs/1/sites/site005 g, managerUser2024, manager001, /orgs/1/sites/site001 g, managerUser2024, manager001, /orgs/1/sites/site002 g, managerUser2024, manager001, /orgs/1/sites/site003 g, managerUser2024, manager001, /orgs/1/sites/site004 g, managerUser2024, manager001, /orgs/1/sites/site005 g, managerUser2025, manager001, /orgs/1/sites/site001 g, managerUser2025, manager001, /orgs/1/sites/site002 g, managerUser2025, manager001, /orgs/1/sites/site003 g, managerUser2025, manager001, /orgs/1/sites/site004 g, managerUser2025, manager001, /orgs/1/sites/site005 g, managerUser2026, manager001, /orgs/1/sites/site001 g, managerUser2026, manager001, /orgs/1/sites/site002 g, managerUser2026, manager001, /orgs/1/sites/site003 g, managerUser2026, manager001, /orgs/1/sites/site004 g, managerUser2026, manager001, /orgs/1/sites/site005 g, managerUser2027, manager001, /orgs/1/sites/site001 g, managerUser2027, manager001, /orgs/1/sites/site002 g, managerUser2027, manager001, /orgs/1/sites/site003 g, managerUser2027, manager001, /orgs/1/sites/site004 g, managerUser2027, manager001, /orgs/1/sites/site005 g, managerUser2028, manager001, /orgs/1/sites/site001 g, managerUser2028, manager001, /orgs/1/sites/site002 g, managerUser2028, manager001, /orgs/1/sites/site003 g, managerUser2028, manager001, /orgs/1/sites/site004 g, managerUser2028, manager001, /orgs/1/sites/site005 g, managerUser2029, manager001, /orgs/1/sites/site001 g, managerUser2029, manager001, /orgs/1/sites/site002 g, managerUser2029, manager001, /orgs/1/sites/site003 g, managerUser2029, manager001, /orgs/1/sites/site004 g, managerUser2029, manager001, /orgs/1/sites/site005 g, managerUser2030, manager001, /orgs/1/sites/site001 g, managerUser2030, manager001, /orgs/1/sites/site002 g, managerUser2030, manager001, /orgs/1/sites/site003 g, managerUser2030, manager001, /orgs/1/sites/site004 g, managerUser2030, manager001, /orgs/1/sites/site005 g, managerUser2031, manager001, /orgs/1/sites/site001 g, managerUser2031, manager001, /orgs/1/sites/site002 g, managerUser2031, manager001, /orgs/1/sites/site003 g, managerUser2031, manager001, /orgs/1/sites/site004 g, managerUser2031, manager001, /orgs/1/sites/site005 g, managerUser2032, manager001, /orgs/1/sites/site001 g, managerUser2032, manager001, /orgs/1/sites/site002 g, managerUser2032, manager001, /orgs/1/sites/site003 g, managerUser2032, manager001, /orgs/1/sites/site004 g, managerUser2032, manager001, /orgs/1/sites/site005 g, managerUser2033, manager001, /orgs/1/sites/site001 g, managerUser2033, manager001, /orgs/1/sites/site002 g, managerUser2033, manager001, /orgs/1/sites/site003 g, managerUser2033, manager001, /orgs/1/sites/site004 g, managerUser2033, manager001, /orgs/1/sites/site005 g, managerUser2034, manager001, /orgs/1/sites/site001 g, managerUser2034, manager001, /orgs/1/sites/site002 g, managerUser2034, manager001, /orgs/1/sites/site003 g, managerUser2034, manager001, /orgs/1/sites/site004 g, managerUser2034, manager001, /orgs/1/sites/site005 g, managerUser2035, manager001, /orgs/1/sites/site001 g, managerUser2035, manager001, /orgs/1/sites/site002 g, managerUser2035, manager001, /orgs/1/sites/site003 g, managerUser2035, manager001, /orgs/1/sites/site004 g, managerUser2035, manager001, /orgs/1/sites/site005 g, managerUser2036, manager001, /orgs/1/sites/site001 g, managerUser2036, manager001, /orgs/1/sites/site002 g, managerUser2036, manager001, /orgs/1/sites/site003 g, managerUser2036, manager001, /orgs/1/sites/site004 g, managerUser2036, manager001, /orgs/1/sites/site005 g, managerUser2037, manager001, /orgs/1/sites/site001 g, managerUser2037, manager001, /orgs/1/sites/site002 g, managerUser2037, manager001, /orgs/1/sites/site003 g, managerUser2037, manager001, /orgs/1/sites/site004 g, managerUser2037, manager001, /orgs/1/sites/site005 g, managerUser2038, manager001, /orgs/1/sites/site001 g, managerUser2038, manager001, /orgs/1/sites/site002 g, managerUser2038, manager001, /orgs/1/sites/site003 g, managerUser2038, manager001, /orgs/1/sites/site004 g, managerUser2038, manager001, /orgs/1/sites/site005 g, managerUser2039, manager001, /orgs/1/sites/site001 g, managerUser2039, manager001, /orgs/1/sites/site002 g, managerUser2039, manager001, /orgs/1/sites/site003 g, managerUser2039, manager001, /orgs/1/sites/site004 g, managerUser2039, manager001, /orgs/1/sites/site005 g, managerUser2040, manager001, /orgs/1/sites/site001 g, managerUser2040, manager001, /orgs/1/sites/site002 g, managerUser2040, manager001, /orgs/1/sites/site003 g, managerUser2040, manager001, /orgs/1/sites/site004 g, managerUser2040, manager001, /orgs/1/sites/site005 g, managerUser2041, manager001, /orgs/1/sites/site001 g, managerUser2041, manager001, /orgs/1/sites/site002 g, managerUser2041, manager001, /orgs/1/sites/site003 g, managerUser2041, manager001, /orgs/1/sites/site004 g, managerUser2041, manager001, /orgs/1/sites/site005 g, managerUser2042, manager001, /orgs/1/sites/site001 g, managerUser2042, manager001, /orgs/1/sites/site002 g, managerUser2042, manager001, /orgs/1/sites/site003 g, managerUser2042, manager001, /orgs/1/sites/site004 g, managerUser2042, manager001, /orgs/1/sites/site005 g, managerUser2043, manager001, /orgs/1/sites/site001 g, managerUser2043, manager001, /orgs/1/sites/site002 g, managerUser2043, manager001, /orgs/1/sites/site003 g, managerUser2043, manager001, /orgs/1/sites/site004 g, managerUser2043, manager001, /orgs/1/sites/site005 g, managerUser2044, manager001, /orgs/1/sites/site001 g, managerUser2044, manager001, /orgs/1/sites/site002 g, managerUser2044, manager001, /orgs/1/sites/site003 g, managerUser2044, manager001, /orgs/1/sites/site004 g, managerUser2044, manager001, /orgs/1/sites/site005 g, managerUser2045, manager001, /orgs/1/sites/site001 g, managerUser2045, manager001, /orgs/1/sites/site002 g, managerUser2045, manager001, /orgs/1/sites/site003 g, managerUser2045, manager001, /orgs/1/sites/site004 g, managerUser2045, manager001, /orgs/1/sites/site005 g, managerUser2046, manager001, /orgs/1/sites/site001 g, managerUser2046, manager001, /orgs/1/sites/site002 g, managerUser2046, manager001, /orgs/1/sites/site003 g, managerUser2046, manager001, /orgs/1/sites/site004 g, managerUser2046, manager001, /orgs/1/sites/site005 g, managerUser2047, manager001, /orgs/1/sites/site001 g, managerUser2047, manager001, /orgs/1/sites/site002 g, managerUser2047, manager001, /orgs/1/sites/site003 g, managerUser2047, manager001, /orgs/1/sites/site004 g, managerUser2047, manager001, /orgs/1/sites/site005 g, managerUser2048, manager001, /orgs/1/sites/site001 g, managerUser2048, manager001, /orgs/1/sites/site002 g, managerUser2048, manager001, /orgs/1/sites/site003 g, managerUser2048, manager001, /orgs/1/sites/site004 g, managerUser2048, manager001, /orgs/1/sites/site005 g, managerUser2049, manager001, /orgs/1/sites/site001 g, managerUser2049, manager001, /orgs/1/sites/site002 g, managerUser2049, manager001, /orgs/1/sites/site003 g, managerUser2049, manager001, /orgs/1/sites/site004 g, managerUser2049, manager001, /orgs/1/sites/site005 g, managerUser2050, manager001, /orgs/1/sites/site001 g, managerUser2050, manager001, /orgs/1/sites/site002 g, managerUser2050, manager001, /orgs/1/sites/site003 g, managerUser2050, manager001, /orgs/1/sites/site004 g, managerUser2050, manager001, /orgs/1/sites/site005 # Group - customer001, / org1 g, customerUser1001, customer001, /orgs/1/sites/site001 g, customerUser1001, customer001, /orgs/1/sites/site002 g, customerUser1001, customer001, /orgs/1/sites/site003 g, customerUser1001, customer001, /orgs/1/sites/site004 g, customerUser1001, customer001, /orgs/1/sites/site005 g, customerUser1001, customer001, /orgs/1/sites/site001 g, customerUser1001, customer001, /orgs/1/sites/site002 g, customerUser1001, customer001, /orgs/1/sites/site003 g, customerUser1001, customer001, /orgs/1/sites/site004 g, customerUser1001, customer001, /orgs/1/sites/site005 g, customerUser1003, customer001, /orgs/1/sites/site001 g, customerUser1003, customer001, /orgs/1/sites/site002 g, customerUser1003, customer001, /orgs/1/sites/site003 g, customerUser1003, customer001, /orgs/1/sites/site004 g, customerUser1003, customer001, /orgs/1/sites/site005 g, customerUser1004, customer001, /orgs/1/sites/site001 g, customerUser1004, customer001, /orgs/1/sites/site002 g, customerUser1004, customer001, /orgs/1/sites/site003 g, customerUser1004, customer001, /orgs/1/sites/site004 g, customerUser1004, customer001, /orgs/1/sites/site005 g, customerUser1005, customer001, /orgs/1/sites/site001 g, customerUser1005, customer001, /orgs/1/sites/site002 g, customerUser1005, customer001, /orgs/1/sites/site003 g, customerUser1005, customer001, /orgs/1/sites/site004 g, customerUser1005, customer001, /orgs/1/sites/site005 g, customerUser1006, customer001, /orgs/1/sites/site001 g, customerUser1006, customer001, /orgs/1/sites/site002 g, customerUser1006, customer001, /orgs/1/sites/site003 g, customerUser1006, customer001, /orgs/1/sites/site004 g, customerUser1006, customer001, /orgs/1/sites/site005 g, customerUser1007, customer001, /orgs/1/sites/site001 g, customerUser1007, customer001, /orgs/1/sites/site002 g, customerUser1007, customer001, /orgs/1/sites/site003 g, customerUser1007, customer001, /orgs/1/sites/site004 g, customerUser1007, customer001, /orgs/1/sites/site005 g, customerUser1008, customer001, /orgs/1/sites/site001 g, customerUser1008, customer001, /orgs/1/sites/site002 g, customerUser1008, customer001, /orgs/1/sites/site003 g, customerUser1008, customer001, /orgs/1/sites/site004 g, customerUser1008, customer001, /orgs/1/sites/site005 g, customerUser1009, customer001, /orgs/1/sites/site001 g, customerUser1009, customer001, /orgs/1/sites/site002 g, customerUser1009, customer001, /orgs/1/sites/site003 g, customerUser1009, customer001, /orgs/1/sites/site004 g, customerUser1009, customer001, /orgs/1/sites/site005 g, customerUser1010, customer001, /orgs/1/sites/site001 g, customerUser1010, customer001, /orgs/1/sites/site002 g, customerUser1010, customer001, /orgs/1/sites/site003 g, customerUser1010, customer001, /orgs/1/sites/site004 g, customerUser1010, customer001, /orgs/1/sites/site005 g, customerUser1011, customer001, /orgs/1/sites/site001 g, customerUser1011, customer001, /orgs/1/sites/site002 g, customerUser1011, customer001, /orgs/1/sites/site003 g, customerUser1011, customer001, /orgs/1/sites/site004 g, customerUser1011, customer001, /orgs/1/sites/site005 g, customerUser1012, customer001, /orgs/1/sites/site001 g, customerUser1012, customer001, /orgs/1/sites/site002 g, customerUser1012, customer001, /orgs/1/sites/site003 g, customerUser1012, customer001, /orgs/1/sites/site004 g, customerUser1012, customer001, /orgs/1/sites/site005 g, customerUser1013, customer001, /orgs/1/sites/site001 g, customerUser1013, customer001, /orgs/1/sites/site002 g, customerUser1013, customer001, /orgs/1/sites/site003 g, customerUser1013, customer001, /orgs/1/sites/site004 g, customerUser1013, customer001, /orgs/1/sites/site005 g, customerUser1014, customer001, /orgs/1/sites/site001 g, customerUser1014, customer001, /orgs/1/sites/site002 g, customerUser1014, customer001, /orgs/1/sites/site003 g, customerUser1014, customer001, /orgs/1/sites/site004 g, customerUser1014, customer001, /orgs/1/sites/site005 g, customerUser1015, customer001, /orgs/1/sites/site001 g, customerUser1015, customer001, /orgs/1/sites/site002 g, customerUser1015, customer001, /orgs/1/sites/site003 g, customerUser1015, customer001, /orgs/1/sites/site004 g, customerUser1015, customer001, /orgs/1/sites/site005 g, customerUser1016, customer001, /orgs/1/sites/site001 g, customerUser1016, customer001, /orgs/1/sites/site002 g, customerUser1016, customer001, /orgs/1/sites/site003 g, customerUser1016, customer001, /orgs/1/sites/site004 g, customerUser1016, customer001, /orgs/1/sites/site005 g, customerUser1017, customer001, /orgs/1/sites/site001 g, customerUser1017, customer001, /orgs/1/sites/site002 g, customerUser1017, customer001, /orgs/1/sites/site003 g, customerUser1017, customer001, /orgs/1/sites/site004 g, customerUser1017, customer001, /orgs/1/sites/site005 g, customerUser1018, customer001, /orgs/1/sites/site001 g, customerUser1018, customer001, /orgs/1/sites/site002 g, customerUser1018, customer001, /orgs/1/sites/site003 g, customerUser1018, customer001, /orgs/1/sites/site004 g, customerUser1018, customer001, /orgs/1/sites/site005 g, customerUser1019, customer001, /orgs/1/sites/site001 g, customerUser1019, customer001, /orgs/1/sites/site002 g, customerUser1019, customer001, /orgs/1/sites/site003 g, customerUser1019, customer001, /orgs/1/sites/site004 g, customerUser1019, customer001, /orgs/1/sites/site005 g, customerUser1020, customer001, /orgs/1/sites/site001 g, customerUser1020, customer001, /orgs/1/sites/site002 g, customerUser1020, customer001, /orgs/1/sites/site003 g, customerUser1020, customer001, /orgs/1/sites/site004 g, customerUser1020, customer001, /orgs/1/sites/site005 g, customerUser1021, customer001, /orgs/1/sites/site001 g, customerUser1021, customer001, /orgs/1/sites/site002 g, customerUser1021, customer001, /orgs/1/sites/site003 g, customerUser1021, customer001, /orgs/1/sites/site004 g, customerUser1021, customer001, /orgs/1/sites/site005 g, customerUser1022, customer001, /orgs/1/sites/site001 g, customerUser1022, customer001, /orgs/1/sites/site002 g, customerUser1022, customer001, /orgs/1/sites/site003 g, customerUser1022, customer001, /orgs/1/sites/site004 g, customerUser1022, customer001, /orgs/1/sites/site005 g, customerUser1023, customer001, /orgs/1/sites/site001 g, customerUser1023, customer001, /orgs/1/sites/site002 g, customerUser1023, customer001, /orgs/1/sites/site003 g, customerUser1023, customer001, /orgs/1/sites/site004 g, customerUser1023, customer001, /orgs/1/sites/site005 g, customerUser1024, customer001, /orgs/1/sites/site001 g, customerUser1024, customer001, /orgs/1/sites/site002 g, customerUser1024, customer001, /orgs/1/sites/site003 g, customerUser1024, customer001, /orgs/1/sites/site004 g, customerUser1024, customer001, /orgs/1/sites/site005 g, customerUser1025, customer001, /orgs/1/sites/site001 g, customerUser1025, customer001, /orgs/1/sites/site002 g, customerUser1025, customer001, /orgs/1/sites/site003 g, customerUser1025, customer001, /orgs/1/sites/site004 g, customerUser1025, customer001, /orgs/1/sites/site005 g, customerUser1026, customer001, /orgs/1/sites/site001 g, customerUser1026, customer001, /orgs/1/sites/site002 g, customerUser1026, customer001, /orgs/1/sites/site003 g, customerUser1026, customer001, /orgs/1/sites/site004 g, customerUser1026, customer001, /orgs/1/sites/site005 g, customerUser1027, customer001, /orgs/1/sites/site001 g, customerUser1027, customer001, /orgs/1/sites/site002 g, customerUser1027, customer001, /orgs/1/sites/site003 g, customerUser1027, customer001, /orgs/1/sites/site004 g, customerUser1027, customer001, /orgs/1/sites/site005 g, customerUser1028, customer001, /orgs/1/sites/site001 g, customerUser1028, customer001, /orgs/1/sites/site002 g, customerUser1028, customer001, /orgs/1/sites/site003 g, customerUser1028, customer001, /orgs/1/sites/site004 g, customerUser1028, customer001, /orgs/1/sites/site005 g, customerUser1029, customer001, /orgs/1/sites/site001 g, customerUser1029, customer001, /orgs/1/sites/site002 g, customerUser1029, customer001, /orgs/1/sites/site003 g, customerUser1029, customer001, /orgs/1/sites/site004 g, customerUser1029, customer001, /orgs/1/sites/site005 g, customerUser1030, customer001, /orgs/1/sites/site001 g, customerUser1030, customer001, /orgs/1/sites/site002 g, customerUser1030, customer001, /orgs/1/sites/site003 g, customerUser1030, customer001, /orgs/1/sites/site004 g, customerUser1030, customer001, /orgs/1/sites/site005 g, customerUser1031, customer001, /orgs/1/sites/site001 g, customerUser1031, customer001, /orgs/1/sites/site002 g, customerUser1031, customer001, /orgs/1/sites/site003 g, customerUser1031, customer001, /orgs/1/sites/site004 g, customerUser1031, customer001, /orgs/1/sites/site005 g, customerUser1032, customer001, /orgs/1/sites/site001 g, customerUser1032, customer001, /orgs/1/sites/site002 g, customerUser1032, customer001, /orgs/1/sites/site003 g, customerUser1032, customer001, /orgs/1/sites/site004 g, customerUser1032, customer001, /orgs/1/sites/site005 g, customerUser1033, customer001, /orgs/1/sites/site001 g, customerUser1033, customer001, /orgs/1/sites/site002 g, customerUser1033, customer001, /orgs/1/sites/site003 g, customerUser1033, customer001, /orgs/1/sites/site004 g, customerUser1033, customer001, /orgs/1/sites/site005 g, customerUser1034, customer001, /orgs/1/sites/site001 g, customerUser1034, customer001, /orgs/1/sites/site002 g, customerUser1034, customer001, /orgs/1/sites/site003 g, customerUser1034, customer001, /orgs/1/sites/site004 g, customerUser1034, customer001, /orgs/1/sites/site005 g, customerUser1035, customer001, /orgs/1/sites/site001 g, customerUser1035, customer001, /orgs/1/sites/site002 g, customerUser1035, customer001, /orgs/1/sites/site003 g, customerUser1035, customer001, /orgs/1/sites/site004 g, customerUser1035, customer001, /orgs/1/sites/site005 g, customerUser1036, customer001, /orgs/1/sites/site001 g, customerUser1036, customer001, /orgs/1/sites/site002 g, customerUser1036, customer001, /orgs/1/sites/site003 g, customerUser1036, customer001, /orgs/1/sites/site004 g, customerUser1036, customer001, /orgs/1/sites/site005 g, customerUser1037, customer001, /orgs/1/sites/site001 g, customerUser1037, customer001, /orgs/1/sites/site002 g, customerUser1037, customer001, /orgs/1/sites/site003 g, customerUser1037, customer001, /orgs/1/sites/site004 g, customerUser1037, customer001, /orgs/1/sites/site005 g, customerUser1038, customer001, /orgs/1/sites/site001 g, customerUser1038, customer001, /orgs/1/sites/site002 g, customerUser1038, customer001, /orgs/1/sites/site003 g, customerUser1038, customer001, /orgs/1/sites/site004 g, customerUser1038, customer001, /orgs/1/sites/site005 g, customerUser1039, customer001, /orgs/1/sites/site001 g, customerUser1039, customer001, /orgs/1/sites/site002 g, customerUser1039, customer001, /orgs/1/sites/site003 g, customerUser1039, customer001, /orgs/1/sites/site004 g, customerUser1039, customer001, /orgs/1/sites/site005 g, customerUser1040, customer001, /orgs/1/sites/site001 g, customerUser1040, customer001, /orgs/1/sites/site002 g, customerUser1040, customer001, /orgs/1/sites/site003 g, customerUser1040, customer001, /orgs/1/sites/site004 g, customerUser1040, customer001, /orgs/1/sites/site005 g, customerUser1041, customer001, /orgs/1/sites/site001 g, customerUser1041, customer001, /orgs/1/sites/site002 g, customerUser1041, customer001, /orgs/1/sites/site003 g, customerUser1041, customer001, /orgs/1/sites/site004 g, customerUser1041, customer001, /orgs/1/sites/site005 g, customerUser1042, customer001, /orgs/1/sites/site001 g, customerUser1042, customer001, /orgs/1/sites/site002 g, customerUser1042, customer001, /orgs/1/sites/site003 g, customerUser1042, customer001, /orgs/1/sites/site004 g, customerUser1042, customer001, /orgs/1/sites/site005 g, customerUser1043, customer001, /orgs/1/sites/site001 g, customerUser1043, customer001, /orgs/1/sites/site002 g, customerUser1043, customer001, /orgs/1/sites/site003 g, customerUser1043, customer001, /orgs/1/sites/site004 g, customerUser1043, customer001, /orgs/1/sites/site005 g, customerUser1044, customer001, /orgs/1/sites/site001 g, customerUser1044, customer001, /orgs/1/sites/site002 g, customerUser1044, customer001, /orgs/1/sites/site003 g, customerUser1044, customer001, /orgs/1/sites/site004 g, customerUser1044, customer001, /orgs/1/sites/site005 g, customerUser1045, customer001, /orgs/1/sites/site001 g, customerUser1045, customer001, /orgs/1/sites/site002 g, customerUser1045, customer001, /orgs/1/sites/site003 g, customerUser1045, customer001, /orgs/1/sites/site004 g, customerUser1045, customer001, /orgs/1/sites/site005 g, customerUser1046, customer001, /orgs/1/sites/site001 g, customerUser1046, customer001, /orgs/1/sites/site002 g, customerUser1046, customer001, /orgs/1/sites/site003 g, customerUser1046, customer001, /orgs/1/sites/site004 g, customerUser1046, customer001, /orgs/1/sites/site005 g, customerUser1047, customer001, /orgs/1/sites/site001 g, customerUser1047, customer001, /orgs/1/sites/site002 g, customerUser1047, customer001, /orgs/1/sites/site003 g, customerUser1047, customer001, /orgs/1/sites/site004 g, customerUser1047, customer001, /orgs/1/sites/site005 g, customerUser1048, customer001, /orgs/1/sites/site001 g, customerUser1048, customer001, /orgs/1/sites/site002 g, customerUser1048, customer001, /orgs/1/sites/site003 g, customerUser1048, customer001, /orgs/1/sites/site004 g, customerUser1048, customer001, /orgs/1/sites/site005 g, customerUser1049, customer001, /orgs/1/sites/site001 g, customerUser1049, customer001, /orgs/1/sites/site002 g, customerUser1049, customer001, /orgs/1/sites/site003 g, customerUser1049, customer001, /orgs/1/sites/site004 g, customerUser1049, customer001, /orgs/1/sites/site005 g, customerUser1050, customer001, /orgs/1/sites/site001 g, customerUser1050, customer001, /orgs/1/sites/site002 g, customerUser1050, customer001, /orgs/1/sites/site003 g, customerUser1050, customer001, /orgs/1/sites/site004 g, customerUser1050, customer001, /orgs/1/sites/site005 # Group - customer001, / org1 g, customerUser2001, customer001, /orgs/1/sites/site001 g, customerUser2001, customer001, /orgs/1/sites/site002 g, customerUser2001, customer001, /orgs/1/sites/site003 g, customerUser2001, customer001, /orgs/1/sites/site004 g, customerUser2001, customer001, /orgs/1/sites/site005 g, customerUser2001, customer001, /orgs/1/sites/site001 g, customerUser2001, customer001, /orgs/1/sites/site002 g, customerUser2001, customer001, /orgs/1/sites/site003 g, customerUser2001, customer001, /orgs/1/sites/site004 g, customerUser2001, customer001, /orgs/1/sites/site005 g, customerUser2003, customer001, /orgs/1/sites/site001 g, customerUser2003, customer001, /orgs/1/sites/site002 g, customerUser2003, customer001, /orgs/1/sites/site003 g, customerUser2003, customer001, /orgs/1/sites/site004 g, customerUser2003, customer001, /orgs/1/sites/site005 g, customerUser2004, customer001, /orgs/1/sites/site001 g, customerUser2004, customer001, /orgs/1/sites/site002 g, customerUser2004, customer001, /orgs/1/sites/site003 g, customerUser2004, customer001, /orgs/1/sites/site004 g, customerUser2004, customer001, /orgs/1/sites/site005 g, customerUser2005, customer001, /orgs/1/sites/site001 g, customerUser2005, customer001, /orgs/1/sites/site002 g, customerUser2005, customer001, /orgs/1/sites/site003 g, customerUser2005, customer001, /orgs/1/sites/site004 g, customerUser2005, customer001, /orgs/1/sites/site005 g, customerUser2006, customer001, /orgs/1/sites/site001 g, customerUser2006, customer001, /orgs/1/sites/site002 g, customerUser2006, customer001, /orgs/1/sites/site003 g, customerUser2006, customer001, /orgs/1/sites/site004 g, customerUser2006, customer001, /orgs/1/sites/site005 g, customerUser2007, customer001, /orgs/1/sites/site001 g, customerUser2007, customer001, /orgs/1/sites/site002 g, customerUser2007, customer001, /orgs/1/sites/site003 g, customerUser2007, customer001, /orgs/1/sites/site004 g, customerUser2007, customer001, /orgs/1/sites/site005 g, customerUser2008, customer001, /orgs/1/sites/site001 g, customerUser2008, customer001, /orgs/1/sites/site002 g, customerUser2008, customer001, /orgs/1/sites/site003 g, customerUser2008, customer001, /orgs/1/sites/site004 g, customerUser2008, customer001, /orgs/1/sites/site005 g, customerUser2009, customer001, /orgs/1/sites/site001 g, customerUser2009, customer001, /orgs/1/sites/site002 g, customerUser2009, customer001, /orgs/1/sites/site003 g, customerUser2009, customer001, /orgs/1/sites/site004 g, customerUser2009, customer001, /orgs/1/sites/site005 g, customerUser2010, customer001, /orgs/1/sites/site001 g, customerUser2010, customer001, /orgs/1/sites/site002 g, customerUser2010, customer001, /orgs/1/sites/site003 g, customerUser2010, customer001, /orgs/1/sites/site004 g, customerUser2010, customer001, /orgs/1/sites/site005 g, customerUser2011, customer001, /orgs/1/sites/site001 g, customerUser2011, customer001, /orgs/1/sites/site002 g, customerUser2011, customer001, /orgs/1/sites/site003 g, customerUser2011, customer001, /orgs/1/sites/site004 g, customerUser2011, customer001, /orgs/1/sites/site005 g, customerUser2012, customer001, /orgs/1/sites/site001 g, customerUser2012, customer001, /orgs/1/sites/site002 g, customerUser2012, customer001, /orgs/1/sites/site003 g, customerUser2012, customer001, /orgs/1/sites/site004 g, customerUser2012, customer001, /orgs/1/sites/site005 g, customerUser2013, customer001, /orgs/1/sites/site001 g, customerUser2013, customer001, /orgs/1/sites/site002 g, customerUser2013, customer001, /orgs/1/sites/site003 g, customerUser2013, customer001, /orgs/1/sites/site004 g, customerUser2013, customer001, /orgs/1/sites/site005 g, customerUser2014, customer001, /orgs/1/sites/site001 g, customerUser2014, customer001, /orgs/1/sites/site002 g, customerUser2014, customer001, /orgs/1/sites/site003 g, customerUser2014, customer001, /orgs/1/sites/site004 g, customerUser2014, customer001, /orgs/1/sites/site005 g, customerUser2015, customer001, /orgs/1/sites/site001 g, customerUser2015, customer001, /orgs/1/sites/site002 g, customerUser2015, customer001, /orgs/1/sites/site003 g, customerUser2015, customer001, /orgs/1/sites/site004 g, customerUser2015, customer001, /orgs/1/sites/site005 g, customerUser2016, customer001, /orgs/1/sites/site001 g, customerUser2016, customer001, /orgs/1/sites/site002 g, customerUser2016, customer001, /orgs/1/sites/site003 g, customerUser2016, customer001, /orgs/1/sites/site004 g, customerUser2016, customer001, /orgs/1/sites/site005 g, customerUser2017, customer001, /orgs/1/sites/site001 g, customerUser2017, customer001, /orgs/1/sites/site002 g, customerUser2017, customer001, /orgs/1/sites/site003 g, customerUser2017, customer001, /orgs/1/sites/site004 g, customerUser2017, customer001, /orgs/1/sites/site005 g, customerUser2018, customer001, /orgs/1/sites/site001 g, customerUser2018, customer001, /orgs/1/sites/site002 g, customerUser2018, customer001, /orgs/1/sites/site003 g, customerUser2018, customer001, /orgs/1/sites/site004 g, customerUser2018, customer001, /orgs/1/sites/site005 g, customerUser2019, customer001, /orgs/1/sites/site001 g, customerUser2019, customer001, /orgs/1/sites/site002 g, customerUser2019, customer001, /orgs/1/sites/site003 g, customerUser2019, customer001, /orgs/1/sites/site004 g, customerUser2019, customer001, /orgs/1/sites/site005 g, customerUser2020, customer001, /orgs/1/sites/site001 g, customerUser2020, customer001, /orgs/1/sites/site002 g, customerUser2020, customer001, /orgs/1/sites/site003 g, customerUser2020, customer001, /orgs/1/sites/site004 g, customerUser2020, customer001, /orgs/1/sites/site005 g, customerUser2021, customer001, /orgs/1/sites/site001 g, customerUser2021, customer001, /orgs/1/sites/site002 g, customerUser2021, customer001, /orgs/1/sites/site003 g, customerUser2021, customer001, /orgs/1/sites/site004 g, customerUser2021, customer001, /orgs/1/sites/site005 g, customerUser2022, customer001, /orgs/1/sites/site001 g, customerUser2022, customer001, /orgs/1/sites/site002 g, customerUser2022, customer001, /orgs/1/sites/site003 g, customerUser2022, customer001, /orgs/1/sites/site004 g, customerUser2022, customer001, /orgs/1/sites/site005 g, customerUser2023, customer001, /orgs/1/sites/site001 g, customerUser2023, customer001, /orgs/1/sites/site002 g, customerUser2023, customer001, /orgs/1/sites/site003 g, customerUser2023, customer001, /orgs/1/sites/site004 g, customerUser2023, customer001, /orgs/1/sites/site005 g, customerUser2024, customer001, /orgs/1/sites/site001 g, customerUser2024, customer001, /orgs/1/sites/site002 g, customerUser2024, customer001, /orgs/1/sites/site003 g, customerUser2024, customer001, /orgs/1/sites/site004 g, customerUser2024, customer001, /orgs/1/sites/site005 g, customerUser2025, customer001, /orgs/1/sites/site001 g, customerUser2025, customer001, /orgs/1/sites/site002 g, customerUser2025, customer001, /orgs/1/sites/site003 g, customerUser2025, customer001, /orgs/1/sites/site004 g, customerUser2025, customer001, /orgs/1/sites/site005 g, customerUser2026, customer001, /orgs/1/sites/site001 g, customerUser2026, customer001, /orgs/1/sites/site002 g, customerUser2026, customer001, /orgs/1/sites/site003 g, customerUser2026, customer001, /orgs/1/sites/site004 g, customerUser2026, customer001, /orgs/1/sites/site005 g, customerUser2027, customer001, /orgs/1/sites/site001 g, customerUser2027, customer001, /orgs/1/sites/site002 g, customerUser2027, customer001, /orgs/1/sites/site003 g, customerUser2027, customer001, /orgs/1/sites/site004 g, customerUser2027, customer001, /orgs/1/sites/site005 g, customerUser2028, customer001, /orgs/1/sites/site001 g, customerUser2028, customer001, /orgs/1/sites/site002 g, customerUser2028, customer001, /orgs/1/sites/site003 g, customerUser2028, customer001, /orgs/1/sites/site004 g, customerUser2028, customer001, /orgs/1/sites/site005 g, customerUser2029, customer001, /orgs/1/sites/site001 g, customerUser2029, customer001, /orgs/1/sites/site002 g, customerUser2029, customer001, /orgs/1/sites/site003 g, customerUser2029, customer001, /orgs/1/sites/site004 g, customerUser2029, customer001, /orgs/1/sites/site005 g, customerUser2030, customer001, /orgs/1/sites/site001 g, customerUser2030, customer001, /orgs/1/sites/site002 g, customerUser2030, customer001, /orgs/1/sites/site003 g, customerUser2030, customer001, /orgs/1/sites/site004 g, customerUser2030, customer001, /orgs/1/sites/site005 g, customerUser2031, customer001, /orgs/1/sites/site001 g, customerUser2031, customer001, /orgs/1/sites/site002 g, customerUser2031, customer001, /orgs/1/sites/site003 g, customerUser2031, customer001, /orgs/1/sites/site004 g, customerUser2031, customer001, /orgs/1/sites/site005 g, customerUser2032, customer001, /orgs/1/sites/site001 g, customerUser2032, customer001, /orgs/1/sites/site002 g, customerUser2032, customer001, /orgs/1/sites/site003 g, customerUser2032, customer001, /orgs/1/sites/site004 g, customerUser2032, customer001, /orgs/1/sites/site005 g, customerUser2033, customer001, /orgs/1/sites/site001 g, customerUser2033, customer001, /orgs/1/sites/site002 g, customerUser2033, customer001, /orgs/1/sites/site003 g, customerUser2033, customer001, /orgs/1/sites/site004 g, customerUser2033, customer001, /orgs/1/sites/site005 g, customerUser2034, customer001, /orgs/1/sites/site001 g, customerUser2034, customer001, /orgs/1/sites/site002 g, customerUser2034, customer001, /orgs/1/sites/site003 g, customerUser2034, customer001, /orgs/1/sites/site004 g, customerUser2034, customer001, /orgs/1/sites/site005 g, customerUser2035, customer001, /orgs/1/sites/site001 g, customerUser2035, customer001, /orgs/1/sites/site002 g, customerUser2035, customer001, /orgs/1/sites/site003 g, customerUser2035, customer001, /orgs/1/sites/site004 g, customerUser2035, customer001, /orgs/1/sites/site005 g, customerUser2036, customer001, /orgs/1/sites/site001 g, customerUser2036, customer001, /orgs/1/sites/site002 g, customerUser2036, customer001, /orgs/1/sites/site003 g, customerUser2036, customer001, /orgs/1/sites/site004 g, customerUser2036, customer001, /orgs/1/sites/site005 g, customerUser2037, customer001, /orgs/1/sites/site001 g, customerUser2037, customer001, /orgs/1/sites/site002 g, customerUser2037, customer001, /orgs/1/sites/site003 g, customerUser2037, customer001, /orgs/1/sites/site004 g, customerUser2037, customer001, /orgs/1/sites/site005 g, customerUser2038, customer001, /orgs/1/sites/site001 g, customerUser2038, customer001, /orgs/1/sites/site002 g, customerUser2038, customer001, /orgs/1/sites/site003 g, customerUser2038, customer001, /orgs/1/sites/site004 g, customerUser2038, customer001, /orgs/1/sites/site005 g, customerUser2039, customer001, /orgs/1/sites/site001 g, customerUser2039, customer001, /orgs/1/sites/site002 g, customerUser2039, customer001, /orgs/1/sites/site003 g, customerUser2039, customer001, /orgs/1/sites/site004 g, customerUser2039, customer001, /orgs/1/sites/site005 g, customerUser2040, customer001, /orgs/1/sites/site001 g, customerUser2040, customer001, /orgs/1/sites/site002 g, customerUser2040, customer001, /orgs/1/sites/site003 g, customerUser2040, customer001, /orgs/1/sites/site004 g, customerUser2040, customer001, /orgs/1/sites/site005 g, customerUser2041, customer001, /orgs/1/sites/site001 g, customerUser2041, customer001, /orgs/1/sites/site002 g, customerUser2041, customer001, /orgs/1/sites/site003 g, customerUser2041, customer001, /orgs/1/sites/site004 g, customerUser2041, customer001, /orgs/1/sites/site005 g, customerUser2042, customer001, /orgs/1/sites/site001 g, customerUser2042, customer001, /orgs/1/sites/site002 g, customerUser2042, customer001, /orgs/1/sites/site003 g, customerUser2042, customer001, /orgs/1/sites/site004 g, customerUser2042, customer001, /orgs/1/sites/site005 g, customerUser2043, customer001, /orgs/1/sites/site001 g, customerUser2043, customer001, /orgs/1/sites/site002 g, customerUser2043, customer001, /orgs/1/sites/site003 g, customerUser2043, customer001, /orgs/1/sites/site004 g, customerUser2043, customer001, /orgs/1/sites/site005 g, customerUser2044, customer001, /orgs/1/sites/site001 g, customerUser2044, customer001, /orgs/1/sites/site002 g, customerUser2044, customer001, /orgs/1/sites/site003 g, customerUser2044, customer001, /orgs/1/sites/site004 g, customerUser2044, customer001, /orgs/1/sites/site005 g, customerUser2045, customer001, /orgs/1/sites/site001 g, customerUser2045, customer001, /orgs/1/sites/site002 g, customerUser2045, customer001, /orgs/1/sites/site003 g, customerUser2045, customer001, /orgs/1/sites/site004 g, customerUser2045, customer001, /orgs/1/sites/site005 g, customerUser2046, customer001, /orgs/1/sites/site001 g, customerUser2046, customer001, /orgs/1/sites/site002 g, customerUser2046, customer001, /orgs/1/sites/site003 g, customerUser2046, customer001, /orgs/1/sites/site004 g, customerUser2046, customer001, /orgs/1/sites/site005 g, customerUser2047, customer001, /orgs/1/sites/site001 g, customerUser2047, customer001, /orgs/1/sites/site002 g, customerUser2047, customer001, /orgs/1/sites/site003 g, customerUser2047, customer001, /orgs/1/sites/site004 g, customerUser2047, customer001, /orgs/1/sites/site005 g, customerUser2048, customer001, /orgs/1/sites/site001 g, customerUser2048, customer001, /orgs/1/sites/site002 g, customerUser2048, customer001, /orgs/1/sites/site003 g, customerUser2048, customer001, /orgs/1/sites/site004 g, customerUser2048, customer001, /orgs/1/sites/site005 g, customerUser2049, customer001, /orgs/1/sites/site001 g, customerUser2049, customer001, /orgs/1/sites/site002 g, customerUser2049, customer001, /orgs/1/sites/site003 g, customerUser2049, customer001, /orgs/1/sites/site004 g, customerUser2049, customer001, /orgs/1/sites/site005 g, customerUser2050, customer001, /orgs/1/sites/site001 g, customerUser2050, customer001, /orgs/1/sites/site002 g, customerUser2050, customer001, /orgs/1/sites/site003 g, customerUser2050, customer001, /orgs/1/sites/site004 g, customerUser2050, customer001, /orgs/1/sites/site005 # Group - staff001, / org2 g, staffUser1001, staff001, /orgs/2/sites/site001 g, staffUser1001, staff001, /orgs/2/sites/site002 g, staffUser1001, staff001, /orgs/2/sites/site003 g, staffUser1001, staff001, /orgs/2/sites/site004 g, staffUser1001, staff001, /orgs/2/sites/site005 g, staffUser1001, staff001, /orgs/2/sites/site001 g, staffUser1001, staff001, /orgs/2/sites/site002 g, staffUser1001, staff001, /orgs/2/sites/site003 g, staffUser1001, staff001, /orgs/2/sites/site004 g, staffUser1001, staff001, /orgs/2/sites/site005 g, staffUser1003, staff001, /orgs/2/sites/site001 g, staffUser1003, staff001, /orgs/2/sites/site002 g, staffUser1003, staff001, /orgs/2/sites/site003 g, staffUser1003, staff001, /orgs/2/sites/site004 g, staffUser1003, staff001, /orgs/2/sites/site005 g, staffUser1004, staff001, /orgs/2/sites/site001 g, staffUser1004, staff001, /orgs/2/sites/site002 g, staffUser1004, staff001, /orgs/2/sites/site003 g, staffUser1004, staff001, /orgs/2/sites/site004 g, staffUser1004, staff001, /orgs/2/sites/site005 g, staffUser1005, staff001, /orgs/2/sites/site001 g, staffUser1005, staff001, /orgs/2/sites/site002 g, staffUser1005, staff001, /orgs/2/sites/site003 g, staffUser1005, staff001, /orgs/2/sites/site004 g, staffUser1005, staff001, /orgs/2/sites/site005 g, staffUser1006, staff001, /orgs/2/sites/site001 g, staffUser1006, staff001, /orgs/2/sites/site002 g, staffUser1006, staff001, /orgs/2/sites/site003 g, staffUser1006, staff001, /orgs/2/sites/site004 g, staffUser1006, staff001, /orgs/2/sites/site005 g, staffUser1007, staff001, /orgs/2/sites/site001 g, staffUser1007, staff001, /orgs/2/sites/site002 g, staffUser1007, staff001, /orgs/2/sites/site003 g, staffUser1007, staff001, /orgs/2/sites/site004 g, staffUser1007, staff001, /orgs/2/sites/site005 g, staffUser1008, staff001, /orgs/2/sites/site001 g, staffUser1008, staff001, /orgs/2/sites/site002 g, staffUser1008, staff001, /orgs/2/sites/site003 g, staffUser1008, staff001, /orgs/2/sites/site004 g, staffUser1008, staff001, /orgs/2/sites/site005 g, staffUser1009, staff001, /orgs/2/sites/site001 g, staffUser1009, staff001, /orgs/2/sites/site002 g, staffUser1009, staff001, /orgs/2/sites/site003 g, staffUser1009, staff001, /orgs/2/sites/site004 g, staffUser1009, staff001, /orgs/2/sites/site005 g, staffUser1010, staff001, /orgs/2/sites/site001 g, staffUser1010, staff001, /orgs/2/sites/site002 g, staffUser1010, staff001, /orgs/2/sites/site003 g, staffUser1010, staff001, /orgs/2/sites/site004 g, staffUser1010, staff001, /orgs/2/sites/site005 g, staffUser1011, staff001, /orgs/2/sites/site001 g, staffUser1011, staff001, /orgs/2/sites/site002 g, staffUser1011, staff001, /orgs/2/sites/site003 g, staffUser1011, staff001, /orgs/2/sites/site004 g, staffUser1011, staff001, /orgs/2/sites/site005 g, staffUser1012, staff001, /orgs/2/sites/site001 g, staffUser1012, staff001, /orgs/2/sites/site002 g, staffUser1012, staff001, /orgs/2/sites/site003 g, staffUser1012, staff001, /orgs/2/sites/site004 g, staffUser1012, staff001, /orgs/2/sites/site005 g, staffUser1013, staff001, /orgs/2/sites/site001 g, staffUser1013, staff001, /orgs/2/sites/site002 g, staffUser1013, staff001, /orgs/2/sites/site003 g, staffUser1013, staff001, /orgs/2/sites/site004 g, staffUser1013, staff001, /orgs/2/sites/site005 g, staffUser1014, staff001, /orgs/2/sites/site001 g, staffUser1014, staff001, /orgs/2/sites/site002 g, staffUser1014, staff001, /orgs/2/sites/site003 g, staffUser1014, staff001, /orgs/2/sites/site004 g, staffUser1014, staff001, /orgs/2/sites/site005 g, staffUser1015, staff001, /orgs/2/sites/site001 g, staffUser1015, staff001, /orgs/2/sites/site002 g, staffUser1015, staff001, /orgs/2/sites/site003 g, staffUser1015, staff001, /orgs/2/sites/site004 g, staffUser1015, staff001, /orgs/2/sites/site005 g, staffUser1016, staff001, /orgs/2/sites/site001 g, staffUser1016, staff001, /orgs/2/sites/site002 g, staffUser1016, staff001, /orgs/2/sites/site003 g, staffUser1016, staff001, /orgs/2/sites/site004 g, staffUser1016, staff001, /orgs/2/sites/site005 g, staffUser1017, staff001, /orgs/2/sites/site001 g, staffUser1017, staff001, /orgs/2/sites/site002 g, staffUser1017, staff001, /orgs/2/sites/site003 g, staffUser1017, staff001, /orgs/2/sites/site004 g, staffUser1017, staff001, /orgs/2/sites/site005 g, staffUser1018, staff001, /orgs/2/sites/site001 g, staffUser1018, staff001, /orgs/2/sites/site002 g, staffUser1018, staff001, /orgs/2/sites/site003 g, staffUser1018, staff001, /orgs/2/sites/site004 g, staffUser1018, staff001, /orgs/2/sites/site005 g, staffUser1019, staff001, /orgs/2/sites/site001 g, staffUser1019, staff001, /orgs/2/sites/site002 g, staffUser1019, staff001, /orgs/2/sites/site003 g, staffUser1019, staff001, /orgs/2/sites/site004 g, staffUser1019, staff001, /orgs/2/sites/site005 g, staffUser1020, staff001, /orgs/2/sites/site001 g, staffUser1020, staff001, /orgs/2/sites/site002 g, staffUser1020, staff001, /orgs/2/sites/site003 g, staffUser1020, staff001, /orgs/2/sites/site004 g, staffUser1020, staff001, /orgs/2/sites/site005 g, staffUser1021, staff001, /orgs/2/sites/site001 g, staffUser1021, staff001, /orgs/2/sites/site002 g, staffUser1021, staff001, /orgs/2/sites/site003 g, staffUser1021, staff001, /orgs/2/sites/site004 g, staffUser1021, staff001, /orgs/2/sites/site005 g, staffUser1022, staff001, /orgs/2/sites/site001 g, staffUser1022, staff001, /orgs/2/sites/site002 g, staffUser1022, staff001, /orgs/2/sites/site003 g, staffUser1022, staff001, /orgs/2/sites/site004 g, staffUser1022, staff001, /orgs/2/sites/site005 g, staffUser1023, staff001, /orgs/2/sites/site001 g, staffUser1023, staff001, /orgs/2/sites/site002 g, staffUser1023, staff001, /orgs/2/sites/site003 g, staffUser1023, staff001, /orgs/2/sites/site004 g, staffUser1023, staff001, /orgs/2/sites/site005 g, staffUser1024, staff001, /orgs/2/sites/site001 g, staffUser1024, staff001, /orgs/2/sites/site002 g, staffUser1024, staff001, /orgs/2/sites/site003 g, staffUser1024, staff001, /orgs/2/sites/site004 g, staffUser1024, staff001, /orgs/2/sites/site005 g, staffUser1025, staff001, /orgs/2/sites/site001 g, staffUser1025, staff001, /orgs/2/sites/site002 g, staffUser1025, staff001, /orgs/2/sites/site003 g, staffUser1025, staff001, /orgs/2/sites/site004 g, staffUser1025, staff001, /orgs/2/sites/site005 g, staffUser1026, staff001, /orgs/2/sites/site001 g, staffUser1026, staff001, /orgs/2/sites/site002 g, staffUser1026, staff001, /orgs/2/sites/site003 g, staffUser1026, staff001, /orgs/2/sites/site004 g, staffUser1026, staff001, /orgs/2/sites/site005 g, staffUser1027, staff001, /orgs/2/sites/site001 g, staffUser1027, staff001, /orgs/2/sites/site002 g, staffUser1027, staff001, /orgs/2/sites/site003 g, staffUser1027, staff001, /orgs/2/sites/site004 g, staffUser1027, staff001, /orgs/2/sites/site005 g, staffUser1028, staff001, /orgs/2/sites/site001 g, staffUser1028, staff001, /orgs/2/sites/site002 g, staffUser1028, staff001, /orgs/2/sites/site003 g, staffUser1028, staff001, /orgs/2/sites/site004 g, staffUser1028, staff001, /orgs/2/sites/site005 g, staffUser1029, staff001, /orgs/2/sites/site001 g, staffUser1029, staff001, /orgs/2/sites/site002 g, staffUser1029, staff001, /orgs/2/sites/site003 g, staffUser1029, staff001, /orgs/2/sites/site004 g, staffUser1029, staff001, /orgs/2/sites/site005 g, staffUser1030, staff001, /orgs/2/sites/site001 g, staffUser1030, staff001, /orgs/2/sites/site002 g, staffUser1030, staff001, /orgs/2/sites/site003 g, staffUser1030, staff001, /orgs/2/sites/site004 g, staffUser1030, staff001, /orgs/2/sites/site005 g, staffUser1031, staff001, /orgs/2/sites/site001 g, staffUser1031, staff001, /orgs/2/sites/site002 g, staffUser1031, staff001, /orgs/2/sites/site003 g, staffUser1031, staff001, /orgs/2/sites/site004 g, staffUser1031, staff001, /orgs/2/sites/site005 g, staffUser1032, staff001, /orgs/2/sites/site001 g, staffUser1032, staff001, /orgs/2/sites/site002 g, staffUser1032, staff001, /orgs/2/sites/site003 g, staffUser1032, staff001, /orgs/2/sites/site004 g, staffUser1032, staff001, /orgs/2/sites/site005 g, staffUser1033, staff001, /orgs/2/sites/site001 g, staffUser1033, staff001, /orgs/2/sites/site002 g, staffUser1033, staff001, /orgs/2/sites/site003 g, staffUser1033, staff001, /orgs/2/sites/site004 g, staffUser1033, staff001, /orgs/2/sites/site005 g, staffUser1034, staff001, /orgs/2/sites/site001 g, staffUser1034, staff001, /orgs/2/sites/site002 g, staffUser1034, staff001, /orgs/2/sites/site003 g, staffUser1034, staff001, /orgs/2/sites/site004 g, staffUser1034, staff001, /orgs/2/sites/site005 g, staffUser1035, staff001, /orgs/2/sites/site001 g, staffUser1035, staff001, /orgs/2/sites/site002 g, staffUser1035, staff001, /orgs/2/sites/site003 g, staffUser1035, staff001, /orgs/2/sites/site004 g, staffUser1035, staff001, /orgs/2/sites/site005 g, staffUser1036, staff001, /orgs/2/sites/site001 g, staffUser1036, staff001, /orgs/2/sites/site002 g, staffUser1036, staff001, /orgs/2/sites/site003 g, staffUser1036, staff001, /orgs/2/sites/site004 g, staffUser1036, staff001, /orgs/2/sites/site005 g, staffUser1037, staff001, /orgs/2/sites/site001 g, staffUser1037, staff001, /orgs/2/sites/site002 g, staffUser1037, staff001, /orgs/2/sites/site003 g, staffUser1037, staff001, /orgs/2/sites/site004 g, staffUser1037, staff001, /orgs/2/sites/site005 g, staffUser1038, staff001, /orgs/2/sites/site001 g, staffUser1038, staff001, /orgs/2/sites/site002 g, staffUser1038, staff001, /orgs/2/sites/site003 g, staffUser1038, staff001, /orgs/2/sites/site004 g, staffUser1038, staff001, /orgs/2/sites/site005 g, staffUser1039, staff001, /orgs/2/sites/site001 g, staffUser1039, staff001, /orgs/2/sites/site002 g, staffUser1039, staff001, /orgs/2/sites/site003 g, staffUser1039, staff001, /orgs/2/sites/site004 g, staffUser1039, staff001, /orgs/2/sites/site005 g, staffUser1040, staff001, /orgs/2/sites/site001 g, staffUser1040, staff001, /orgs/2/sites/site002 g, staffUser1040, staff001, /orgs/2/sites/site003 g, staffUser1040, staff001, /orgs/2/sites/site004 g, staffUser1040, staff001, /orgs/2/sites/site005 g, staffUser1041, staff001, /orgs/2/sites/site001 g, staffUser1041, staff001, /orgs/2/sites/site002 g, staffUser1041, staff001, /orgs/2/sites/site003 g, staffUser1041, staff001, /orgs/2/sites/site004 g, staffUser1041, staff001, /orgs/2/sites/site005 g, staffUser1042, staff001, /orgs/2/sites/site001 g, staffUser1042, staff001, /orgs/2/sites/site002 g, staffUser1042, staff001, /orgs/2/sites/site003 g, staffUser1042, staff001, /orgs/2/sites/site004 g, staffUser1042, staff001, /orgs/2/sites/site005 g, staffUser1043, staff001, /orgs/2/sites/site001 g, staffUser1043, staff001, /orgs/2/sites/site002 g, staffUser1043, staff001, /orgs/2/sites/site003 g, staffUser1043, staff001, /orgs/2/sites/site004 g, staffUser1043, staff001, /orgs/2/sites/site005 g, staffUser1044, staff001, /orgs/2/sites/site001 g, staffUser1044, staff001, /orgs/2/sites/site002 g, staffUser1044, staff001, /orgs/2/sites/site003 g, staffUser1044, staff001, /orgs/2/sites/site004 g, staffUser1044, staff001, /orgs/2/sites/site005 g, staffUser1045, staff001, /orgs/2/sites/site001 g, staffUser1045, staff001, /orgs/2/sites/site002 g, staffUser1045, staff001, /orgs/2/sites/site003 g, staffUser1045, staff001, /orgs/2/sites/site004 g, staffUser1045, staff001, /orgs/2/sites/site005 g, staffUser1046, staff001, /orgs/2/sites/site001 g, staffUser1046, staff001, /orgs/2/sites/site002 g, staffUser1046, staff001, /orgs/2/sites/site003 g, staffUser1046, staff001, /orgs/2/sites/site004 g, staffUser1046, staff001, /orgs/2/sites/site005 g, staffUser1047, staff001, /orgs/2/sites/site001 g, staffUser1047, staff001, /orgs/2/sites/site002 g, staffUser1047, staff001, /orgs/2/sites/site003 g, staffUser1047, staff001, /orgs/2/sites/site004 g, staffUser1047, staff001, /orgs/2/sites/site005 g, staffUser1048, staff001, /orgs/2/sites/site001 g, staffUser1048, staff001, /orgs/2/sites/site002 g, staffUser1048, staff001, /orgs/2/sites/site003 g, staffUser1048, staff001, /orgs/2/sites/site004 g, staffUser1048, staff001, /orgs/2/sites/site005 g, staffUser1049, staff001, /orgs/2/sites/site001 g, staffUser1049, staff001, /orgs/2/sites/site002 g, staffUser1049, staff001, /orgs/2/sites/site003 g, staffUser1049, staff001, /orgs/2/sites/site004 g, staffUser1049, staff001, /orgs/2/sites/site005 g, staffUser1050, staff001, /orgs/2/sites/site001 g, staffUser1050, staff001, /orgs/2/sites/site002 g, staffUser1050, staff001, /orgs/2/sites/site003 g, staffUser1050, staff001, /orgs/2/sites/site004 g, staffUser1050, staff001, /orgs/2/sites/site005 # Group - staff001, / org2 g, staffUser2001, staff001, /orgs/2/sites/site001 g, staffUser2001, staff001, /orgs/2/sites/site002 g, staffUser2001, staff001, /orgs/2/sites/site003 g, staffUser2001, staff001, /orgs/2/sites/site004 g, staffUser2001, staff001, /orgs/2/sites/site005 g, staffUser2001, staff001, /orgs/2/sites/site001 g, staffUser2001, staff001, /orgs/2/sites/site002 g, staffUser2001, staff001, /orgs/2/sites/site003 g, staffUser2001, staff001, /orgs/2/sites/site004 g, staffUser2001, staff001, /orgs/2/sites/site005 g, staffUser2003, staff001, /orgs/2/sites/site001 g, staffUser2003, staff001, /orgs/2/sites/site002 g, staffUser2003, staff001, /orgs/2/sites/site003 g, staffUser2003, staff001, /orgs/2/sites/site004 g, staffUser2003, staff001, /orgs/2/sites/site005 g, staffUser2004, staff001, /orgs/2/sites/site001 g, staffUser2004, staff001, /orgs/2/sites/site002 g, staffUser2004, staff001, /orgs/2/sites/site003 g, staffUser2004, staff001, /orgs/2/sites/site004 g, staffUser2004, staff001, /orgs/2/sites/site005 g, staffUser2005, staff001, /orgs/2/sites/site001 g, staffUser2005, staff001, /orgs/2/sites/site002 g, staffUser2005, staff001, /orgs/2/sites/site003 g, staffUser2005, staff001, /orgs/2/sites/site004 g, staffUser2005, staff001, /orgs/2/sites/site005 g, staffUser2006, staff001, /orgs/2/sites/site001 g, staffUser2006, staff001, /orgs/2/sites/site002 g, staffUser2006, staff001, /orgs/2/sites/site003 g, staffUser2006, staff001, /orgs/2/sites/site004 g, staffUser2006, staff001, /orgs/2/sites/site005 g, staffUser2007, staff001, /orgs/2/sites/site001 g, staffUser2007, staff001, /orgs/2/sites/site002 g, staffUser2007, staff001, /orgs/2/sites/site003 g, staffUser2007, staff001, /orgs/2/sites/site004 g, staffUser2007, staff001, /orgs/2/sites/site005 g, staffUser2008, staff001, /orgs/2/sites/site001 g, staffUser2008, staff001, /orgs/2/sites/site002 g, staffUser2008, staff001, /orgs/2/sites/site003 g, staffUser2008, staff001, /orgs/2/sites/site004 g, staffUser2008, staff001, /orgs/2/sites/site005 g, staffUser2009, staff001, /orgs/2/sites/site001 g, staffUser2009, staff001, /orgs/2/sites/site002 g, staffUser2009, staff001, /orgs/2/sites/site003 g, staffUser2009, staff001, /orgs/2/sites/site004 g, staffUser2009, staff001, /orgs/2/sites/site005 g, staffUser2010, staff001, /orgs/2/sites/site001 g, staffUser2010, staff001, /orgs/2/sites/site002 g, staffUser2010, staff001, /orgs/2/sites/site003 g, staffUser2010, staff001, /orgs/2/sites/site004 g, staffUser2010, staff001, /orgs/2/sites/site005 g, staffUser2011, staff001, /orgs/2/sites/site001 g, staffUser2011, staff001, /orgs/2/sites/site002 g, staffUser2011, staff001, /orgs/2/sites/site003 g, staffUser2011, staff001, /orgs/2/sites/site004 g, staffUser2011, staff001, /orgs/2/sites/site005 g, staffUser2012, staff001, /orgs/2/sites/site001 g, staffUser2012, staff001, /orgs/2/sites/site002 g, staffUser2012, staff001, /orgs/2/sites/site003 g, staffUser2012, staff001, /orgs/2/sites/site004 g, staffUser2012, staff001, /orgs/2/sites/site005 g, staffUser2013, staff001, /orgs/2/sites/site001 g, staffUser2013, staff001, /orgs/2/sites/site002 g, staffUser2013, staff001, /orgs/2/sites/site003 g, staffUser2013, staff001, /orgs/2/sites/site004 g, staffUser2013, staff001, /orgs/2/sites/site005 g, staffUser2014, staff001, /orgs/2/sites/site001 g, staffUser2014, staff001, /orgs/2/sites/site002 g, staffUser2014, staff001, /orgs/2/sites/site003 g, staffUser2014, staff001, /orgs/2/sites/site004 g, staffUser2014, staff001, /orgs/2/sites/site005 g, staffUser2015, staff001, /orgs/2/sites/site001 g, staffUser2015, staff001, /orgs/2/sites/site002 g, staffUser2015, staff001, /orgs/2/sites/site003 g, staffUser2015, staff001, /orgs/2/sites/site004 g, staffUser2015, staff001, /orgs/2/sites/site005 g, staffUser2016, staff001, /orgs/2/sites/site001 g, staffUser2016, staff001, /orgs/2/sites/site002 g, staffUser2016, staff001, /orgs/2/sites/site003 g, staffUser2016, staff001, /orgs/2/sites/site004 g, staffUser2016, staff001, /orgs/2/sites/site005 g, staffUser2017, staff001, /orgs/2/sites/site001 g, staffUser2017, staff001, /orgs/2/sites/site002 g, staffUser2017, staff001, /orgs/2/sites/site003 g, staffUser2017, staff001, /orgs/2/sites/site004 g, staffUser2017, staff001, /orgs/2/sites/site005 g, staffUser2018, staff001, /orgs/2/sites/site001 g, staffUser2018, staff001, /orgs/2/sites/site002 g, staffUser2018, staff001, /orgs/2/sites/site003 g, staffUser2018, staff001, /orgs/2/sites/site004 g, staffUser2018, staff001, /orgs/2/sites/site005 g, staffUser2019, staff001, /orgs/2/sites/site001 g, staffUser2019, staff001, /orgs/2/sites/site002 g, staffUser2019, staff001, /orgs/2/sites/site003 g, staffUser2019, staff001, /orgs/2/sites/site004 g, staffUser2019, staff001, /orgs/2/sites/site005 g, staffUser2020, staff001, /orgs/2/sites/site001 g, staffUser2020, staff001, /orgs/2/sites/site002 g, staffUser2020, staff001, /orgs/2/sites/site003 g, staffUser2020, staff001, /orgs/2/sites/site004 g, staffUser2020, staff001, /orgs/2/sites/site005 g, staffUser2021, staff001, /orgs/2/sites/site001 g, staffUser2021, staff001, /orgs/2/sites/site002 g, staffUser2021, staff001, /orgs/2/sites/site003 g, staffUser2021, staff001, /orgs/2/sites/site004 g, staffUser2021, staff001, /orgs/2/sites/site005 g, staffUser2022, staff001, /orgs/2/sites/site001 g, staffUser2022, staff001, /orgs/2/sites/site002 g, staffUser2022, staff001, /orgs/2/sites/site003 g, staffUser2022, staff001, /orgs/2/sites/site004 g, staffUser2022, staff001, /orgs/2/sites/site005 g, staffUser2023, staff001, /orgs/2/sites/site001 g, staffUser2023, staff001, /orgs/2/sites/site002 g, staffUser2023, staff001, /orgs/2/sites/site003 g, staffUser2023, staff001, /orgs/2/sites/site004 g, staffUser2023, staff001, /orgs/2/sites/site005 g, staffUser2024, staff001, /orgs/2/sites/site001 g, staffUser2024, staff001, /orgs/2/sites/site002 g, staffUser2024, staff001, /orgs/2/sites/site003 g, staffUser2024, staff001, /orgs/2/sites/site004 g, staffUser2024, staff001, /orgs/2/sites/site005 g, staffUser2025, staff001, /orgs/2/sites/site001 g, staffUser2025, staff001, /orgs/2/sites/site002 g, staffUser2025, staff001, /orgs/2/sites/site003 g, staffUser2025, staff001, /orgs/2/sites/site004 g, staffUser2025, staff001, /orgs/2/sites/site005 g, staffUser2026, staff001, /orgs/2/sites/site001 g, staffUser2026, staff001, /orgs/2/sites/site002 g, staffUser2026, staff001, /orgs/2/sites/site003 g, staffUser2026, staff001, /orgs/2/sites/site004 g, staffUser2026, staff001, /orgs/2/sites/site005 g, staffUser2027, staff001, /orgs/2/sites/site001 g, staffUser2027, staff001, /orgs/2/sites/site002 g, staffUser2027, staff001, /orgs/2/sites/site003 g, staffUser2027, staff001, /orgs/2/sites/site004 g, staffUser2027, staff001, /orgs/2/sites/site005 g, staffUser2028, staff001, /orgs/2/sites/site001 g, staffUser2028, staff001, /orgs/2/sites/site002 g, staffUser2028, staff001, /orgs/2/sites/site003 g, staffUser2028, staff001, /orgs/2/sites/site004 g, staffUser2028, staff001, /orgs/2/sites/site005 g, staffUser2029, staff001, /orgs/2/sites/site001 g, staffUser2029, staff001, /orgs/2/sites/site002 g, staffUser2029, staff001, /orgs/2/sites/site003 g, staffUser2029, staff001, /orgs/2/sites/site004 g, staffUser2029, staff001, /orgs/2/sites/site005 g, staffUser2030, staff001, /orgs/2/sites/site001 g, staffUser2030, staff001, /orgs/2/sites/site002 g, staffUser2030, staff001, /orgs/2/sites/site003 g, staffUser2030, staff001, /orgs/2/sites/site004 g, staffUser2030, staff001, /orgs/2/sites/site005 g, staffUser2031, staff001, /orgs/2/sites/site001 g, staffUser2031, staff001, /orgs/2/sites/site002 g, staffUser2031, staff001, /orgs/2/sites/site003 g, staffUser2031, staff001, /orgs/2/sites/site004 g, staffUser2031, staff001, /orgs/2/sites/site005 g, staffUser2032, staff001, /orgs/2/sites/site001 g, staffUser2032, staff001, /orgs/2/sites/site002 g, staffUser2032, staff001, /orgs/2/sites/site003 g, staffUser2032, staff001, /orgs/2/sites/site004 g, staffUser2032, staff001, /orgs/2/sites/site005 g, staffUser2033, staff001, /orgs/2/sites/site001 g, staffUser2033, staff001, /orgs/2/sites/site002 g, staffUser2033, staff001, /orgs/2/sites/site003 g, staffUser2033, staff001, /orgs/2/sites/site004 g, staffUser2033, staff001, /orgs/2/sites/site005 g, staffUser2034, staff001, /orgs/2/sites/site001 g, staffUser2034, staff001, /orgs/2/sites/site002 g, staffUser2034, staff001, /orgs/2/sites/site003 g, staffUser2034, staff001, /orgs/2/sites/site004 g, staffUser2034, staff001, /orgs/2/sites/site005 g, staffUser2035, staff001, /orgs/2/sites/site001 g, staffUser2035, staff001, /orgs/2/sites/site002 g, staffUser2035, staff001, /orgs/2/sites/site003 g, staffUser2035, staff001, /orgs/2/sites/site004 g, staffUser2035, staff001, /orgs/2/sites/site005 g, staffUser2036, staff001, /orgs/2/sites/site001 g, staffUser2036, staff001, /orgs/2/sites/site002 g, staffUser2036, staff001, /orgs/2/sites/site003 g, staffUser2036, staff001, /orgs/2/sites/site004 g, staffUser2036, staff001, /orgs/2/sites/site005 g, staffUser2037, staff001, /orgs/2/sites/site001 g, staffUser2037, staff001, /orgs/2/sites/site002 g, staffUser2037, staff001, /orgs/2/sites/site003 g, staffUser2037, staff001, /orgs/2/sites/site004 g, staffUser2037, staff001, /orgs/2/sites/site005 g, staffUser2038, staff001, /orgs/2/sites/site001 g, staffUser2038, staff001, /orgs/2/sites/site002 g, staffUser2038, staff001, /orgs/2/sites/site003 g, staffUser2038, staff001, /orgs/2/sites/site004 g, staffUser2038, staff001, /orgs/2/sites/site005 g, staffUser2039, staff001, /orgs/2/sites/site001 g, staffUser2039, staff001, /orgs/2/sites/site002 g, staffUser2039, staff001, /orgs/2/sites/site003 g, staffUser2039, staff001, /orgs/2/sites/site004 g, staffUser2039, staff001, /orgs/2/sites/site005 g, staffUser2040, staff001, /orgs/2/sites/site001 g, staffUser2040, staff001, /orgs/2/sites/site002 g, staffUser2040, staff001, /orgs/2/sites/site003 g, staffUser2040, staff001, /orgs/2/sites/site004 g, staffUser2040, staff001, /orgs/2/sites/site005 g, staffUser2041, staff001, /orgs/2/sites/site001 g, staffUser2041, staff001, /orgs/2/sites/site002 g, staffUser2041, staff001, /orgs/2/sites/site003 g, staffUser2041, staff001, /orgs/2/sites/site004 g, staffUser2041, staff001, /orgs/2/sites/site005 g, staffUser2042, staff001, /orgs/2/sites/site001 g, staffUser2042, staff001, /orgs/2/sites/site002 g, staffUser2042, staff001, /orgs/2/sites/site003 g, staffUser2042, staff001, /orgs/2/sites/site004 g, staffUser2042, staff001, /orgs/2/sites/site005 g, staffUser2043, staff001, /orgs/2/sites/site001 g, staffUser2043, staff001, /orgs/2/sites/site002 g, staffUser2043, staff001, /orgs/2/sites/site003 g, staffUser2043, staff001, /orgs/2/sites/site004 g, staffUser2043, staff001, /orgs/2/sites/site005 g, staffUser2044, staff001, /orgs/2/sites/site001 g, staffUser2044, staff001, /orgs/2/sites/site002 g, staffUser2044, staff001, /orgs/2/sites/site003 g, staffUser2044, staff001, /orgs/2/sites/site004 g, staffUser2044, staff001, /orgs/2/sites/site005 g, staffUser2045, staff001, /orgs/2/sites/site001 g, staffUser2045, staff001, /orgs/2/sites/site002 g, staffUser2045, staff001, /orgs/2/sites/site003 g, staffUser2045, staff001, /orgs/2/sites/site004 g, staffUser2045, staff001, /orgs/2/sites/site005 g, staffUser2046, staff001, /orgs/2/sites/site001 g, staffUser2046, staff001, /orgs/2/sites/site002 g, staffUser2046, staff001, /orgs/2/sites/site003 g, staffUser2046, staff001, /orgs/2/sites/site004 g, staffUser2046, staff001, /orgs/2/sites/site005 g, staffUser2047, staff001, /orgs/2/sites/site001 g, staffUser2047, staff001, /orgs/2/sites/site002 g, staffUser2047, staff001, /orgs/2/sites/site003 g, staffUser2047, staff001, /orgs/2/sites/site004 g, staffUser2047, staff001, /orgs/2/sites/site005 g, staffUser2048, staff001, /orgs/2/sites/site001 g, staffUser2048, staff001, /orgs/2/sites/site002 g, staffUser2048, staff001, /orgs/2/sites/site003 g, staffUser2048, staff001, /orgs/2/sites/site004 g, staffUser2048, staff001, /orgs/2/sites/site005 g, staffUser2049, staff001, /orgs/2/sites/site001 g, staffUser2049, staff001, /orgs/2/sites/site002 g, staffUser2049, staff001, /orgs/2/sites/site003 g, staffUser2049, staff001, /orgs/2/sites/site004 g, staffUser2049, staff001, /orgs/2/sites/site005 g, staffUser2050, staff001, /orgs/2/sites/site001 g, staffUser2050, staff001, /orgs/2/sites/site002 g, staffUser2050, staff001, /orgs/2/sites/site003 g, staffUser2050, staff001, /orgs/2/sites/site004 g, staffUser2050, staff001, /orgs/2/sites/site005 # Group - manager001, / org2 g, managerUser1001, manager001, /orgs/2/sites/site001 g, managerUser1001, manager001, /orgs/2/sites/site002 g, managerUser1001, manager001, /orgs/2/sites/site003 g, managerUser1001, manager001, /orgs/2/sites/site004 g, managerUser1001, manager001, /orgs/2/sites/site005 g, managerUser1001, manager001, /orgs/2/sites/site001 g, managerUser1001, manager001, /orgs/2/sites/site002 g, managerUser1001, manager001, /orgs/2/sites/site003 g, managerUser1001, manager001, /orgs/2/sites/site004 g, managerUser1001, manager001, /orgs/2/sites/site005 g, managerUser1003, manager001, /orgs/2/sites/site001 g, managerUser1003, manager001, /orgs/2/sites/site002 g, managerUser1003, manager001, /orgs/2/sites/site003 g, managerUser1003, manager001, /orgs/2/sites/site004 g, managerUser1003, manager001, /orgs/2/sites/site005 g, managerUser1004, manager001, /orgs/2/sites/site001 g, managerUser1004, manager001, /orgs/2/sites/site002 g, managerUser1004, manager001, /orgs/2/sites/site003 g, managerUser1004, manager001, /orgs/2/sites/site004 g, managerUser1004, manager001, /orgs/2/sites/site005 g, managerUser1005, manager001, /orgs/2/sites/site001 g, managerUser1005, manager001, /orgs/2/sites/site002 g, managerUser1005, manager001, /orgs/2/sites/site003 g, managerUser1005, manager001, /orgs/2/sites/site004 g, managerUser1005, manager001, /orgs/2/sites/site005 g, managerUser1006, manager001, /orgs/2/sites/site001 g, managerUser1006, manager001, /orgs/2/sites/site002 g, managerUser1006, manager001, /orgs/2/sites/site003 g, managerUser1006, manager001, /orgs/2/sites/site004 g, managerUser1006, manager001, /orgs/2/sites/site005 g, managerUser1007, manager001, /orgs/2/sites/site001 g, managerUser1007, manager001, /orgs/2/sites/site002 g, managerUser1007, manager001, /orgs/2/sites/site003 g, managerUser1007, manager001, /orgs/2/sites/site004 g, managerUser1007, manager001, /orgs/2/sites/site005 g, managerUser1008, manager001, /orgs/2/sites/site001 g, managerUser1008, manager001, /orgs/2/sites/site002 g, managerUser1008, manager001, /orgs/2/sites/site003 g, managerUser1008, manager001, /orgs/2/sites/site004 g, managerUser1008, manager001, /orgs/2/sites/site005 g, managerUser1009, manager001, /orgs/2/sites/site001 g, managerUser1009, manager001, /orgs/2/sites/site002 g, managerUser1009, manager001, /orgs/2/sites/site003 g, managerUser1009, manager001, /orgs/2/sites/site004 g, managerUser1009, manager001, /orgs/2/sites/site005 g, managerUser1010, manager001, /orgs/2/sites/site001 g, managerUser1010, manager001, /orgs/2/sites/site002 g, managerUser1010, manager001, /orgs/2/sites/site003 g, managerUser1010, manager001, /orgs/2/sites/site004 g, managerUser1010, manager001, /orgs/2/sites/site005 g, managerUser1011, manager001, /orgs/2/sites/site001 g, managerUser1011, manager001, /orgs/2/sites/site002 g, managerUser1011, manager001, /orgs/2/sites/site003 g, managerUser1011, manager001, /orgs/2/sites/site004 g, managerUser1011, manager001, /orgs/2/sites/site005 g, managerUser1012, manager001, /orgs/2/sites/site001 g, managerUser1012, manager001, /orgs/2/sites/site002 g, managerUser1012, manager001, /orgs/2/sites/site003 g, managerUser1012, manager001, /orgs/2/sites/site004 g, managerUser1012, manager001, /orgs/2/sites/site005 g, managerUser1013, manager001, /orgs/2/sites/site001 g, managerUser1013, manager001, /orgs/2/sites/site002 g, managerUser1013, manager001, /orgs/2/sites/site003 g, managerUser1013, manager001, /orgs/2/sites/site004 g, managerUser1013, manager001, /orgs/2/sites/site005 g, managerUser1014, manager001, /orgs/2/sites/site001 g, managerUser1014, manager001, /orgs/2/sites/site002 g, managerUser1014, manager001, /orgs/2/sites/site003 g, managerUser1014, manager001, /orgs/2/sites/site004 g, managerUser1014, manager001, /orgs/2/sites/site005 g, managerUser1015, manager001, /orgs/2/sites/site001 g, managerUser1015, manager001, /orgs/2/sites/site002 g, managerUser1015, manager001, /orgs/2/sites/site003 g, managerUser1015, manager001, /orgs/2/sites/site004 g, managerUser1015, manager001, /orgs/2/sites/site005 g, managerUser1016, manager001, /orgs/2/sites/site001 g, managerUser1016, manager001, /orgs/2/sites/site002 g, managerUser1016, manager001, /orgs/2/sites/site003 g, managerUser1016, manager001, /orgs/2/sites/site004 g, managerUser1016, manager001, /orgs/2/sites/site005 g, managerUser1017, manager001, /orgs/2/sites/site001 g, managerUser1017, manager001, /orgs/2/sites/site002 g, managerUser1017, manager001, /orgs/2/sites/site003 g, managerUser1017, manager001, /orgs/2/sites/site004 g, managerUser1017, manager001, /orgs/2/sites/site005 g, managerUser1018, manager001, /orgs/2/sites/site001 g, managerUser1018, manager001, /orgs/2/sites/site002 g, managerUser1018, manager001, /orgs/2/sites/site003 g, managerUser1018, manager001, /orgs/2/sites/site004 g, managerUser1018, manager001, /orgs/2/sites/site005 g, managerUser1019, manager001, /orgs/2/sites/site001 g, managerUser1019, manager001, /orgs/2/sites/site002 g, managerUser1019, manager001, /orgs/2/sites/site003 g, managerUser1019, manager001, /orgs/2/sites/site004 g, managerUser1019, manager001, /orgs/2/sites/site005 g, managerUser1020, manager001, /orgs/2/sites/site001 g, managerUser1020, manager001, /orgs/2/sites/site002 g, managerUser1020, manager001, /orgs/2/sites/site003 g, managerUser1020, manager001, /orgs/2/sites/site004 g, managerUser1020, manager001, /orgs/2/sites/site005 g, managerUser1021, manager001, /orgs/2/sites/site001 g, managerUser1021, manager001, /orgs/2/sites/site002 g, managerUser1021, manager001, /orgs/2/sites/site003 g, managerUser1021, manager001, /orgs/2/sites/site004 g, managerUser1021, manager001, /orgs/2/sites/site005 g, managerUser1022, manager001, /orgs/2/sites/site001 g, managerUser1022, manager001, /orgs/2/sites/site002 g, managerUser1022, manager001, /orgs/2/sites/site003 g, managerUser1022, manager001, /orgs/2/sites/site004 g, managerUser1022, manager001, /orgs/2/sites/site005 g, managerUser1023, manager001, /orgs/2/sites/site001 g, managerUser1023, manager001, /orgs/2/sites/site002 g, managerUser1023, manager001, /orgs/2/sites/site003 g, managerUser1023, manager001, /orgs/2/sites/site004 g, managerUser1023, manager001, /orgs/2/sites/site005 g, managerUser1024, manager001, /orgs/2/sites/site001 g, managerUser1024, manager001, /orgs/2/sites/site002 g, managerUser1024, manager001, /orgs/2/sites/site003 g, managerUser1024, manager001, /orgs/2/sites/site004 g, managerUser1024, manager001, /orgs/2/sites/site005 g, managerUser1025, manager001, /orgs/2/sites/site001 g, managerUser1025, manager001, /orgs/2/sites/site002 g, managerUser1025, manager001, /orgs/2/sites/site003 g, managerUser1025, manager001, /orgs/2/sites/site004 g, managerUser1025, manager001, /orgs/2/sites/site005 g, managerUser1026, manager001, /orgs/2/sites/site001 g, managerUser1026, manager001, /orgs/2/sites/site002 g, managerUser1026, manager001, /orgs/2/sites/site003 g, managerUser1026, manager001, /orgs/2/sites/site004 g, managerUser1026, manager001, /orgs/2/sites/site005 g, managerUser1027, manager001, /orgs/2/sites/site001 g, managerUser1027, manager001, /orgs/2/sites/site002 g, managerUser1027, manager001, /orgs/2/sites/site003 g, managerUser1027, manager001, /orgs/2/sites/site004 g, managerUser1027, manager001, /orgs/2/sites/site005 g, managerUser1028, manager001, /orgs/2/sites/site001 g, managerUser1028, manager001, /orgs/2/sites/site002 g, managerUser1028, manager001, /orgs/2/sites/site003 g, managerUser1028, manager001, /orgs/2/sites/site004 g, managerUser1028, manager001, /orgs/2/sites/site005 g, managerUser1029, manager001, /orgs/2/sites/site001 g, managerUser1029, manager001, /orgs/2/sites/site002 g, managerUser1029, manager001, /orgs/2/sites/site003 g, managerUser1029, manager001, /orgs/2/sites/site004 g, managerUser1029, manager001, /orgs/2/sites/site005 g, managerUser1030, manager001, /orgs/2/sites/site001 g, managerUser1030, manager001, /orgs/2/sites/site002 g, managerUser1030, manager001, /orgs/2/sites/site003 g, managerUser1030, manager001, /orgs/2/sites/site004 g, managerUser1030, manager001, /orgs/2/sites/site005 g, managerUser1031, manager001, /orgs/2/sites/site001 g, managerUser1031, manager001, /orgs/2/sites/site002 g, managerUser1031, manager001, /orgs/2/sites/site003 g, managerUser1031, manager001, /orgs/2/sites/site004 g, managerUser1031, manager001, /orgs/2/sites/site005 g, managerUser1032, manager001, /orgs/2/sites/site001 g, managerUser1032, manager001, /orgs/2/sites/site002 g, managerUser1032, manager001, /orgs/2/sites/site003 g, managerUser1032, manager001, /orgs/2/sites/site004 g, managerUser1032, manager001, /orgs/2/sites/site005 g, managerUser1033, manager001, /orgs/2/sites/site001 g, managerUser1033, manager001, /orgs/2/sites/site002 g, managerUser1033, manager001, /orgs/2/sites/site003 g, managerUser1033, manager001, /orgs/2/sites/site004 g, managerUser1033, manager001, /orgs/2/sites/site005 g, managerUser1034, manager001, /orgs/2/sites/site001 g, managerUser1034, manager001, /orgs/2/sites/site002 g, managerUser1034, manager001, /orgs/2/sites/site003 g, managerUser1034, manager001, /orgs/2/sites/site004 g, managerUser1034, manager001, /orgs/2/sites/site005 g, managerUser1035, manager001, /orgs/2/sites/site001 g, managerUser1035, manager001, /orgs/2/sites/site002 g, managerUser1035, manager001, /orgs/2/sites/site003 g, managerUser1035, manager001, /orgs/2/sites/site004 g, managerUser1035, manager001, /orgs/2/sites/site005 g, managerUser1036, manager001, /orgs/2/sites/site001 g, managerUser1036, manager001, /orgs/2/sites/site002 g, managerUser1036, manager001, /orgs/2/sites/site003 g, managerUser1036, manager001, /orgs/2/sites/site004 g, managerUser1036, manager001, /orgs/2/sites/site005 g, managerUser1037, manager001, /orgs/2/sites/site001 g, managerUser1037, manager001, /orgs/2/sites/site002 g, managerUser1037, manager001, /orgs/2/sites/site003 g, managerUser1037, manager001, /orgs/2/sites/site004 g, managerUser1037, manager001, /orgs/2/sites/site005 g, managerUser1038, manager001, /orgs/2/sites/site001 g, managerUser1038, manager001, /orgs/2/sites/site002 g, managerUser1038, manager001, /orgs/2/sites/site003 g, managerUser1038, manager001, /orgs/2/sites/site004 g, managerUser1038, manager001, /orgs/2/sites/site005 g, managerUser1039, manager001, /orgs/2/sites/site001 g, managerUser1039, manager001, /orgs/2/sites/site002 g, managerUser1039, manager001, /orgs/2/sites/site003 g, managerUser1039, manager001, /orgs/2/sites/site004 g, managerUser1039, manager001, /orgs/2/sites/site005 g, managerUser1040, manager001, /orgs/2/sites/site001 g, managerUser1040, manager001, /orgs/2/sites/site002 g, managerUser1040, manager001, /orgs/2/sites/site003 g, managerUser1040, manager001, /orgs/2/sites/site004 g, managerUser1040, manager001, /orgs/2/sites/site005 g, managerUser1041, manager001, /orgs/2/sites/site001 g, managerUser1041, manager001, /orgs/2/sites/site002 g, managerUser1041, manager001, /orgs/2/sites/site003 g, managerUser1041, manager001, /orgs/2/sites/site004 g, managerUser1041, manager001, /orgs/2/sites/site005 g, managerUser1042, manager001, /orgs/2/sites/site001 g, managerUser1042, manager001, /orgs/2/sites/site002 g, managerUser1042, manager001, /orgs/2/sites/site003 g, managerUser1042, manager001, /orgs/2/sites/site004 g, managerUser1042, manager001, /orgs/2/sites/site005 g, managerUser1043, manager001, /orgs/2/sites/site001 g, managerUser1043, manager001, /orgs/2/sites/site002 g, managerUser1043, manager001, /orgs/2/sites/site003 g, managerUser1043, manager001, /orgs/2/sites/site004 g, managerUser1043, manager001, /orgs/2/sites/site005 g, managerUser1044, manager001, /orgs/2/sites/site001 g, managerUser1044, manager001, /orgs/2/sites/site002 g, managerUser1044, manager001, /orgs/2/sites/site003 g, managerUser1044, manager001, /orgs/2/sites/site004 g, managerUser1044, manager001, /orgs/2/sites/site005 g, managerUser1045, manager001, /orgs/2/sites/site001 g, managerUser1045, manager001, /orgs/2/sites/site002 g, managerUser1045, manager001, /orgs/2/sites/site003 g, managerUser1045, manager001, /orgs/2/sites/site004 g, managerUser1045, manager001, /orgs/2/sites/site005 g, managerUser1046, manager001, /orgs/2/sites/site001 g, managerUser1046, manager001, /orgs/2/sites/site002 g, managerUser1046, manager001, /orgs/2/sites/site003 g, managerUser1046, manager001, /orgs/2/sites/site004 g, managerUser1046, manager001, /orgs/2/sites/site005 g, managerUser1047, manager001, /orgs/2/sites/site001 g, managerUser1047, manager001, /orgs/2/sites/site002 g, managerUser1047, manager001, /orgs/2/sites/site003 g, managerUser1047, manager001, /orgs/2/sites/site004 g, managerUser1047, manager001, /orgs/2/sites/site005 g, managerUser1048, manager001, /orgs/2/sites/site001 g, managerUser1048, manager001, /orgs/2/sites/site002 g, managerUser1048, manager001, /orgs/2/sites/site003 g, managerUser1048, manager001, /orgs/2/sites/site004 g, managerUser1048, manager001, /orgs/2/sites/site005 g, managerUser1049, manager001, /orgs/2/sites/site001 g, managerUser1049, manager001, /orgs/2/sites/site002 g, managerUser1049, manager001, /orgs/2/sites/site003 g, managerUser1049, manager001, /orgs/2/sites/site004 g, managerUser1049, manager001, /orgs/2/sites/site005 g, managerUser1050, manager001, /orgs/2/sites/site001 g, managerUser1050, manager001, /orgs/2/sites/site002 g, managerUser1050, manager001, /orgs/2/sites/site003 g, managerUser1050, manager001, /orgs/2/sites/site004 g, managerUser1050, manager001, /orgs/2/sites/site005 # Group - manager001, / org2 g, managerUser2001, manager001, /orgs/2/sites/site001 g, managerUser2001, manager001, /orgs/2/sites/site002 g, managerUser2001, manager001, /orgs/2/sites/site003 g, managerUser2001, manager001, /orgs/2/sites/site004 g, managerUser2001, manager001, /orgs/2/sites/site005 g, managerUser2001, manager001, /orgs/2/sites/site001 g, managerUser2001, manager001, /orgs/2/sites/site002 g, managerUser2001, manager001, /orgs/2/sites/site003 g, managerUser2001, manager001, /orgs/2/sites/site004 g, managerUser2001, manager001, /orgs/2/sites/site005 g, managerUser2003, manager001, /orgs/2/sites/site001 g, managerUser2003, manager001, /orgs/2/sites/site002 g, managerUser2003, manager001, /orgs/2/sites/site003 g, managerUser2003, manager001, /orgs/2/sites/site004 g, managerUser2003, manager001, /orgs/2/sites/site005 g, managerUser2004, manager001, /orgs/2/sites/site001 g, managerUser2004, manager001, /orgs/2/sites/site002 g, managerUser2004, manager001, /orgs/2/sites/site003 g, managerUser2004, manager001, /orgs/2/sites/site004 g, managerUser2004, manager001, /orgs/2/sites/site005 g, managerUser2005, manager001, /orgs/2/sites/site001 g, managerUser2005, manager001, /orgs/2/sites/site002 g, managerUser2005, manager001, /orgs/2/sites/site003 g, managerUser2005, manager001, /orgs/2/sites/site004 g, managerUser2005, manager001, /orgs/2/sites/site005 g, managerUser2006, manager001, /orgs/2/sites/site001 g, managerUser2006, manager001, /orgs/2/sites/site002 g, managerUser2006, manager001, /orgs/2/sites/site003 g, managerUser2006, manager001, /orgs/2/sites/site004 g, managerUser2006, manager001, /orgs/2/sites/site005 g, managerUser2007, manager001, /orgs/2/sites/site001 g, managerUser2007, manager001, /orgs/2/sites/site002 g, managerUser2007, manager001, /orgs/2/sites/site003 g, managerUser2007, manager001, /orgs/2/sites/site004 g, managerUser2007, manager001, /orgs/2/sites/site005 g, managerUser2008, manager001, /orgs/2/sites/site001 g, managerUser2008, manager001, /orgs/2/sites/site002 g, managerUser2008, manager001, /orgs/2/sites/site003 g, managerUser2008, manager001, /orgs/2/sites/site004 g, managerUser2008, manager001, /orgs/2/sites/site005 g, managerUser2009, manager001, /orgs/2/sites/site001 g, managerUser2009, manager001, /orgs/2/sites/site002 g, managerUser2009, manager001, /orgs/2/sites/site003 g, managerUser2009, manager001, /orgs/2/sites/site004 g, managerUser2009, manager001, /orgs/2/sites/site005 g, managerUser2010, manager001, /orgs/2/sites/site001 g, managerUser2010, manager001, /orgs/2/sites/site002 g, managerUser2010, manager001, /orgs/2/sites/site003 g, managerUser2010, manager001, /orgs/2/sites/site004 g, managerUser2010, manager001, /orgs/2/sites/site005 g, managerUser2011, manager001, /orgs/2/sites/site001 g, managerUser2011, manager001, /orgs/2/sites/site002 g, managerUser2011, manager001, /orgs/2/sites/site003 g, managerUser2011, manager001, /orgs/2/sites/site004 g, managerUser2011, manager001, /orgs/2/sites/site005 g, managerUser2012, manager001, /orgs/2/sites/site001 g, managerUser2012, manager001, /orgs/2/sites/site002 g, managerUser2012, manager001, /orgs/2/sites/site003 g, managerUser2012, manager001, /orgs/2/sites/site004 g, managerUser2012, manager001, /orgs/2/sites/site005 g, managerUser2013, manager001, /orgs/2/sites/site001 g, managerUser2013, manager001, /orgs/2/sites/site002 g, managerUser2013, manager001, /orgs/2/sites/site003 g, managerUser2013, manager001, /orgs/2/sites/site004 g, managerUser2013, manager001, /orgs/2/sites/site005 g, managerUser2014, manager001, /orgs/2/sites/site001 g, managerUser2014, manager001, /orgs/2/sites/site002 g, managerUser2014, manager001, /orgs/2/sites/site003 g, managerUser2014, manager001, /orgs/2/sites/site004 g, managerUser2014, manager001, /orgs/2/sites/site005 g, managerUser2015, manager001, /orgs/2/sites/site001 g, managerUser2015, manager001, /orgs/2/sites/site002 g, managerUser2015, manager001, /orgs/2/sites/site003 g, managerUser2015, manager001, /orgs/2/sites/site004 g, managerUser2015, manager001, /orgs/2/sites/site005 g, managerUser2016, manager001, /orgs/2/sites/site001 g, managerUser2016, manager001, /orgs/2/sites/site002 g, managerUser2016, manager001, /orgs/2/sites/site003 g, managerUser2016, manager001, /orgs/2/sites/site004 g, managerUser2016, manager001, /orgs/2/sites/site005 g, managerUser2017, manager001, /orgs/2/sites/site001 g, managerUser2017, manager001, /orgs/2/sites/site002 g, managerUser2017, manager001, /orgs/2/sites/site003 g, managerUser2017, manager001, /orgs/2/sites/site004 g, managerUser2017, manager001, /orgs/2/sites/site005 g, managerUser2018, manager001, /orgs/2/sites/site001 g, managerUser2018, manager001, /orgs/2/sites/site002 g, managerUser2018, manager001, /orgs/2/sites/site003 g, managerUser2018, manager001, /orgs/2/sites/site004 g, managerUser2018, manager001, /orgs/2/sites/site005 g, managerUser2019, manager001, /orgs/2/sites/site001 g, managerUser2019, manager001, /orgs/2/sites/site002 g, managerUser2019, manager001, /orgs/2/sites/site003 g, managerUser2019, manager001, /orgs/2/sites/site004 g, managerUser2019, manager001, /orgs/2/sites/site005 g, managerUser2020, manager001, /orgs/2/sites/site001 g, managerUser2020, manager001, /orgs/2/sites/site002 g, managerUser2020, manager001, /orgs/2/sites/site003 g, managerUser2020, manager001, /orgs/2/sites/site004 g, managerUser2020, manager001, /orgs/2/sites/site005 g, managerUser2021, manager001, /orgs/2/sites/site001 g, managerUser2021, manager001, /orgs/2/sites/site002 g, managerUser2021, manager001, /orgs/2/sites/site003 g, managerUser2021, manager001, /orgs/2/sites/site004 g, managerUser2021, manager001, /orgs/2/sites/site005 g, managerUser2022, manager001, /orgs/2/sites/site001 g, managerUser2022, manager001, /orgs/2/sites/site002 g, managerUser2022, manager001, /orgs/2/sites/site003 g, managerUser2022, manager001, /orgs/2/sites/site004 g, managerUser2022, manager001, /orgs/2/sites/site005 g, managerUser2023, manager001, /orgs/2/sites/site001 g, managerUser2023, manager001, /orgs/2/sites/site002 g, managerUser2023, manager001, /orgs/2/sites/site003 g, managerUser2023, manager001, /orgs/2/sites/site004 g, managerUser2023, manager001, /orgs/2/sites/site005 g, managerUser2024, manager001, /orgs/2/sites/site001 g, managerUser2024, manager001, /orgs/2/sites/site002 g, managerUser2024, manager001, /orgs/2/sites/site003 g, managerUser2024, manager001, /orgs/2/sites/site004 g, managerUser2024, manager001, /orgs/2/sites/site005 g, managerUser2025, manager001, /orgs/2/sites/site001 g, managerUser2025, manager001, /orgs/2/sites/site002 g, managerUser2025, manager001, /orgs/2/sites/site003 g, managerUser2025, manager001, /orgs/2/sites/site004 g, managerUser2025, manager001, /orgs/2/sites/site005 g, managerUser2026, manager001, /orgs/2/sites/site001 g, managerUser2026, manager001, /orgs/2/sites/site002 g, managerUser2026, manager001, /orgs/2/sites/site003 g, managerUser2026, manager001, /orgs/2/sites/site004 g, managerUser2026, manager001, /orgs/2/sites/site005 g, managerUser2027, manager001, /orgs/2/sites/site001 g, managerUser2027, manager001, /orgs/2/sites/site002 g, managerUser2027, manager001, /orgs/2/sites/site003 g, managerUser2027, manager001, /orgs/2/sites/site004 g, managerUser2027, manager001, /orgs/2/sites/site005 g, managerUser2028, manager001, /orgs/2/sites/site001 g, managerUser2028, manager001, /orgs/2/sites/site002 g, managerUser2028, manager001, /orgs/2/sites/site003 g, managerUser2028, manager001, /orgs/2/sites/site004 g, managerUser2028, manager001, /orgs/2/sites/site005 g, managerUser2029, manager001, /orgs/2/sites/site001 g, managerUser2029, manager001, /orgs/2/sites/site002 g, managerUser2029, manager001, /orgs/2/sites/site003 g, managerUser2029, manager001, /orgs/2/sites/site004 g, managerUser2029, manager001, /orgs/2/sites/site005 g, managerUser2030, manager001, /orgs/2/sites/site001 g, managerUser2030, manager001, /orgs/2/sites/site002 g, managerUser2030, manager001, /orgs/2/sites/site003 g, managerUser2030, manager001, /orgs/2/sites/site004 g, managerUser2030, manager001, /orgs/2/sites/site005 g, managerUser2031, manager001, /orgs/2/sites/site001 g, managerUser2031, manager001, /orgs/2/sites/site002 g, managerUser2031, manager001, /orgs/2/sites/site003 g, managerUser2031, manager001, /orgs/2/sites/site004 g, managerUser2031, manager001, /orgs/2/sites/site005 g, managerUser2032, manager001, /orgs/2/sites/site001 g, managerUser2032, manager001, /orgs/2/sites/site002 g, managerUser2032, manager001, /orgs/2/sites/site003 g, managerUser2032, manager001, /orgs/2/sites/site004 g, managerUser2032, manager001, /orgs/2/sites/site005 g, managerUser2033, manager001, /orgs/2/sites/site001 g, managerUser2033, manager001, /orgs/2/sites/site002 g, managerUser2033, manager001, /orgs/2/sites/site003 g, managerUser2033, manager001, /orgs/2/sites/site004 g, managerUser2033, manager001, /orgs/2/sites/site005 g, managerUser2034, manager001, /orgs/2/sites/site001 g, managerUser2034, manager001, /orgs/2/sites/site002 g, managerUser2034, manager001, /orgs/2/sites/site003 g, managerUser2034, manager001, /orgs/2/sites/site004 g, managerUser2034, manager001, /orgs/2/sites/site005 g, managerUser2035, manager001, /orgs/2/sites/site001 g, managerUser2035, manager001, /orgs/2/sites/site002 g, managerUser2035, manager001, /orgs/2/sites/site003 g, managerUser2035, manager001, /orgs/2/sites/site004 g, managerUser2035, manager001, /orgs/2/sites/site005 g, managerUser2036, manager001, /orgs/2/sites/site001 g, managerUser2036, manager001, /orgs/2/sites/site002 g, managerUser2036, manager001, /orgs/2/sites/site003 g, managerUser2036, manager001, /orgs/2/sites/site004 g, managerUser2036, manager001, /orgs/2/sites/site005 g, managerUser2037, manager001, /orgs/2/sites/site001 g, managerUser2037, manager001, /orgs/2/sites/site002 g, managerUser2037, manager001, /orgs/2/sites/site003 g, managerUser2037, manager001, /orgs/2/sites/site004 g, managerUser2037, manager001, /orgs/2/sites/site005 g, managerUser2038, manager001, /orgs/2/sites/site001 g, managerUser2038, manager001, /orgs/2/sites/site002 g, managerUser2038, manager001, /orgs/2/sites/site003 g, managerUser2038, manager001, /orgs/2/sites/site004 g, managerUser2038, manager001, /orgs/2/sites/site005 g, managerUser2039, manager001, /orgs/2/sites/site001 g, managerUser2039, manager001, /orgs/2/sites/site002 g, managerUser2039, manager001, /orgs/2/sites/site003 g, managerUser2039, manager001, /orgs/2/sites/site004 g, managerUser2039, manager001, /orgs/2/sites/site005 g, managerUser2040, manager001, /orgs/2/sites/site001 g, managerUser2040, manager001, /orgs/2/sites/site002 g, managerUser2040, manager001, /orgs/2/sites/site003 g, managerUser2040, manager001, /orgs/2/sites/site004 g, managerUser2040, manager001, /orgs/2/sites/site005 g, managerUser2041, manager001, /orgs/2/sites/site001 g, managerUser2041, manager001, /orgs/2/sites/site002 g, managerUser2041, manager001, /orgs/2/sites/site003 g, managerUser2041, manager001, /orgs/2/sites/site004 g, managerUser2041, manager001, /orgs/2/sites/site005 g, managerUser2042, manager001, /orgs/2/sites/site001 g, managerUser2042, manager001, /orgs/2/sites/site002 g, managerUser2042, manager001, /orgs/2/sites/site003 g, managerUser2042, manager001, /orgs/2/sites/site004 g, managerUser2042, manager001, /orgs/2/sites/site005 g, managerUser2043, manager001, /orgs/2/sites/site001 g, managerUser2043, manager001, /orgs/2/sites/site002 g, managerUser2043, manager001, /orgs/2/sites/site003 g, managerUser2043, manager001, /orgs/2/sites/site004 g, managerUser2043, manager001, /orgs/2/sites/site005 g, managerUser2044, manager001, /orgs/2/sites/site001 g, managerUser2044, manager001, /orgs/2/sites/site002 g, managerUser2044, manager001, /orgs/2/sites/site003 g, managerUser2044, manager001, /orgs/2/sites/site004 g, managerUser2044, manager001, /orgs/2/sites/site005 g, managerUser2045, manager001, /orgs/2/sites/site001 g, managerUser2045, manager001, /orgs/2/sites/site002 g, managerUser2045, manager001, /orgs/2/sites/site003 g, managerUser2045, manager001, /orgs/2/sites/site004 g, managerUser2045, manager001, /orgs/2/sites/site005 g, managerUser2046, manager001, /orgs/2/sites/site001 g, managerUser2046, manager001, /orgs/2/sites/site002 g, managerUser2046, manager001, /orgs/2/sites/site003 g, managerUser2046, manager001, /orgs/2/sites/site004 g, managerUser2046, manager001, /orgs/2/sites/site005 g, managerUser2047, manager001, /orgs/2/sites/site001 g, managerUser2047, manager001, /orgs/2/sites/site002 g, managerUser2047, manager001, /orgs/2/sites/site003 g, managerUser2047, manager001, /orgs/2/sites/site004 g, managerUser2047, manager001, /orgs/2/sites/site005 g, managerUser2048, manager001, /orgs/2/sites/site001 g, managerUser2048, manager001, /orgs/2/sites/site002 g, managerUser2048, manager001, /orgs/2/sites/site003 g, managerUser2048, manager001, /orgs/2/sites/site004 g, managerUser2048, manager001, /orgs/2/sites/site005 g, managerUser2049, manager001, /orgs/2/sites/site001 g, managerUser2049, manager001, /orgs/2/sites/site002 g, managerUser2049, manager001, /orgs/2/sites/site003 g, managerUser2049, manager001, /orgs/2/sites/site004 g, managerUser2049, manager001, /orgs/2/sites/site005 g, managerUser2050, manager001, /orgs/2/sites/site001 g, managerUser2050, manager001, /orgs/2/sites/site002 g, managerUser2050, manager001, /orgs/2/sites/site003 g, managerUser2050, manager001, /orgs/2/sites/site004 g, managerUser2050, manager001, /orgs/2/sites/site005 # Group - customer001, / org2 g, customerUser1001, customer001, /orgs/2/sites/site001 g, customerUser1001, customer001, /orgs/2/sites/site002 g, customerUser1001, customer001, /orgs/2/sites/site003 g, customerUser1001, customer001, /orgs/2/sites/site004 g, customerUser1001, customer001, /orgs/2/sites/site005 g, customerUser1001, customer001, /orgs/2/sites/site001 g, customerUser1001, customer001, /orgs/2/sites/site002 g, customerUser1001, customer001, /orgs/2/sites/site003 g, customerUser1001, customer001, /orgs/2/sites/site004 g, customerUser1001, customer001, /orgs/2/sites/site005 g, customerUser1003, customer001, /orgs/2/sites/site001 g, customerUser1003, customer001, /orgs/2/sites/site002 g, customerUser1003, customer001, /orgs/2/sites/site003 g, customerUser1003, customer001, /orgs/2/sites/site004 g, customerUser1003, customer001, /orgs/2/sites/site005 g, customerUser1004, customer001, /orgs/2/sites/site001 g, customerUser1004, customer001, /orgs/2/sites/site002 g, customerUser1004, customer001, /orgs/2/sites/site003 g, customerUser1004, customer001, /orgs/2/sites/site004 g, customerUser1004, customer001, /orgs/2/sites/site005 g, customerUser1005, customer001, /orgs/2/sites/site001 g, customerUser1005, customer001, /orgs/2/sites/site002 g, customerUser1005, customer001, /orgs/2/sites/site003 g, customerUser1005, customer001, /orgs/2/sites/site004 g, customerUser1005, customer001, /orgs/2/sites/site005 g, customerUser1006, customer001, /orgs/2/sites/site001 g, customerUser1006, customer001, /orgs/2/sites/site002 g, customerUser1006, customer001, /orgs/2/sites/site003 g, customerUser1006, customer001, /orgs/2/sites/site004 g, customerUser1006, customer001, /orgs/2/sites/site005 g, customerUser1007, customer001, /orgs/2/sites/site001 g, customerUser1007, customer001, /orgs/2/sites/site002 g, customerUser1007, customer001, /orgs/2/sites/site003 g, customerUser1007, customer001, /orgs/2/sites/site004 g, customerUser1007, customer001, /orgs/2/sites/site005 g, customerUser1008, customer001, /orgs/2/sites/site001 g, customerUser1008, customer001, /orgs/2/sites/site002 g, customerUser1008, customer001, /orgs/2/sites/site003 g, customerUser1008, customer001, /orgs/2/sites/site004 g, customerUser1008, customer001, /orgs/2/sites/site005 g, customerUser1009, customer001, /orgs/2/sites/site001 g, customerUser1009, customer001, /orgs/2/sites/site002 g, customerUser1009, customer001, /orgs/2/sites/site003 g, customerUser1009, customer001, /orgs/2/sites/site004 g, customerUser1009, customer001, /orgs/2/sites/site005 g, customerUser1010, customer001, /orgs/2/sites/site001 g, customerUser1010, customer001, /orgs/2/sites/site002 g, customerUser1010, customer001, /orgs/2/sites/site003 g, customerUser1010, customer001, /orgs/2/sites/site004 g, customerUser1010, customer001, /orgs/2/sites/site005 g, customerUser1011, customer001, /orgs/2/sites/site001 g, customerUser1011, customer001, /orgs/2/sites/site002 g, customerUser1011, customer001, /orgs/2/sites/site003 g, customerUser1011, customer001, /orgs/2/sites/site004 g, customerUser1011, customer001, /orgs/2/sites/site005 g, customerUser1012, customer001, /orgs/2/sites/site001 g, customerUser1012, customer001, /orgs/2/sites/site002 g, customerUser1012, customer001, /orgs/2/sites/site003 g, customerUser1012, customer001, /orgs/2/sites/site004 g, customerUser1012, customer001, /orgs/2/sites/site005 g, customerUser1013, customer001, /orgs/2/sites/site001 g, customerUser1013, customer001, /orgs/2/sites/site002 g, customerUser1013, customer001, /orgs/2/sites/site003 g, customerUser1013, customer001, /orgs/2/sites/site004 g, customerUser1013, customer001, /orgs/2/sites/site005 g, customerUser1014, customer001, /orgs/2/sites/site001 g, customerUser1014, customer001, /orgs/2/sites/site002 g, customerUser1014, customer001, /orgs/2/sites/site003 g, customerUser1014, customer001, /orgs/2/sites/site004 g, customerUser1014, customer001, /orgs/2/sites/site005 g, customerUser1015, customer001, /orgs/2/sites/site001 g, customerUser1015, customer001, /orgs/2/sites/site002 g, customerUser1015, customer001, /orgs/2/sites/site003 g, customerUser1015, customer001, /orgs/2/sites/site004 g, customerUser1015, customer001, /orgs/2/sites/site005 g, customerUser1016, customer001, /orgs/2/sites/site001 g, customerUser1016, customer001, /orgs/2/sites/site002 g, customerUser1016, customer001, /orgs/2/sites/site003 g, customerUser1016, customer001, /orgs/2/sites/site004 g, customerUser1016, customer001, /orgs/2/sites/site005 g, customerUser1017, customer001, /orgs/2/sites/site001 g, customerUser1017, customer001, /orgs/2/sites/site002 g, customerUser1017, customer001, /orgs/2/sites/site003 g, customerUser1017, customer001, /orgs/2/sites/site004 g, customerUser1017, customer001, /orgs/2/sites/site005 g, customerUser1018, customer001, /orgs/2/sites/site001 g, customerUser1018, customer001, /orgs/2/sites/site002 g, customerUser1018, customer001, /orgs/2/sites/site003 g, customerUser1018, customer001, /orgs/2/sites/site004 g, customerUser1018, customer001, /orgs/2/sites/site005 g, customerUser1019, customer001, /orgs/2/sites/site001 g, customerUser1019, customer001, /orgs/2/sites/site002 g, customerUser1019, customer001, /orgs/2/sites/site003 g, customerUser1019, customer001, /orgs/2/sites/site004 g, customerUser1019, customer001, /orgs/2/sites/site005 g, customerUser1020, customer001, /orgs/2/sites/site001 g, customerUser1020, customer001, /orgs/2/sites/site002 g, customerUser1020, customer001, /orgs/2/sites/site003 g, customerUser1020, customer001, /orgs/2/sites/site004 g, customerUser1020, customer001, /orgs/2/sites/site005 g, customerUser1021, customer001, /orgs/2/sites/site001 g, customerUser1021, customer001, /orgs/2/sites/site002 g, customerUser1021, customer001, /orgs/2/sites/site003 g, customerUser1021, customer001, /orgs/2/sites/site004 g, customerUser1021, customer001, /orgs/2/sites/site005 g, customerUser1022, customer001, /orgs/2/sites/site001 g, customerUser1022, customer001, /orgs/2/sites/site002 g, customerUser1022, customer001, /orgs/2/sites/site003 g, customerUser1022, customer001, /orgs/2/sites/site004 g, customerUser1022, customer001, /orgs/2/sites/site005 g, customerUser1023, customer001, /orgs/2/sites/site001 g, customerUser1023, customer001, /orgs/2/sites/site002 g, customerUser1023, customer001, /orgs/2/sites/site003 g, customerUser1023, customer001, /orgs/2/sites/site004 g, customerUser1023, customer001, /orgs/2/sites/site005 g, customerUser1024, customer001, /orgs/2/sites/site001 g, customerUser1024, customer001, /orgs/2/sites/site002 g, customerUser1024, customer001, /orgs/2/sites/site003 g, customerUser1024, customer001, /orgs/2/sites/site004 g, customerUser1024, customer001, /orgs/2/sites/site005 g, customerUser1025, customer001, /orgs/2/sites/site001 g, customerUser1025, customer001, /orgs/2/sites/site002 g, customerUser1025, customer001, /orgs/2/sites/site003 g, customerUser1025, customer001, /orgs/2/sites/site004 g, customerUser1025, customer001, /orgs/2/sites/site005 g, customerUser1026, customer001, /orgs/2/sites/site001 g, customerUser1026, customer001, /orgs/2/sites/site002 g, customerUser1026, customer001, /orgs/2/sites/site003 g, customerUser1026, customer001, /orgs/2/sites/site004 g, customerUser1026, customer001, /orgs/2/sites/site005 g, customerUser1027, customer001, /orgs/2/sites/site001 g, customerUser1027, customer001, /orgs/2/sites/site002 g, customerUser1027, customer001, /orgs/2/sites/site003 g, customerUser1027, customer001, /orgs/2/sites/site004 g, customerUser1027, customer001, /orgs/2/sites/site005 g, customerUser1028, customer001, /orgs/2/sites/site001 g, customerUser1028, customer001, /orgs/2/sites/site002 g, customerUser1028, customer001, /orgs/2/sites/site003 g, customerUser1028, customer001, /orgs/2/sites/site004 g, customerUser1028, customer001, /orgs/2/sites/site005 g, customerUser1029, customer001, /orgs/2/sites/site001 g, customerUser1029, customer001, /orgs/2/sites/site002 g, customerUser1029, customer001, /orgs/2/sites/site003 g, customerUser1029, customer001, /orgs/2/sites/site004 g, customerUser1029, customer001, /orgs/2/sites/site005 g, customerUser1030, customer001, /orgs/2/sites/site001 g, customerUser1030, customer001, /orgs/2/sites/site002 g, customerUser1030, customer001, /orgs/2/sites/site003 g, customerUser1030, customer001, /orgs/2/sites/site004 g, customerUser1030, customer001, /orgs/2/sites/site005 g, customerUser1031, customer001, /orgs/2/sites/site001 g, customerUser1031, customer001, /orgs/2/sites/site002 g, customerUser1031, customer001, /orgs/2/sites/site003 g, customerUser1031, customer001, /orgs/2/sites/site004 g, customerUser1031, customer001, /orgs/2/sites/site005 g, customerUser1032, customer001, /orgs/2/sites/site001 g, customerUser1032, customer001, /orgs/2/sites/site002 g, customerUser1032, customer001, /orgs/2/sites/site003 g, customerUser1032, customer001, /orgs/2/sites/site004 g, customerUser1032, customer001, /orgs/2/sites/site005 g, customerUser1033, customer001, /orgs/2/sites/site001 g, customerUser1033, customer001, /orgs/2/sites/site002 g, customerUser1033, customer001, /orgs/2/sites/site003 g, customerUser1033, customer001, /orgs/2/sites/site004 g, customerUser1033, customer001, /orgs/2/sites/site005 g, customerUser1034, customer001, /orgs/2/sites/site001 g, customerUser1034, customer001, /orgs/2/sites/site002 g, customerUser1034, customer001, /orgs/2/sites/site003 g, customerUser1034, customer001, /orgs/2/sites/site004 g, customerUser1034, customer001, /orgs/2/sites/site005 g, customerUser1035, customer001, /orgs/2/sites/site001 g, customerUser1035, customer001, /orgs/2/sites/site002 g, customerUser1035, customer001, /orgs/2/sites/site003 g, customerUser1035, customer001, /orgs/2/sites/site004 g, customerUser1035, customer001, /orgs/2/sites/site005 g, customerUser1036, customer001, /orgs/2/sites/site001 g, customerUser1036, customer001, /orgs/2/sites/site002 g, customerUser1036, customer001, /orgs/2/sites/site003 g, customerUser1036, customer001, /orgs/2/sites/site004 g, customerUser1036, customer001, /orgs/2/sites/site005 g, customerUser1037, customer001, /orgs/2/sites/site001 g, customerUser1037, customer001, /orgs/2/sites/site002 g, customerUser1037, customer001, /orgs/2/sites/site003 g, customerUser1037, customer001, /orgs/2/sites/site004 g, customerUser1037, customer001, /orgs/2/sites/site005 g, customerUser1038, customer001, /orgs/2/sites/site001 g, customerUser1038, customer001, /orgs/2/sites/site002 g, customerUser1038, customer001, /orgs/2/sites/site003 g, customerUser1038, customer001, /orgs/2/sites/site004 g, customerUser1038, customer001, /orgs/2/sites/site005 g, customerUser1039, customer001, /orgs/2/sites/site001 g, customerUser1039, customer001, /orgs/2/sites/site002 g, customerUser1039, customer001, /orgs/2/sites/site003 g, customerUser1039, customer001, /orgs/2/sites/site004 g, customerUser1039, customer001, /orgs/2/sites/site005 g, customerUser1040, customer001, /orgs/2/sites/site001 g, customerUser1040, customer001, /orgs/2/sites/site002 g, customerUser1040, customer001, /orgs/2/sites/site003 g, customerUser1040, customer001, /orgs/2/sites/site004 g, customerUser1040, customer001, /orgs/2/sites/site005 g, customerUser1041, customer001, /orgs/2/sites/site001 g, customerUser1041, customer001, /orgs/2/sites/site002 g, customerUser1041, customer001, /orgs/2/sites/site003 g, customerUser1041, customer001, /orgs/2/sites/site004 g, customerUser1041, customer001, /orgs/2/sites/site005 g, customerUser1042, customer001, /orgs/2/sites/site001 g, customerUser1042, customer001, /orgs/2/sites/site002 g, customerUser1042, customer001, /orgs/2/sites/site003 g, customerUser1042, customer001, /orgs/2/sites/site004 g, customerUser1042, customer001, /orgs/2/sites/site005 g, customerUser1043, customer001, /orgs/2/sites/site001 g, customerUser1043, customer001, /orgs/2/sites/site002 g, customerUser1043, customer001, /orgs/2/sites/site003 g, customerUser1043, customer001, /orgs/2/sites/site004 g, customerUser1043, customer001, /orgs/2/sites/site005 g, customerUser1044, customer001, /orgs/2/sites/site001 g, customerUser1044, customer001, /orgs/2/sites/site002 g, customerUser1044, customer001, /orgs/2/sites/site003 g, customerUser1044, customer001, /orgs/2/sites/site004 g, customerUser1044, customer001, /orgs/2/sites/site005 g, customerUser1045, customer001, /orgs/2/sites/site001 g, customerUser1045, customer001, /orgs/2/sites/site002 g, customerUser1045, customer001, /orgs/2/sites/site003 g, customerUser1045, customer001, /orgs/2/sites/site004 g, customerUser1045, customer001, /orgs/2/sites/site005 g, customerUser1046, customer001, /orgs/2/sites/site001 g, customerUser1046, customer001, /orgs/2/sites/site002 g, customerUser1046, customer001, /orgs/2/sites/site003 g, customerUser1046, customer001, /orgs/2/sites/site004 g, customerUser1046, customer001, /orgs/2/sites/site005 g, customerUser1047, customer001, /orgs/2/sites/site001 g, customerUser1047, customer001, /orgs/2/sites/site002 g, customerUser1047, customer001, /orgs/2/sites/site003 g, customerUser1047, customer001, /orgs/2/sites/site004 g, customerUser1047, customer001, /orgs/2/sites/site005 g, customerUser1048, customer001, /orgs/2/sites/site001 g, customerUser1048, customer001, /orgs/2/sites/site002 g, customerUser1048, customer001, /orgs/2/sites/site003 g, customerUser1048, customer001, /orgs/2/sites/site004 g, customerUser1048, customer001, /orgs/2/sites/site005 g, customerUser1049, customer001, /orgs/2/sites/site001 g, customerUser1049, customer001, /orgs/2/sites/site002 g, customerUser1049, customer001, /orgs/2/sites/site003 g, customerUser1049, customer001, /orgs/2/sites/site004 g, customerUser1049, customer001, /orgs/2/sites/site005 g, customerUser1050, customer001, /orgs/2/sites/site001 g, customerUser1050, customer001, /orgs/2/sites/site002 g, customerUser1050, customer001, /orgs/2/sites/site003 g, customerUser1050, customer001, /orgs/2/sites/site004 g, customerUser1050, customer001, /orgs/2/sites/site005 # Group - customer001, / org2 g, customerUser2001, customer001, /orgs/2/sites/site001 g, customerUser2001, customer001, /orgs/2/sites/site002 g, customerUser2001, customer001, /orgs/2/sites/site003 g, customerUser2001, customer001, /orgs/2/sites/site004 g, customerUser2001, customer001, /orgs/2/sites/site005 g, customerUser2001, customer001, /orgs/2/sites/site001 g, customerUser2001, customer001, /orgs/2/sites/site002 g, customerUser2001, customer001, /orgs/2/sites/site003 g, customerUser2001, customer001, /orgs/2/sites/site004 g, customerUser2001, customer001, /orgs/2/sites/site005 g, customerUser2003, customer001, /orgs/2/sites/site001 g, customerUser2003, customer001, /orgs/2/sites/site002 g, customerUser2003, customer001, /orgs/2/sites/site003 g, customerUser2003, customer001, /orgs/2/sites/site004 g, customerUser2003, customer001, /orgs/2/sites/site005 g, customerUser2004, customer001, /orgs/2/sites/site001 g, customerUser2004, customer001, /orgs/2/sites/site002 g, customerUser2004, customer001, /orgs/2/sites/site003 g, customerUser2004, customer001, /orgs/2/sites/site004 g, customerUser2004, customer001, /orgs/2/sites/site005 g, customerUser2005, customer001, /orgs/2/sites/site001 g, customerUser2005, customer001, /orgs/2/sites/site002 g, customerUser2005, customer001, /orgs/2/sites/site003 g, customerUser2005, customer001, /orgs/2/sites/site004 g, customerUser2005, customer001, /orgs/2/sites/site005 g, customerUser2006, customer001, /orgs/2/sites/site001 g, customerUser2006, customer001, /orgs/2/sites/site002 g, customerUser2006, customer001, /orgs/2/sites/site003 g, customerUser2006, customer001, /orgs/2/sites/site004 g, customerUser2006, customer001, /orgs/2/sites/site005 g, customerUser2007, customer001, /orgs/2/sites/site001 g, customerUser2007, customer001, /orgs/2/sites/site002 g, customerUser2007, customer001, /orgs/2/sites/site003 g, customerUser2007, customer001, /orgs/2/sites/site004 g, customerUser2007, customer001, /orgs/2/sites/site005 g, customerUser2008, customer001, /orgs/2/sites/site001 g, customerUser2008, customer001, /orgs/2/sites/site002 g, customerUser2008, customer001, /orgs/2/sites/site003 g, customerUser2008, customer001, /orgs/2/sites/site004 g, customerUser2008, customer001, /orgs/2/sites/site005 g, customerUser2009, customer001, /orgs/2/sites/site001 g, customerUser2009, customer001, /orgs/2/sites/site002 g, customerUser2009, customer001, /orgs/2/sites/site003 g, customerUser2009, customer001, /orgs/2/sites/site004 g, customerUser2009, customer001, /orgs/2/sites/site005 g, customerUser2010, customer001, /orgs/2/sites/site001 g, customerUser2010, customer001, /orgs/2/sites/site002 g, customerUser2010, customer001, /orgs/2/sites/site003 g, customerUser2010, customer001, /orgs/2/sites/site004 g, customerUser2010, customer001, /orgs/2/sites/site005 g, customerUser2011, customer001, /orgs/2/sites/site001 g, customerUser2011, customer001, /orgs/2/sites/site002 g, customerUser2011, customer001, /orgs/2/sites/site003 g, customerUser2011, customer001, /orgs/2/sites/site004 g, customerUser2011, customer001, /orgs/2/sites/site005 g, customerUser2012, customer001, /orgs/2/sites/site001 g, customerUser2012, customer001, /orgs/2/sites/site002 g, customerUser2012, customer001, /orgs/2/sites/site003 g, customerUser2012, customer001, /orgs/2/sites/site004 g, customerUser2012, customer001, /orgs/2/sites/site005 g, customerUser2013, customer001, /orgs/2/sites/site001 g, customerUser2013, customer001, /orgs/2/sites/site002 g, customerUser2013, customer001, /orgs/2/sites/site003 g, customerUser2013, customer001, /orgs/2/sites/site004 g, customerUser2013, customer001, /orgs/2/sites/site005 g, customerUser2014, customer001, /orgs/2/sites/site001 g, customerUser2014, customer001, /orgs/2/sites/site002 g, customerUser2014, customer001, /orgs/2/sites/site003 g, customerUser2014, customer001, /orgs/2/sites/site004 g, customerUser2014, customer001, /orgs/2/sites/site005 g, customerUser2015, customer001, /orgs/2/sites/site001 g, customerUser2015, customer001, /orgs/2/sites/site002 g, customerUser2015, customer001, /orgs/2/sites/site003 g, customerUser2015, customer001, /orgs/2/sites/site004 g, customerUser2015, customer001, /orgs/2/sites/site005 g, customerUser2016, customer001, /orgs/2/sites/site001 g, customerUser2016, customer001, /orgs/2/sites/site002 g, customerUser2016, customer001, /orgs/2/sites/site003 g, customerUser2016, customer001, /orgs/2/sites/site004 g, customerUser2016, customer001, /orgs/2/sites/site005 g, customerUser2017, customer001, /orgs/2/sites/site001 g, customerUser2017, customer001, /orgs/2/sites/site002 g, customerUser2017, customer001, /orgs/2/sites/site003 g, customerUser2017, customer001, /orgs/2/sites/site004 g, customerUser2017, customer001, /orgs/2/sites/site005 g, customerUser2018, customer001, /orgs/2/sites/site001 g, customerUser2018, customer001, /orgs/2/sites/site002 g, customerUser2018, customer001, /orgs/2/sites/site003 g, customerUser2018, customer001, /orgs/2/sites/site004 g, customerUser2018, customer001, /orgs/2/sites/site005 g, customerUser2019, customer001, /orgs/2/sites/site001 g, customerUser2019, customer001, /orgs/2/sites/site002 g, customerUser2019, customer001, /orgs/2/sites/site003 g, customerUser2019, customer001, /orgs/2/sites/site004 g, customerUser2019, customer001, /orgs/2/sites/site005 g, customerUser2020, customer001, /orgs/2/sites/site001 g, customerUser2020, customer001, /orgs/2/sites/site002 g, customerUser2020, customer001, /orgs/2/sites/site003 g, customerUser2020, customer001, /orgs/2/sites/site004 g, customerUser2020, customer001, /orgs/2/sites/site005 g, customerUser2021, customer001, /orgs/2/sites/site001 g, customerUser2021, customer001, /orgs/2/sites/site002 g, customerUser2021, customer001, /orgs/2/sites/site003 g, customerUser2021, customer001, /orgs/2/sites/site004 g, customerUser2021, customer001, /orgs/2/sites/site005 g, customerUser2022, customer001, /orgs/2/sites/site001 g, customerUser2022, customer001, /orgs/2/sites/site002 g, customerUser2022, customer001, /orgs/2/sites/site003 g, customerUser2022, customer001, /orgs/2/sites/site004 g, customerUser2022, customer001, /orgs/2/sites/site005 g, customerUser2023, customer001, /orgs/2/sites/site001 g, customerUser2023, customer001, /orgs/2/sites/site002 g, customerUser2023, customer001, /orgs/2/sites/site003 g, customerUser2023, customer001, /orgs/2/sites/site004 g, customerUser2023, customer001, /orgs/2/sites/site005 g, customerUser2024, customer001, /orgs/2/sites/site001 g, customerUser2024, customer001, /orgs/2/sites/site002 g, customerUser2024, customer001, /orgs/2/sites/site003 g, customerUser2024, customer001, /orgs/2/sites/site004 g, customerUser2024, customer001, /orgs/2/sites/site005 g, customerUser2025, customer001, /orgs/2/sites/site001 g, customerUser2025, customer001, /orgs/2/sites/site002 g, customerUser2025, customer001, /orgs/2/sites/site003 g, customerUser2025, customer001, /orgs/2/sites/site004 g, customerUser2025, customer001, /orgs/2/sites/site005 g, customerUser2026, customer001, /orgs/2/sites/site001 g, customerUser2026, customer001, /orgs/2/sites/site002 g, customerUser2026, customer001, /orgs/2/sites/site003 g, customerUser2026, customer001, /orgs/2/sites/site004 g, customerUser2026, customer001, /orgs/2/sites/site005 g, customerUser2027, customer001, /orgs/2/sites/site001 g, customerUser2027, customer001, /orgs/2/sites/site002 g, customerUser2027, customer001, /orgs/2/sites/site003 g, customerUser2027, customer001, /orgs/2/sites/site004 g, customerUser2027, customer001, /orgs/2/sites/site005 g, customerUser2028, customer001, /orgs/2/sites/site001 g, customerUser2028, customer001, /orgs/2/sites/site002 g, customerUser2028, customer001, /orgs/2/sites/site003 g, customerUser2028, customer001, /orgs/2/sites/site004 g, customerUser2028, customer001, /orgs/2/sites/site005 g, customerUser2029, customer001, /orgs/2/sites/site001 g, customerUser2029, customer001, /orgs/2/sites/site002 g, customerUser2029, customer001, /orgs/2/sites/site003 g, customerUser2029, customer001, /orgs/2/sites/site004 g, customerUser2029, customer001, /orgs/2/sites/site005 g, customerUser2030, customer001, /orgs/2/sites/site001 g, customerUser2030, customer001, /orgs/2/sites/site002 g, customerUser2030, customer001, /orgs/2/sites/site003 g, customerUser2030, customer001, /orgs/2/sites/site004 g, customerUser2030, customer001, /orgs/2/sites/site005 g, customerUser2031, customer001, /orgs/2/sites/site001 g, customerUser2031, customer001, /orgs/2/sites/site002 g, customerUser2031, customer001, /orgs/2/sites/site003 g, customerUser2031, customer001, /orgs/2/sites/site004 g, customerUser2031, customer001, /orgs/2/sites/site005 g, customerUser2032, customer001, /orgs/2/sites/site001 g, customerUser2032, customer001, /orgs/2/sites/site002 g, customerUser2032, customer001, /orgs/2/sites/site003 g, customerUser2032, customer001, /orgs/2/sites/site004 g, customerUser2032, customer001, /orgs/2/sites/site005 g, customerUser2033, customer001, /orgs/2/sites/site001 g, customerUser2033, customer001, /orgs/2/sites/site002 g, customerUser2033, customer001, /orgs/2/sites/site003 g, customerUser2033, customer001, /orgs/2/sites/site004 g, customerUser2033, customer001, /orgs/2/sites/site005 g, customerUser2034, customer001, /orgs/2/sites/site001 g, customerUser2034, customer001, /orgs/2/sites/site002 g, customerUser2034, customer001, /orgs/2/sites/site003 g, customerUser2034, customer001, /orgs/2/sites/site004 g, customerUser2034, customer001, /orgs/2/sites/site005 g, customerUser2035, customer001, /orgs/2/sites/site001 g, customerUser2035, customer001, /orgs/2/sites/site002 g, customerUser2035, customer001, /orgs/2/sites/site003 g, customerUser2035, customer001, /orgs/2/sites/site004 g, customerUser2035, customer001, /orgs/2/sites/site005 g, customerUser2036, customer001, /orgs/2/sites/site001 g, customerUser2036, customer001, /orgs/2/sites/site002 g, customerUser2036, customer001, /orgs/2/sites/site003 g, customerUser2036, customer001, /orgs/2/sites/site004 g, customerUser2036, customer001, /orgs/2/sites/site005 g, customerUser2037, customer001, /orgs/2/sites/site001 g, customerUser2037, customer001, /orgs/2/sites/site002 g, customerUser2037, customer001, /orgs/2/sites/site003 g, customerUser2037, customer001, /orgs/2/sites/site004 g, customerUser2037, customer001, /orgs/2/sites/site005 g, customerUser2038, customer001, /orgs/2/sites/site001 g, customerUser2038, customer001, /orgs/2/sites/site002 g, customerUser2038, customer001, /orgs/2/sites/site003 g, customerUser2038, customer001, /orgs/2/sites/site004 g, customerUser2038, customer001, /orgs/2/sites/site005 g, customerUser2039, customer001, /orgs/2/sites/site001 g, customerUser2039, customer001, /orgs/2/sites/site002 g, customerUser2039, customer001, /orgs/2/sites/site003 g, customerUser2039, customer001, /orgs/2/sites/site004 g, customerUser2039, customer001, /orgs/2/sites/site005 g, customerUser2040, customer001, /orgs/2/sites/site001 g, customerUser2040, customer001, /orgs/2/sites/site002 g, customerUser2040, customer001, /orgs/2/sites/site003 g, customerUser2040, customer001, /orgs/2/sites/site004 g, customerUser2040, customer001, /orgs/2/sites/site005 g, customerUser2041, customer001, /orgs/2/sites/site001 g, customerUser2041, customer001, /orgs/2/sites/site002 g, customerUser2041, customer001, /orgs/2/sites/site003 g, customerUser2041, customer001, /orgs/2/sites/site004 g, customerUser2041, customer001, /orgs/2/sites/site005 g, customerUser2042, customer001, /orgs/2/sites/site001 g, customerUser2042, customer001, /orgs/2/sites/site002 g, customerUser2042, customer001, /orgs/2/sites/site003 g, customerUser2042, customer001, /orgs/2/sites/site004 g, customerUser2042, customer001, /orgs/2/sites/site005 g, customerUser2043, customer001, /orgs/2/sites/site001 g, customerUser2043, customer001, /orgs/2/sites/site002 g, customerUser2043, customer001, /orgs/2/sites/site003 g, customerUser2043, customer001, /orgs/2/sites/site004 g, customerUser2043, customer001, /orgs/2/sites/site005 g, customerUser2044, customer001, /orgs/2/sites/site001 g, customerUser2044, customer001, /orgs/2/sites/site002 g, customerUser2044, customer001, /orgs/2/sites/site003 g, customerUser2044, customer001, /orgs/2/sites/site004 g, customerUser2044, customer001, /orgs/2/sites/site005 g, customerUser2045, customer001, /orgs/2/sites/site001 g, customerUser2045, customer001, /orgs/2/sites/site002 g, customerUser2045, customer001, /orgs/2/sites/site003 g, customerUser2045, customer001, /orgs/2/sites/site004 g, customerUser2045, customer001, /orgs/2/sites/site005 g, customerUser2046, customer001, /orgs/2/sites/site001 g, customerUser2046, customer001, /orgs/2/sites/site002 g, customerUser2046, customer001, /orgs/2/sites/site003 g, customerUser2046, customer001, /orgs/2/sites/site004 g, customerUser2046, customer001, /orgs/2/sites/site005 g, customerUser2047, customer001, /orgs/2/sites/site001 g, customerUser2047, customer001, /orgs/2/sites/site002 g, customerUser2047, customer001, /orgs/2/sites/site003 g, customerUser2047, customer001, /orgs/2/sites/site004 g, customerUser2047, customer001, /orgs/2/sites/site005 g, customerUser2048, customer001, /orgs/2/sites/site001 g, customerUser2048, customer001, /orgs/2/sites/site002 g, customerUser2048, customer001, /orgs/2/sites/site003 g, customerUser2048, customer001, /orgs/2/sites/site004 g, customerUser2048, customer001, /orgs/2/sites/site005 g, customerUser2049, customer001, /orgs/2/sites/site001 g, customerUser2049, customer001, /orgs/2/sites/site002 g, customerUser2049, customer001, /orgs/2/sites/site003 g, customerUser2049, customer001, /orgs/2/sites/site004 g, customerUser2049, customer001, /orgs/2/sites/site005 g, customerUser2050, customer001, /orgs/2/sites/site001 g, customerUser2050, customer001, /orgs/2/sites/site002 g, customerUser2050, customer001, /orgs/2/sites/site003 g, customerUser2050, customer001, /orgs/2/sites/site004 g, customerUser2050, customer001, /orgs/2/sites/site005examples/priority_indeterminate_policy.csv000064400000000045152475271650015264 0ustar00p, alice, data1, read, intdeterminateexamples/priority_model.conf000064400000000337152475271650012313 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_model_explicit.conf000064400000000351152475271650014210 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = priority, 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_model_explicit_customized.conf000064400000000404152475271650016455 0ustar00[request_definition] r = subject, obj, act [policy_definition] p = customized_priority, obj, act, eft, subject [role_definition] g = _, _ [policy_effect] e = priority(p.eft) || deny [matchers] m = g(r.subject, p.subject) && r.obj == p.obj && r.act == p.actexamples/priority_policy.csv000064400000000437152475271650012361 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/priority_policy_explicit.csv000064400000000507152475271650014260 0ustar00p, 10, data1_deny_group, data1, read, deny p, 10, data1_deny_group, data1, write, deny p, 10, data2_allow_group, data2, read, allow p, 10, data2_allow_group, data2, write, allow p, 1, alice, data1, write, allow p, 1, alice, data1, read, allow p, 1, bob, data2, read, deny g, bob, data2_allow_group g, alice, data1_deny_groupexamples/priority_policy_explicit_customized.csv000064400000000510152475271650016520 0ustar00p, 10, data1, read, deny, data1_deny_group p, 10, data1, write, deny, data1_deny_group p, 10, data2, read, allow, data2_allow_group p, 10, data2, write, allow, data2_allow_group p, 1, data1, write, allow, alice p, 1, data1, read, allow, alice p, 1, data2, read, deny, bob g, bob, data2_allow_group g, alice, data1_deny_group examples/rbac_model.conf000064400000000337152475271650011341 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_model_matcher_using_in_op_bracket.conf000064400000000377152475271650017134 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.act || r.obj in ['data2', 'data3'] examples/rbac_policy.csv000064400000000172152475271650011403 0ustar00p, alice, data1, read p, bob, data2, write p, data2_admin, data2, read p, data2_admin, data2, write g, alice, data2_adminexamples/rbac_with_all_pattern_model.conf000064400000000406152475271650014756 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 = r.sub == p.sub && g(r.obj, p.obj, r.dom) && r.dom == p.dom && r.act == p.act examples/rbac_with_all_pattern_policy.csv000064400000000146152475271650015024 0ustar00p, alice, domain1, book_group, read p, alice, domain2, book_group, write g, /book/:id, book_group, * examples/rbac_with_deny_model.conf000064400000000404152475271650013406 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.csv000064400000000263152475271650013456 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_different_types_of_roles_model.conf000064400000000415152475271650017533 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, dom, obj, act [role_definition] g = _, _, _, (_, _) g2 = _, _ [policy_effect] e = some(where (p.eft == allow)) [matchers] m = g(r.sub, p.sub, p.dom) && g2(r.obj, p.dom) && regexMatch(r.act, p.act) examples/rbac_with_different_types_of_roles_policy.csv000064400000000523152475271650017600 0ustar00p, role:owner, domain1, _, (read|write) p, role:developer, domain1, _, read p, role:owner, domain2, _, (read|write) p, role:developer, domain2, _, read g, alice, role:owner, domain1, _, _ g, bob, role:developer, domain2, _, 9999-12-30 00:00:00 g, carol, role:owner, domain2, _, 0000-01-02 00:00:00 g2, data1, domain1 g2, data2, domain2 examples/rbac_with_domain_pattern_model.conf000064400000000406152475271650015455 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.act examples/rbac_with_domain_pattern_model_and_keymatch_model.conf000064400000000415152475271650021344 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) && keyMatch(r.dom, p.dom) && r.obj == p.obj && r.act == p.actexamples/rbac_with_domain_pattern_model_and_keymatch_policy.csv000064400000000636152475271650021416 0ustar00p,perm1,*,data1,read p,perm2,*,data1,write p,perm3,*,data2,read p,perm4,*,data2,write g,adminrole,perm1,* g,adminrole,perm2,* g,adminrole,perm3,* g,adminrole,perm4,* g,readerrole,perm1,* g,readerrole,perm3,* g,admingroup, adminrole, * g,readergroup, readerrole, * g,usergroup4, readergroup, domain4 g,usergroup4, perm4, domain4 g,alice,admingroup,domain1 g,alice,readergroup,domain2 g,alice,readergroup,domain4examples/rbac_with_domain_pattern_policy.csv000064400000000251152475271650015520 0ustar00p, admin, domain1, data1, read p, admin, domain1, data1, write p, admin, domain2, data2, read p, admin, domain2, data2, write g, alice, admin, * g, bob, admin, domain2 examples/rbac_with_domain_temporal_roles_model.conf000064400000000415152475271650017027 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_domain_temporal_roles_policy.csv000064400000001737152475271650017104 0ustar00p, alice, domain1, data1, read p, alice, domain1, data1, write p, data2_admin, domain2, data2, read p, data2_admin, domain2, data2, write p, data3_admin, domain3, data3, read p, data3_admin, domain3, data3, write p, data4_admin, domain4, data4, read p, data4_admin, domain4, data4, write p, data5_admin, domain5, data5, read p, data5_admin, domain5, data5, write p, data6_admin, domain6, data6, read p, data6_admin, domain6, data6, write p, data7_admin, domain7, data7, read p, data7_admin, domain7, data7, write p, data8_admin, domain8, data8, read p, data8_admin, domain8, data8, write g, alice, data2_admin, domain2, 0000-01-01 00:00:00, 0000-01-02 00:00:00 g, alice, data3_admin, domain3, 0000-01-01 00:00:00, 9999-12-30 00:00:00 g, alice, data4_admin, domain4, _, _ g, alice, data5_admin, domain5, _, 9999-12-30 00:00:00 g, alice, data6_admin, domain6, _, 0000-01-02 00:00:00 g, alice, data7_admin, domain7, 0000-01-01 00:00:00, _ g, alice, data8_admin, domain8, 9999-12-30 00:00:00, _examples/rbac_with_domains_model.conf000064400000000405152475271650014102 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.csv000064400000000256152475271650014153 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_domains_policy2.csv000064400000000373152475271650014235 0ustar00p, admin, domain1, data1, read p, admin, domain1, data1, write p, admin, domain2, data2, read p, admin, domain2, data2, write p, user, domain3, data2, read g, alice, admin, domain1 g, alice, admin, domain2 g, bob, admin, domain2 g, bob, user, domain3 examples/rbac_with_hierarchy_policy.csv000064400000000331152475271650014471 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_hierarchy_with_domains_policy.csv000064400000000417152475271650017243 0ustar00p, role:reader, domain1, data1, read p, role:writer, domain1, data1, write p, alice, domain1, data2, read p, alice, domain2, data2, read g, role:global_admin, role:reader, domain1 g, role:global_admin, role:writer, domain1 g, alice, role:global_admin, domain1examples/rbac_with_not_deny_model.conf000064400000000345152475271650014272 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.act examples/rbac_with_pattern_model.conf000064400000000366152475271650014133 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) && regexMatch(r.act, p.act) examples/rbac_with_pattern_policy.csv000064400000000502152475271650014170 0ustar00p, alice, /pen/1, GET p, alice, /pen2/1, GET p, book_admin, book_group, GET p, pen_admin, pen_group, GET g, alice, book_admin g, bob, pen_admin g, cathy, /book/1/2/3/4/5 g, cathy, pen_admin g2, /book/*, book_group g2, /book/:id, book_group g2, /pen/:id, pen_group g2, /book2/{id}, book_group g2, /pen2/{id}, pen_groupexamples/rbac_with_resource_roles_model.conf000064400000000353152475271650015505 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.csv000064400000000231152475271650015545 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_groupexamples/rbac_with_temporal_roles_model.conf000064400000000347152475271650015504 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_with_temporal_roles_policy.csv000064400000001420152475271650015542 0ustar00p, alice, data1, read p, alice, data1, write p, data2_admin, data2, read p, data2_admin, data2, write p, data3_admin, data3, read p, data3_admin, data3, write p, data4_admin, data4, read p, data4_admin, data4, write p, data5_admin, data5, read p, data5_admin, data5, write p, data6_admin, data6, read p, data6_admin, data6, write p, data7_admin, data7, read p, data7_admin, data7, write p, data8_admin, data8, read p, data8_admin, data8, write g, alice, data2_admin, 0000-01-01 00:00:00, 0000-01-02 00:00:00 g, alice, data3_admin, 0000-01-01 00:00:00, 9999-12-30 00:00:00 g, alice, data4_admin, _, _ g, alice, data5_admin, _, 9999-12-30 00:00:00 g, alice, data6_admin, _, 0000-01-02 00:00:00 g, alice, data7_admin, 0000-01-01 00:00:00, _ g, alice, data8_admin, 9999-12-30 00:00:00, _examples/subject_priority_model.conf000064400000000346152475271650014032 0ustar00[request_definition] r = sub, obj, act [policy_definition] p = sub, obj, act, eft [role_definition] g = _, _ [policy_effect] e = subjectPriority(p.eft) || deny [matchers] m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.actexamples/subject_priority_model_with_domain.conf000064400000000477152475271650016421 0ustar00[request_definition] r = sub, obj, dom, act [policy_definition] # sub can't change position and must be first p = sub, obj, dom, act, eft [role_definition] g = _, _, _ [policy_effect] e = subjectPriority(p.eft) || deny [matchers] m = g(r.sub, p.sub, r.dom) && r.dom == p.dom && r.obj == p.obj && r.act == p.actexamples/subject_priority_policy.csv000064400000000414152475271650014073 0ustar00p, root, data1, read, deny p, admin, data1, read, deny p, editor, data1, read, deny p, subscriber, data1, read, deny p, jane, data1, read, allow p, alice, data1, read, allow g, admin, root g, editor, admin g, subscriber, admin g, jane, editor g, alice, subscriberexamples/subject_priority_policy_with_domain.csv000064400000000310152475271650016450 0ustar00p, admin, data1, domain1, write, deny p, alice, data1, domain1, write, allow p, admin, data2, domain2, write, deny p, bob, data2, domain2, write, allow g, alice, admin, domain1 g, bob, admin, domain2phpstan.neon000064400000000264152475271650007122 0ustar00parameters: reportUnmatchedIgnoredErrors: false tipsOfTheDay: false level: 7 paths: - src ignoreErrors: - identifier: missingType.iterableValue phpunit.xml000064400000001724152475271650006777 0ustar00 ./tests/ ./tests/Benchmark ./tests/Benchmark/ ./src src/CachedEnforcer.php000064400000014125152475271650010720 0ustar00enableCache = true; $this->cache = new ArrayAdapter(); $this->expireTime = null; parent::__construct($model, $adapter, $logger, $enableLog); } /** * 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. * * @param mixed ...$rvals * * @return bool * * @throws Exceptions\CasbinException */ public function enforce(...$rvals): bool { if (!$this->enableCache) { return parent::enforce(...$rvals); } $key = $this->getKey(...$rvals); $res = $this->getCachedResult($key); if (!is_null($res)) { return $res; } $value = parent::enforce(...$rvals); $this->setCachedResult($key, $value); return $value; } /** * Determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions. * * @param bool $enableCache * * @return void */ public function enableCache(bool $enableCache = true): void { $this->enableCache = $enableCache; } /** * Sets the cache adapter for the enforcer. * * * @param CacheItemPoolInterface $cache * * @return void */ public function setCache(CacheItemPoolInterface $cache): void { $this->cache = $cache; } /** * Sets the expire time for the cache in seconds. If the value is null, the cache will never expire. * * @param int|null $expireTime * * @return void */ public function setExpireTime(int|null $expireTime): void { $this->expireTime = $expireTime; } /** * Invalidates the cache. */ public function invalidateCache(): void { $this->cache->clear(); } /** * Reloads the policy from file/database. */ public function loadPolicy(): void { if ($this->enableCache) { $this->cache->clear(); } parent::loadPolicy(); } /** * Removes an authorization rule from the current policy. * * @param mixed ...$params * * @return bool */ public function removePolicy(...$params): bool { if ($this->enableCache) { $key = $this->getKey(...$params); $this->cache->deleteItem($key); } return parent::removePolicy(...$params); } /** * Removes an authorization rules from the current policy. * * @param array $rules * * @return bool */ public function removePolicies(array $rules): bool { if ($this->enableCache) { foreach ($rules as $rule) { $key = $this->getKey(...$rule); $this->cache->deleteItem($key); } } return parent::removePolicies($rules); } /** * Clears all policy. */ public function clearPolicy(): void { if ($this->enableCache) { if (!$this->cache->clear()) { $this->logger->logError(new CasbinException('clear cache failed')); } } parent::clearPolicy(); } /** * Gets the cached result from the cache by key. * * If the key does not exist in the cache, it returns null. * * @param string $key * * @return bool|null */ public function getCachedResult(string $key): bool|null { $value = $this->cache->getItem($key)->get(); return $value; } /** * Sets the cached result to the cache by key. * * @param string $key * @param bool $value * * @return void */ public function setCachedResult(string $key, bool $value): void { $item = $this->cache->getItem($key); $item->set($value); $item->expiresAfter($this->expireTime); $this->cache->save($item); } /** * Gets the cache key by combining the input parameters. * * @param mixed ...$rvals * * @return string */ public function getCacheKey(...$rvals): string { $key = ''; foreach ($rvals as $rval) { if (is_string($rval)) { $key .= $rval; } elseif ($rval instanceof CacheableParam) { $key .= $rval->getCacheKey(); } else { return ''; } $key .= '$$'; } return $key; } /** * Gets the cache key by combining the input parameters. * * @param mixed ...$rvals * * @return string */ private function getKey(...$rvals): string { return $this->getCacheKey(...$rvals); } } src/Config/Config.php000064400000013715152475271650010503 0ustar00> */ public array $data = []; /** * Create an empty configuration representation from file. * * @param string $confName * * @return ConfigContract * @throws CasbinException */ public static function newConfig(string $confName): ConfigContract { $c = new static(); $c->parse($confName); return $c; } /** * Create an empty configuration representation from text. * * @param string $text * * @return ConfigContract * @throws CasbinException */ public static function newConfigFromText(string $text): ConfigContract { $c = new Config(); $c->parseBuffer($text); return $c; } /** * Adds a new section->key:value to the configuration. * * @param string $section * @param string $option * @param string $value * * @return bool */ public function addConfig(string $section, string $option, string $value): bool { if (empty($section)) { $section = self::DEFAULT_SECTION; } if (!isset($this->data[$section])) { $this->data[$section] = []; } $this->data[$section][$option] = $value; return true; } /** * @param string $fname * * @return bool * * @throws CasbinException */ private function parse(string $fname): bool { $buf = file_get_contents($fname); return $buf === false ? false : $this->parseBuffer($buf); } /** * @param string $buf * * @return bool * * @throws CasbinException */ private function parseBuffer(string $buf): bool { $section = ''; $lineNum = 0; $buffer = ''; $canWrite = null; $buf = preg_replace('/[\r\n]+/', PHP_EOL, $buf); $buf = explode(PHP_EOL, $buf ?? ''); $len = count($buf); for ($i = 0; $i <= $len; ++$i) { if ($canWrite) { $this->write($section, $lineNum, $buffer); $canWrite = false; } ++$lineNum; $line = $buf[$i] ?? ''; if ($i == $len) { 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; } /** * @param string $section * @param int $lineNum * @param string $b * * @throws CasbinException */ private function write(string $section, int $lineNum, string &$b): void { 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 = ''; } /** * Lookups up the value using the provided key and converts the value to a string. * * @param string $key * * @return string */ public function getString(string $key): string { return $this->get($key); } /** * Lookups up the value using the provided key and converts the value to an array of string * by splitting the string by comma. * * @param string $key * * @return array */ public function getStrings(string $key): array { $v = $this->get($key); if ('' == $v) { return []; } return explode(',', $v); } /** * Sets the value for the specific key in the Config. * * @param string $key * @param string $value * * @throws CasbinException */ public function set(string $key, string $value): void { if (0 == \strlen($key)) { throw new CasbinException('key is empty'); } $section = ''; $keys = explode('::', strtolower($key)); if (count($keys) >= 2) { $section = $keys[0]; $option = $keys[1]; } else { $option = $keys[0]; } $this->addConfig($section, $option, $value); } /** * section.key or key. * * @param string $key * * @return string */ public function get(string $key): string { $keys = explode('::', $key); if (count($keys) >= 2) { $section = $keys[0]; $option = $keys[1]; } else { $section = self::DEFAULT_SECTION; $option = $keys[0]; } return $this->data[$section][$option] ?? ''; } } src/Config/ConfigContract.php000064400000002115152475271650012171 0ustar00 */ protected array $rmMap; /** * CondRmMap. * * @var array */ protected array $condRmMap; /** * $enabled. * * @var bool */ protected bool $enabled; /** * $autoSave. * * @var bool */ protected bool $autoSave; /** * $autoBuildRoleLinks. * * @var bool */ protected bool $autoBuildRoleLinks; /** * $autoNotifyWatcher. * * @var bool */ protected bool $autoNotifyWatcher; /** * $logger. * * @var Logger */ protected Logger $logger; /** * Enforcer constructor. * Creates an enforcer via file or DB. * File: * $e = new Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") * MySQL DB: * $a = DatabaseAdapter::newAdapter([ * 'type' => '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). * * @param string|Model|null $model * @param string|Adapter|null $adapter * @param Logger|null $logger * @param bool|null $enableLog * * @throws CasbinException */ public function __construct(string|Model|null $model = null, string|Adapter|null $adapter = null, ?Logger $logger = null, ?bool $enableLog = null) { $this->logger = $logger ?? new DefaultLogger(); if (!is_null($enableLog)) { $this->enableLog($enableLog); } if (is_null($model) && is_null($adapter)) { return; } if (is_string($model)) { if (is_string($adapter) || is_null($adapter)) { $this->initWithFile($model, $adapter ?? ''); } else if ($adapter instanceof Adapter) { $this->initWithAdapter($model, $adapter); } } else if ($model instanceof Model) { if ($adapter instanceof Adapter || is_null($adapter)) { $this->initWithModelAndAdapter($model, $adapter); } else { throw new CasbinException('Invalid parameters for enforcer.'); } } 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 * * @throws CasbinException */ public function initWithFile(string $modelPath, string $policyPath): void { $adapter = new FileAdapter($policyPath); $this->initWithAdapter($modelPath, $adapter); } /** * Initializes an enforcer with a database adapter. * * @param string $modelPath * @param Adapter $adapter * * @throws CasbinException */ public function initWithAdapter(string $modelPath, Adapter $adapter): void { $m = Model::newModelFromFile($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 $adapter): void { $this->adapter = $adapter; $this->model = $m; $this->model->setLogger($this->logger); $this->model->printModel(); $this->fm = Model::loadFunctionMap(); $this->initialize(); // Do not initialize the full policy when using a filtered adapter $ok = $this->adapter instanceof FilteredAdapter ? $this->adapter->isFiltered() : false; if (!is_null($this->adapter) && !$ok) { $this->loadPolicy(); } } /** * Sets the current logger. * * @param Logger $logger */ public function setLogger(Logger $logger): void { $this->logger = $logger; $this->model->setLogger($this->logger); foreach ($this->rmMap as $rm) { $rm->setLogger($this->logger); } foreach ($this->condRmMap as $rm) { $rm->setLogger($this->logger); } } /** * Initializes an enforcer with a database adapter. */ protected function initialize(): void { $this->rmMap = []; $this->condRmMap = []; $this->eft = new DefaultEffector(); $this->watcher = null; $this->enabled = true; $this->autoSave = true; $this->autoBuildRoleLinks = true; $this->autoNotifyWatcher = true; $this->initRmMap(); } /** * Reloads the model from the model CONF file. * Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy(). * * @throws CasbinException */ public function loadModel(): void { $this->model = Model::newModelFromFile($this->modelPath); $this->model->printModel(); $this->fm = Model::loadFunctionMap(); $this->initialize(); } /** * Gets the current model. * * @return Model */ public function getModel(): Model { return $this->model; } /** * Sets the current model. * * @param Model $model */ public function setModel(Model $model): void { $this->model = $model; $this->fm = $this->model->loadFunctionMap(); $this->initialize(); } /** * Gets the current adapter. * * @return Adapter|null */ public function getAdapter(): ?Adapter { return $this->adapter; } /** * Sets the current adapter. * * @param Adapter $adapter */ public function setAdapter(Adapter $adapter): void { $this->adapter = $adapter; } /** * Sets the current watcher. * * @param Watcher $watcher */ public function setWatcher(Watcher $watcher): void { $this->watcher = $watcher; $this->watcher->setUpdateCallback(fn () => $this->loadPolicy()); } /** * Gets the current role manager. * * @return RoleManager */ public function getRoleManager(): RoleManager { return $this->rmMap['g']; } /** * Gets the current role manager. * * @param RoleManager $rm */ public function setRoleManager(RoleManager $rm): void { $this->rmMap['g'] = $rm; } /** * Sets the current effector. * * @param Effector $eft */ public function setEffector(Effector $eft): void { $this->eft = $eft; } /** * Clears all policy. */ public function clearPolicy(): void { $this->model->clearPolicy(); } /** * Reloads the policy from file/database. */ public function loadPolicy(): void { $newModel = $this->loadPolicyFromAdapter($this->model); if (!is_null($newModel)) { $this->applyModifiedModel($newModel); } } /** * Loads policy from the current adapter. * * @param Model $baseModel * * @return Model|null */ public function loadPolicyFromAdapter(Model $baseModel): ?Model { $newModel = clone $baseModel; $newModel->clearPolicy(); try { $this->adapter?->loadPolicy($newModel); $newModel->sortPoliciesBySubjectHierarchy(); $newModel->sortPoliciesByPriority(); } catch (InvalidFilePathException) { return null; } catch (\Throwable $e) { throw $e; } return $newModel; } /** * Applies a modified model to the current enforcer. * * @param Model $newModel */ public function applyModifiedModel(Model $newModel): void { $flag = false; $needToRebuild = false; try { if ($this->autoBuildRoleLinks) { $needToRebuild = true; $this->rebuildRoleLinks($newModel); $this->rebuildConditionalRoleLinks($newModel); } $this->model = $newModel; } catch (\Throwable $e) { $flag = true; throw $e; } finally { if ($flag) { if ($this->autoBuildRoleLinks && $needToRebuild) { $this->buildRoleLinks(); } } } } /** * Rebuilds the role inheritance relations based on the new model. * * @param Model $newModel */ public function rebuildRoleLinks(Model $newModel): void { if (count($this->rmMap) !== 0) { foreach ($this->rmMap as $rm) { $rm->clear(); } $newModel->buildRoleLinks($this->rmMap); } } /** * Rebuilds the conditional role inheritance relations based on the new model. * * @param Model $newModel */ public function rebuildConditionalRoleLinks(Model $newModel): void { if (!empty($this->condRmMap)) { foreach ($this->condRmMap as $rm) { $rm->clear(); } $newModel->buildConditionalRoleLinks($this->condRmMap); } } /** * Reloads a filtered policy from file/database. * * @param mixed $filter * * @throws CasbinException */ public function _loadFilteredPolicy($filter): void { if ($this->adapter instanceof FilteredAdapter) { $filteredAdapter = $this->adapter; $filteredAdapter->loadFilteredPolicy($this->model, $filter); } else { throw new CasbinException('filtered policies are not supported by this adapter'); } $this->model->sortPoliciesBySubjectHierarchy(); $this->model->sortPoliciesByPriority(); $this->initRmMap(); $this->model->printPolicy(); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } } /** * Reloads a filtered policy from file/database. * * @param mixed $filter * * @throws CasbinException */ public function loadFilteredPolicy($filter): void { $this->model->clearPolicy(); $this->_loadFilteredPolicy($filter); } /** * LoadIncrementalFilteredPolicy append a filtered policy from file/database. * * @param mixed $filter * @return void */ public function loadIncrementalFilteredPolicy($filter): void { $this->_loadFilteredPolicy($filter); } /** * Returns true if the loaded policy has been filtered. * * @return bool */ public function isFiltered(): bool { if (!$this->adapter instanceof FilteredAdapter) { return false; } $filteredAdapter = $this->adapter; return $filteredAdapter->isFiltered(); } /** * Saves the current policy (usually after changed with Casbin API) back to file/database. * * @throws CasbinException */ public function savePolicy(): void { if ($this->isFiltered()) { throw new CasbinException('cannot save a filtered policy'); } $this->adapter?->savePolicy($this->model); if ($this->autoNotifyWatcher) { if ($this->watcher instanceof WatcherEx) { $this->watcher->updateForSavePolicy($this->model); } else { $this->watcher?->update(); } } } /** * initRmMap initializes rmMap. * * @return void */ public function initRmMap(): void { if (isset($this->model['g'])) { foreach ($this->model['g'] as $ptype => $value) { if (isset($this->rmMap[$ptype])) { $rm = $this->rmMap[$ptype]; $rm->clear(); continue; } $tokensCount = count($value->tokens); $paramsTokensCount = count($value->paramsTokens); if ($tokensCount <= 2) { if ($paramsTokensCount === 0) { $value->rm = new DefaultRoleManager(10); $this->rmMap[$ptype] = $value->rm; } else { $value->condRm = new DefaultConditionalRoleManager(10); $this->condRmMap[$ptype] = $value->condRm; } } if ($tokensCount > 2) { if ($paramsTokensCount === 0) { $value->rm = new DefaultDomainManager(10); $this->rmMap[$ptype] = $value->rm; } else { $value->condRm = new DefaultConditionalDomainManager(10); $this->condRmMap[$ptype] = $value->condRm; } $matchFunc = 'keyMatch(r_dom, p_dom)'; if (str_contains($this->model['m']['m']->value, $matchFunc)) { $this->addNamedDomainMatchingFunc('g', 'keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); } } } } } /** * Changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function. * * @param bool $enabled */ public function enableEnforce(bool $enabled = true): void { $this->enabled = $enabled; } /** * Changes whether Casbin will log messages to the Logger. * * @param bool $enabled */ public function enableLog(bool $enabled = true): void { $this->logger->enableLog($enabled); } /** * Controls whether to save a policy rule automatically notify the Watcher when it is added or removed. * * @param bool $enabled */ public function enableAutoNotifyWatcher(bool $enabled = true): void { $this->autoNotifyWatcher = $enabled; } /** * Controls whether to save a policy rule automatically to the adapter when it is added or removed. * * @param bool $autoSave */ public function enableAutoSave(bool $autoSave = true): void { $this->autoSave = $autoSave; } /** * Controls whether to rebuild the role inheritance relations when a role is added or deleted. * * @param bool $autoBuildRoleLinks */ public function enableAutoBuildRoleLinks(bool $autoBuildRoleLinks = true): void { $this->autoBuildRoleLinks = $autoBuildRoleLinks; } /** * Manually rebuild the role inheritance relations. */ public function buildRoleLinks(): void { foreach ($this->rmMap as $rm) { $rm->clear(); } $this->model->buildRoleLinks($this->rmMap); } /** * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". * * @param string $matcher * @param array $explains * @param mixed ...$rvals * * @return bool * * @throws CasbinException */ protected function enforcing(string $matcher, &$explains = [], ...$rvals): bool { if (!$this->enabled) { return true; } $functions = $this->fm->getFunctions(); if (isset($this->model['g'])) { foreach ($this->model['g'] as $key => $ast) { if (!is_null($ast->rm)) { $functions[$key] = BuiltinOperations::generateGFunction($ast->rm); } if (!is_null($ast->condRm)) { $functions[$key] = BuiltinOperations::generateConditionalGFunction($ast->condRm); } } } if (!isset($this->model['m']['m'])) { throw new CasbinException('model is undefined'); } $rType = "r"; $pType = "p"; $eType = "e"; $mType = "m"; switch (true) { case $rvals[0] instanceof EnforceContext: $enforceContext = $rvals[0]; $rType = $enforceContext->rType; $pType = $enforceContext->pType; $eType = $enforceContext->eType; $mType = $enforceContext->mType; array_shift($rvals); break; default: break; } $expString = ''; if ('' === $matcher) { $expString = $this->model['m'][$mType]->value; } else { $expString = Util::removeComments(Util::escapeAssertion($matcher)); } $rTokens = array_values($this->model['r'][$rType]->tokens); $pTokens = array_values($this->model['p'][$pType]->tokens); if (count($rTokens) != count($rvals)) { throw new CasbinException(\sprintf('invalid request size: expected %d, got %d', count($rTokens), count($rvals))); } $rParameters = array_combine($rTokens, $rvals); if (false == $rParameters) { throw new CasbinException('invalid request size'); } $expressionLanguage = $this->getExpressionLanguage($functions); $expression = ""; $hasEval = Util::hasEval($expString); if (!$hasEval) { $expression = $expressionLanguage->parse($expString, array_merge($rTokens, $pTokens)); } $policyEffects = []; $matcherResults = []; $effect = 0; $explainIndex = 0; $policyLen = count($this->model['p'][$pType]->policy); if (0 != $policyLen && str_contains($expString, $pType . '_')) { foreach ($this->model['p'][$pType]->policy as $policyIndex => $pvals) { $parameters = array_combine($pTokens, $pvals); if (false == $parameters) { throw new CasbinException('invalid policy size'); } if ($hasEval) { $ruleNames = Util::getEvalValue($expString); $replacements = []; $pTokens_flipped = array_flip($pTokens); foreach ($ruleNames as $ruleName) { if (isset($pTokens_flipped[$ruleName])) { $rule = Util::escapeAssertion($pvals[$pTokens_flipped[$ruleName]]); $replacements[$ruleName] = $rule; } else { throw new CasbinException('please make sure rule exists in policy when using eval() in matcher'); } } $expWithRule = Util::replaceEvalWithMap($expString, $replacements); $expression = $expressionLanguage->parse($expWithRule, array_merge($rTokens, $pTokens)); } $parameters = array_merge($rParameters, $parameters); $result = $expressionLanguage->evaluate($expression, $parameters); // set to no-match at first $matcherResults[$policyIndex] = 0; if (is_bool($result)) { if ($result) { $matcherResults[$policyIndex] = 1; } } elseif (is_float($result)) { if ($result != 0) { $matcherResults[$policyIndex] = 1; } } else { throw new CasbinException('matcher result should be bool, int or float'); } if (isset($parameters[$pType . '_eft'])) { $eft = $parameters[$pType . '_eft']; if ('allow' == $eft) { $policyEffects[$policyIndex] = Effector::ALLOW; } elseif ('deny' == $eft) { $policyEffects[$policyIndex] = Effector::DENY; } else { $policyEffects[$policyIndex] = Effector::INDETERMINATE; } } else { $policyEffects[$policyIndex] = Effector::ALLOW; } [$effect, $explainIndex] = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, $policyIndex, $policyLen); if ($effect != Effector::INDETERMINATE) { break; } } } else { if ($hasEval) { throw new EvalFunctionException("please make sure rule exists in policy when using eval() in matcher"); } $matcherResults[0] = 1; $parameters = $rParameters; foreach ($this->model['p'][$pType]->tokens as $token) { $parameters[$token] = ''; } $result = $expressionLanguage->evaluate($expression, $parameters); if ($result) { $policyEffects[0] = Effector::ALLOW; } else { $policyEffects[0] = Effector::INDETERMINATE; } [$effect, $explainIndex] = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, 0, 1); } if ($explains !== null) { if (($explainIndex != -1) && (count($this->model['p'][$pType]->policy) > $explainIndex)) { $explains = $this->model['p'][$pType]->policy[$explainIndex]; } } $result = $effect == Effector::ALLOW; $this->logger->logEnforce($matcher, $rvals, $result, $explains); return $result; } /** * @param array $functions * * @return ExpressionLanguage */ protected function getExpressionLanguage(array $functions): ExpressionLanguage { $expressionLanguage = new ExpressionLanguage(); foreach ($functions as $key => $func) { $expressionLanguage->register( $key, static fn (...$args): string => sprintf($key . '(%1$s)', implode(',', $args)), static fn ($arguments, ...$args) => $func(...$args) ); } return $expressionLanguage; } /** * @param string $expString * * @return string */ protected function getExpString(string $expString): string { return preg_replace_callback( '/([\s\S]*in\s+)\(([\s\S]+)\)([\s\S]*)/', static fn($m): string => $m[1] . '[' . $m[2] . ']' . $m[3], $expString ); } /** * Decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). * * @param mixed ...$rvals * * @return bool * * @throws CasbinException */ public function enforce(...$rvals): bool { $explains = []; return $this->enforcing('', $explains, ...$rvals); } /** * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action", * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "". * * @param string $matcher * @param mixed ...$rvals * * @return bool * * @throws CasbinException */ public function enforceWithMatcher(string $matcher, ...$rvals): bool { $explains = []; return $this->enforcing($matcher, $explains, ...$rvals); } /** * EnforceEx explain enforcement by informing matched rules * * @param mixed ...$rvals * @return array */ public function enforceEx(...$rvals) { $explain = []; $result = $this->enforcing("", $explain, ...$rvals); return [$result, $explain]; } /** * BuildIncrementalRoleLinks provides incremental build the role inheritance relations. * * @param integer $op policy operations. * @param string $ptype policy type. * @param string[][] $rules the rules. * @return void */ public function buildIncrementalRoleLinks(int $op, string $ptype, array $rules): void { $this->model->buildIncrementalRoleLinks($this->rmMap, $op, "g", $ptype, $rules); } /** * BuildIncrementalConditionalRoleLinks provides incremental build the conditional role inheritance relations. * * @param integer $op policy operations. * @param string $ptype policy type. * @param string[][] $rules the rules. * @return void */ public function buildIncrementalConditionalRoleLinks(int $op, string $ptype, array $rules): void { $this->model->buildIncrementalConditionalRoleLinks($this->condRmMap, $op, "g", $ptype, $rules); } /** * BatchEnforce enforce in batches * * @param string[][] $requests * @return bool[] */ public function batchEnforce(array $requests): array { return array_map(fn (array $request) => $this->enforce(...$request), $requests); } /** * BatchEnforceWithMatcher enforce with matcher in batches * * @param string $matcher * @param string[][] $requests * @return bool[] */ public function batchEnforceWithMatcher(string $matcher, array $requests): array { return array_map(fn (array $request) => $this->enforceWithMatcher($matcher, ...$request), $requests); } /** * AddNamedMatchingFunc add MatchingFunc by ptype RoleManager * * @param string $ptype * @param string $name * @param Closure $fn * @return boolean */ public function addNamedMatchingFunc(string $ptype, string $name, Closure $fn): bool { if (isset($this->rmMap[$ptype])) { $rm = &$this->rmMap[$ptype]; $rm->addMatchingFunc($name, $fn); return true; } return false; } /** * AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager * * @param string $ptype * @param string $name * @param Closure $fn * @return boolean */ public function addNamedDomainMatchingFunc(string $ptype, string $name, Closure $fn): bool { if (isset($this->rmMap[$ptype])) { $rm = &$this->rmMap[$ptype]; $rm->addDomainMatchingFunc($name, $fn); return true; } return false; } /** * AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName, * when fn returns true, Link is valid, otherwise invalid. * * @param string $ptype * @param string $user * @param string $role * @param Closure $fn * @return boolean */ public function addNamedLinkConditionFunc(string $ptype, string $user, string $role, Closure $fn): bool { if (isset($this->condRmMap[$ptype])) { $rm = &$this->condRmMap[$ptype]; $rm->addLinkConditionFunc($user, $role, $fn); return true; } return false; } /** * AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain}, * when fn returns true, Link is valid, otherwise invalid. * * @param string $ptype * @param string $user * @param string $role * @param string $domain * @param Closure $fn * * @return boolean */ public function addNamedDomainLinkConditionFunc(string $ptype, string $user, string $role, string $domain, Closure $fn): bool { if (isset($this->condRmMap[$ptype])) { $rm = &$this->condRmMap[$ptype]; $rm->addDomainLinkConditionFunc($user, $role, $domain, $fn); return true; } return false; } /** * SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName. * * @param string $ptype * @param string $user * @param string $role * @param string ...$params * * @return boolean */ public function setNamedLinkConditionFuncParams(string $ptype, string $user, string $role, string ...$params): bool { if (isset($this->condRmMap[$ptype])) { $rm = &$this->condRmMap[$ptype]; $rm->setLinkConditionFuncParams($user, $role, ...$params); return true; } return false; } /** * SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn * for Link userName->{roleName, domain}. * * @param string $ptype * @param string $user * @param string $role * @param string $domain * @param string ...$params * * @return boolean */ public function setNamedDomainLinkConditionFuncParams(string $ptype, string $user, string $role, string $domain, string ...$params): bool { if (isset($this->condRmMap[$ptype])) { $rm = &$this->condRmMap[$ptype]; $rm->setDomainLinkConditionFuncParams($user, $role, $domain, ...$params); return true; } return false; } } src/Effector/DefaultEffector.php000064400000007376152475271650012676 0ustar00 $eft) { if ($matches[$i] == 0) { continue; } if ($eft === Effector::ALLOW) { $result = Effector::ALLOW; // set hit rule to first matched allow rule $explainIndex = $i; break; } } break; case Constants::PRIORITY_EFFECT: case Constants::SUBJECT_PRIORITY_EFFECT: // reverse merge, short-circuit may be earlier for ($i = count($effects) - 1; $i >= 0; $i--) { if ($matches[$i] == 0) { continue; } if ($effects[$i] != Effector::INDETERMINATE) { if ($effects[$i] === Effector::ALLOW) { $result = Effector::ALLOW; } else { $result = Effector::DENY; } $explainIndex = $i; break; } } break; default: throw new CasbinException('unsupported effect'); } return [$result, $explainIndex]; } } src/Effector/Effector.php000064400000001050152475271650011350 0ustar00rType = "r" . $suffix; $this->pType = "p" . $suffix; $this->eType = "e" . $suffix; $this->mType = "m" . $suffix; } } src/Enforcer.php000064400000062362152475271650007636 0ustar00model['g']['g']->rm->getRoles($name, ...$domain); } /** * Gets the users that has a role. * * @param string $name * @param string ...$domain * * @return string[] */ public function getUsersForRole(string $name, string ...$domain): array { return $this->model['g']['g']->rm->getUsers($name, ...$domain); } /** * Determines whether a user has a role. * * @param string $name * @param string $role * @param string ...$domain * * @return bool */ public function hasRoleForUser(string $name, string $role, string ...$domain): bool { $roles = $this->getRolesForUser($name, ...$domain); return in_array($role, $roles, true); } /** * Adds a role for a user. * returns false if the user already has the role (aka not affected). * * @param string $user * @param string $role * @param string ...$domain * @return bool */ public function addRoleForUser(string $user, string $role, string ...$domain): bool { return $this->addGroupingPolicy(...array_merge([$user, $role], $domain)); } /** * @param string $user * @param string[] $roles * @param string ...$domain * * @return bool */ public function addRolesForUser(string $user, array $roles, string ...$domain): bool { return $this->addGroupingPolicies( array_map(function ($role) use ($user, $domain) { return array_merge([$user, $role], $domain); }, $roles) ); } /** * Deletes a role for a user. * returns false if the user does not have the role (aka not affected). * * @param string $user * @param string $role * @param string ...$domain * * @return bool */ public function deleteRoleForUser(string $user, string $role, string ...$domain): bool { return $this->removeGroupingPolicy(...array_merge([$user, $role], $domain)); } /** * Deletes all roles for a user. * Returns false if the user does not have any roles (aka not affected). * * @param string $user * @param string ...$domain * * @return bool * @throws CasbinException */ public function deleteRolesForUser(string $user, string ...$domain): bool { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } return $this->removeFilteredGroupingPolicy(0, ...array_merge([$user, ''], $domain)); } /** * Deletes a user. * Returns false if the user does not exist (aka not affected). * * @param string $user * * @return bool */ public function deleteUser(string $user): bool { $res1 = $this->removeFilteredGroupingPolicy(0, $user); $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); $res2 = $this->removeFilteredPolicy($subIndex, $user); return $res1 || $res2; } /** * Deletes a role. * * @param string $role * @return bool */ public function deleteRole(string $role): bool { $res1 = $this->removeFilteredGroupingPolicy(1, $role); $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); $res2 = $this->removeFilteredPolicy($subIndex, $role); return $res1 || $res2; } /** * Deletes a permission. * Returns false if the permission does not exist (aka not affected). * * @param string ...$permission * * @return bool */ public function deletePermission(string ...$permission): bool { return $this->removeFilteredPolicy(1, ...$permission); } /** * Adds a permission for a user or role. * Returns false if the user or role already has the permission (aka not affected). * * @param string $user * @param string ...$permission * * @return bool */ public function addPermissionForUser(string $user, string ...$permission): bool { $params = array_merge([$user], $permission); return $this->addPolicy(...$params); } /** * AddPermissionsForUser adds multiple permissions for a user or role. * Returns false if the user or role already has one of the permissions (aka not affected). * * @param string $user * @param array ...$permissions * @return bool */ public function addPermissionsForUser(string $user, array ...$permissions): bool { $rules = []; foreach ($permissions as $permission) { $rules[] = array_merge([$user], $permission); } return $this->addPolicies($rules); } /** * Deletes a permission for a user or role. * Returns false if the user or role does not have the permission (aka not affected). * * @param string $user * @param string ...$permission * * @return bool */ public function deletePermissionForUser(string $user, string ...$permission): bool { $params = array_merge([$user], $permission); return $this->removePolicy(...$params); } /** * Deletes permissions for a user or role. * Returns false if the user or role does not have any permissions (aka not affected). * * @param string $user * * @return bool */ public function deletePermissionsForUser(string $user): bool { $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); return $this->removeFilteredPolicy($subIndex, $user); } /** * Gets permissions for a user or role. * * @param string $user * @param string ...$domain * * @return array */ public function getPermissionsForUser(string $user, string ...$domain): array { $permission = []; foreach ($this->model['p'] as $ptype => $assertion) { $args = []; $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); $args[$subIndex] = $user; if (count($domain) > 0) { $domIndex = $this->model->getFieldIndex($ptype, Constants::DOMAIN_INDEX); $args[$domIndex] = $domain[0]; } $perm = $this->getFilteredPolicy(0, ...$args); $permission = array_merge($permission, $perm); } return $permission; } /** * Determines whether a user has a permission. * * @param string $user * @param string ...$permission * * @return bool */ public function hasPermissionForUser(string $user, string ...$permission): bool { $params = array_merge([$user], $permission); return $this->hasPolicy($params); } /** * 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"]. * * @param string $name * @param string ...$domain * * @return array */ public function getImplicitRolesForUser(string $name, string ...$domain): array { $res = []; $roleSet = []; $roleSet[$name] = true; $q = []; $q[] = $name; for (; count($q) > 0;) { $name = $q[0]; $q = array_slice($q, 1); foreach ($this->rmMap as $rm) { $roles = $rm->getRoles($name, ...$domain); foreach ($roles as $r) { if (!isset($roleSet[$r])) { $res[] = $r; $q[] = $r; $roleSet[$r] = true; } } } } return $res; } /** * GetImplicitUsersForRole gets implicit users for a role. * * @param string $name * @param string ...$domain * @return array */ public function getImplicitUsersForRole(string $name, string ...$domain): array { $res = []; $roleSet = []; $roleSet[$name] = true; $q = []; $q[] = $name; for (; count($q) > 0;) { $name = $q[0]; $q = array_slice($q, 1); foreach ($this->rmMap as $rm) { $roles = $rm->getUsers($name, ...$domain); foreach ($roles as $r) { if (!isset($roleSet[$r])) { $res[] = $r; $q[] = $r; $roleSet[$r] = true; } } } } return $res; } /** * GetDomainsForUser gets all domains that a subject inherits. * * @param string $user * * @return string[] */ public function getDomainsForUser(string $user): array { $domains = []; foreach ($this->rmMap as $rm) { $res = $rm->getDomains($user); $domains = array_merge($domains, $res); } return $domains; } /** * GetImplicitResourcesForUser returns all policies that user obtaining in domain * * @param string $user * @param string ...$domain * @return array */ public function getImplicitResourcesForUser(string $user, string ...$domain): array { $permissions = $this->getImplicitPermissionsForUser($user, ...$domain); $res = []; foreach ($permissions as $permission) { if ($permission[0] == $user) { $res[] = $permission; continue; } $resLocal = [[$user]]; $tokensLength = count($permission); $t = [[]]; foreach (array_slice($permission, 1) as $token) { $tokens = $this->getImplicitUsersForRole($token, ...$domain); $tokens[] = $token; $t[] = $tokens; } for ($i = 1; $i < $tokensLength; $i++) { $n = []; foreach ($t[$i] as $tokens) { foreach ($resLocal as $policy) { $temp = []; $temp = array_merge($temp, $policy); $temp[] = $tokens; $n[] = $temp; } } $resLocal = $n; } $res = array_merge($res, $resLocal); } return $res; } /** * 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"]]. * * @param string $user * @param string ...$domain * * @return array * @throws CasbinException */ public function getImplicitPermissionsForUser(string $user, string ...$domain): array { $roles = array_merge( [$user], $this->getImplicitRolesForUser($user, ...$domain) ); $len = count($domain); if ($len > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $res = []; foreach ($roles as $role) { if (1 == $len) { $permissions = $this->getPermissionsForUserInDomain($role, $domain[0]); } else { $permissions = $this->getPermissionsForUser($role); } $res = array_merge($res, $permissions); } return $res; } /** * Gets implicit users for a permission. * For example: * p, admin, data1, read * p, bob, data1, read * g, alice, admin * getImplicitUsersForPermission("data1", "read") will get: ["alice", "bob"]. * Note: only users will be returned, roles (2nd arg in "g") will be excluded. * * @param string ...$permission * * @return array * @throws CasbinException */ public function getImplicitUsersForPermission(string ...$permission): array { $pSubjects = $this->getAllSubjects(); $gInherit = $this->model->getValuesForFieldInPolicyAllTypes("g", 1); $gSubjects = $this->model->getValuesForFieldInPolicyAllTypes("g", 0); $subjects = array_merge($pSubjects, $gSubjects); Util::ArrayRemoveDuplicates($subjects); $subjects = array_diff($subjects, $gInherit); $res = []; foreach ($subjects as $user) { $req = $permission; array_unshift($req, $user); $allowed = $this->enforce(...$req); if ($allowed) { $res[] = $user; } } return $res; } /** * Convert permissions to string as a hash to deduplicate. * * @param array $permissions * * @return array */ private function removeDumplicatePermissions(array $permissions): array { $permissionsSet = []; $res = []; foreach ($permissions as $permission) { $permissionStr = Util::arrayToString($permission); if (isset($permissionsSet[$permissionStr])) { continue; } $permissionsSet[$permissionStr] = true; $res[] = $permission; } return $res; } /** * GetAllowedObjectConditions returns a string array of object conditions that the user can access. * For example: conditions, err := e.GetAllowedObjectConditions("alice", "read", "r.obj.") * Note: * * 0. prefix: You can customize the prefix of the object conditions, and "r.obj." is commonly used as a prefix. * After removing the prefix, the remaining part is the condition of the object. * If there is an obj policy that does not meet the prefix requirement, an ObjConditionException will be thrown. * * 1. If the 'objectConditions' array is empty, an EmptyConditionException will be thrown. * This error is thrown because some data adapters' ORM return full table data by default * when they receive an empty condition, which tends to behave contrary to expectations.(e.g. DBALAdapter) * If you are using an adapter that does not behave like this, you can choose to ignore this error. * * @param string $user * @param string $action * @param string $prefix * * @return array * @throws ObjConditionException * @throws EmptyConditionException */ public function getAllowedObjectConditions(string $user, string $action, string $prefix): array { $permission = $this->getImplicitPermissionsForUser($user); $objectConditions = []; foreach ($permission as $policy) { if ($policy[2] == $action) { if (!str_starts_with($policy[1], $prefix)) { throw new ObjConditionException('need to meet the prefix required by the object condition'); } $objectConditions[] = substr($policy[1], strlen($prefix)); } } if (empty($objectConditions)) { throw new EmptyConditionException('GetAllowedObjectConditions have an empty condition'); } return $objectConditions; } /** * GetImplicitUsersForResource return implicit user based on resource. * For example: * p, alice, data1, read * p, bob, data2, write * p, data2_admin, data2, read * p, data2_admin, data2, write * g, alice, data2_admin * GetImplicitUsersForResource("data2") will return [[bob data2 write] [alice data2 read] [alice data2 write]] * GetImplicitUsersForResource("data1") will return [[alice data1 read]] * Note: only users will be returned, roles (2nd arg in "g") will be excluded. * * @param string $resource * * @return array */ public function getImplicitUsersForResource(string $resource): array { $permissions = []; $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); $objIndex = $this->model->getFieldIndex('p', Constants::OBJECT_INDEX); $rm = $this->getRoleManager(); $roles = $this->getAllRoles(); $isRole = array_flip($roles); foreach ($this->model['p']['p']->policy as $rule) { $obj = $rule[$objIndex]; if ($obj != $resource) { continue; } $sub = $rule[$subIndex]; if (!isset($isRole[$sub])) { $permissions[] = $rule; } else { $users = $rm->getUsers($sub); foreach ($users as $user) { $implicitRule = array_merge([], $rule); $implicitRule[$subIndex] = $user; $permissions[] = $implicitRule; } } } $res = $this->removeDumplicatePermissions($permissions); return $res; } /** * GetImplicitUsersForResourceByDomain return implicit user based on resource and domain. * Compared to GetImplicitUsersForResource, domain is supported. * * @param string $resource * @param string $domain * * @return array */ public function getImplicitUsersForResourceByDomain(string $resource, string $domain): array { $permissions = []; $subIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); $objIndex = $this->model->getFieldIndex('p', Constants::OBJECT_INDEX); $domIndex = $this->model->getFieldIndex('p', Constants::DOMAIN_INDEX); $rm = $this->getRoleManager(); $roles = $this->getAllRolesByDomain($domain); $isRole = array_flip($roles); foreach ($this->model['p']['p']->policy as $rule) { $obj = $rule[$objIndex]; if ($obj != $resource) { continue; } $sub = $rule[$subIndex]; if (!isset($isRole[$sub])) { $permissions[] = $rule; } else { if ($rule[$domIndex] != $domain) { continue; } $users = $rm->getUsers($sub, $domain); foreach ($users as $user) { $implicitRule = array_merge([], $rule); $implicitRule[$subIndex] = $user; $permissions[] = $implicitRule; } } } $res = $this->removeDumplicatePermissions($permissions); return $res; } /** * GetAllUsersByDomain would get all users associated with the domain. * * @param string $domain * @return string[] */ public function getAllUsersByDomain(string $domain): array { $m = []; $g = $this->model['g']['g']; $p = $this->model['p']['p']; $users = []; $index = $this->model->getFieldIndex('p', Constants::DOMAIN_INDEX); $getUser = function (int $index, array $policies, string $domain, array $m): array { if (count($policies) == 0 || count($policies[0]) <= $index) { return []; } $res = []; foreach ($policies as $policy) { $ok = isset($m[$policy[0]]); if ($policy[$index] == $domain && !$ok) { $res[] = $policy[0]; $m[$policy[0]] = []; } } return $res; }; $users = array_merge($users, $getUser(2, $g->policy, $domain, $m)); $users = array_merge($users, $getUser($index, $p->policy, $domain, $m)); return $users; } /** * Gets the users that has a role inside a domain. Add by Gordon. * * @param string $name * @param string $domain * * @return array */ public function getUsersForRoleInDomain(string $name, string $domain): array { return $this->model['g']['g']->rm->getUsers($name, $domain); } /** * Gets the roles that a user has inside a domain. * * @param string $name * @param string $domain * * @return array */ public function getRolesForUserInDomain(string $name, string $domain): array { return $this->model['g']['g']->rm->getRoles($name, $domain); } /** * Gets permissions for a user or role inside a domain. * * @param string $name * @param string $domain * * @return array */ public function getPermissionsForUserInDomain(string $name, string $domain): array { return $this->getFilteredPolicy(0, $name, $domain); } /** * Adds a role for a user inside a domain. * returns false if the user already has the role (aka not affected). * * @param string $user * @param string $role * @param string $domain * * @return bool */ public function addRoleForUserInDomain(string $user, string $role, string $domain): bool { return $this->addGroupingPolicy($user, $role, $domain); } /** * Deletes a role for a user inside a domain. * Returns false if the user does not have the role (aka not affected). * * @param string $user * @param string $role * @param string $domain * * @return bool */ public function deleteRoleForUserInDomain(string $user, string $role, string $domain): bool { return $this->removeGroupingPolicy($user, $role, $domain); } /** * DeleteRolesForUserInDomain deletes all roles for a user inside a domain. * Returns false if the user does not have any roles (aka not affected). * * @param string $user * @param string $domain * * @return bool */ public function deleteRolesForUserInDomain(string $user, string $domain): bool { $roles = $this->model['g']['g']->rm->getRoles($user, $domain); $rules = []; foreach ($roles as $role) { $rules[] = [$user, $role, $domain]; } return $this->removeGroupingPolicies($rules); } /** * DeleteAllUsersByDomain would delete all users associated with the domain. * * @param string $domain * @return bool */ public function deleteAllUsersByDomain(string $domain): bool { $g = $this->model['g']['g']; $p = $this->model['p']['p']; $index = $this->model->getFieldIndex('p', Constants::DOMAIN_INDEX); $getUser = function (int $index, array $policies, string $domain): array { if (count($policies) == 0 || count($policies[0]) <= $index) { return []; } $res = []; foreach ($policies as $policy) { if ($policy[$index] == $domain) { $res[] = $policy; } } return $res; }; $users = $getUser(2, $g->policy, $domain); $this->removeGroupingPolicies($users); $users = $getUser($index, $p->policy, $domain); $this->removePolicies($users); return true; } /** * DeleteDomains would delete all associated users and roles. * It would delete all domains if parameter is not provided. * * @param string ...$domains * @return bool */ public function deleteDomains(string ...$domains): bool { if (count($domains) == 0) { $this->clearPolicy(); return true; } foreach ($domains as $domain) { $this->deleteAllUsersByDomain($domain); } return true; } /** * GetAllDomains would get all domains. * * @return array */ public function getAllDomains(): array { return $this->getRoleManager()->getAllDomains(); } /** * GetAllRolesByDomain would get all roles associated with the domain. * Note: Not applicable to Domains with inheritance relationship (implicit roles) * * @param string $domain * * @return array */ public function getAllRolesByDomain(string $domain): array { $g = $this->model['g']['g']; $policies = $g->policy; $roles = []; $existMap = []; foreach ($policies as $policy) { if ($policy[count($policy) - 1] == $domain) { $role = $policy[count($policy) - 2]; if (!isset($existMap[$role])) { $roles[] = $role; $existMap[$role] = true; } } } return $roles; } } src/Exceptions/BatchOperationException.php000064400000000303152475271650014760 0ustar00adapter) && $this->autoSave; } /** * @return bool */ protected function shouldNotify(): bool { return !is_null($this->watcher) && $this->autoNotifyWatcher; } /** * Adds a rule to the current policy without notify. * * @param string $sec * @param string $ptype * @param array $rule * * @return bool */ protected function addPolicyWithoutNotifyInternal(string $sec, string $ptype, array $rule): bool { if ($this->model->hasPolicy($sec, $ptype, $rule)) { return false; } if ($this->shouldPersist()) { try { $this->adapter->addPolicy($sec, $ptype, $rule); } catch (NotImplementedException $e) { } } $this->model->addPolicy($sec, $ptype, $rule); if ($sec == "g") { $this->buildIncrementalRoleLinks(Policy::POLICY_ADD, $ptype, [$rule]); } return true; } /** * Adds rules to the current policy without notify. * If autoRemoveRepeat == true, existing rules are automatically filtered * Otherwise, false is returned directly. * * @param string $sec * @param string $ptype * @param array $rules * @param bool $autoRemoveRepeat * * @return bool */ protected function addPoliciesWithoutNotifyInternal(string $sec, string $ptype, array $rules, bool $autoRemoveRepeat): bool { if (!$autoRemoveRepeat) { if ($this->model->hasPolicies($sec, $ptype, $rules)) { return false; } } if ($this->shouldPersist() && $this->adapter instanceof BatchAdapter) { try { $this->adapter->addPolicies($sec, $ptype, $rules); } catch (NotImplementedException $e) { } } $this->model->addPolicies($sec, $ptype, $rules); if ($sec == "g") { $this->buildIncrementalRoleLinks(Policy::POLICY_ADD, $ptype, $rules); $this->buildIncrementalConditionalRoleLinks(Policy::POLICY_ADD, $ptype, $rules); } return true; } /** * Updates a rule from the current policy without notify. * * @param string $sec * @param string $ptype * @param array $oldRule * @param array $newRule * * @return bool */ protected function updatePolicyWithoutNotifyInternal(string $sec, string $ptype, array $oldRule, array $newRule): bool { if ($this->shouldPersist() && $this->adapter instanceof UpdatableAdapter) { try { $this->adapter->updatePolicy($sec, $ptype, $oldRule, $newRule); } catch (NotImplementedException $e) { } } $ruleUpdated = $this->model->updatePolicy($sec, $ptype, $oldRule, $newRule); if (!$ruleUpdated) { return false; } if ($sec == "g") { // remove the old rule $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, [$oldRule]); // add the new rule $this->buildIncrementalRoleLinks(Policy::POLICY_ADD, $ptype, [$newRule]); } return true; } /** * Updates rules from the current policy without notify. * * @param string $sec * @param string $ptype * @param array $oldRules * @param array $newRules * * @return bool */ protected function updatePoliciesWithoutNotifyInternal(string $sec, string $ptype, array $oldRules, array $newRules): bool { if ($this->shouldPersist() && $this->adapter instanceof UpdatableAdapter) { try { $this->adapter->updatePolicies($sec, $ptype, $oldRules, $newRules); } catch (NotImplementedException $e) { } } $ruleUpdated = $this->model->updatePolicies($sec, $ptype, $oldRules, $newRules); if (!$ruleUpdated) { return false; } if ($sec == "g") { // remove the old rule $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, $oldRules); // add the new rule $this->buildIncrementalRoleLinks(Policy::POLICY_ADD, $ptype, $newRules); } return true; } /** * Removes a rule from the current policy without notify. * * @param string $sec * @param string $ptype * @param array $rule * * @return bool */ protected function removePolicyWithoutNotifyInternal(string $sec, string $ptype, array $rule): bool { if ($this->shouldPersist()) { try { $this->adapter->removePolicy($sec, $ptype, $rule); } catch (NotImplementedException $e) { } } $ruleRemoved = $this->model->removePolicy($sec, $ptype, $rule); if (!$ruleRemoved) { return false; } if ($sec == "g") { $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, [$rule]); } return true; } /** * Removes rules from the current policy without notify. * * @param string $sec * @param string $ptype * @param array $rules * * @return bool */ protected function removePoliciesWithoutNotifyInternal(string $sec, string $ptype, array $rules): bool { if (!$this->model->hasPolicies($sec, $ptype, $rules)) { return false; } if ($this->shouldPersist() && $this->adapter instanceof BatchAdapter) { try { $this->adapter->removePolicies($sec, $ptype, $rules); } catch (NotImplementedException $e) { } } $ruleRemoved = $this->model->removePolicies($sec, $ptype, $rules); if (!$ruleRemoved) { return false; } if ($sec == "g") { $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, $rules); } return true; } /** * Removes rules based on field filters from the current policy without notify. * * @param string $sec * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ protected function removeFilteredPolicyWithoutNotifyInternal(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): bool { if ($this->shouldPersist()) { try { $this->adapter->removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); } catch (NotImplementedException $e) { } } $ruleRemoved = $this->model->removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); if (!$ruleRemoved) { return false; } if ($sec == "g") { $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, $ruleRemoved); } return true; } /** * Updates rules based on field filters from the current policy without notify. * * @param string $sec * @param string $ptype * @param array $newRules * @param int $fieldIndex * @param string ...$fieldValues * * @return array */ protected function updateFilteredPoliciesWithoutNotifyInternal(string $sec, string $ptype, array $newRules, int $fieldIndex, string ...$fieldValues): array { $oldRules = []; if ($this->shouldPersist()) { try { if ($this->adapter instanceof UpdatableAdapter) { $oldRules = $this->adapter->updateFilteredPolicies($sec, $ptype, $newRules, $fieldIndex, ...$fieldValues); } } catch (NotImplementedException $e) { } } $ruleChanged = $this->model->removePolicies($sec, $ptype, $oldRules); $this->model->addPolicies($sec, $ptype, $newRules); $ruleChanged = $ruleChanged && count($newRules) !== 0; if (!$ruleChanged) { return []; } if ($sec == "g") { // remove the old rules $this->buildIncrementalRoleLinks(Policy::POLICY_REMOVE, $ptype, $oldRules); // add the new rules $this->buildIncrementalRoleLinks(Policy::POLICY_ADD, $ptype, $newRules); } return $oldRules; } /** * Adds a rule to the current policy. * * @param string $sec * @param string $ptype * @param array $rule * * @return bool */ protected function addPolicyInternal(string $sec, string $ptype, array $rule): bool { if (!$this->addPolicyWithoutNotifyInternal($sec, $ptype, $rule)) { return false; } if ($this->shouldNotify()) { if ($this->watcher instanceof WatcherEx) { $this->watcher->updateForAddPolicy($sec, $ptype, ...$rule); } else { $this->watcher->update(); } } return true; } /** * Adds rules to the current policy. * * @param string $sec * @param string $ptype * @param array $rules * * @return bool * @throws Exceptions\CasbinException */ protected function addPoliciesInternal(string $sec, string $ptype, array $rules, bool $autoRemoveRepeat): bool { if (!$this->addPoliciesWithoutNotifyInternal($sec, $ptype, $rules, $autoRemoveRepeat)) { return false; } if ($this->shouldNotify()) { $this->watcher->update(); } return true; } /** * Updates a rule from the current policy. * * @param string $sec * @param string $ptype * @param string[] $oldRule * @param string[] $newRule * * @return bool */ protected function updatePolicyInternal(string $sec, string $ptype, array $oldRule, array $newRule): bool { if (!$this->updatePolicyWithoutNotifyInternal($sec, $ptype, $oldRule, $newRule)) { return false; } if ($this->shouldNotify()) { try { if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicy($oldRule, $newRule); } else { $this->watcher->update(); } } catch (\Exception $e) { $this->logger->logError($e); return false; } } return true; } /** * Updates rules from the current policy. * * @param string $sec * @param string $ptype * @param string[][] $oldRules * @param string[][] $newRules * * @return bool */ protected function updatePoliciesInternal(string $sec, string $ptype, array $oldRules, array $newRules): bool { if (!$this->updatePoliciesWithoutNotifyInternal($sec, $ptype, $oldRules, $newRules)) { return false; } if ($this->shouldNotify()) { try { if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicies($oldRules, $newRules); } else { $this->watcher->update(); } } catch (\Exception $e) { $this->logger->logError($e); return false; } } return true; } /** * Removes a rule from the current policy. * * @param string $sec * @param string $ptype * @param array $rule * * @return bool */ protected function removePolicyInternal(string $sec, string $ptype, array $rule): bool { if (!$this->removePolicyWithoutNotifyInternal($sec, $ptype, $rule)) { return false; } if ($this->shouldNotify()) { if ($this->watcher instanceof WatcherEx) { $this->watcher->updateForRemovePolicy($sec, $ptype, ...$rule); } else { $this->watcher->update(); } } return true; } /** * Removes a rules from the current policy. * * @param string $sec * @param string $ptype * @param array $rules * * @return bool */ protected function removePoliciesInternal(string $sec, string $ptype, array $rules): bool { if (!$this->removePoliciesWithoutNotifyInternal($sec, $ptype, $rules)) { return false; } if ($this->shouldNotify()) { // error intentionally ignored $this->watcher->update(); } return true; } /** * Removes rules based on field filters from the current policy. * * @param string $sec * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ protected function removeFilteredPolicyInternal(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): bool { if (!$this->removeFilteredPolicyWithoutNotifyInternal($sec, $ptype, $fieldIndex, ...$fieldValues)) { return false; } if ($this->shouldNotify()) { // error intentionally ignored if ($this->watcher instanceof WatcherEx) { $this->watcher->updateForRemoveFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); } else { $this->watcher->update(); } } return true; } /** * Removes rules based on field filters from the current policy. * * @param string $sec * @param string $ptype * @param array $newRules * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ protected function updateFilteredPoliciesInternal(string $sec, string $ptype, array $newRules, int $fieldIndex, string ...$fieldValues): bool { $oldRules = $this->updateFilteredPoliciesWithoutNotifyInternal($sec, $ptype, $newRules, $fieldIndex, ...$fieldValues); if (count($oldRules) === 0) { return false; } if ($this->shouldNotify()) { // error intentionally ignored if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicies($oldRules, $newRules); } else { $this->watcher->update(); } return true; } return true; } } src/Log/Log.php000064400000003657152475271650007337 0ustar00logModel($model); } /** * Log enforcer information. * * @param string $matcher * @param array $request * @param bool $result * @param array $explains * * @return void */ public static function logEnforce(string $matcher, array $request, bool $result, array $explains): void { self::$logger->logEnforce($matcher, $request, $result, $explains); } /** * Log role information. * * @param array $roles * * @return void */ public static function logRole(array $roles): void { self::$logger->logRole($roles); } /** * Log policy information. * * @param array $policy * * @return void */ public static function logPolicy(array $policy): void { self::$logger->logPolicy($policy); } /** * Log error information. * * @param \Exception $err * @param string ...$msg * * @return void */ public static function logError(\Exception $err, string ...$msg): void { self::$logger->logError($err, ...$msg); } } Log::setLogger(new DefaultLogger()); src/Log/Logger.php000064400000002567152475271650010034 0ustar00psrLogger = $psrLogger; return; } $this->psrLogger = new class extends AbstractLogger { public string $path = ''; public function __construct() { $this->path = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'casbin.log'; } public function log($level, $message, array $context = []): void { $timestamp = date('Y-m-d H:i:s'); $message = (string) $message; foreach ($context as $key => $value) { $message = str_replace("{{$key}}", (string) $value, $message); } $content = sprintf("[%s] %s: %s" . PHP_EOL, $timestamp, strtoupper($level), $message); file_put_contents($this->path, $content, FILE_APPEND | LOCK_EX); } }; } /** * enableLog. * * @param bool $enable */ public function enableLog(bool $enable): void { $this->enabled = $enable; } /** * @return bool */ public function isEnabled(): bool { return $this->enabled; } /** * Log model information. * * @param array $model * * @return void */ public function logModel(array $model): void { if (!$this->enabled) { return; } $str = ''; foreach ($model as $v) { $str .= sprintf("%s " . PHP_EOL, '[' . implode(' ', $v) . ']'); } $this->psrLogger->info('Model: {info}', ['info' => $str]); } /** * Log enforcer information. * * @param string $matcher * @param array $request * @param bool $result * @param array $explains * * @return void */ public function logEnforce(string $matcher, array $request, bool $result, array $explains): void { if (!$this->enabled) { return; } $reqStr = implode(', ', array_values($request)); $reqStr .= sprintf(" ---> %s" . PHP_EOL, var_export($result, true)); $hpStr = implode(', ', array_values($explains)); if (count($explains) > 0) { $hpStr .= PHP_EOL; } $this->psrLogger->info('Request: {request}Hit Policy: {hitPolicy}', ['request' => $reqStr, 'hitPolicy' => $hpStr]); } /** * Log policy information. * * @param array $policy * * @return void */ public function logPolicy(array $policy): void { if (!$this->enabled) { return; } $str = ''; foreach ($policy as $ptype => $ast) { $str .= $ptype . ' : ['; foreach ($ast as $rule) { $str .= '[' . implode(' ', $rule) . '] '; } $str .= PHP_EOL; } if ($str !== '') { $str = rtrim($str) . ']'; } $this->psrLogger->info('Policy: {policy}', ['policy' => $str]); } /** * Log role information. * * @param array $roles * * @return void */ public function logRole(array $roles): void { if (!$this->enabled) { return; } $this->psrLogger->info('Roles: {roles}', ['roles' => implode(', ', $roles)]); } /** * Log error information. * * @param \Exception $err * @param string ...$msg * * @return void */ public function logError(\Exception $err, string ...$msg): void { if (!$this->enabled) { return; } $errStr = $err->getMessage(); if (!empty($msg)) { $errStr .= ' ' . implode(' ', $msg); } $this->psrLogger->error($errStr); } } src/ManagementEnforcer.php000064400000053104152475271650011625 0ustar00model->getValuesForFieldInPolicyAllTypesByName('p', Constants::SUBJECT_INDEX); } /** * Gets the list of subjects that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedSubjects(string $ptype): array { $fieldIndex = $this->model->getFieldIndex('p', Constants::SUBJECT_INDEX); return $this->model->getValuesForFieldInPolicy('p', $ptype, $fieldIndex); } /** * Gets the list of objects that show up in the current policy. * * @return array */ public function getAllObjects(): array { return $this->model->getValuesForFieldInPolicyAllTypesByName('p', Constants::OBJECT_INDEX); } /** * Gets the list of objects that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedObjects(string $ptype): array { $fieldIndex = $this->model->getFieldIndex('p', Constants::OBJECT_INDEX); return $this->model->getValuesForFieldInPolicy('p', $ptype, $fieldIndex); } /** * Gets the list of actions that show up in the current policy. * * @return array */ public function getAllActions(): array { return $this->model->getValuesForFieldInPolicyAllTypesByName('p', Constants::ACTION_INDEX); } /** * Gets the list of actions that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedActions(string $ptype): array { $fieldIndex = $this->model->getFieldIndex('p', Constants::ACTION_INDEX); return $this->model->getValuesForFieldInPolicy('p', $ptype, $fieldIndex); } /** * Gets the list of roles that show up in the current policy. * * @return array */ public function getAllRoles(): array { return $this->model->getValuesForFieldInPolicyAllTypes('g', 1); } /** * Gets the list of roles that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedRoles(string $ptype): array { return $this->model->getValuesForFieldInPolicy('g', $ptype, 1); } /** * Gets all the authorization rules in the policy. * * @return array */ public function getPolicy(): array { return $this->getNamedPolicy('p'); } /** * Gets all the authorization rules in the policy, field filters can be specified. * * @param int $fieldIndex * @param string ...$fieldValues * * @return array */ public function getFilteredPolicy(int $fieldIndex, string ...$fieldValues): array { return $this->getFilteredNamedPolicy('p', $fieldIndex, ...$fieldValues); } /** * Gets all the authorization rules in the named policy. * * @param string $ptype * * @return array */ public function getNamedPolicy(string $ptype): array { return $this->model->getPolicy('p', $ptype); } /** * Gets all the authorization rules in the named policy, field filters can be specified. * * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return array */ public function getFilteredNamedPolicy(string $ptype, int $fieldIndex, string ...$fieldValues): array { return $this->model->getFilteredPolicy('p', $ptype, $fieldIndex, ...$fieldValues); } /** * Gets all the role inheritance rules in the policy. * * @return array */ public function getGroupingPolicy(): array { return $this->getNamedGroupingPolicy('g'); } /** * Gets all the role inheritance rules in the policy, field filters can be specified. * * @param int $fieldIndex * @param string ...$fieldValues * * @return array */ public function getFilteredGroupingPolicy(int $fieldIndex, string ...$fieldValues): array { return $this->getFilteredNamedGroupingPolicy('g', $fieldIndex, ...$fieldValues); } /** * Gets all the role inheritance rules in the policy. * * @param string $ptype * * @return array */ public function getNamedGroupingPolicy(string $ptype): array { return $this->model->getPolicy('g', $ptype); } /** * Gets all the role inheritance rules in the policy, field filters can be specified. * * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return array */ public function getFilteredNamedGroupingPolicy(string $ptype, int $fieldIndex, string ...$fieldValues): array { return $this->model->getFilteredPolicy('g', $ptype, $fieldIndex, ...$fieldValues); } /** * Determines whether an authorization rule exists. * * @param mixed ...$params * * @return bool */ public function hasPolicy(...$params): bool { return $this->hasNamedPolicy('p', ...$params); } /** * Determines whether a named authorization rule exists. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function hasNamedPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } return $this->model->hasPolicy('p', $ptype, $params); } /** * 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. * * @param mixed ...$params * * @return bool */ public function addPolicy(...$params): bool { return $this->addNamedPolicy('p', ...$params); } /** * AddPolicies adds authorization rules to the current policy. * If the rule already exists, the function returns false for the corresponding rule and the rule will not be added. * Otherwise the function returns true for the corresponding rule by adding the new rule. * * @param string[][] $rules * * @return bool * @throws Exceptions\CasbinException */ public function addPolicies(array $rules): bool { return $this->addNamedPolicies('p', $rules); } /** * AddPoliciesEx adds authorization rules to the current policy. * If the rule already exists, the rule will not be added. * But unlike AddPolicies, other non-existent rules are added instead of returning false directly. * * @param string[][] $rules * * @return bool */ public function addPoliciesEx(array $rules): bool { return $this->addNamedPoliciesEx('p', $rules); } /** * 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. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function addNamedPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } return $this->addPolicyInternal('p', $ptype, $params); } /** * AddNamedPolicies adds authorization rules to the current named policy. * If the rule already exists, the function returns false for the corresponding rule and the rule will not be added. * Otherwise the function returns true for the corresponding by adding the new rule. * * @param string $ptype * @param string[][] $rules * * @return bool * @throws Exceptions\CasbinException */ public function addNamedPolicies(string $ptype, array $rules): bool { return $this->addPoliciesInternal('p', $ptype, $rules, false); } /** * AddNamedPoliciesEx adds authorization rules to the current named policy. * If the rule already exists, the rule will not be added. * But unlike AddNamedPolicies, other non-existent rules are added instead of returning false directly. * * @param string $ptype * @param string[][] $rules * * @return bool */ public function addNamedPoliciesEx(string $ptype, array $rules): bool { return $this->addPoliciesInternal('p', $ptype, $rules, true); } /** * Removes an authorization rule from the current policy. * * @param mixed ...$params * * @return bool */ public function removePolicy(...$params): bool { return $this->removeNamedPolicy('p', ...$params); } /** * Removes an authorization rules from the current policy. * * @param array $rules * * @return bool */ public function removePolicies(array $rules): bool { return $this->removeNamedPolicies('p', $rules); } /** * Removes an authorization rule from the current policy. * * @param string[] $oldRule * @param string[] $newRule * * @return bool */ public function updatePolicy(array $oldRule, array $newRule): bool { return $this->updateNamedPolicy("p", $oldRule, $newRule); } /** * Updates an authorization rule from the current policy. * * @param string $ptype * @param string[] $oldRule * @param string[] $newRule * * @return bool */ public function updateNamedPolicy(string $ptype, array $oldRule, array $newRule): bool { return $this->updatePolicyInternal("p", $ptype, $oldRule, $newRule); } /** * UpdatePolicies updates authorization rules from the current policies. * * @param string[][] $oldPolices * @param string[][] $newPolicies * @return boolean */ public function updatePolicies(array $oldPolices, array $newPolicies): bool { return $this->updateNamedPolicies("p", $oldPolices, $newPolicies); } /** * Updates authorization rules from the current policy. * * @param string $ptype * @param string[][] $oldPolices * @param string[][] $newPolicies * @return boolean */ public function updateNamedPolicies(string $ptype, array $oldPolices, array $newPolicies): bool { return $this->updatePoliciesInternal("p", $ptype, $oldPolices, $newPolicies); } public function updateFilteredPolicies(array $newPolicies, int $fieldIndex, string ...$fieldValues): bool { return $this->updateFilteredNamedPolicies("p", $newPolicies, $fieldIndex, ...$fieldValues); } /** * Undocumented function * * @param string $ptype * @param array $newPolicies * @param integer $fieldIndex * @param string ...$fieldValues * @return boolean */ public function updateFilteredNamedPolicies(string $ptype, array $newPolicies, int $fieldIndex, string ...$fieldValues): bool { return $this->updateFilteredPoliciesInternal("p", $ptype, $newPolicies, $fieldIndex, ...$fieldValues); } /** * Removes an authorization rule from the current policy, field filters can be specified. * * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ public function removeFilteredPolicy(int $fieldIndex, string ...$fieldValues): bool { return $this->removeFilteredNamedPolicy('p', $fieldIndex, ...$fieldValues); } /** * Removes an authorization rule from the current named policy. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function removeNamedPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } return $this->removePolicyInternal('p', $ptype, $params); } /** * Removes an authorization rules from the current named policy. * * @param string $ptype * @param array $rules * * @return bool */ public function removeNamedPolicies(string $ptype, array $rules): bool { return $this->removePoliciesInternal('p', $ptype, $rules); } /** * Removes an authorization rule from the current named policy, field filters can be specified. * * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ public function removeFilteredNamedPolicy(string $ptype, int $fieldIndex, string ...$fieldValues): bool { return $this->removeFilteredPolicyInternal('p', $ptype, $fieldIndex, ...$fieldValues); } /** * Determines whether a role inheritance rule exists. * * @param mixed ...$params * * @return bool */ public function hasGroupingPolicy(...$params): bool { return $this->hasNamedGroupingPolicy('g', ...$params); } /** * Determines whether a named role inheritance rule exists. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function hasNamedGroupingPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } return $this->model->hasPolicy('g', $ptype, $params); } /** * 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. * * @param mixed ...$params * * @return bool */ public function addGroupingPolicy(...$params): bool { return $this->addNamedGroupingPolicy('g', ...$params); } /** * AddGroupingPolicy adds a role inheritance rules 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. * * @param array $rules * * @return bool */ public function addGroupingPolicies(array $rules): bool { return $this->addNamedGroupingPolicies('g', $rules); } /** * AddGroupingPolicyEx adds a role inheritance rules to the current policy. * If the rule already exists, the rule will not be added. * But unlike AddGroupingPolicy, other non-existent rules are added instead of returning false directly. * * @param array $rules * * @return bool */ public function addGroupingPoliciesEx(array $rules): bool { return $this->addNamedGroupingPoliciesEx('g', $rules); } /** * 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. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function addNamedGroupingPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } $ruleAdded = $this->addPolicyInternal('g', $ptype, $params); return $ruleAdded; } /** * AddNamedGroupingPolicy adds a named role inheritance rules 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. * * @param string $ptype * @param array $rules * * @return bool */ public function addNamedGroupingPolicies(string $ptype, array $rules): bool { return $this->addPoliciesInternal('g', $ptype, $rules, false); } public function addNamedGroupingPoliciesEx(string $ptype, array $rules): bool { return $this->addPoliciesInternal('g', $ptype, $rules, true); } /** * Removes a role inheritance rule from the current policy. * * @param mixed ...$params * * @return bool */ public function removeGroupingPolicy(...$params): bool { return $this->removeNamedGroupingPolicy('g', ...$params); } /** * Removes a role inheritance rules from the current policy. * * @param array $rules * * @return bool */ public function removeGroupingPolicies(array $rules): bool { return $this->removeNamedGroupingPolicies('g', $rules); } /** * Removes a role inheritance rule from the current policy, field filters can be specified. * * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ public function removeFilteredGroupingPolicy(int $fieldIndex, string ...$fieldValues): bool { return $this->removeFilteredNamedGroupingPolicy('g', $fieldIndex, ...$fieldValues); } /** * Removes a role inheritance rule from the current named policy. * * @param string $ptype * @param mixed ...$params * * @return bool */ public function removeNamedGroupingPolicy(string $ptype, ...$params): bool { if (1 == count($params) && is_array($params[0])) { $params = $params[0]; } $ruleRemoved = $this->removePolicyInternal('g', $ptype, $params); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleRemoved; } /** * Removes a role inheritance rules from the current named policy. * * @param string $ptype * @param array $rules * * @return bool */ public function removeNamedGroupingPolicies(string $ptype, array $rules): bool { $ruleRemoved = $this->removePoliciesInternal('g', $ptype, $rules); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleRemoved; } /** * Removes a role inheritance rule from the current named policy, field filters can be specified. * * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return bool */ public function removeFilteredNamedGroupingPolicy(string $ptype, int $fieldIndex, string ...$fieldValues): bool { $ruleRemoved = $this->removeFilteredPolicyInternal('g', $ptype, $fieldIndex, ...$fieldValues); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleRemoved; } /** * Adds a customized function. * * @param string $name * @param Closure $func */ public function addFunction(string $name, Closure $func): void { $this->fm->addFunction($name, $func); } /** * Adds 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. * * @param string $sec * @param string $ptype * @param string[] $params * * @return void */ public function selfAddPolicy(string $sec, string $ptype, array $params): void { $this->addPolicyWithoutNotifyInternal($sec, $ptype, $params); } /** * Adds authorization rules to the current policy. * If the rule already exists, the function returns false for the corresponding rule and the rule will not be added. * Otherwise the function returns true for the corresponding rule by adding the new rule. * * @param string $sec * @param string $ptype * @param string[][] $params * * @return bool */ public function selfAddPolices(string $sec, string $ptype, array $params): bool { return $this->addPoliciesWithoutNotifyInternal($sec, $ptype, $params, false); } /** * Adds authorization rules to the current named policy with autoNotifyWatcher disabled. * If the rule already exists, the rule will not be added. * But unlike SelfAddPolicies, other non-existent rules are added instead of returning false directly * * @param string $sec * @param string $ptype * @param string[][] $params * * @return bool */ public function selfAddPolicesEx(string $sec, string $ptype, array $params): bool { return $this->addPoliciesWithoutNotifyInternal($sec, $ptype, $params, true); } /** * Gets the index for a given ptype and field. * * @param string $ptype * @param string $field * * @return int $fieldIndex * @throws Exceptions\CasbinException */ public function getFieldIndex(string $ptype, string $field): int { return $this->model->getFieldIndex($ptype, $field); } /** * Sets the index for a given ptype and field. * * @param string $ptype * @param string $field * @param int $index */ public function setFieldIndex(string $ptype, string $field, int $index): void { $this->model->setFieldIndex($ptype, $field, $index); } } src/Model/Assertion.php000064400000014173152475271650011077 0ustar00 */ public array $policyMap = []; /** * $rm. * * @var RoleManager|null */ public ?RoleManager $rm = null; /** * $condRmMap * * @var ConditionalRoleManager|null */ public ?ConditionalRoleManager $condRm = null; /** * $fieldIndexMap * * @var array */ public array $fieldIndexMap = []; /** * $logger. * * @var Logger|null */ public ?Logger $logger = null; /** * Sets the current logger. * * @param Logger $logger * * @return void */ public function setLogger($logger): void { $this->logger = $logger; } /** * @param RoleManager $rm * * @throws CasbinException */ public function buildRoleLinks(RoleManager $rm): void { $this->rm = $rm; $count = substr_count($this->value, '_'); if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } foreach ($this->policy as $rule) { if (count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (count($rule) > $count) { $rule = array_slice($rule, 0, $count); } $this->rm->addLink($rule[0], $rule[1], ...array_slice($rule, 2)); } } /** * @param RoleManager $rm * @param integer $op * @param string[][] $rules * * @return void * * @throws CasbinException */ public function buildIncrementalRoleLinks(RoleManager $rm, int $op, array $rules): void { $this->rm = $rm; $count = substr_count($this->value, '_'); if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } foreach ($rules as $rule) { if (count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (count($rule) > $count) { $rule = array_slice($rule, 0, $count); } match ($op) { Policy::POLICY_ADD => $this->rm->addLink($rule[0], $rule[1], ...array_slice($rule, 2)), Policy::POLICY_REMOVE => $this->rm->deleteLink($rule[0], $rule[1], ...array_slice($rule, 2)), default => throw new CasbinException('invalid policy operation') }; } } /** * @param ConditionalRoleManager $condRm * * @return void * * @throws CasbinException */ public function buildConditionalRoleLinks(ConditionalRoleManager $condRm): void { $this->condRm = $condRm; $count = substr_count($this->value, '_'); if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } foreach ($this->policy as $rule) { if (count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (count($rule) > $count) { $rule = array_slice($rule, 0, $count); } $domainRule = array_slice($rule, 2, count($this->tokens) - 2); $this->addConditionalRoleLink($rule, $domainRule); } } /** * @param ConditionalRoleManager $condRm * @param integer $op * @param string[][] $rules * * @return void * * @throws CasbinException */ public function buildIncrementalConditionalRoleLinks(ConditionalRoleManager $condRm, int $op, array $rules): void { $this->condRm = $condRm; $count = substr_count($this->value, '_'); if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } foreach ($rules as $rule) { if (count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (count($rule) > $count) { $rule = array_slice($rule, 0, $count); } $domainRule = array_slice($rule, 2, count($this->tokens) - 2); match ($op) { Policy::POLICY_ADD => $this->addConditionalRoleLink($rule, $domainRule), Policy::POLICY_REMOVE => $this->condRm->deleteLink($rule[0], $rule[1], ...array_slice($rule, 2)), default => throw new CasbinException('invalid policy operation') }; } } /** * @param array $rule * @param array $domainRule * * @return void */ public function addConditionalRoleLink(array $rule, array $domainRule): void { if (count($domainRule) === 0) { $this->condRm->addLink($rule[0], $rule[1]); $this->condRm->setLinkConditionFuncParams($rule[0], $rule[1], ...array_slice($rule, count($this->tokens))); } else { $domain = $domainRule[0]; $this->condRm->addLink($rule[0], $rule[1], $domain); $this->condRm->setDomainLinkConditionFuncParams($rule[0], $rule[1], $domain, ...array_slice($rule, count($this->tokens))); } } } src/Model/FunctionMap.php000064400000003350152475271650011346 0ustar00 */ private array $functions = []; /** * @param string $name * @param Closure $func */ public function addFunction(string $name, Closure $func): void { $this->functions[$name] = $func; } /** * Loads an initial function map. * * @return FunctionMap */ public static function loadFunctionMap(): self { $fm = new self(); $fm->addFunction('keyMatch', fn(...$args) => BuiltinOperations::keyMatchFunc(...$args)); $fm->addFunction('keyGet', fn(...$args) => BuiltinOperations::keyGetFunc(...$args)); $fm->addFunction('keyMatch2', fn(...$args) => BuiltinOperations::keyMatch2Func(...$args)); $fm->addFunction('keyGet2', fn(...$args) => BuiltinOperations::keyGet2Func(...$args)); $fm->addFunction('keyMatch3', fn(...$args) => BuiltinOperations::keyMatch3Func(...$args)); $fm->addFunction('keyMatch4', fn(...$args) => BuiltinOperations::keyMatch4Func(...$args)); $fm->addFunction('keyMatch5', fn(...$args) => BuiltinOperations::keyMatch5Func(...$args)); $fm->addFunction('regexMatch', fn(...$args) => BuiltinOperations::regexMatchFunc(...$args)); $fm->addFunction('ipMatch', fn(...$args) => BuiltinOperations::ipMatchFunc(...$args)); $fm->addFunction('globMatch', fn(...$args) => BuiltinOperations::globMatchFunc(...$args)); return $fm; } /** * @return array */ public function getFunctions(): array { return $this->functions; } } src/Model/Model.php000064400000024320152475271650010163 0ustar00 */ protected array $sectionNameMap = [ 'r' => 'request_definition', 'p' => 'policy_definition', 'g' => 'role_definition', 'e' => 'policy_effect', 'm' => 'matchers', ]; /** * @var string */ protected string $paramsRegex = '/\((.*?)\)/'; public function __construct() { $this->setLogger(new DefaultLogger()); } public function __clone() { $this->sectionNameMap = $this->sectionNameMap; $newAstMap = []; foreach ($this->items as $ptype => $ast) { foreach ($ast as $i => $v) { $newAstMap[$ptype][$i] = clone $v; } } $this->items = $newAstMap; } /** * @param ConfigContract $cfg * @param string $sec * @param string $key * * @return bool * @throws CasbinException */ private function loadAssertion(ConfigContract $cfg, string $sec, string $key): bool { $value = $cfg->getString($this->sectionNameMap[$sec] . '::' . $key); return $this->addDef($sec, $key, $value); } /** * Get ParamsToken from Assertion.Value * * @param string $value * * @return array */ private function getParamsToken(string $value): array { if (!preg_match($this->paramsRegex, $value, $paramsString)) { return []; }; $paramsString = trim(substr($paramsString[0], 1, -1)); return explode(',', $paramsString); } /** * Adds an assertion to the model. * * @param string $sec * @param string $key * @param string $value * * @return bool * @throws CasbinException */ public function addDef(string $sec, string $key, string $value): bool { if ('' == $value) { return false; } $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 . '_' . trim($token); } } else if ('g' == $sec) { $ast->paramsTokens = $this->getParamsToken($ast->value); $ast->tokens = explode(',', $ast->value); $ast->tokens = array_slice($ast->tokens, 0, count($ast->tokens) - count($ast->paramsTokens)); } else { $ast->value = Util::removeComments(Util::escapeAssertion($ast->value)); } $this->items[$sec][$key] = $ast; return true; } /** * @param int $i * * @return string */ private function getKeySuffix(int $i): string { if (1 == $i) { return ''; } return (string)$i; } /** * @param ConfigContract $cfg * @param string $sec * @throws CasbinException */ private function loadSection(ConfigContract $cfg, string $sec): void { $i = 1; for (;;) { if (!$this->loadAssertion($cfg, $sec, $sec . $this->getKeySuffix($i))) { break; } else { ++$i; } } } /** * Creates an empty model. * * @return Model */ public static function newModel(): self { return new self(); } /** * Creates a model from a .CONF file. * * @param string $path * * @return Model * @throws CasbinException */ public static function newModelFromFile(string $path): self { $m = self::newModel(); $m->loadModel($path); return $m; } /** * Creates a model from a string which contains model text. * * @param string $text * * @return Model * @throws CasbinException */ public static function newModelFromString(string $text): self { $m = self::newModel(); $m->loadModelFromText($text); return $m; } /** * Loads the model from model CONF file. * * @param string $path * @throws CasbinException */ public function loadModel(string $path): void { $cfg = Config::newConfig($path); $this->loadSection($cfg, 'r'); $this->loadSection($cfg, 'p'); $this->loadSection($cfg, 'e'); $this->loadSection($cfg, 'm'); $this->loadSection($cfg, 'g'); } /** * Loads the model from the text. * * @param string $text * @throws CasbinException */ public function loadModelFromText(string $text): void { $cfg = Config::newConfigFromText($text); $this->loadSection($cfg, 'r'); $this->loadSection($cfg, 'p'); $this->loadSection($cfg, 'e'); $this->loadSection($cfg, 'm'); $this->loadSection($cfg, 'g'); } /** * Prints the model to the log. */ public function printModel(): void { if (!$this->getLogger()->isEnabled()) { return; } $modelInfo = []; foreach ($this->items as $sec => $astMap) { foreach ($astMap as $key => $ast) { $modelInfo[] = [$sec, $key, $ast->value]; } } $this->getLogger()->logModel($modelInfo); } /** * Loads an initial function map. * * @return FunctionMap */ public static function loadFunctionMap(): FunctionMap { return FunctionMap::loadFunctionMap(); } public function getNameWithDomain(string $domain, string $name): string { return $domain . self::DEFAULT_SEPARATOR . $name; } public function getSubjectHierarchyMap(array $policies): array { $subjectHierarchyMap = []; // Tree structure of role $policyMap = []; foreach ($policies as $policy) { if (count($policy) < 2) { throw new CasbinException('policy g expect 2 more params'); } $domain = self::DEFAULT_DOMAIN; if (count($policy) != 2) { $domain = $policy[2]; } $child = $this->getNameWithDomain($domain, $policy[0]); $parent = $this->getNameWithDomain($domain, $policy[1]); $policyMap[$parent][] = $child; if (!isset($subjectHierarchyMap[$child])) { $subjectHierarchyMap[$child] = 0; } if (!isset($subjectHierarchyMap[$parent])) { $subjectHierarchyMap[$parent] = 0; } $subjectHierarchyMap[$child] = 1; } // Use queues for levelOrder $queue = []; foreach ($subjectHierarchyMap as $k => $v) { $root = $k; if ($v != 0) { continue; } $lv = 0; $queue[] = $root; while (count($queue) != 0) { $sz = count($queue); for ($i = 0; $i < $sz; $i++) { $node = $queue[array_key_first($queue)]; unset($queue[array_key_first($queue)]); $nodeValue = $node; $subjectHierarchyMap[$nodeValue] = $lv; if (isset($policyMap[$nodeValue])) { foreach ($policyMap[$nodeValue] as $child) { $queue[] = $child; } } } $lv++; } } return $subjectHierarchyMap; } public function sortPoliciesBySubjectHierarchy(): void { if ($this->items['e']['e']->value != Constants::SUBJECT_PRIORITY_EFFECT) { return; } $subIndex = 0; foreach ($this->items['p'] as $ptype => $assertion) { try { $domainIndex = $this->getFieldIndex($ptype, Constants::DOMAIN_INDEX); } catch (CasbinException) { $domainIndex = -1; } $policies = &$assertion->policy; $subjectHierarchyMap = $this->getSubjectHierarchyMap($this->items['g']['g']->policy); usort($policies, function ($i, $j) use ($subIndex, $domainIndex, $subjectHierarchyMap): int { $domain1 = self::DEFAULT_DOMAIN; $domain2 = self::DEFAULT_DOMAIN; if ($domainIndex != -1) { $domain1 = $i[$domainIndex]; $domain2 = $j[$domainIndex]; } $name1 = $this->getNameWithDomain($domain1, $i[$subIndex]); $name2 = $this->getNameWithDomain($domain2, $j[$subIndex]); $p1 = $subjectHierarchyMap[$name1] ?? 0; $p2 = $subjectHierarchyMap[$name2] ?? 0; if ($p1 == $p2) { return 0; } return ($p1 > $p2) ? -1 : 1; }); foreach ($assertion->policy as $i => $policy) { $assertion->policyMap[implode(',', $policy)] = $i; } } } public function sortPoliciesByPriority(): void { foreach ($this->items['p'] as $ptype => $assertion) { try { $priorityIndex = $this->getFieldIndex($ptype, Constants::PRIORITY_INDEX); } catch (CasbinException) { continue; } $policies = &$assertion->policy; usort($policies, function ($i, $j) use ($priorityIndex): int { $p1 = $i[$priorityIndex]; $p2 = $j[$priorityIndex]; if ($p1 == $p2) { return 0; } return ($p1 < $p2) ? -1 : 1; }); foreach ($assertion->policy as $i => $policy) { $assertion->policyMap[implode(',', $policy)] = $i; } } } } src/Model/Policy.php000064400000044001152475271650010360 0ustar00> * @author techlee@qq.com */ abstract class Policy implements ArrayAccess { public const POLICY_ADD = 0; public const POLICY_REMOVE = 1; const DEFAULT_SEP = ","; /** * All of the Model items. * * @var array> */ protected array $items = []; /** * $logger. * * @var Logger|null */ protected ?Logger $logger = null; /** * BuildIncrementalRoleLinks provides incremental build the role inheritance relations. * * @param RoleManager[] $rmMap * @param integer $op * @param string $sec * @param string $ptype * @param string[][] $rules * @return void */ public function buildIncrementalRoleLinks(array $rmMap, int $op, string $sec, string $ptype, array $rules): void { if ($sec == "g" && isset($rmMap[$ptype]) && isset($this->items[$sec][$ptype])) { $this->items[$sec][$ptype]->buildIncrementalRoleLinks($rmMap[$ptype], $op, $rules); } } /** * Initializes the roles in RBAC. * * @param RoleManager[] $rmMap * @throws CasbinException */ public function buildRoleLinks(array $rmMap): void { $this->printPolicy(); if (!isset($this->items['g'])) { return; } foreach ($this->items['g'] as $ptype => $ast) { if (isset($rmMap[$ptype])) { $rm = $rmMap[$ptype]; $ast->buildRoleLinks($rm); } } } /** * BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations. * * @param ConditionalRoleManager[] $condRmMap * @param integer $op * @param string $sec * @param string $ptype * @param string[][] $rules * @return void */ public function buildIncrementalConditionalRoleLinks(array $condRmMap, int $op, string $sec, string $ptype, array $rules): void { if ($sec == "g" && isset($condRmMap[$ptype]) && isset($this->items[$sec][$ptype])) { $this->items[$sec][$ptype]->buildIncrementalConditionalRoleLinks($condRmMap[$ptype], $op, $rules); } } /** * Initializes the roles in RBAC with conditions. * * @param ConditionalRoleManager[] $condRmMap * @throws CasbinException */ public function buildConditionalRoleLinks(array $condRmMap): void { $this->printPolicy(); if (!isset($this->items['g'])) { return; } foreach ($this->items['g'] as $ptype => $ast) { if (isset($condRmMap[$ptype])) { $rm = $condRmMap[$ptype]; $ast->buildConditionalRoleLinks($rm); } } } /** * Prints the policy to log. */ public function printPolicy(): void { if (!$this->getLogger()->isEnabled()) { return; } $policy = []; foreach (['p', 'g'] as $sec) { if (!isset($this->items[$sec])) { continue; } foreach ($this->items[$sec] as $ptype => $ast) { $policy[$ptype] = array_merge( $policy[$ptype] ?? [], $ast->policy ); } } $this->getLogger()->logPolicy($policy); } /** * Clears all current policy. */ public function clearPolicy(): void { foreach (['p', 'g'] as $sec) { if (!isset($this->items[$sec])) { return; } foreach ($this->items[$sec] as $key => $ast) { $this->items[$sec][$key]->policy = []; $this->items[$sec][$key]->policyMap = []; } } } /** * Gets all rules in a policy. * * @param string $sec * @param string $ptype * * @return string[][] */ public function getPolicy(string $sec, string $ptype): array { return $this->items[$sec][$ptype]->policy; } /** * Gets rules based on field filters from a policy. * * @param string $sec * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @return string[][] */ public function getFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): array { $res = []; foreach ($this->items[$sec][$ptype]->policy as $rule) { $matched = true; foreach ($fieldValues as $i => $fieldValue) { if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) { $matched = false; break; } } if ($matched) { $res[] = $rule; } } return $res; } /** * Determines whether a model has the specified policy rule. * * @param string $sec * @param string $ptype * @param string[] $rule * * @return bool */ public function hasPolicy(string $sec, string $ptype, array $rule): bool { if (!isset($this->items[$sec][$ptype])) { return false; } return isset($this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)]); } /** * Determines whether a model has any of the specified policies. If one is found we return true. * * @param string $sec * @param string $ptype * @param string[][] $rules * * @return bool */ public function hasPolicies(string $sec, string $ptype, array $rules): bool { foreach ($rules as $rule) { if ($this->hasPolicy($sec, $ptype, $rule)) { return true; } } return false; } /** * Adds a policy rule to the model. * * @param string $sec * @param string $ptype * @param string[] $rule */ public function addPolicy(string $sec, string $ptype, array $rule): void { $assertion = &$this->items[$sec][$ptype]; $assertion->policy[] = $rule; $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($this->items[$sec][$ptype]->policy) - 1; $hasPriority = isset($assertion->fieldIndexMap[Constants::PRIORITY_INDEX]); if ($sec == 'p' && $hasPriority) { $idxInsert = $rule[$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]]; for ($i = count($assertion->policy) - 1; $i > 0; $i--) { $idx = $assertion->policy[$i - 1][$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]]; if ($idx > $idxInsert) { $assertion->policy[$i] = $assertion->policy[$i - 1]; $assertion->policyMap[implode(self::DEFAULT_SEP, $assertion->policy[$i - 1])]++; } else { break; } } $assertion->policy[$i] = $rule; $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = $i; } } /** * Adds a policy rules to the model. * * @param string $sec * @param string $ptype * @param string[][] $rules */ public function addPolicies(string $sec, string $ptype, array $rules): void { $this->addPoliciesWithAffected($sec, $ptype, $rules); } /** * Adds policy rules to the model, and returns affected rules. * * @param string $sec * @param string $ptype * @param string[][] $rules * * @return string[][] */ public function addPoliciesWithAffected(string $sec, string $ptype, array $rules): array { $affected = []; foreach ($rules as $rule) { $hashKey = implode(self::DEFAULT_SEP, $rule); if (isset($this->items[$sec][$ptype]->policyMap[$hashKey])) { continue; } $affected[] = $rule; $this->addPolicy($sec, $ptype, $rule); } return $affected; } /** * Updates a policy rule from the model. * * @param string $sec * @param string $ptype * @param string[] $oldRule * @param string[] $newRule * * @return bool */ public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newRule): bool { $oldPolicy = implode(self::DEFAULT_SEP, $oldRule); if (!isset($this->items[$sec][$ptype]->policyMap[$oldPolicy])) { return false; } $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy]; $this->items[$sec][$ptype]->policy[$index] = $newRule; unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]); $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRule)] = $index; return true; } /** * UpdatePolicies updates a policy rule from the model. * * @param string $sec * @param string $ptype * @param string[][] $oldRules * @param string[][] $newRules * @return boolean */ public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): bool { $modifiedRuleIndex = []; $newIndex = 0; foreach ($oldRules as $oldIndex => $oldRule) { $oldPolicy = implode(self::DEFAULT_SEP, $oldRule); $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy] ?? null; if (is_null($index)) { // rollback foreach ($modifiedRuleIndex as $index => $oldNewIndex) { $this->items[$sec][$ptype]->policy[$index] = $oldRules[$oldNewIndex[0]]; $oldPolicy = implode(self::DEFAULT_SEP, $oldRules[$oldNewIndex[0]]); $newPolicy = implode(self::DEFAULT_SEP, $newRules[$oldNewIndex[1]]); unset($this->items[$sec][$ptype]->policyMap[$newPolicy]); $this->items[$sec][$ptype]->policyMap[$oldPolicy] = $index; } return false; } $this->items[$sec][$ptype]->policy[$index] = $newRules[$newIndex]; unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]); $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRules[$newIndex])] = $index; $modifiedRuleIndex[$index] = [$oldIndex, $newIndex]; $newIndex++; } return true; } /** * Removes a policy rule from the model. * * @param string $sec * @param string $ptype * @param array $rule * * @return bool */ public function removePolicy(string $sec, string $ptype, array $rule): bool { if (!isset($this->items[$sec][$ptype])) { return false; } $hashKey = implode(self::DEFAULT_SEP, $rule); if (!isset($this->items[$sec][$ptype]->policyMap[$hashKey])) { return false; } $index = $this->items[$sec][$ptype]->policyMap[$hashKey]; array_splice($this->items[$sec][$ptype]->policy, $index, 1); unset($this->items[$sec][$ptype]->policyMap[$hashKey]); $count = count($this->items[$sec][$ptype]->policy); for ($i = $index; $i < $count; $i++) { $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $this->items[$sec][$ptype]->policy[$i])] = $i; } return true; } /** * Removes a policy rules from the model. * * @param string $sec * @param string $ptype * @param string[][] $rules * * @return bool */ public function removePolicies(string $sec, string $ptype, array $rules): bool { if (!isset($this->items[$sec][$ptype])) { return false; } foreach ($rules as $rule) { $this->removePolicy($sec, $ptype, $rule); } return true; } /** * Removes policy rules based on field filters from the model. * * @param string $sec * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * If more than one rule is removed, return the removed rule array, otherwise return false * @return string[][]|false */ public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues) { $tmp = []; $effects = []; $res = false; if (!isset($this->items[$sec][$ptype])) { return $res; } $this->items[$sec][$ptype]->policyMap = []; foreach ($this->items[$sec][$ptype]->policy as $index => $rule) { $matched = true; foreach ($fieldValues as $i => $fieldValue) { if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) { $matched = false; break; } } if ($matched) { $effects[] = $rule; } else { $tmp[] = $rule; $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($tmp) - 1; } } if (count($tmp) != count($this->items[$sec][$ptype]->policy)) { $this->items[$sec][$ptype]->policy = $tmp; $res = true; } return $res ? $effects : false; } /** * Gets all values for a field for all rules in a policy, duplicated values are removed. * * @param string $sec * @param string $ptype * @param int $fieldIndex * * @return string[] */ public function getValuesForFieldInPolicy(string $sec, string $ptype, int $fieldIndex): array { $values = []; if (!isset($this->items[$sec][$ptype])) { return $values; } foreach ($this->items[$sec][$ptype]->policy as $rule) { $values[] = $rule[$fieldIndex]; } Util::arrayRemoveDuplicates($values); return $values; } /** * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed. * * @param string $sec * @param int $fieldIndex * * @return string[] */ public function getValuesForFieldInPolicyAllTypes(string $sec, int $fieldIndex): array { $values = []; foreach ($this->items[$sec] as $key => $ptype) { $values = array_merge($values, $this->getValuesForFieldInPolicy($sec, $key, $fieldIndex)); } Util::arrayRemoveDuplicates($values); return $values; } /** * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed. * * @param string $sec * @param string $field * * @return array * @throws CasbinException */ public function getValuesForFieldInPolicyAllTypesByName(string $sec, string $field): array { $values = []; foreach ($this->items[$sec] as $ptype => $rules) { $index = $this->getFieldIndex($ptype, $field); $v = $this->getValuesForFieldInPolicy($sec, $ptype, $index); $values = array_merge($values, $v); } Util::arrayRemoveDuplicates($values); return $values; } /** * Gets the index for a given ptype and field. * * @param string $ptype * @param string $field * * @return int $fieldIndex * @throws CasbinException */ public function getFieldIndex(string $ptype, string $field): int { $assertion = &$this->items['p'][$ptype]; if (isset($assertion->fieldIndexMap[$field])) { return $assertion->fieldIndexMap[$field]; } $pattern = $ptype . '_' . $field; $index = -1; foreach ($assertion->tokens as $i => $token) { if ($token == $pattern) { $index = $i; break; } } if ($index == -1) { throw new CasbinException($field . ' index is not set, please use enforcer.SetFieldIndex() to set index'); } $assertion->fieldIndexMap[$field] = $index; return $index; } /** * Sets the index for a given ptype and field. * * @param string $ptype * @param string $field * @param int $index */ public function setFieldIndex(string $ptype, string $field, int $index): void { $assertion = &$this->items['p'][$ptype]; $assertion->fieldIndexMap[$field] = $index; } /** * Sets the current logger. * * @param Logger $logger * * @return void */ public function setLogger(Logger $logger): void { array_walk($this->items, function (array $astMap) use ($logger) { array_walk($astMap, fn(Assertion $ast) => $ast->setLogger($logger)); }); $this->logger = $logger; } /** * Returns the current logger. * * @return Logger */ public function getLogger(): Logger { return $this->logger; } /** * Determine if the given Model option exists. * * @param string $offset * * @return bool */ public function offsetExists($offset): bool { return isset($this->items[$offset]); } /** * Get a Model option. * * @param string $offset * * @return array|null */ public function offsetGet($offset): ?array { return $this->items[$offset] ?? null; } /** * Set a Model option. * * @param string $offset * @param array $value */ public function offsetSet($offset, $value): void { $this->items[$offset] = $value; } /** * Unset a Model option. * * @param string $offset */ public function offsetUnset($offset): void { unset($this->items[$offset]); } } src/Persist/Adapter.php000064400000002556152475271650011103 0ustar00loadPolicyArray($tokens, $model); } /** * Loads a policy rule to model. * * @param array $rule * @param Model $model */ public function loadPolicyArray(array $rule, Model $model): void { $key = $rule[0]; $sec = $key[0]; if (!isset($model[$sec][$key])) { return; } $assertions = $model[$sec]; $assertion = $assertions[$key]; if (!($assertion instanceof Assertion)) { return; } $rule = array_slice($rule, 1); $assertion->policy[] = $rule; $assertion->policyMap[implode(Policy::DEFAULT_SEP, $rule)] = count($assertion->policy) - 1; $assertions[$key] = $assertion; $model[$sec] = $assertions; } } src/Persist/Adapters/FileAdapter.php000064400000014130152475271650013435 0ustar00filePath = $filePath; } /** * Loads all policy rules from the storage. * * @param Model $model * * @throws CasbinException */ public function loadPolicy(Model $model): void { if (!file_exists($this->filePath)) { throw new InvalidFilePathException('invalid file path, file path cannot be empty'); } $this->loadPolicyFile($model); } /** * Saves all policy rules to the storage. * * @param Model $model * * @throws CasbinException */ public function savePolicy(Model $model): void { if ('' == $this->filePath) { throw new InvalidFilePathException('invalid file path, file path cannot be empty'); } $writeString = ''; if (isset($model['p'])) { foreach ($model['p'] as $ptype => $ast) { foreach ($ast->policy as $rule) { $writeString .= $ptype . ', '; $writeString .= Util::arrayToString($rule); $writeString .= PHP_EOL; } } } if (isset($model['g'])) { foreach ($model['g'] as $ptype => $ast) { foreach ($ast->policy as $rule) { $writeString .= $ptype . ', '; $writeString .= Util::arrayToString($rule); $writeString .= PHP_EOL; } } } $this->savePolicyFile(rtrim($writeString, PHP_EOL)); } /** * @param Model $model * @throws InvalidFilePathException */ protected function loadPolicyFile(Model $model): void { $file = fopen($this->filePath, 'rb'); if (false === $file) { throw new InvalidFilePathException(sprintf('Unable to access to the specified path "%s"', $this->filePath)); } while ($line = fgets($file)) { $this->loadPolicyLine(trim($line), $model); } fclose($file); } /** * @param string $text */ protected function savePolicyFile(string $text): void { file_put_contents($this->filePath, $text, LOCK_EX); } /** * Adds a policy rule to the storage. * * @param string $sec * @param string $ptype * @param string[] $rule * * @throws NotImplementedException */ public function addPolicy(string $sec, string $ptype, array $rule): void { throw new NotImplementedException('not implemented'); } /** * Adds a policy rule to the storage. * * @param string $sec * @param string $ptype * @param string[][] $rules * * @throws NotImplementedException */ public function addPolicies(string $sec, string $ptype, array $rules): void { throw new NotImplementedException('not implemented'); } /** * Removes a policy rule from the storage. * * @param string $sec * @param string $ptype * @param string[] $rule * * @throws NotImplementedException */ public function removePolicy(string $sec, string $ptype, array $rule): void { throw new NotImplementedException('not implemented'); } /** * Removes a policy rules from the storage. * * @param string $sec * @param string $ptype * @param string[][] $rules * * @throws NotImplementedException */ public function removePolicies(string $sec, string $ptype, array $rules): void { throw new NotImplementedException('not implemented'); } /** * Removes policy rules that match the filter from the storage. * * @param string $sec * @param string $ptype * @param int $fieldIndex * @param string ...$fieldValues * * @throws NotImplementedException */ public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void { throw new NotImplementedException('not implemented'); } /** * Updates a policy rule from storage. * This is part of the Auto-Save feature. * * @param string $sec * @param string $ptype * @param string[] $oldRule * @param string[] $newPolicy */ public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newPolicy): void { throw new NotImplementedException('not implemented'); } /** * UpdatePolicies updates some policy rules to storage, like db, redis. * * @param string $sec * @param string $ptype * @param string[][] $oldRules * @param string[][] $newRules * @return void */ public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void { throw new NotImplementedException('not implemented'); } /** * UpdateFilteredPolicies deletes old rules and adds new rules. * * @param string $sec * @param string $ptype * @param array $newPolicies * @param integer $fieldIndex * @param string ...$fieldValues * @return array */ public function updateFilteredPolicies(string $sec, string $ptype, array $newPolicies, int $fieldIndex, string ...$fieldValues): array { throw new NotImplementedException('not implemented'); } } src/Persist/Adapters/FileFilteredAdapter.php000075500000010341152475271650015117 0ustar00filtered = true; parent::__construct($filePath); } /** * Loads all policy rules from the storage. * * @param Model $model * * @throws CasbinException */ public function loadPolicy(Model $model): void { $this->filtered = false; parent::loadPolicy($model); } /** * Loads only policy rules that match the filter. * * @param Model $model * @param mixed $filter * * @throws CasbinException */ public function loadFilteredPolicy(Model $model, $filter): void { if (is_null($filter)) { $this->loadPolicy($model); return; } if (!file_exists($this->filePath)) { throw new InvalidFilePathException('invalid file path, file path cannot be empty'); } if (!$filter instanceof Filter) { throw new InvalidFilterTypeException('invalid filter type'); } $this->loadFilteredPolicyFile($model, $filter, [$this, 'loadPolicyLine']); $this->filtered = true; } /** * Returns true if the loaded policy has been filtered. * * @return bool */ public function isFiltered(): bool { return $this->filtered; } /** * SavePolicy saves all policy rules to the storage. * * @param Model $model * @throws CannotSaveFilteredPolicy|CasbinException */ public function savePolicy(Model $model): void { if ($this->filtered) { throw new CannotSaveFilteredPolicy('cannot save a filtered policy'); } parent::savePolicy($model); } /** * LoadFilteredPolicyFile function. * * @param Model $model * @param Filter $filter * @param callable $handler * @throws InvalidFilePathException */ protected function loadFilteredPolicyFile(Model $model, Filter $filter, callable $handler): void { $file = fopen($this->filePath, 'rb'); if (false === $file) { throw new InvalidFilePathException(sprintf('Unable to access to the specified path "%s"', $this->filePath)); } while ($line = fgets($file)) { $line = trim($line); if (self::filterLine($line, $filter)) { continue; } call_user_func($handler, $line, $model); } } /** * FilterLine function. * * @param string $line * @param Filter $filter * * @return bool */ protected static function filterLine(string $line, Filter $filter): bool { $p = explode(',', $line); if (0 == strlen($p[0])) { return true; } $filterSlice = match (trim($p[0])) { 'p' => $filter->p, 'g' => $filter->g, default => [] }; return self::filterWords($p, $filterSlice); } /** * FilterWords function. * * @param array $line * @param array $filter * * @return bool */ protected static function filterWords(array $line, array $filter): bool { if (count($line) < count($filter) + 1) { return true; } $skipLine = false; foreach ($filter as $i => $v) { if (strlen($v) > 0 && \trim($v) != trim($line[$i + 1])) { $skipLine = true; break; } } return $skipLine; } } src/Persist/Adapters/Filter.php000075500000001232152475271650012504 0ustar00p = $p; $this->g = $g; } } src/Persist/BatchAdapter.php000064400000001413152475271650012034 0ustar00roleName, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param Closure $linkConditionFunc */ public function addLinkConditionFunc(string $userName, string $roleName, Closure $linkConditionFunc): void; /** * SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName * * @param string $userName * @param string $roleName * @param string ...$params */ public function setLinkConditionFuncParams(string $userName, string $roleName, string ...$params): void; /** * AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain}, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param string $domain * @param Closure $linkConditionFunc */ public function addDomainLinkConditionFunc(string $userName, string $roleName, string $domain, Closure $linkConditionFunc): void; /** * SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn * for Link userName->{roleName, domain} * * @param string $userName * @param string $roleName * @param string $domain * @param string ...$params */ public function setDomainLinkConditionFuncParams(string $userName, string $roleName, string $domain, string ...$params): void; } src/Rbac/DefaultRoleManager/ConditionalDomainManager.php000064400000012625152475271650017326 0ustar00 */ protected array $rmMap = []; /** * ConditionalDomainManager constructor. * * @param int $maxHierarchyLevel */ public function __construct(int $maxHierarchyLevel) { parent::__construct($maxHierarchyLevel); } /** * Gets the RoleManager for the given domain. * * @param string $domain * @param bool $store * @return ConditionalRoleManager */ public function &getRoleManager(string $domain, bool $store): ConditionalRoleManager { if (isset($this->rmMap[$domain])) { return $this->rmMap[$domain]; } $rm = new ConditionalRoleManager($this->maxHierarchyLevel, $this->matchingFunc); if ($store) { $this->rmMap[$domain] = $rm; } if (!is_null($this->domainMatchingFunc)) { foreach ($this->rmMap as $domain2 => &$rm2) { if ($domain !== $domain2 && $this->match($domain, $domain2)) { $rm->copyFrom($rm2); } } } return $rm; } /** * Adds the inheritance link between role: name1 and role: name2. * aka role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains */ public function addLink(string $name1, string $name2, string ...$domains): void { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, true); $rm->addLink($name1, $name2); $this->rangeAffectedRoleManagers($domain, static fn (&$rm) => $rm->addLink($name1, $name2)); } /** * Deletes the inheritance link between role: name1 and role: name2. * aka role: name1 does not inherit role: name2 any more. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains */ public function deleteLink(string $name1, string $name2, string ...$domains): void { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, true); $rm->deleteLink($name1, $name2); $this->rangeAffectedRoleManagers($domain, static fn (&$rm) => $rm->deleteLink($name1, $name2)); } /** * Determines whether role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains * * @return bool */ public function hasLink(string $name1, string $name2, string ...$domains): bool { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, true); return $rm->hasLink($name1, $name2, ...$domains); } /** * AddLinkConditionFunc Add condition function fn for Link userName->roleName, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param Closure $linkConditionFunc */ public function addLinkConditionFunc(string $userName, string $roleName, Closure $linkConditionFunc): void { foreach ($this->rmMap as $_ => &$rm) { $rm->addLinkConditionFunc($userName, $roleName, $linkConditionFunc); } } /** * AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain}, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param string $domain * @param Closure $linkConditionFunc */ public function addDomainLinkConditionFunc(string $userName, string $roleName, string $domain, Closure $linkConditionFunc): void { foreach ($this->rmMap as $_ => &$rm) { $rm->addDomainLinkConditionFunc($userName, $roleName, $domain, $linkConditionFunc); } } /** * SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName * * @param string $userName * @param string $roleName * @param string ...$params */ public function setLinkConditionFuncParams(string $userName, string $roleName, string ...$params): void { foreach ($this->rmMap as $_ => &$rm) { $rm->setLinkConditionFuncParams($userName, $roleName, ...$params); } } /** * SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn * for Link userName->{roleName, domain} * * @param string $userName * @param string $roleName * @param string $domain * @param string ...$params */ public function setDomainLinkConditionFuncParams(string $userName, string $roleName, string $domain, string ...$params): void { foreach ($this->rmMap as $_ => &$rm) { $rm->setDomainLinkConditionFuncParams($userName, $roleName, $domain, ...$params); } } } src/Rbac/DefaultRoleManager/ConditionalRoleManager.php000064400000022160152475271650017013 0ustar00clear(); $this->maxHierarchyLevel = $maxHierarchyLevel; $this->setLogger(new DefaultLogger()); $this->matchingFunc = $matchingFunc; } /** * Determines whether role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domain * * @return bool */ public function hasLink(string $name1, string $name2, string ...$domain): bool { if ($name1 == $name2 || (!is_null($this->matchingFunc) && $this->match($name1, $name2))) { return true; } $userGet = &$this->getRole($name1); $roleGet = &$this->getRole($name2); $user = &$userGet[0]; $role = &$roleGet[0]; $userCreated = $userGet[1]; $roleCreated = $roleGet[1]; try { return $this->hasLinkHelper($role->name, [$user->name => $user], $this->maxHierarchyLevel, ...$domain); } finally { if ($userCreated) { $this->removeRole($user->name); } if ($roleCreated) { $this->removeRole($role->name); } } } /** * @param string $targetName * @param array $roles * @param int $level * @return bool */ protected function hasLinkHelper(string $targetName, array $roles, int $level, string ...$domain): bool { if ($level < 0 || count($roles) == 0) { return false; } $nextRoles = []; foreach ($roles as $name => $role) { if ($targetName === $role->name || (!is_null($this->matchingFunc) && $this->match($role->name, $targetName))) { return true; } try { $role->rangeRoles(function ($name, $nextRole) use (&$role, $domain, &$nextRoles) { if (!$this->getNextRoles($role, $nextRole, $domain, $nextRoles)) { throw new CasbinException('failed to get next roles'); }; }); } catch (CasbinException) { continue; } } return $this->hasLinkHelper($targetName, $nextRoles, $level - 1); } /** * @param Role $currentRole * @param Role $nextRole * @param array $domain * @param array $nextRoles * * @return bool */ protected function getNextRoles(Role $currentRole, Role $nextRole, array $domain, array &$nextRoles): bool { $passLinkConditionFunc = true; try { if (count($domain) === 0) { $linkConditionFunc = $this->getLinkConditionFunc($currentRole->name, $nextRole->name); if (!is_null($linkConditionFunc)) { $params = $this->getLinkConditionFuncParams($currentRole->name, $nextRole->name); $passLinkConditionFunc = $linkConditionFunc(...$params); } } else { $linkConditionFunc = $this->getDomainLinkConditionFunc($currentRole->name, $nextRole->name, $domain[0]); if (!is_null($linkConditionFunc)) { $params = $this->getDomainLinkConditionFuncParams($currentRole->name, $nextRole->name, $domain[0]); $passLinkConditionFunc = $linkConditionFunc(...$params); } } } catch (Exception $e) { $this->logger->logError($e, 'hasLinkHelper LinkCondition Error'); return false; } if ($passLinkConditionFunc) { $nextRoles[$nextRole->name] = $nextRole; } return true; } /** * @param string $userName * @param string $roleName * * @return Closure|null */ private function getLinkConditionFunc(string $userName, string $roleName): ?Closure { return $this->getDomainLinkConditionFunc($userName, $roleName, RoleManager::DEFAULT_DOMAIN); } /** * @param string $userName * @param string $roleName * @param string $domain * * @return Closure|null */ private function getDomainLinkConditionFunc(string $userName, string $roleName, string $domain): ?Closure { $userGet = &$this->getRole($userName); $roleGet = &$this->getRole($roleName); $user = &$userGet[0]; $role = &$roleGet[0]; $userCreated = $userGet[1]; $roleCreated = $roleGet[1]; if ($userCreated) { $this->removeRole($user->name); return null; } if ($roleCreated) { $this->removeRole($role->name); return null; } return $user->getLinkConditionFunc($role, $domain); } /** * @param string $userName * @param string $roleName * * @return array|null */ private function getLinkConditionFuncParams(string $userName, string $roleName): ?array { return $this->getDomainLinkConditionFuncParams($userName, $roleName, RoleManager::DEFAULT_DOMAIN); } /** * @param string $userName * @param string $roleName * @param string $domain * * @return array|null */ private function getDomainLinkConditionFuncParams(string $userName, string $roleName, string $domain): ?array { $userGet = &$this->getRole($userName); $roleGet = &$this->getRole($roleName); $user = &$userGet[0]; $role = &$roleGet[0]; $userCreated = $userGet[1]; $roleCreated = $roleGet[1]; if ($userCreated) { $this->removeRole($user->name); return null; } if ($roleCreated) { $this->removeRole($role->name); return null; } return $user->getLinkConditionFuncParams($role, $domain); } /** * AddLinkConditionFunc Add condition function fn for Link userName->roleName, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param Closure $linkConditionFunc */ public function addLinkConditionFunc(string $userName, string $roleName, Closure $linkConditionFunc): void { $this->addDomainLinkConditionFunc($userName, $roleName, RoleManager::DEFAULT_DOMAIN, $linkConditionFunc); } /** * AddDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain}, * when fn returns true, Link is valid, otherwise invalid * * @param string $userName * @param string $roleName * @param string $domain * @param Closure $linkConditionFunc */ public function addDomainLinkConditionFunc(string $userName, string $roleName, string $domain, Closure $linkConditionFunc): void { $userGet = &$this->getRole($userName); $roleGet = &$this->getRole($roleName); $user = &$userGet[0]; $role = &$roleGet[0]; $user->addLinkConditionFunc($role, $domain, $linkConditionFunc); } /** * SetLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName * * @param string $userName * @param string $roleName * @param string ...$params */ public function setLinkConditionFuncParams(string $userName, string $roleName, string ...$params): void { $this->setDomainLinkConditionFuncParams($userName, $roleName, RoleManager::DEFAULT_DOMAIN, ...$params); } /** * SetDomainLinkConditionFuncParams Sets the parameters of the condition function fn * for Link userName->{roleName, domain} * * @param string $userName * @param string $roleName * @param string $domain * @param string ...$params */ public function setDomainLinkConditionFuncParams(string $userName, string $roleName, string $domain, string ...$params): void { $userGet = &$this->getRole($userName); $roleGet = &$this->getRole($roleName); $user = &$userGet[0]; $role = &$roleGet[0]; $user->setLinkConditionFuncParams($role, $domain, ...$params); } /** * @param ConditionalRoleManager $roleManager */ public function copyFrom(ConditionalRoleManager &$roleManager): void { $this->rangeLinks( $roleManager->allRoles, fn ($name1, $name2, $domain) => $this->addLink($name1, $name2, $domain), ); } } src/Rbac/DefaultRoleManager/DomainManager.php000064400000001255152475271650015137 0ustar00 */ protected array $rmMap = []; /** * DomainManager constructor. * * @param int $maxHierarchyLevel */ public function __construct(int $maxHierarchyLevel) { parent::__construct($maxHierarchyLevel); } } src/Rbac/DefaultRoleManager/Role.php000064400000014071152475271650013336 0ustar00 */ public array $roles = []; /** * @var array */ private array $users = []; /** * @var array */ private array $matched = []; /** * @var array */ private array $matchedBy = []; /** * @var array */ private array $linkConditionFuncMap = []; /** * @var array */ private array $linkConditionFuncParamsMap = []; /** * Role constructor. * * @param string $name */ public function __construct(string $name) { $this->name = $name; } /** * @param self $role */ public function addRole(self $role): void { $this->roles[$role->name] = $role; $role->addUser($this); } /** * @param self $role */ public function removeRole(self $role): void { unset($this->roles[$role->name]); $role->removeUser($this); } /** * @param self $user */ public function addUser(self $user): void { $this->users[$user->name] = $user; } /** * @param self $user */ public function removeUser(self $user): void { unset($this->users[$user->name]); } /** * @param self $role */ public function addMatch(self $role): void { $this->matched[$role->name] = $role; $role->matchedBy[$this->name] = $this; } /** * @param self $role */ public function removeMatch(self $role): void { unset($this->matched[$role->name]); unset($role->matchedBy[$this->name]); } /** * RemoveMatches removes all matches of this role. */ public function removeMatches(): void { foreach ($this->matched as &$role) { $this->removeMatch($role); } foreach ($this->matchedBy as &$role) { $role->removeMatch($this); } } /** * Applies a callback to all roles that this role matches. * * @param Closure $fn */ public function rangeRoles(Closure $fn): void { array_walk($this->roles, function (&$role, $name) use ($fn) { $fn($name, $role); }); array_walk($this->roles, function ($role) use ($fn) { array_walk($role->matched, function (&$value, $key) use ($fn) { $fn($key, $value); }); }); array_walk($this->matchedBy, function ($role) use ($fn) { array_walk($role->roles, function (&$value, $key) use ($fn) { $fn($key, $value); }); }); } /** * Applies a callback to all users that this role matches. * * @param Closure $fn */ public function rangeUsers(Closure $fn): void { array_walk($this->users, function (&$user, $name) use ($fn) { $fn($name, $user); }); array_walk($this->users, function ($user) use ($fn) { array_walk($user->matched, function (&$value, $key) use ($fn) { $fn($key, $value); }); }); array_walk($this->matchedBy, function ($user) use ($fn) { array_walk($user->users, function (&$value, $key) use ($fn) { $fn($key, $value); }); }); } /** * Converts the role to a string. * * @return string */ public function toString(): string { $len = count($this->roles); if (0 == $len) { return ''; } $names = implode(', ', $this->getRoles()); if (1 == $len) { return $this->name . ' < ' . $names; } else { return $this->name . ' < (' . $names . ')'; } } /** * @return string[] */ public function getRoles(): array { $names = []; $this->rangeRoles(static function ($name, $role) use (&$names) { $names[] = $name; }); return Util::removeDumplicateElement($names); } /** * @return string[] */ public function getUsers(): array { $names = []; $this->rangeUsers(static function ($name, $user) use (&$names) { $names[] = $name; }); return $names; } /** * @param Role $role * @param string $domain * @param Closure $fn */ public function addLinkConditionFunc(Role $role, string $domain, Closure $fn): void { $this->linkConditionFuncMap[$this->getLinkConditionFuncKey($role, $domain)] = $fn; } /** * @param Role $role * @param string $domain * * @return Closure|null */ public function getLinkConditionFunc(Role $role, string $domain): ?Closure { $key = $this->getLinkConditionFuncKey($role, $domain); return $this->linkConditionFuncMap[$key] ?? null; } /** * @param Role $role * @param string $domain * @param array $params */ public function setLinkConditionFuncParams(Role $role, string $domain, ...$params): void { $this->linkConditionFuncParamsMap[$this->getLinkConditionFuncKey($role, $domain)] = $params; } /** * @param Role $role * @param string $domain * * @return array|null */ public function getLinkConditionFuncParams(Role $role, string $domain): ?array { $key = $this->getLinkConditionFuncKey($role, $domain); return $this->linkConditionFuncParamsMap[$key] ?? null; } /** * @param Role $role * @param string $domain * * @return string */ private function getLinkConditionFuncKey(Role $role, string $domain): string { return $role->name . '_' . $domain; } } src/Rbac/DefaultRoleManager/RoleManager.php000064400000002233152475271650014626 0ustar00clear(); $this->maxHierarchyLevel = $maxHierarchyLevel; $this->matchingFunc = $matchingFunc; $this->setLogger(new DefaultLogger()); } /** * @param RoleManager $roleManager */ public function copyFrom(RoleManager &$roleManager): void { $this->rangeLinks($roleManager->allRoles, function ($name1, $name2, $domain) { $this->addLink($name1, $name2, $domain); }); } } src/Rbac/DefaultRoleManager/Traits/BaseManager.php000064400000001421152475271650016043 0ustar00logger = $logger; } } src/Rbac/DefaultRoleManager/Traits/DomainManager.php000064400000017566152475271650016421 0ustar00matchingFunc = $fn; foreach ($this->rmMap as $_ => &$rm) { $rm->addMatchingFunc($name, $fn); } } /** * Support use domain pattern in g. * * @param string $name * @param Closure $fn */ public function addDomainMatchingFunc(string $name, Closure $fn): void { $this->domainMatchingFunc = $fn; foreach ($this->rmMap as $_ => &$rm) { $rm->addDomainMatchingFunc($name, $fn); } $this->rebuild(); } /** * Clears the map of RoleManagers. */ public function rebuild(): void { $rmMap = $this->rmMap; $this->clear(); foreach ($rmMap as $domain => &$rm) { $rm->rangeSelfLinks(function ($name1, $name2, $_) use ($domain) { $this->addLink($name1, $name2, $domain); }); } } /** * Clears all stored data and resets the role manager to the initial state. */ public function clear(): void { $this->rmMap = []; } /** * Gets the domain from the given arguments. * * @param string|null $domain * @return string */ public function getDomain(?string $domain = null): string { if (is_null($domain)) { return RoleManagerContract::DEFAULT_DOMAIN; } return $domain; } /** * Determines whether a string matches a pattern. * * @param string $str * @param string $pattern * @return bool */ public function match(string $str, string $pattern): bool { if ($str === $pattern) { return true; } if (!is_null($this->domainMatchingFunc)) { return call_user_func($this->domainMatchingFunc, $str, $pattern) === true; } else { return false; } } /** * Applies a callback to all RoleManagers that match the given domain. * * @param string $domain * @param Closure $fn */ public function rangeAffectedRoleManagers(string $domain, Closure $fn): void { if (!is_null($this->domainMatchingFunc)) { foreach ($this->rmMap as $domain2 => &$rm) { if ($domain !== $domain2 && $this->match($domain2, $domain)) { $fn($rm); } } } } /** * Gets the RoleManager for the given domain. * * @param string $domain * @param bool $store * @return DefaultRoleManager */ public function &getRoleManager(string $domain, bool $store): DefaultRoleManager { if (isset($this->rmMap[$domain])) { return $this->rmMap[$domain]; } $rm = new DefaultRoleManager($this->maxHierarchyLevel, $this->matchingFunc); if ($store) { $this->rmMap[$domain] = $rm; } if (!is_null($this->domainMatchingFunc)) { foreach ($this->rmMap as $domain2 => &$rm2) { if ($domain !== $domain2 && $this->match($domain, $domain2)) { $rm->copyFrom($rm2); } } } return $rm; } /** * Adds the inheritance link between role: name1 and role: name2. * aka role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains */ public function addLink(string $name1, string $name2, string ...$domains): void { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, true); $rm->addLink($name1, $name2); $this->rangeAffectedRoleManagers($domain, function (&$rm) use ($name1, $name2) { $rm->addLink($name1, $name2); }); } /** * Deletes the inheritance link between role: name1 and role: name2. * aka role: name1 does not inherit role: name2 any more. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains */ public function deleteLink(string $name1, string $name2, string ...$domains): void { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, true); $rm->deleteLink($name1, $name2); $this->rangeAffectedRoleManagers($domain, function (&$rm) use ($name1, $name2) { $rm->deleteLink($name1, $name2); }); } /** * Determines whether role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domains * * @return bool */ public function hasLink(string $name1, string $name2, string ...$domains): bool { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, false); return $rm->hasLink($name1, $name2, ...$domains); } /** * Gets the roles that a subject inherits. * domain is a prefix to the roles. * * @param string $name * @param string ...$domains * * @return string[] */ public function getRoles(string $name, string ...$domains): array { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, false); return $rm->getRoles($name, ...$domains); } /** * Gets the users that inherits a subject. * domain is an unreferenced parameter here, may be used in other implementations. * * @param string $name * @param string ...$domains * * @return string[] */ public function getUsers(string $name, string ...$domains): array { $domain = $this->getDomain(...$domains); $rm = &$this->getRoleManager($domain, false); return $rm->getUsers($name, ...$domains); } /** * Converts the roles to a string array. * * @return string[] */ public function toString(): array { $roles = []; foreach ($this->rmMap as $domain => &$rm) { $domainRoles = $rm->toString(); $roles[] = sprintf('%s: %s', $domain, implode(', ', $domainRoles)); } return $roles; } /** * Prints all the roles to log. */ public function printRoles(): void { if (!$this->logger->isEnabled()) { return; } $roles = $this->toString(); $this->logger->logRole($roles); } /** * Gets the domains that a subject inherits. * * @param string $name * * @return string[] */ public function getDomains(string $name): array { $domains = []; foreach ($this->rmMap as $domain => &$rm) { $roleGet = $rm->getRole($name); $role = $roleGet[0]; $roleCreated = $roleGet[1]; if (count($role->getUsers()) > 0 || count($role->getRoles()) > 0) { $domains[] = $domain; } if ($roleCreated) { $this->removeRole($role->name); } } return $domains; } /** * Gets all the domains. * * @return string[] */ public function getAllDomains(): array { $domains = []; foreach ($this->rmMap as $domain => $_) { $domains[] = $domain; } return $domains; } } src/Rbac/DefaultRoleManager/Traits/RoleManager.php000064400000022556152475271650016106 0ustar00 */ protected array $allRoles = []; /** * Clears the map of Roles. * * @return void */ protected function rebuild(): void { $roles = $this->allRoles; $this->clear(); $this->rangeLinks($roles, function (string $name1, string $name2, string $domain) { $this->addLink($name1, $name2, $domain); }); } /** * Determines whether a string matches a pattern. * * @param string $str * @param string $pattern * * @return bool */ public function match(string $str, string $pattern): bool { if ($str === $pattern) { return true; } if (!is_null($this->matchingFunc)) { return call_user_func($this->matchingFunc, $str, $pattern) === true; } return false; } /** * Applies a callback to all roles that match the given name or pattern. * * @param string $name * @param bool $isPattern * @param Closure $fn * * @return void */ protected function rangeMatchRoles(string $name, bool $isPattern, Closure $fn): void { foreach ($this->allRoles as $name2 => &$role) { if ($isPattern && $name !== $name2 && $this->match($name2, $name)) { $fn($role); } else if (!$isPattern && $name !== $name2 && $this->match($name, $name2)) { $fn($role); } } } /** * Gets the role by given name. * * @param string $name * * @return array */ public function &getRole(string $name): array { if (isset($this->allRoles[$name])) { $res = [&$this->allRoles[$name], false]; return $res; } $role = new Role($name); $this->allRoles[$name] = $role; if (!is_null($this->matchingFunc)) { $this->rangeMatchRoles($name, false, function (Role &$r) use (&$role) { $r->addMatch($role); }); $this->rangeMatchRoles($name, true, function (Role &$r) use (&$role) { $role->addMatch($r); }); } $res = [&$this->allRoles[$name], true]; return $res; } /** * @param array $map * @param string $name * * @return mixed */ protected function loadAndDelete(array &$map, string $name): mixed { if (isset($map[$name])) { $value = $map[$name]; unset($map[$name]); } return $value ?? null; } /** * Removes the role with the given name. * * @param string $name */ protected function removeRole(string $name): void { $role = $this->loadAndDelete($this->allRoles, $name); if (!is_null($role)) { $role->removeMatches(); } } /** * Support use pattern in g. * * @param string $name * @param Closure $fn */ public function addMatchingFunc(string $name, Closure $fn): void { $this->matchingFunc = $fn; $this->rebuild(); } /** * Support use domain pattern in g. * * @param string $name * @param Closure $fn */ public function addDomainMatchingFunc(string $name, Closure $fn): void { $this->domainMatchingFunc = $fn; } /** * Clears all stored data and resets the role manager to the initial state. */ public function clear(): void { $this->allRoles = []; } /** * Adds the inheritance link between role: name1 and role: name2. * aka role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domain */ public function addLink(string $name1, string $name2, string ...$domain): void { $userGet = &$this->getRole($name1); $roleGet = &$this->getRole($name2); $userGet[0]->addRole($roleGet[0]); } /** * Deletes the inheritance link between role: name1 and role: name2. * aka role: name1 does not inherit role: name2 any more. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domain */ public function deleteLink(string $name1, string $name2, string ...$domain): void { $userGet = &$this->getRole($name1); $roleGet = &$this->getRole($name2); $userGet[0]->removeRole($roleGet[0]); } /** * Determines whether role: name1 inherits role: name2. * domain is a prefix to the roles. * * @param string $name1 * @param string $name2 * @param string ...$domain * * @return bool */ public function hasLink(string $name1, string $name2, string ...$domain): bool { if ($name1 == $name2 || (!is_null($this->matchingFunc) && $this->match($name1, $name2))) { return true; } $userGet = &$this->getRole($name1); $roleGet = &$this->getRole($name2); $user = &$userGet[0]; $role = &$roleGet[0]; $userCreated = $userGet[1]; $roleCreated = $roleGet[1]; try { return $this->hasLinkHelper($role->name, [$user->name => $user], $this->maxHierarchyLevel); } finally { if ($userCreated) { $this->removeRole($user->name); } if ($roleCreated) { $this->removeRole($role->name); } } } /** * @param string $targetName * @param array $roles * @param int $level * @return bool */ protected function hasLinkHelper(string $targetName, array $roles, int $level): bool { if ($level < 0 || count($roles) == 0) { return false; } $nextRoles = []; foreach ($roles as $name => $role) { if ($targetName === $role->name || (!is_null($this->matchingFunc) && $this->match($role->name, $targetName))) { return true; } $role->rangeRoles(function ($name, &$role) use (&$nextRoles) { $nextRoles[$name] = $role; }); } return $this->hasLinkHelper($targetName, $nextRoles, $level - 1); } /** * Gets the roles that a subject inherits. * domain is a prefix to the roles. * * @param string $name * @param string ...$domain * * @return string[] */ public function getRoles(string $name, string ...$domain): array { $userGet = &$this->getRole($name); $user = &$userGet[0]; $userCreated = $userGet[1]; try { return $user->getRoles(); } finally { if ($userCreated) { $this->removeRole($user->name); } } } /** * Gets the users that inherits a subject. * domain is an unreferenced parameter here, may be used in other implementations. * * @param string $name * @param string ...$domain * * @return string[] */ public function getUsers(string $name, string ...$domain): array { $roleGet = &$this->getRole($name); $role = &$roleGet[0]; $roleCreated = $roleGet[1]; try { return $role->getUsers(); } finally { if ($roleCreated) { $this->removeRole($role->name); } } } /** * Converts the roles to a string array. * * @return array */ public function toString(): array { $roles = []; $roles = array_map(function (&$role) { return $role->toString(); }, $this->allRoles); return $roles; } /** * Prints all the roles to log. */ public function printRoles(): void { if (!$this->logger->isEnabled()) { return; } $roles = $this->toString(); $this->logger->logRole($roles); } /** * Gets the domains that a subject inherits. * * @param string $name * * @return string[] */ public function getDomains(string $name): array { return [RoleManagerContract::DEFAULT_DOMAIN]; } /** * Gets all the domains. * * @return string[] */ public function getAllDomains(): array { return [RoleManagerContract::DEFAULT_DOMAIN]; } /** * Applies a callback to all the links between users and roles. * * @param array &$users * @param Closure $fn */ public function rangeLinks(array &$users, Closure $fn): void { foreach ($users as &$user) { foreach ($user->roles as $roleName => $_) { $fn($user->name, $roleName, RoleManagerContract::DEFAULT_DOMAIN); } } } /** * Applies a callback to all the links between users and roles in itself. * * @param Closure $fn */ public function rangeSelfLinks(Closure $fn): void { $this->rangeLinks($this->allRoles, $fn); } } src/Rbac/RoleManager.php000064400000005735152475271650011137 0ustar00 $i) { if (substr($key1, 0, $i) == substr($key2, 0, $i)) { return substr($key1, $i); } } return ''; } /** * KeyGetFunc is the wrapper for KeyGet * * @param mixed ...$args * @return string */ public static function keyGetFunc(...$args) { [$name1, $name2] = $args; return self::keyGet($name1, $name2); } /** * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. * For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource". * * @param string $key1 * @param string $key2 * * @return bool */ public static function keyMatch2(string $key1, string $key2): bool { if ('*' === $key2) { $key2 = '.*'; } $key2 = str_replace(['/*'], ['/.*'], $key2); $pattern = '/:[^\/]+/'; $key2 = preg_replace_callback($pattern, static fn ($m) => '[^\/]+', $key2); return self::regexMatch($key1, '^' . $key2 . '$'); } /** * The wrapper for KeyMatch2. * * @param mixed ...$args * * @return bool */ public static function keyMatch2Func(...$args): bool { [$name1, $name2] = $args; return self::keyMatch2($name1, $name2); } /** * KeyGet2 returns value matched pattern * For example, "/resource1" matches "/:resource" * if the pathVar == "resource", then "resource1" will be returned * * @param string $key1 * @param string $key2 * @param string $pathVar * @return string */ public static function keyGet2(string $key1, string $key2, string $pathVar): string { $key2 = str_replace(['/*'], ['/.*'], $key2); $pattern = '/:[^\/]+/'; $keys = []; preg_match_all($pattern, $key2, $keys); $keys = $keys[0]; $key2 = preg_replace_callback($pattern, static fn ($m): string => '([^\/]+)', $key2); $key2 = "~^" . $key2 . "$~"; $values = []; preg_match($key2, $key1, $values); if (count($values) === 0) { return ''; } foreach ($keys as $i => $key) { if ($pathVar == substr($key, 1)) { return $values[$i + 1]; } } return ''; } /** * KeyGet2Func is the wrapper for KeyGet2 * * @param mixed ...$args * @return string */ public static function keyGet2Func(...$args) { [$name1, $name2, $key] = $args; return self::keyGet2($name1, $name2, $key); } /** * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. * For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}". * * @param string $key1 * @param string $key2 * * @return bool */ public static function keyMatch3(string $key1, string $key2): bool { $key2 = str_replace(['/*'], ['/.*'], $key2); $pattern = '/\{[^\/]+\}/'; $key2 = preg_replace_callback($pattern, static fn ($m): string => '[^\/]+', $key2); return self::regexMatch($key1, '^' . $key2 . '$'); } /** * The wrapper for KeyMatch3. * * @param mixed ...$args * * @return bool */ public static function keyMatch3Func(...$args): bool { [$name1, $name2] = $args; return self::keyMatch3($name1, $name2); } /** * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. * Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns: * "/parent/123/child/123" matches "/parent/{id}/child/{id}" * "/parent/123/child/456" does not match "/parent/{id}/child/{id}" * But KeyMatch3 will match both. * * @param string $key1 * @param string $key2 * * @return bool */ public static function keyMatch4(string $key1, string $key2): bool { $key2 = str_replace(['/*'], ['/.*'], $key2); $tokens = []; $pattern = '/\{([^\/]+)\}/'; $key2 = preg_replace_callback( $pattern, function ($m) use (&$tokens) { $tokens[] = $m[1]; return '([^\/]+)'; }, $key2 ); $matched = preg_match_all('~^' . $key2 . '$~', $key1, $matches); if (!boolval($matched)) { return false; } $values = []; foreach ($tokens as $key => $token) { if (!isset($values[$token])) { $values[$token] = $matches[$key + 1]; } if ($values[$token] != $matches[$key + 1]) { return false; } } return true; } /** * The wrapper for KeyMatch4. * * @param mixed ...$args * * @return bool */ public static function keyMatch4Func(...$args): bool { [$name1, $name2] = $args; return self::keyMatch4($name1, $name2); } /** * Determines whether key1 matches the pattern of key2 and ignores the parameters in key2. * For example, "/foo/bar?status=1&type=2" matches "/foo/bar" * * @param string $key1 * @param string $key2 * * @return bool */ public static function keyMatch5(string $key1, string $key2): bool { $pos = strpos($key1, '?'); if ($pos === false) { return $key1 == $key2; } return substr($key1, 0, $pos) == $key2; } /** * the wrapper for KeyMatch5. * * @param mixed ...$args * * @return bool */ public static function keyMatch5Func(...$args): bool { [$name1, $name2] = $args; return self::keyMatch5($name1, $name2); } /** * Determines whether key1 matches the pattern of key2 in regular expression. * * @param string $key1 * @param string $key2 * * @return bool */ public static function regexMatch(string $key1, string $key2): bool { return (bool)preg_match('~' . $key2 . '~', $key1); } /** * The wrapper for RegexMatch. * * @param mixed ...$args * * @return bool */ public static function regexMatchFunc(...$args): bool { [$name1, $name2] = $args; return self::regexMatch($name1, $name2); } /** * Determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR * pattern. * * @param string $ip1 * @param string $ip2 * * @return bool * * @throws Exception */ public static function ipMatch(string $ip1, string $ip2): bool { return Util::ipInSubnet($ip1, $ip2); } /** * The wrapper for IPMatch. * * @param mixed ...$args * * @return bool * * @throws Exception */ public static function ipMatchFunc(...$args): bool { [$ip1, $ip2] = $args; return self::ipMatch($ip1, $ip2); } /** * Returns true if the specified `string` matches the given glob `pattern`. * * @param string $str * @param string $pattern * * @return bool * * @throws Exception */ public static function globMatch(string $str, string $pattern): bool { return fnmatch($pattern, $str, FNM_PATHNAME | FNM_PERIOD); } /** * The wrapper for globMatch. * * @param mixed ...$args * * @return bool * * @throws Exception */ public static function globMatchFunc(...$args): bool { $str = $args[0]; $pattern = $args[1]; return self::globMatch($str, $pattern); } /** * The factory method of the g(_, _) function. * * @param RoleManager|null $rm * * @return Closure */ public static function generateGFunction(?RoleManager $rm = null): Closure { $memorized = []; return function (...$args) use ($rm, &$memorized) { $key = implode(chr(0b0), $args); if (isset($memorized[$key])) { return $memorized[$key]; } [$name1, $name2] = $args; if (null === $rm) { $v = $name1 == $name2; } elseif (2 == count($args)) { $v = $rm->hasLink($name1, $name2); } else { $domain = (string)$args[2]; $v = $rm->hasLink($name1, $name2, $domain); } $memorized[$key] = $v; return $v; }; } /** * The factory method of the g(_, _[, _]) function with conditions. * * @param ConditionalRoleManager|null $crm * * @return Closure */ public static function generateConditionalGFunction(?ConditionalRoleManager $crm = null): Closure { return function (...$args) use ($crm) { [$name1, $name2] = $args; if (is_null($crm)) { $v = $name1 == $name2; } elseif (2 == count($args)) { $v = $crm->hasLink($name1, $name2); } else { $domain = (string)$args[2]; $v = $crm->hasLink($name1, $name2, $domain); } return $v; }; } /** * The wrapper for timeMatch. * * @param mixed ...$args * * @return bool */ public static function timeMatchFunc(...$args): bool { [$startTime, $endTime] = $args; return self::timeMatch($startTime, $endTime); } /** * Determines whether the current time is between startTime and endTime. * You can use "_" to indicate that the parameter is ignored. * * @param string $startTime * @param string $endTime * * @return bool */ public static function timeMatch(string $startTime, string $endTime): bool { $now = new DateTime(); if ($startTime !== '_') { if (false === strtotime($startTime)) { return false; } $start = new DateTime($startTime); if ($now < $start) { return false; } } if ($endTime !== '_') { if (false === strtotime($endTime)) { return false; } $end = new DateTime($endTime); if ($now > $end) { return false; } } return true; } } src/Util/Util.php000064400000012157152475271650007722 0ustar00[^),]*)\)/'; /** * Escapes the dots in the assertion, because the expression evaluation doesn't support such variable names. * * @param string $s * * @return string */ public static function escapeAssertion(string $s): string { if (str_starts_with($s, 'r') || str_starts_with($s, 'p')) { $pos = strpos($s, '.'); if ($pos !== false) { $s[$pos] = '_'; } } $ss = preg_replace_callback( "~(\|| |=|\)|\(|&|<|>|,|\+|-|!|\*|\/)((r|p)[0-9]*)(\.)~", static fn ($m): string => $m[1] . $m[2] . '_', $s ); return is_string($ss) ? $ss : $s; } /** *Removes the comments starting with # in the text. * * @param string $s * * @return string */ public static function removeComments(string $s): string { $pos = strpos($s, '#'); return false === $pos ? $s : trim(substr($s, 0, $pos)); } /** * Gets a printable string for a string array. * * @param array $s * * @return string */ public static function arrayToString(array $s): string { return implode(', ', $s); } /** * Removes any duplicated elements in a string array. * * @param array $s */ public static function arrayRemoveDuplicates(array &$s): void { $s = array_keys(array_flip($s)); } /** * Determine whether matcher contains function eval. * * @param string $s * * @return bool */ public static function hasEval(string $s): bool { return (bool)preg_match(self::REGEXP, $s); } /** * Replace function eval with the value of its parameters. * * @param string $s * @param string $rule * * @return string */ public static function replaceEval(string $s, string $rule): string { return (string)preg_replace(self::REGEXP, '(' . $rule . ')', $s); } /** * Returns the parameters of function eval. * * @param string $s * * @return array */ public static function getEvalValue(string $s): array { preg_match_all(self::REGEXP, $s, $matches); return $matches['rule']; } /** * ReplaceEvalWithMap replace function eval with the value of its parameters via given sets. * * @param string $src * @param array $sets * @return string */ public static function replaceEvalWithMap(string $src, array $sets): string { return preg_replace_callback(self::REGEXP, function ($matches) use ($sets): string { $key = $matches['rule']; if (isset($sets[$key])) { $value = $sets[$key]; return '(' . $value . ')'; } return $matches[0]; }, $src); } /** * Remove duplicate elements from an array. * * @param array $s * @return array */ public static function removeDumplicateElement(array $s): array { return array_unique($s); } /** * Check if two arrays are equal, order-insensitive. * * @param array $a * @param array $b * * @return bool */ public static function setEquals(array $a, array $b): bool { if (count($a) !== count($b)) { return false; } sort($a); sort($b); return $a == $b; } /** * Determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern. * * @param string $ipAddress * @param string $cidrAddress * * @return bool */ public static function ipInSubnet(string $ipAddress, string $cidrAddress): bool { if (!str_contains($cidrAddress, '/')) { return $ipAddress === $cidrAddress; } [$subnet, $prefixLength] = explode('/', $cidrAddress); $prefixLength = intval($prefixLength); // IPv6 if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { $ip = inet_pton($ipAddress); $subnet = inet_pton($subnet); if ($ip === false || $subnet === false) { return false; } $mask = str_repeat("f", intdiv($prefixLength, 4)); $mask .= ['', '8', 'c', 'e'][$prefixLength % 4]; $mask = str_pad($mask, 32, '0'); $mask = pack("H*", $mask); return ($ip & $mask) === ($subnet & $mask); } // IPv4 if (filter_var($ipAddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && filter_var($subnet, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { $ip = ip2long($ipAddress); $subnet = ip2long($subnet); $mask = 0xffffffff << (32 - $prefixLength); return ($ip & $mask) === ($subnet & $mask); } return false; } } tests/Benchmark/ModelBenchmarkTest.php000064400000015336152475271650014052 0ustar00benchmark(function () use ($rawEnforce) { $rawEnforce("alice", "data1", "read"); }, 10000); } public function testBaseModel(): void { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . "/basic_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "data1", "read"); }, 10000); } public function testRBACModel(): void { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . "/rbac_model.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "data2", "read"); }, 10000); } public function testRBACModelSmall(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_model.conf"); // 100 roles, 10 resources. for ($i = 0; $i < 100; $i++) { $e->addPolicy(sprintf("group%d", $i), sprintf("data%d", $i / 10), "read"); } // 1000 users. for ($i = 0; $i < 1000; $i++) { $e->addGroupingPolicy(sprintf("user%d", $i), sprintf("group%d", $i / 10)); } $this->benchmark(function () use ($e) { $e->enforce("user501", "data9", "read"); }, 1000); } public function testRBACModelMedium(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_model.conf"); // 1000 roles, 100 resources. $pPolicies = []; for ($i = 0; $i < 1000; $i++) { $pPolicies[] = [sprintf("group%d", $i), sprintf("data%d", $i / 10), "read"]; } $e->addPolicies($pPolicies); // 10000 users. $gPolicies = []; for ($i = 0; $i < 10000; $i++) { $gPolicies[] = [sprintf("user%d", $i), sprintf("group%d", $i / 10)]; } $e->addGroupingPolicies($gPolicies); $this->benchmark(function () use ($e) { $e->enforce("user5001", "data99", "read"); }, 100); } public function testRBACModelLarge(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_model.conf"); // 10000 roles, 1000 resources. $pPolicies = []; for ($i = 0; $i < 10000; $i++) { $pPolicies[] = [sprintf("group%d", $i), sprintf("data%d", $i / 10), "read"]; } $e->addPolicies($pPolicies); // 100000 users. $gPolicies = []; for ($i = 0; $i < 100000; $i++) { $gPolicies[] = [sprintf("user%d", $i), sprintf("group%d", $i / 10)]; } $e->addGroupingPolicies($gPolicies); $this->benchmark(function () use ($e) { $e->enforce("user50001", "data999", "read"); }, 10); } public function testRBACModelWithResourceRoles(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_with_resource_roles_model.conf", $this->modelAndPolicyPath . "/rbac_with_resource_roles_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "data1", "read"); }, 1000); } public function testRBACModelWithDomains(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_with_domains_model.conf", $this->modelAndPolicyPath . "/rbac_with_domains_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "domain1", "data1", "read"); }, 1000); } public function testABACModel(): void { $e = new Enforcer($this->modelAndPolicyPath . "/abac_model.conf"); $data1 = new \stdClass(); $data1->Name = "data1"; $data1->Owner = "alice"; $this->benchmark(function () use ($e, $data1) { $e->enforce("alice", $data1, "read"); }, 1000); } public function testKeyMatchModel(): void { $e = new Enforcer($this->modelAndPolicyPath . "/keymatch_model.conf", $this->modelAndPolicyPath . "/keymatch_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "/alice_data/resource1", "GET"); }, 1000); } public function testRBACModelWithDeny(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_with_deny_model.conf", $this->modelAndPolicyPath . "/rbac_with_deny_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "data1", "read"); }, 1000); } public function testPriorityModel(): void { $e = new Enforcer($this->modelAndPolicyPath . "/priority_model.conf", $this->modelAndPolicyPath . "/priority_policy.csv"); $this->benchmark(function () use ($e) { $e->enforce("alice", "data1", "read"); }, 1000); } public function testRBACModelWithDomainPatternLarge(): void { $e = new Enforcer($this->modelAndPolicyPath . "/performance/rbac_with_pattern_large_scale_model.conf", $this->modelAndPolicyPath . "/performance/rbac_with_pattern_large_scale_policy.csv"); $e->addNamedDomainMatchingFunc("g", "keyMatch4", fn(...$args) => BuiltinOperations::keyMatch4Func(...$args)); $e->buildRoleLinks(); $this->benchmark(function () use ($e) { $e->enforce("staffUser1001", "/orgs/1/sites/site001", "App001.Module001.Action1001"); }, 1000); } protected function benchmark(Closure $closure, int $n = 100): void { $x = microtime(true); for ($i = 0; $i < $n; $i++) { $closure(); } $x = microtime(true) - $x; $fn = debug_backtrace()[1]['function'] ?? __FUNCTION__; printf( "%s %s %s ms/op". PHP_EOL, str_pad($fn, 45, " ", STR_PAD_RIGHT), str_pad(strval($n), 8, " ", STR_PAD_LEFT), str_pad(sprintf("%.6f", $x * 1000 / $n), 12, " ", STR_PAD_LEFT) ); $this->assertTrue(true); } } tests/EnforcerTest.php000064400000056371152475271650011054 0ustar00addDef('r', 'r', 'sub, obj, act'); $m->addDef('p', 'p', 'sub, obj, act'); $m->addDef('e', 'e', 'some(where (p.eft == allow))'); $m->addDef('m', 'm', 'r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)'); $a = new FileAdapter($this->modelAndPolicyPath . '/keymatch_policy.csv'); $e = new Enforcer($m, $a); $this->assertTrue($e->enforce('alice', '/alice_data/resource1', 'GET')); $this->assertFalse($e->enforce('bob', '/alice_data/resource1', 'GET')); $e = new Enforcer($m); $a->loadPolicy($e->getModel()); $this->assertTrue($e->enforce('alice', '/alice_data/resource1', 'GET')); $this->assertFalse($e->enforce('bob', '/alice_data/resource1', 'GET')); } public function testKeyMatchModelInMemoryDeny() { $m = Model::newModel(); $m->addDef('r', 'r', 'sub, obj, act'); $m->addDef('p', 'p', 'sub, obj, act'); $m->addDef('e', 'e', '!some(where (p.eft == deny))'); $m->addDef('m', 'm', 'r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)'); $a = new FileAdapter($this->modelAndPolicyPath . '/keymatch_policy.csv'); $e = new Enforcer($m, $a); $this->assertTrue($e->enforce('alice', '/alice_data/resource1', 'GET')); } public function testRBACModelInMemoryIndeterminate() { $m = Model::newModel(); $m->addDef('r', 'r', 'sub, obj, act'); $m->addDef('p', 'p', 'sub, obj, act'); $m->addDef('g', 'g', '_, _'); $m->addDef('e', 'e', 'some(where (p.eft == allow))'); $m->addDef('m', 'm', 'g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act'); $e = new Enforcer($m); $e->addPermissionForUser('alice', 'data1', 'invalid'); $this->assertFalse($e->enforce('alice', 'data1', 'read')); } public function testEnforceBasic() { $e = new Enforcer($this->modelAndPolicyPath . '/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 testEnforceExBasic() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [true, ['alice', 'data1', 'read']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [true, ['bob', 'data2', 'write']]); $this->assertEquals($e->enforceEx('bob', 'data1', 'write'), [false, []]); } public function testEnforceBasicNoPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf'); $this->assertEquals($e->enforce('alice', 'data1', 'read'), false); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('bob', 'data2', 'write'), false); $this->assertEquals($e->enforce('bob', 'data1', 'write'), false); } public function testEnforceExBasicNoPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [false, []]); $this->assertEquals($e->enforceEx('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 testEnforceExBasicWithRoot() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_with_root_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $this->assertEquals($e->enforceEx('root', 'any', 'any'), [true, ['alice', 'data1', 'read']]); } public function testEnforceBasicWithRootNoPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_with_root_model.conf'); $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')); $this->assertFalse($e->enforce('bob', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data1', 'write')); $this->assertFalse($e->enforce('bob', 'data2', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'write')); $this->assertTrue($e->enforce('root', 'data1', 'read')); $this->assertTrue($e->enforce('root', 'data1', 'write')); $this->assertTrue($e->enforce('root', 'data2', 'read')); $this->assertTrue($e->enforce('root', 'data2', 'write')); } public function testEnforceExBasicWithRootNoPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_with_root_model.conf'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data1', 'write'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data2', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data1', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [false, []]); $this->assertEquals($e->enforceEx('root', 'data1', 'read'), [true, []]); $this->assertEquals($e->enforceEx('root', 'data1', 'write'), [true, []]); $this->assertEquals($e->enforceEx('root', 'data2', 'read'), [true, []]); $this->assertEquals($e->enforceEx('root', 'data2', 'write'), [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 testEnforceExBasicWithoutResources() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_without_resources_model.conf', $this->modelAndPolicyPath . '/basic_without_resources_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'read'), [true, ['alice', 'read']]); $this->assertEquals($e->enforceEx('alice', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'write'), [true, ['bob', 'write']]); $this->assertEquals($e->enforceEx('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 testEnforceExBasicWithoutUsers() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_without_users_model.conf', $this->modelAndPolicyPath . '/basic_without_users_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', '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 testEnforceExIpMatch() { $e = new Enforcer($this->modelAndPolicyPath . '/ipmatch_model.conf', $this->modelAndPolicyPath . '/ipmatch_policy.csv'); $this->assertEquals($e->enforceEx('192.168.2.1', 'data1', 'read'), [true, ['192.168.2.0/24', 'data1', 'read']]); $this->assertEquals($e->enforceEx('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 testEnforceExKeyMatch() { $e = new Enforcer($this->modelAndPolicyPath . '/keymatch_model.conf', $this->modelAndPolicyPath . '/keymatch_policy.csv'); $this->assertEquals($e->enforceEx('alice', '/alice_data/test', 'GET'), [true, ['alice', '/alice_data/*', 'GET']]); $this->assertEquals($e->enforceEx('alice', '/bob_data/test', 'GET'), [false, []]); $this->assertEquals($e->enforceEx('cathy', '/cathy_data', 'GET'), [true, ['cathy', '/cathy_data', '(GET)|(POST)']]); $this->assertEquals($e->enforceEx('cathy', '/cathy_data', 'POST'), [true, ['cathy', '/cathy_data', '(GET)|(POST)']]); $this->assertEquals($e->enforceEx('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 testEnforceExKeyMatch2() { $e = new Enforcer($this->modelAndPolicyPath . '/keymatch2_model.conf', $this->modelAndPolicyPath . '/keymatch2_policy.csv'); $this->assertEquals($e->enforceEx('alice', '/alice_data/resource', 'GET'), [true, ['alice', '/alice_data/:resource', 'GET']]); $this->assertEquals($e->enforceEx('alice', '/alice_data2/123/using/456', 'GET'), [true, ['alice', '/alice_data2/:id/using/:resId', 'GET']]); } 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 testEnforceExPriority() { $e = new Enforcer($this->modelAndPolicyPath . '/priority_model.conf', $this->modelAndPolicyPath . '/priority_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [true, ['alice', 'data1', 'read', 'allow']]); $this->assertEquals($e->enforceEx('alice', 'data1', 'write'), [false, ['data1_deny_group', 'data1', 'write', 'deny']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data1', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'read'), [true, ['data2_allow_group', 'data2', 'read', 'allow']]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [false, ['bob', 'data2', 'write', 'deny']]); } 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 testEnforceExPriorityIndeterminate() { $e = new Enforcer($this->modelAndPolicyPath . '/priority_model.conf', $this->modelAndPolicyPath . '/priority_indeterminate_policy.csv'); $this->assertEquals($e->enforceEx('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 testEnforceExRbac() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [true, ['alice', 'data1', 'read']]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [true, ['bob', 'data2', 'write']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [true, ['data2_admin', 'data2', 'read']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'write'), [true, ['data2_admin', 'data2', 'write']]); } 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 testEnforceExRbacWithDeny() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_deny_model.conf', $this->modelAndPolicyPath . '/rbac_with_deny_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [true, ['alice', 'data1', 'read', 'allow']]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [true, ['bob', 'data2', 'write', 'allow']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [true, ['data2_admin', 'data2', 'read', 'allow']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'write'), [false, ['alice', 'data2', 'write', 'deny']]); } 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 testEnforceExRbacWithDomains() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'domain1', 'data1', 'read'), [true, ['admin', 'domain1', 'data1', 'read']]); $this->assertEquals($e->enforceEx('alice', 'domain1', 'data1', 'write'), [true, ['admin', 'domain1', 'data1', 'write']]); $this->assertEquals($e->enforceEx('alice', 'domain1', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'domain1', 'data2', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'domain2', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'domain2', 'data1', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'domain2', 'data2', 'read'), [true, ['admin', 'domain2', 'data2', 'read']]); $this->assertEquals($e->enforceEx('bob', 'domain2', 'data2', 'write'), [true, ['admin', 'domain2', 'data2', 'write']]); } 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 testEnforceExRbacWithNotDeny() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_not_deny_model.conf', $this->modelAndPolicyPath . '/rbac_with_deny_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data2', 'write'), [false, ['alice', 'data2', 'write', 'deny']]); } 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); } public function testEnforceExRbacWithResourceRoles() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_resource_roles_model.conf', $this->modelAndPolicyPath . '/rbac_with_resource_roles_policy.csv'); $this->assertEquals($e->enforceEx('alice', 'data1', 'read'), [true, ['alice', 'data1', 'read']]); $this->assertEquals($e->enforceEx('alice', 'data1', 'write'), [true, ['data_group_admin', 'data_group', 'write']]); $this->assertEquals($e->enforceEx('alice', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('alice', 'data2', 'write'), [true, ['data_group_admin', 'data_group', 'write']]); $this->assertEquals($e->enforceEx('bob', 'data1', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data1', 'write'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'read'), [false, []]); $this->assertEquals($e->enforceEx('bob', 'data2', 'write'), [true, ['bob', 'data2', 'write']]); } public function testMultiplePolicyDefinitions() { $e = new Enforcer($this->modelAndPolicyPath . '/multiple_policy_definitions_model.conf', $this->modelAndPolicyPath . '/multiple_policy_definitions_policy.csv'); $enforceContext = new EnforceContext('2'); $enforceContext->eType = "e"; $this->assertEquals($e->enforce('alice', 'data2', 'read'), true); $tmp = new \stdClass(); $tmp->Age = 70; $this->assertEquals($e->enforce($enforceContext, $tmp, '/data1', 'read'), false); $tmp->Age = 30; $this->assertEquals($e->enforce($enforceContext, $tmp, '/data1', 'read'), true); } public function testMatcherUsingInOperatorBracket() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model_matcher_using_in_op_bracket.conf'); $e->addPermissionForUser('alice', 'data1', 'read'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data3', 'read')); $this->assertFalse($e->enforce('anyone', 'data1', 'read')); $this->assertTrue($e->enforce('anyone', 'data2', 'read')); $this->assertTrue($e->enforce('anyone', 'data3', 'read')); } public function testRbacWithDomain() { $e = new Enforcer( $this->modelAndPolicyPath . '/rbac_with_domain_pattern_model_and_keymatch_model.conf', $this->modelAndPolicyPath . '/rbac_with_domain_pattern_model_and_keymatch_policy.csv' ); $this->assertTrue($e->enforce('alice', 'domain1', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'domain1', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'domain1', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'domain1', 'data2', 'write')); $this->assertTrue($e->enforce('alice', 'domain2', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'domain2', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'domain2', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'domain2', 'data2', 'write')); $this->assertFalse($e->enforce('alice', 'domain3', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'domain3', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'domain3', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'domain3', 'data2', 'write')); $this->assertFalse($e->enforce('alice', 'domain4', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'domain4', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'domain4', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'domain4', 'data2', 'write')); } } tests/Unit/CachedEnforcerTest.php000064400000007614152475271650013057 0ustar00modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e->setExpireTime(60); $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->assertFalse($e->removePolicy('alice', 'data1', 'read')); $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')); $e = new CachedEnforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e->enableCache(false); $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 = new CachedEnforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $e->setCache(new ArrayAdapter()); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); $e->removePolicies([ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], ]); $this->assertFalse($e->removePolicies([ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], ])); $this->assertFalse($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'write')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); $e = new CachedEnforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $e->loadPolicy(); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); $e->clearPolicy(); $this->assertFalse($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $e->invalidateCache(); } public function testGetCacheKey() { $e = new CachedEnforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $this->assertEquals('alice$$data1$$read$$', $e->getCacheKey('alice', 'data1', 'read')); $this->assertEquals('alice$$data1$$read$$', $e->getCacheKey(new class() implements CacheableParam { public function getCacheKey(): string { return 'alice'; } }, 'data1', 'read')); $this->assertEquals('', $e->getCacheKey('alice', 'data1', true)); } } tests/Unit/Config/ConfigTest.php000064400000004313152475271650012627 0ustar00getAndSetConfig($cfg); } public function testNewConfigFromText() { $cfg = Config::newConfigFromText(file_get_contents(__DIR__ . '/test.ini')); $this->getAndSetConfig($cfg); try { $cfg = Config::newConfigFromText(<<<'EOT' [mysql] mysql.dev.host = 127.0.0.1 mysql.dev.user EOT ); } catch (\Exception $e) { $this->assertTrue($e instanceof CasbinException); } } private function getAndSetConfig(Config $cfg) { // $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); $v = $cfg->getStrings('noexist'); $this->assertEquals([], $v); try { $cfg->set('', ''); } catch (\Exception $e) { $this->assertTrue($e instanceof CasbinException); } $cfg->set('nosec', 'nosec'); $this->assertEquals('nosec', $cfg->getString('nosec')); } } tests/Unit/Config/test.ini000064400000001273152475271650011533 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/CoreEnforcerTest.php000064400000032603152475271650012574 0ustar00modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv', null, true); $this->assertTrue($e->enforce('alice', 'data1', 'read')); // The log can also be enabled or disabled at run-time. $e->enableLog(false); $this->assertTrue($e->enforce('alice', 'data1', 'read')); // Test setting the logger at run-time. $logger = Mockery::mock(Logger::class); $logger->shouldReceive('enableLog') ->with(true); $logger->shouldReceive('isEnabled') ->andReturn(true); $logger->shouldReceive('logPolicy') ->twice() ->withAnyArgs(); $logger->shouldReceive('logModel') ->twice() ->withAnyArgs(); $logger->shouldReceive('logRole') ->twice() ->withAnyArgs(); // Prints basic rbac model and policys $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); /** @var Logger $logger */ $e->setLogger($logger); $e->enableLog(true); $e->getModel()->printModel(); $e->getModel()->printPolicy(); $e->getRoleManager()->printRoles(); // Prints rbac model with domain and policys with keymatch $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domain_pattern_model_and_keymatch_model.conf', $this->modelAndPolicyPath . '/rbac_with_domain_pattern_model_and_keymatch_policy.csv'); $e->setLogger($logger); $e->enableLog(true); $e->getModel()->printModel(); $e->getModel()->printPolicy(); $e->getRoleManager()->printRoles(); } public function testEnableAutoSave() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e->enableAutoSave(false); // Because AutoSave is disabled, the policy change only affects the policy in Casbin enforcer, // it doesn't affect the policy in the storage. $e->removePolicy('alice', 'data1', 'read'); // Reload the policy from the storage to see the effect. $e->loadPolicy(); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $e->enableAutoSave(true); // Because AutoSave is enabled, the policy change not only affects the policy in Casbin enforcer, // but also affects the policy in the storage. $e->removePolicy('alice', 'data1', 'read'); // However, the file adapter doesn't implement the AutoSave feature, so enabling it has no effect at all here. // Reload the policy from the storage to see the effect. $e->loadPolicy(); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); } public function testInitEmpty() { $e = new Enforcer(enableLog: true); $m = Model::newModelFromString( <<<'EOT' [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 EOT ); $e->setModel($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 testGetAndSetModel() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e2 = new Enforcer($this->modelAndPolicyPath . '/basic_with_root_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $this->assertFalse($e->enforce('root', 'data1', 'read')); $e->setModel($e2->getModel()); $this->assertTrue($e->enforce('root', 'data1', 'read')); } public function testGetAndSetAdapter() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e2 = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_inverse_policy.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $a2 = $e2->getAdapter(); $e->setAdapter($a2); $e->loadModel(); $e->loadPolicy(); $this->assertFalse($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data1', 'write')); } public function testGetRoleManager() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf'); $rm = $e->getRoleManager(); $this->assertTrue($rm instanceof RoleManager); } public function testSetAdapterFromFile() { $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')); } public function testClearPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $e->clearPolicy(); $this->assertFalse($e->enforce('alice', 'data1', 'read')); } public function testSavePolicy() { $policyFile = __DIR__ . '/Persist/Adapters/rbac_policy_test.csv'; file_put_contents($policyFile, '', LOCK_EX); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $policyFile); $this->assertEquals($e->enforce('alice', 'data1', 'read'), false); $this->assertEquals($e->enforce('bob', 'data2', 'write'), false); $this->assertEquals($e->enforce('alice', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'data2', 'write'), false); $m = $e->getModel(); $m->addPolicy('p', 'p', ['alice', 'data1', 'read']); $m->addPolicy('p', 'p', ['bob', 'data2', 'write']); $m->addPolicy('p', 'p', ['data2_admin', 'data2', 'read']); $m->addPolicy('p', 'p', ['data2_admin', 'data2', 'write']); $m->addPolicy('g', 'g', ['alice', 'data2_admin']); $e->savePolicy(); $e2 = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $policyFile); $this->assertEquals($e2->enforce('alice', 'data1', 'read'), true); $this->assertEquals($e2->enforce('bob', 'data2', 'write'), true); $this->assertEquals($e2->enforce('alice', 'data2', 'read'), true); $this->assertEquals($e2->enforce('alice', 'data2', 'write'), true); file_put_contents($policyFile, '', LOCK_EX); } public function testFilteredPolicy() { $adapter = new FileFilteredAdapter($this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $adapter); $this->assertTrue($e->isFiltered()); $e->loadPolicy(); $this->assertTrue($e->hasPolicy('admin', 'domain1', 'data1', 'read')); $this->assertTrue($e->hasPolicy('admin', 'domain2', 'data2', 'read')); $e->loadFilteredPolicy(new Filter( ['', 'domain1'], ['', '', 'domain1'] )); $this->assertTrue($e->hasPolicy('admin', 'domain1', 'data1', 'read')); $this->assertFalse($e->hasPolicy('admin', 'domain2', 'data2', 'read')); $th = null; try { $e->savePolicy(); } catch (\Throwable $th) { //throw $th; } $this->assertInstanceOf(CasbinException::class, $th); $th = null; try { $e->getAdapter()->savePolicy($e->getModel()); } catch (\Throwable $th) { //throw $th; } $this->assertInstanceOf(CasbinException::class, $th); } public function testAppendFilteredPolicy() { $e = new Enforcer(); $adapter = new FileFilteredAdapter($this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->initWithAdapter($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $adapter); $e->loadPolicy(); // validate initial conditions $this->assertTrue($e->hasPolicy('admin', 'domain1', 'data1', 'read')); $this->assertTrue($e->hasPolicy('admin', 'domain2', 'data2', 'read')); $filter = new Filter(); $filter->p = ['', 'domain1']; $filter->g = ['', '', 'domain1']; $e->loadFilteredPolicy($filter); $this->assertTrue($adapter->isFiltered()); // only policies for domain1 should be loaded $this->assertTrue($e->hasPolicy('admin', 'domain1', 'data1', 'read')); $this->assertFalse($e->hasPolicy('admin', 'domain2', 'data2', 'read')); // disable clear policy and load second domain $filter = new Filter(); $filter->p = ['', 'domain2']; $filter->g = ['', '', 'domain2']; $e->loadIncrementalFilteredPolicy($filter); // both domain policies should be loaded $this->assertTrue($e->hasPolicy('admin', 'domain1', 'data1', 'read')); $this->assertTrue($e->hasPolicy('admin', 'domain2', 'data2', 'read')); } public function testEnableEnforce() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e->enableEnforce(false); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); $this->assertTrue($e->enforce('bob', 'data1', 'read')); $this->assertTrue($e->enforce('bob', 'data1', 'write')); $this->assertTrue($e->enforce('bob', 'data2', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $e->enableEnforce(true); $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 testPriorityExplicit() { $e = new Enforcer($this->modelAndPolicyPath . '/priority_model_explicit.conf', $this->modelAndPolicyPath . '/priority_policy_explicit.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $this->assertFalse($e->enforce('data1_deny_group', 'data1', 'read')); $this->assertFalse($e->enforce('data1_deny_group', 'data1', 'write')); $this->assertTrue($e->enforce('data2_allow_group', 'data2', 'read')); $this->assertTrue($e->enforce('data2_allow_group', 'data2', 'write')); $e->addPolicy('1', 'bob', 'data2', 'write', 'deny'); $this->assertTrue($e->enforce('alice', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'read')); $this->assertFalse($e->enforce('bob', 'data2', 'write')); $this->assertFalse($e->enforce('data1_deny_group', 'data1', 'read')); $this->assertFalse($e->enforce('data1_deny_group', 'data1', 'write')); $this->assertTrue($e->enforce('data2_allow_group', 'data2', 'read')); $this->assertTrue($e->enforce('data2_allow_group', 'data2', 'write')); } public function testLoadPolicyError() { $this->expectException(CasbinException::class); $this->expectExceptionMessage('loadPolicy error'); $adapter = Mockery::mock(Adapter::class); $adapter->shouldReceive('loadPolicy') ->once() ->withAnyArgs() ->andThrow(new CasbinException('loadPolicy error')); /** @var Adapter $adapter */ $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $adapter); $e->loadPolicy(); } } tests/Unit/EnforcerTest.php000064400000107366152475271650011774 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'), []); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals(['admin'], $e->getRolesForUser('alice', 'domain1')); $this->assertEquals([], $e->getRolesForUser('bob', 'domain1')); $this->assertEquals([], $e->getRolesForUser('admin', 'domain1')); $this->assertEquals([], $e->getRolesForUser('non_exist', 'domain1')); $this->assertEquals([], $e->getRolesForUser('alice', 'domain2')); $this->assertEquals(['admin'], $e->getRolesForUser('bob', 'domain2')); $this->assertEquals([], $e->getRolesForUser('admin', 'domain2')); $this->assertEquals([], $e->getRolesForUser('non_exist', 'domain2')); } 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')); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertTrue($e->hasRoleForUser('alice', 'admin', 'domain1')); $this->assertFalse($e->hasRoleForUser('alice', 'admin', 'domain2')); } 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']); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertTrue($e->hasRoleForUser('alice', 'admin', 'domain1')); $this->assertFalse($e->hasRoleForUser('bob', 'admin', 'domain1')); $e->deleteRoleForUser('alice', 'admin', 'domain1'); $e->addRoleForUser('bob', 'admin', 'domain1'); $this->assertEquals([], $e->getRolesForUser('alice', 'domain1')); $this->assertEquals(['admin'], $e->getRolesForUser('bob', 'domain1')); $this->assertEquals(['admin'], $e->getRolesForUser('bob', 'domain2')); $this->assertEquals([], $e->getRolesForUser('non_exist', 'domain1')); $this->assertEquals([], $e->getRolesForUser('non_exist', 'domain2')); } public function testAddRolesForUser() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $e->addRolesForUser('alice', ["data1_admin", "data2_admin", "data3_admin"]); // The "alice" already has "data2_admin" , it will be return false. So "alice" just has "data2_admin". $this->assertEquals(["data2_admin"], $e->getRolesForUser('alice')); $e->deleteRoleForUser('alice', 'data2_admin'); $e->addRolesForUser('alice', ["data1_admin", "data2_admin", "data3_admin"]); $this->assertEquals(["data1_admin", "data2_admin", "data3_admin"], $e->getRolesForUser('alice')); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); } 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']); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertTrue($e->hasRoleForUser('alice', 'admin', 'domain1')); $this->assertFalse($e->hasRoleForUser('bob', 'admin', 'domain1')); $e->deleteRoleForUser('alice', 'admin', 'domain1'); $e->addRoleForUser('bob', 'admin', 'domain1'); $this->assertFalse($e->hasRoleForUser('alice', 'admin', 'domain1')); $this->assertTrue($e->hasRoleForUser('bob', 'admin', 'domain1')); } 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 testDeleteRolesForUserInDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getRolesForUser('bob', 'domain2'), ['admin']); $e->deleteRolesForUserInDomain('bob', 'domain2'); $this->assertEquals($e->getRolesForUser('bob', 'domain2'), []); $this->assertEquals($e->getRolesForUser('alice', 'domain1'), ['admin']); $e->deleteRolesForUserInDomain('alice', 'domain1'); $this->assertEquals($e->getRolesForUser('alice', 'domain1'), []); $e->addRoleForUserInDomain('bob', 'admin', 'domain1'); $this->assertEquals($e->getRolesForUser('bob', 'domain1'), ['admin']); $e->deleteRolesForUserInDomain('bob', 'domain1'); $this->assertEquals($e->getRolesForUser('bob', 'domain1'), []); } public function testDeleteUser() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin']); $e->deleteUser('alice'); $this->assertFalse($e->hasPolicy('alice', 'data1', 'read')); $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 testAddPermissionsForUser() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_without_resources_model.conf', $this->modelAndPolicyPath . '/basic_without_resources_policy.csv'); $e->addPermissionsForUser('jack', ['read'], ['write']); $this->assertTrue($e->enforce('jack', 'read')); $this->assertTrue($e->enforce('jack', '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']]); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getPermissionsForUser('alice', 'domain1'), []); $this->assertEquals($e->getPermissionsForUser('bob', 'domain1'), []); $this->assertEquals($e->getPermissionsForUser('admin', 'domain1'), [['admin', 'domain1', 'data1', 'read'], ['admin', 'domain1', 'data1', 'write']]); $this->assertEquals($e->getPermissionsForUser('non_exist', 'domain1'), []); } 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->getPermissionsForUser('alice'), [['alice', 'data1', 'read']]); $this->assertEquals($e->getPermissionsForUser('bob'), [['bob', 'data2', 'write']]); $this->assertEquals($e->getImplicitRolesForUser('alice'), ['admin', 'data1_admin', 'data2_admin']); $this->assertEquals($e->getImplicitRolesForUser('bob'), []); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_hierarchy_with_domains_policy.csv'); $this->assertEquals($e->getImplicitRolesForUser('alice', 'domain1'), ['role:global_admin', 'role:reader', 'role:writer']); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); $roleManager = $e->getRoleManager(); if ($roleManager instanceof RoleManager) { $roleManager->addMatchingFunc('matcher', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); } $this->assertEquals($e->getImplicitRolesForUser('cathy'), ['/book/1/2/3/4/5', 'pen_admin']); $this->assertEquals($e->getRolesForUser('cathy'), ['/book/1/2/3/4/5', 'pen_admin']); } public function testGetImplicitResourcesForUser() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); $this->assertEqualsCanonicalizing([ ["alice", "/pen/1", "GET"], ["alice", "/pen2/1", "GET"], ["alice", "/book/:id", "GET"], ["alice", "/book2/{id}", "GET"], ["alice", "/book/*", "GET"], ["alice", "book_group", "GET"], ], $e->getImplicitResourcesForUser('alice')); $this->assertEqualsCanonicalizing([ ["bob", "pen_group", "GET"], ["bob", "/pen/:id", "GET"], ["bob", "/pen2/{id}", "GET"], ], $e->getImplicitResourcesForUser('bob')); $this->assertEqualsCanonicalizing([ ["cathy", "pen_group", "GET"], ["cathy", "/pen/:id", "GET"], ["cathy", "/pen2/{id}", "GET"], ], $e->getImplicitResourcesForUser('cathy')); } public function testImplicitUsersForRole() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); $this->assertEqualsCanonicalizing(['alice'], $e->getImplicitUsersForRole('book_admin')); $this->assertEqualsCanonicalizing(['cathy', 'bob'], $e->getImplicitUsersForRole('pen_admin')); $this->assertEqualsCanonicalizing(['/book/*', '/book/:id', '/book2/{id}'], $e->getImplicitUsersForRole('book_group')); $this->assertEqualsCanonicalizing(['/pen/:id', '/pen2/{id}'], $e->getImplicitUsersForRole('pen_group')); } 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'], ]); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_hierarchy_with_domains_policy.csv'); $this->assertEquals($e->getImplicitPermissionsForUser('alice', 'domain1'), [ ['alice', 'domain1', 'data2', 'read'], ['role:reader', 'domain1', 'data1', 'read'], ['role:writer', 'domain1', 'data1', 'write'], ]); } public function testGetImplicitUsersForPermission() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_with_hierarchy_policy.csv'); $this->assertEquals($e->getImplicitUsersForPermission('data1', 'read'), ['alice']); $this->assertEquals($e->getImplicitUsersForPermission('data1', 'write'), ['alice']); $this->assertEquals($e->getImplicitUsersForPermission('data2', 'read'), ['alice']); $this->assertEquals($e->getImplicitUsersForPermission('data2', 'write'), ['alice', 'bob']); } public function testGetUsersForRoleInDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getUsersForRoleInDomain('admin', 'domain1'), ['alice']); $this->assertEquals($e->getUsersForRoleInDomain('non_exist', 'domain1'), []); $this->assertEquals($e->getUsersForRoleInDomain('admin', 'domain2'), ['bob']); $this->assertEquals($e->getUsersForRoleInDomain('non_exist', 'domain2'), []); $e->deleteRoleForUserInDomain('alice', 'admin', 'domain1'); $e->addRoleForUserInDomain('bob', 'admin', 'domain1'); $this->assertEquals($e->getUsersForRoleInDomain('admin', 'domain1'), ['bob']); $this->assertEquals($e->getUsersForRoleInDomain('non_exist', 'domain1'), []); $this->assertEquals($e->getUsersForRoleInDomain('admin', 'domain2'), ['bob']); $this->assertEquals($e->getUsersForRoleInDomain('non_exist', 'domain2'), []); } public function testGetRolesForUserInDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getRolesForUserInDomain('alice', 'domain1'), ['admin']); $this->assertEquals($e->getRolesForUserInDomain('bob', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('admin', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('non_exist', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('alice', 'domain2'), []); $this->assertEquals($e->getRolesForUserInDomain('bob', 'domain2'), ['admin']); $this->assertEquals($e->getRolesForUserInDomain('admin', 'domain2'), []); $this->assertEquals($e->getRolesForUserInDomain('non_exist', 'domain2'), []); $e->deleteRoleForUserInDomain('alice', 'admin', 'domain1'); $e->addRoleForUserInDomain('bob', 'admin', 'domain1'); $this->assertEquals($e->getRolesForUserInDomain('alice', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('bob', 'domain1'), ['admin']); $this->assertEquals($e->getRolesForUserInDomain('admin', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('non_exist', 'domain1'), []); $this->assertEquals($e->getRolesForUserInDomain('alice', 'domain2'), []); $this->assertEquals($e->getRolesForUserInDomain('bob', 'domain2'), ['admin']); $this->assertEquals($e->getRolesForUserInDomain('admin', 'domain2'), []); $this->assertEquals($e->getRolesForUserInDomain('non_exist', 'domain2'), []); } public function testGetPermissionsForUserInDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getPermissionsForUserInDomain('alice', 'domain1'), []); $this->assertEquals($e->getPermissionsForUserInDomain('bob', 'domain1'), []); $this->assertEquals($e->getPermissionsForUserInDomain('admin', 'domain1'), [['admin', 'domain1', 'data1', 'read'], ['admin', 'domain1', 'data1', 'write']]); $this->assertEquals($e->getPermissionsForUserInDomain('non_exist', 'domain1'), []); $this->assertEquals($e->getPermissionsForUserInDomain('alice', 'domain2'), []); $this->assertEquals($e->getPermissionsForUserInDomain('bob', 'domain2'), []); $this->assertEquals($e->getPermissionsForUserInDomain('admin', 'domain2'), [['admin', 'domain2', 'data2', 'read'], ['admin', 'domain2', 'data2', 'write']]); $this->assertEquals($e->getPermissionsForUserInDomain('non_exist', 'domain2'), []); } public function testGetDomainsForUser() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy2.csv'); $this->assertEquals($e->getDomainsForUser('alice'), ['domain1', 'domain2'], true); $this->assertEquals($e->getDomainsForUser('bob'), ['domain2', 'domain3'], true); $this->assertEquals($e->getDomainsForUser('user'), ['domain3'], true); } public function testGetAllRolesByDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals(['admin'], $e->getAllRolesByDomain('domain1')); $this->assertEquals(['admin'], $e->getAllRolesByDomain('domain2')); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy2.csv'); $this->assertEquals(['admin'], $e->getAllRolesByDomain('domain1')); $this->assertEquals(['admin'], $e->getAllRolesByDomain('domain2')); $this->assertEquals(['user'], $e->getAllRolesByDomain('domain3')); } public function testGetAllUsersByDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals(['alice', 'admin'], $e->getAllUsersByDomain('domain1')); $this->assertEquals(['bob', 'admin'], $e->getAllUsersByDomain('domain2')); } public function testFailedToLoadPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); $e->addNamedMatchingFunc('g2', 'matchingFunc', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $this->assertTrue($e->enforce('alice', '/pen/1', 'GET')); $this->assertTrue($e->enforce('alice', '/pen2/1', 'GET')); $e->setAdapter(new FileAdapter('not found')); $e->loadPolicy(); $this->assertTrue($e->enforce('alice', '/pen/1', 'GET')); $this->assertTrue($e->enforce('alice', '/pen2/1', 'GET')); } public function testReloadPolicyWithFunc() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); $e->addNamedMatchingFunc('g2', 'matchingFunc', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $this->assertTrue($e->enforce('alice', '/pen/1', 'GET')); $this->assertTrue($e->enforce('alice', '/pen2/1', 'GET')); $e->loadPolicy(); $this->assertTrue($e->enforce('alice', '/pen/1', 'GET')); $this->assertTrue($e->enforce('alice', '/pen2/1', 'GET')); } public function testBatchEnforce() { $e = new Enforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $res = $e->batchEnforce([ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], ['jack', 'data3', 'read'] ]); $this->assertEquals([true, true, false], $res); } public function testSubjectPriority() { $e = new Enforcer($this->modelAndPolicyPath . '/subject_priority_model.conf', $this->modelAndPolicyPath . '/subject_priority_policy.csv'); $this->assertTrue($e->enforce('jane', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data1', 'read')); } public function testSubjectPriorityWithDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/subject_priority_model_with_domain.conf', $this->modelAndPolicyPath . '/subject_priority_policy_with_domain.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'domain1', 'write')); $this->assertTrue($e->enforce('bob', 'data2', 'domain2', 'write')); } public function testDeleteAllUsersByDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->deleteAllUsersByDomain('domain1'); $this->assertEquals([ ['admin', 'domain2', 'data2', 'read'], ['admin', 'domain2', 'data2', 'write'], ], $e->getPolicy()); $this->assertEquals([ ['bob', 'admin', 'domain2'] ], $e->getGroupingPolicy()); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->deleteAllUsersByDomain('domain2'); $this->assertEquals([ ['admin', 'domain1', 'data1', 'read'], ['admin', 'domain1', 'data1', 'write'], ], $e->getPolicy()); $this->assertEquals([ ['alice', 'admin', 'domain1'] ], $e->getGroupingPolicy()); } public function testDeleteDomains() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->deleteDomains(); $this->assertEquals([], $e->getPolicy()); $this->assertEquals([], $e->getGroupingPolicy()); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->deleteDomains('domain1'); $this->assertEquals([ ['admin', 'domain2', 'data2', 'read'], ['admin', 'domain2', 'data2', 'write'], ], $e->getPolicy()); $this->assertEquals([ ['bob', 'admin', 'domain2'] ], $e->getGroupingPolicy()); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $e->deleteDomains('domain1', 'domain2'); $this->assertEquals([], $e->getPolicy()); $this->assertEquals([], $e->getGroupingPolicy()); } public function testCustomizedFieldIndex() { $e = new Enforcer($this->modelAndPolicyPath . '/priority_model_explicit_customized.conf', $this->modelAndPolicyPath . '/priority_policy_explicit_customized.csv'); $this->assertEquals(0, $e->getFieldIndex('p', 'customized_priority')); $this->assertEquals(1, $e->getFieldIndex('p', Constants::OBJECT_INDEX)); $this->assertEquals(2, $e->getFieldIndex('p', Constants::ACTION_INDEX)); $this->assertEquals(3, $e->getFieldIndex('p', 'eft')); $this->assertEquals(4, $e->getFieldIndex('p', 'subject')); $this->assertTrue($e->enforce('bob', 'data2', 'read')); $e->setFieldIndex('p', Constants::PRIORITY_INDEX, 0); $e->loadPolicy(); $this->assertFalse($e->enforce('bob', 'data2', 'read')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $e->addPolicy('1', 'data2', 'write', 'deny', 'bob'); $this->assertFalse($e->enforce('bob', 'data2', 'write')); $this->expectException(CasbinException::class); $e->deletePermissionsForUser('bob'); $e->setFieldIndex('p', Constants::SUBJECT_INDEX, 4); $this->assertTrue($e->deletePermissionsForUser('bob')); $this->assertTrue($e->enforce('bob', 'data2', 'write')); $this->assertTrue($e->deleteRole('data2_allow_group')); $this->assertFalse($e->enforce('bob', 'data2', 'write')); } public function testGetAllowedObjectConditions() { $e = new Enforcer($this->modelAndPolicyPath . '/object_conditions_model.conf', $this->modelAndPolicyPath . '/object_conditions_policy.csv'); $this->assertEquals($e->getAllowedObjectConditions('alice', 'read', 'r.obj.'), ['price < 25', 'category_id = 2']); $this->assertEquals($e->getAllowedObjectConditions('admin', 'read', 'r.obj.'), ['category_id = 2']); $this->assertEquals($e->getAllowedObjectConditions('bob', 'write', 'r.obj.'), ['author = bob']); // test err try { $e->getAllowedObjectConditions('alice', 'write', 'r.obj.'); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $err) { $this->assertEquals('GetAllowedObjectConditions have an empty condition', $err->getMessage()); } try { $e->getAllowedObjectConditions('bob', 'read', 'r.obj.'); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $err) { $this->assertEquals('GetAllowedObjectConditions have an empty condition', $err->getMessage()); } $e->addPolicy('alice', 'price > 50', 'read'); try { $e->getAllowedObjectConditions('alice', 'read', 'r.obj.'); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $err) { $this->assertEquals('need to meet the prefix required by the object condition', $err->getMessage()); } // test prefix $e->clearPolicy(); $e->getRoleManager()->deleteLink('alice', 'admin'); $e->addPolicies([['alice', 'r.book.price < 25', 'read'], ['admin', 'r.book.category_id = 2', 'read'], ['bob', 'r.book.author = bob', 'write']]); $this->assertEquals($e->getAllowedObjectConditions('alice', 'read', 'r.book.'), ['price < 25']); $this->assertEquals($e->getAllowedObjectConditions('admin', 'read', 'r.book.'), ['category_id = 2']); $this->assertEquals($e->getAllowedObjectConditions('bob', 'write', 'r.book.'), ['author = bob']); } public function testGetImplicitUsersForResource() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals($e->getImplicitUsersForResource('data1'), [['alice', 'data1', 'read']]); $this->assertEquals($e->getImplicitUsersForResource('data2'), [['bob', 'data2', 'write'], ['alice', 'data2', 'read'], ['alice', 'data2', 'write']]); // test duplicate permissions $e->addGroupingPolicy('alice', 'data2_admin_2'); $e->addPolicies([['data2_admin_2', 'data2', 'read'], ['data2_admin_2', 'data2', 'write']]); $this->assertEquals($e->getImplicitUsersForResource('data2'), [['bob', 'data2', 'write'], ['alice', 'data2', 'read'], ['alice', 'data2', 'write']]); } public function testGetImplicitUsersForResourceByDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getImplicitUsersForResourceByDomain('data1', 'domain1'), [['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data1', 'write']]); $this->assertEquals($e->getImplicitUsersForResourceByDomain('data2', 'domain1'), []); $this->assertEquals($e->getImplicitUsersForResourceByDomain('data2', 'domain2'), [['bob', 'domain2', 'data2', 'read'], ['bob', 'domain2', 'data2', 'write']]); } public function testLinkConditionFunc() { $trueFunc = function (...$args) { if (count($args) !== 0) { return $args[0] === "_" || $args[0] === "true"; } return false; }; $falseFunc = function (...$args) { if (count($args) !== 0) { return $args[0] === "_" || $args[0] === "false"; } return false; }; $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_temporal_roles_model.conf'); $e->addPolicies([ ['alice', 'data1', 'read'], ['alice', 'data1', 'write'], ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ['data3_admin', 'data3', 'read'], ['data3_admin', 'data3', 'write'], ['data4_admin', 'data4', 'read'], ['data4_admin', 'data4', 'write'], ['data5_admin', 'data5', 'read'], ['data5_admin', 'data5', 'write'], ]); $e->addGroupingPolicies([ ['alice', 'data2_admin', '_', '_'], ['alice', 'data3_admin', '_', '_'], ['alice', 'data4_admin', '_', '_'], ['alice', 'data5_admin', '_', '_'], ]); $e->addNamedLinkConditionFunc('g', 'alice', 'data2_admin', $trueFunc); $e->addNamedLinkConditionFunc('g', 'alice', 'data3_admin', $trueFunc); $e->addNamedLinkConditionFunc('g', 'alice', 'data4_admin', $falseFunc); $e->addNamedLinkConditionFunc('g', 'alice', 'data5_admin', $falseFunc); $e->setNamedLinkConditionFuncParams('g', 'alice', 'data2_admin', 'true'); $e->setNamedLinkConditionFuncParams('g', 'alice', 'data3_admin', 'not true'); $e->setNamedLinkConditionFuncParams('g', 'alice', 'data4_admin', 'false'); $e->setNamedLinkConditionFuncParams('g', 'alice', 'data5_admin', 'not false'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'data2', 'write')); $this->assertFalse($e->enforce('alice', 'data3', 'read')); $this->assertFalse($e->enforce('alice', 'data3', 'write')); $this->assertTrue($e->enforce('alice', 'data4', 'read')); $this->assertTrue($e->enforce('alice', 'data4', 'write')); $this->assertFalse($e->enforce('alice', 'data5', 'read')); $this->assertFalse($e->enforce('alice', 'data5', 'write')); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domain_temporal_roles_model.conf'); $e->addPolicies([ ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data1', 'write'], ['data2_admin', 'domain2', 'data2', 'read'], ['data2_admin', 'domain2', 'data2', 'write'], ['data3_admin', 'domain3', 'data3', 'read'], ['data3_admin', 'domain3', 'data3', 'write'], ['data4_admin', 'domain4', 'data4', 'read'], ['data4_admin', 'domain4', 'data4', 'write'], ['data5_admin', 'domain5', 'data5', 'read'], ['data5_admin', 'domain5', 'data5', 'write'], ]); $e->addGroupingPolicies([ ['alice', 'data2_admin', 'domain2', '_', '_'], ['alice', 'data3_admin', 'domain3', '_', '_'], ['alice', 'data4_admin', 'domain4', '_', '_'], ['alice', 'data5_admin', 'domain5', '_', '_'], ]); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data2_admin', 'domain2', $trueFunc); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data3_admin', 'domain3', $trueFunc); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data4_admin', 'domain4', $falseFunc); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data5_admin', 'domain5', $falseFunc); $e->setNamedDomainLinkConditionFuncParams('g', 'alice', 'data2_admin', 'domain2', 'true'); $e->setNamedDomainLinkConditionFuncParams('g', 'alice', 'data3_admin', 'domain3', 'not true'); $e->setNamedDomainLinkConditionFuncParams('g', 'alice', 'data4_admin', 'domain4', 'false'); $e->setNamedDomainLinkConditionFuncParams('g', 'alice', 'data5_admin', 'domain5', 'not false'); $this->assertTrue($e->enforce('alice', 'domain1', 'data1', 'read')); $this->assertTrue($e->enforce('alice', 'domain1', 'data1', 'write')); $this->assertTrue($e->enforce('alice', 'domain2', 'data2', 'read')); $this->assertTrue($e->enforce('alice', 'domain2', 'data2', 'write')); $this->assertFalse($e->enforce('alice', 'domain3', 'data3', 'read')); $this->assertFalse($e->enforce('alice', 'domain3', 'data3', 'write')); $this->assertTrue($e->enforce('alice', 'domain4', 'data4', 'read')); $this->assertTrue($e->enforce('alice', 'domain4', 'data4', 'write')); $this->assertFalse($e->enforce('alice', 'domain5', 'data5', 'read')); $this->assertFalse($e->enforce('alice', 'domain5', 'data5', 'write')); } } tests/Unit/Log/LogTest.php000064400000002666152475271650011470 0ustar00expectNotToPerformAssertions(); $logger = Mockery::mock(Logger::class); $logger->shouldReceive('enableLog') ->once() ->with(true); $logger->shouldReceive('isEnabled') ->once() ->andReturn(true); $logger->shouldReceive('logPolicy') ->once() ->with([]); $logger->shouldReceive('logModel') ->once() ->with([]); $logger->shouldReceive('logEnforce') ->once() ->with('my_matcher', ['bob'], true, []); $logger->shouldReceive('logRole') ->once() ->with([]); $logger->shouldReceive('logError') ->once() ->with(Mockery::type(\Exception::class), 'test'); /** @var Logger $logger */ Log::setLogger($logger); Log::getLogger()->enableLog(true); Log::getLogger()->isEnabled(); Log::logModel([]); Log::logEnforce('my_matcher', ['bob'], true, []); Log::logPolicy([]); Log::logRole([]); Log::logError(new \Exception(), 'test'); } } tests/Unit/Log/Logger/DefaultLoggerTest.php000064400000004552152475271650014706 0ustar00enableLog(true); $enable = $logger->isEnabled(); $this->assertTrue($enable); $logfile = $logger->psrLogger->path; if (file_exists($logfile)) { unlink($logfile); } $logger->logModel([]); $logger->logEnforce('my_matcher', ['bob'], true, []); $logger->logPolicy(['p'=>[['alice', 'data1', 'read']]]); $logger->logRole([]); $logger->logError(new \Exception('test')); $pattern = '/^.*? INFO: Model:\s*' . PHP_EOL . '^.*? INFO: Request: bob ---> true' . PHP_EOL . 'Hit Policy:\s*' . PHP_EOL . '^.*? INFO: Policy: p : \[\[alice data1 read\]\]\s*' . PHP_EOL . '^.*? INFO: Roles:\s*' . PHP_EOL . '^.*? ERROR: test' . PHP_EOL . '$/m'; $this->assertTrue(file_exists($logfile)); $this->assertMatchesRegularExpression($pattern, file_get_contents($logfile)); $logger->enableLog(false); $enable = $logger->isEnabled(); $this->assertFalse($enable); // reach the `return` statement inside $logger->logModel([]); $logger->logEnforce('my_matcher', ['bob'], true, []); $logger->logPolicy([]); $logger->logRole([]); $logger->logError(new \Exception('test')); } public function testDefaultLoggerWithPsrLogger() { $this->expectNotToPerformAssertions(); $psrLogger = Mockery::mock(\Psr\Log\AbstractLogger::class); $psrLogger->shouldReceive('info')->withAnyArgs()->andReturn(null); $psrLogger->shouldReceive('error')->withAnyArgs()->andReturn(null); $logger = new DefaultLogger($psrLogger); $logger->enableLog(true); $logger->logModel([]); $logger->logEnforce('my_matcher', ['bob'], true, []); $logger->logPolicy([]); $logger->logRole([]); $logger->logError(new \Exception('test')); $psrLogger->shouldHaveReceived('info')->atLeast()->times(4); $psrLogger->shouldHaveReceived('error')->atLeast()->times(1); Mockery::close(); } } tests/Unit/ManagementEnforcerTest.php000064400000043650152475271650013764 0ustar00modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals(['alice', 'bob', 'data2_admin'], $e->getAllNamedSubjects('p')); $this->assertEquals([], $e->getAllNamedSubjects('g')); } public function testGetAllNamedObjects() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals(['data1', 'data2'], $e->getAllNamedObjects('p')); $this->assertEquals([], $e->getAllNamedObjects('g')); } public function testGetAllNamedActions() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals(['read', 'write'], $e->getAllNamedActions('p')); } public function testGetAllNamedRoles() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals(['data2_admin'], $e->getAllNamedRoles('g')); } public function testGetList() { $e = new Enforcer($this->modelAndPolicyPath . '/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']); $this->assertEquals($e->getAllDomains(), ['']); } public function testGetListWithDomains() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domains_model.conf', $this->modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertEquals($e->getAllSubjects(), ['admin']); $this->assertEquals($e->getAllObjects(), ['data1', 'data2']); $this->assertEquals($e->getAllActions(), ['read', 'write']); $this->assertEquals($e->getAllRoles(), ['admin']); $this->assertEquals($e->getAllDomains(), ['domain1', 'domain2']); } 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'); $rules = [ ['jack', 'data4', 'read'], ['katy', 'data4', 'write'], ['leyo', 'data4', 'read'], ['ham', 'data4', 'write'], ]; $e->addPolicies($rules); $e->addPolicies($rules); $this->assertEquals([ ['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write'], ['eve', 'data3', 'read'], ['jack', 'data4', 'read'], ['katy', 'data4', 'write'], ['leyo', 'data4', 'read'], ['ham', 'data4', 'write'], ], $e->getPolicy()); $e->removePolicies($rules); $e->removePolicies($rules); $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'], ]); $e->clearPolicy(); $e->addPoliciesEx([['user1', 'data1', 'read'], ['user1', 'data1', 'read']]); $this->assertEquals($e->getPolicy(), [['user1', 'data1', 'read']]); $e->addPoliciesEx([['user1', 'data1', 'read'], ['user2', 'data2', 'read']]); $this->assertEquals($e->getPolicy(), [['user1', 'data1', 'read'], ['user2', 'data2', 'read']]); $e->addNamedPoliciesEx('p', [['user1', 'data1', 'read'], ['user2', 'data2', 'read'], ['user3', 'data3', 'read']]); $this->assertEquals($e->getPolicy(), [['user1', 'data1', 'read'], ['user2', 'data2', 'read'], ['user3', 'data3', 'read']]); $e->selfAddPolicesEx('p', 'p', [['user1', 'data1', 'read'], ['user2', 'data2', 'read'], ['user3', 'data3', 'read'], ['user4', 'data4', 'read']]); $this->assertEquals($e->getPolicy(), [['user1', 'data1', 'read'], ['user2', 'data2', 'read'], ['user3', 'data3', 'read'], ['user4', 'data4', 'read']]); } public function testModifyGroupingPolicyAPI() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin']); $this->assertEquals($e->getRolesForUser('bob'), []); $this->assertEquals($e->getRolesForUser('env'), []); $this->assertEquals($e->getRolesForUser('non_exist'), []); $result = $e->removeGroupingPolicy('alice', 'data2_admin'); $e->addGroupingPolicy('bob', 'data1_admin'); $e->addGroupingPolicy('eve', 'data3_admin'); $groupingRules = [ ['ham', 'data4_admin'], ['jack', 'data5_admin'], ]; $e->addGroupingPolicies($groupingRules); $this->assertEquals($e->getRolesForUser('ham'), ['data4_admin']); $this->assertEquals($e->getRolesForUser('jack'), ['data5_admin']); $e->removeGroupingPolicies($groupingRules); $this->assertEquals($e->getRolesForUser('alice'), []); $nameGroupingPolicy = ['alice', 'data2_admin']; $this->assertEquals($e->getRolesForUser('alice'), []); $e->addNamedGroupingPolicy('g', $nameGroupingPolicy); $this->assertEquals($e->getRolesForUser('alice'), ['data2_admin']); $e->removeNamedGroupingPolicy('g', $nameGroupingPolicy); $e->addNamedGroupingPolicies('g', $groupingRules); $e->addNamedGroupingPolicies('g', $groupingRules); $this->assertEquals($e->getRolesForUser('ham'), ['data4_admin']); $this->assertEquals($e->getRolesForUser('jack'), ['data5_admin']); $e->removeNamedGroupingPolicies('g', $groupingRules); $e->removeNamedGroupingPolicies('g', $groupingRules); $this->assertEquals($e->getRolesForUser('alice'), []); $this->assertEquals($e->getRolesForUser('bob'), ['data1_admin']); $this->assertEquals($e->getRolesForUser('eve'), ['data3_admin']); $this->assertEquals($e->getRolesForUser('non_exist'), []); $this->assertEquals($e->getUsersForRole('data1_admin'), ['bob']); $this->assertEquals($e->getUsersForRole('data2_admin'), []); $this->assertEquals($e->getUsersForRole('data3_admin'), ['eve']); $e->removeFilteredGroupingPolicy(0, 'bob'); $this->assertEquals($e->getRolesForUser('alice'), []); $this->assertEquals($e->getRolesForUser('bob'), []); $this->assertEquals($e->getRolesForUser('eve'), ['data3_admin']); $this->assertEquals($e->getRolesForUser('non_exist'), []); $this->assertEquals($e->getUsersForRole('data1_admin'), []); $this->assertEquals($e->getUsersForRole('data2_admin'), []); $this->assertEquals($e->getUsersForRole('data3_admin'), ['eve']); $e->clearPolicy(); $e->addGroupingPoliciesEx([['user1', 'member']]); $this->assertEquals($e->getUsersForRole('member'), ['user1']); $e->addGroupingPoliciesEx([['user1', 'member'], ['user2', 'member']]); $this->assertEquals($e->getUsersForRole('member'), ['user1', 'user2']); $e->addNamedGroupingPoliciesEx('g', [['user1', 'member'], ['user2', 'member'], ['user3', 'member']]); $this->assertEquals($e->getUsersForRole('member'), ['user1', 'user2', 'user3']); } public function testUpdatePolicy() { // p, alice, data1, read // p, bob, data2, write // p, data2_admin, data2, read // p, data2_admin, data2, write // // g, alice, data2_admin $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); $this->assertFalse($e->hasPolicy('alice', 'data1', 'write')); $e->updatePolicy(['alice', 'data1', 'read'], ['alice', 'data1', 'write']); $this->assertFalse($e->hasPolicy('alice', 'data1', 'read')); $this->assertTrue($e->hasPolicy('alice', 'data1', 'write')); } public function testUpdatePolicies() { // p, alice, data1, read // p, bob, data2, write // p, data2_admin, data2, read // p, data2_admin, data2, write // // g, alice, data2_admin $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $this->modelAndPolicyPath . '/rbac_policy.csv'); $watcherUpdatable = new SampleWatcherUpdatable(); $e->setWatcher($watcherUpdatable); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); $this->assertFalse($e->hasPolicy('alice', 'data1', 'write')); $this->assertTrue($e->hasPolicy('bob', 'data2', 'write')); $this->assertFalse($e->hasPolicy('bob', 'data2', 'read')); $oldPolicies = [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'] ]; $newPolicies = [ ['alice', 'data1', 'write'], ['bob', 'data2', 'read'] ]; $e->updatePolicies($newPolicies, $oldPolicies); $watcherUpdatable->setUpdateCallback(function () { throw new \Exception(''); }); $e->updatePolicies($oldPolicies, $newPolicies); $this->assertFalse($e->hasPolicy('alice', 'data1', 'read')); $this->assertTrue($e->hasPolicy('alice', 'data1', 'write')); $this->assertFalse($e->hasPolicy('bob', 'data2', 'write')); $this->assertTrue($e->hasPolicy('bob', 'data2', 'read')); $watcher = new SampleWatcher(); $e->setWatcher($watcher); $watcher->setUpdateCallback(function () { }); $e->updatePolicies($newPolicies, $oldPolicies); } public function testUpdateFilteredPolicies() { // p, alice, data1, read // p, bob, data2, write // p, data2_admin, data2, read // p, data2_admin, data2, write // // g, alice, data2_admin $testAdapter = Mockery::mock(FileAdapter::class); $model = Mockery::type("Casbin\Model\Model"); $testAdapter->shouldReceive('loadPolicy')->once()->with($model)->andReturn(null); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $testAdapter); $rules = [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'] ]; $testAdapter->shouldReceive('addPolicies')->once()->with('p', 'p', $rules)->andReturn(null); $e->addPolicies($rules); $watcherUpdatable = new SampleWatcherUpdatable(); $e->setWatcher($watcherUpdatable); $watcherUpdatable->setUpdateCallback(function () { }); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); $this->assertFalse($e->hasPolicy('alice', 'data1', 'write')); $this->assertTrue($e->hasPolicy('bob', 'data2', 'write')); $this->assertFalse($e->hasPolicy('bob', 'data2', 'read')); $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read')->andReturn([['alice', 'data1', 'read']]); $e->updateFilteredPolicies([['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read'); $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [['bob', 'data2', 'read']], 0, 'bob', 'data2', 'write')->andReturn([['bob', 'data2', 'write']]); $e->updateFilteredPolicies([['bob', 'data2', 'read']], 0, 'bob', 'data2', 'write'); $this->assertFalse($e->hasPolicy('alice', 'data1', 'read')); $this->assertTrue($e->hasPolicy('alice', 'data1', 'write')); $this->assertFalse($e->hasPolicy('bob', 'data2', 'write')); $this->assertTrue($e->hasPolicy('bob', 'data2', 'read')); $watcher = new SampleWatcher(); $e->setWatcher($watcher); $watcher->setUpdateCallback(function () { }); $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [['alice', 'data1', 'read']], 0, 'alice', 'data1', 'write')->andReturn([['alice', 'data1', 'write']]); $e->updateFilteredPolicies([['alice', 'data1', 'read']], 0, 'alice', 'data1', 'write'); $this->assertFalse($e->hasPolicy('alice', 'data1', 'write')); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); } public function testUpdateFilteredPoliciesWithoutWatcher() { $testAdapter = Mockery::mock(FileAdapter::class); $model = Mockery::type("Casbin\Model\Model"); $testAdapter->shouldReceive('loadPolicy')->once()->with($model)->andReturn(null); $e = new Enforcer($this->modelAndPolicyPath . '/rbac_model.conf', $testAdapter); $rules = [ ['alice', 'data1', 'read'], ]; $testAdapter->shouldReceive('addPolicies')->once()->with('p', 'p', $rules)->andReturn(null); $e->addPolicies($rules); $this->assertTrue($e->hasPolicy('alice', 'data1', 'read')); $this->assertFalse($e->hasPolicy('alice', 'data1', 'write')); // throw exception $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read')->andReturn([['alice', 'data1', 'read']])->andThrow(new NotImplementedException()); $e->updateFilteredPolicies([['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read'); $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read')->andReturn([['alice', 'data1', 'read']]); $e->updateFilteredPolicies([['alice', 'data1', 'write']], 0, 'alice', 'data1', 'read'); // if $ruleChanged is 0 $testAdapter->shouldReceive('updateFilteredPolicies')->once()->with('p', 'p', [], 0, 'alice', 'data1', 'read')->andReturn([['alice', 'data1', 'read']]); $e->updateFilteredPolicies([], 0, 'alice', 'data1', 'read'); $this->assertFalse($e->hasPolicy('alice', 'data1', 'read')); $this->assertTrue($e->hasPolicy('alice', 'data1', 'write')); } } tests/Unit/Model/AssertionTest.php000064400000012677152475271650013240 0ustar00shouldReceive('addLink') ->once() ->with('alice', 'admin'); $ast = new Assertion(); $ast->policy = [['alice']]; $ast->value = '_'; /** @var RoleManager $rm */ try { $ast->buildRoleLinks($rm); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('the number of "_" in role definition should be at least 2', $e->getMessage()); } $ast->value = '_, _'; try { $ast->buildRoleLinks($rm); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('grouping policy elements do not meet role definition', $e->getMessage()); } $ast->policy = [['alice', 'admin', 'root']]; $ast->buildRoleLinks($rm); } public function testBuildConditionalRoleLinks() { $condRm = Mockery::mock(ConditionalRoleManager::class); $condRm->shouldReceive('addLink') ->once() ->with('alice', 'admin'); $condRm->shouldReceive('setLinkConditionFuncParams') ->once() ->with('alice', 'admin'); $ast = new Assertion(); $ast->policy = [['alice']]; $ast->value = '_'; /** @var ConditionalRoleManager $condRm */ try { $ast->buildConditionalRoleLinks($condRm); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('the number of "_" in role definition should be at least 2', $e->getMessage()); } $ast->value = '_, _'; try { $ast->buildConditionalRoleLinks($condRm); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('grouping policy elements do not meet role definition', $e->getMessage()); } $ast->policy = [['alice', 'admin', 'root']]; $ast->tokens = ['alice', 'admin', 'root']; $ast->buildConditionalRoleLinks($condRm); } public function testBuildIncrementalRoleLinks() { $rm = Mockery::mock(RoleManager::class); $rm->shouldReceive('addLink') ->once() ->with('alice', 'admin'); $rm->shouldReceive('deleteLink') ->once() ->with('alice', 'admin'); $ast = new Assertion(); $ast->policy = [['alice']]; $ast->value = '_'; /** @var RoleManager $rm */ try { $ast->buildIncrementalRoleLinks($rm, Policy::POLICY_ADD, [['alice']]); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('the number of "_" in role definition should be at least 2', $e->getMessage()); } $ast->value = '_, _'; try { $ast->buildIncrementalRoleLinks($rm, Policy::POLICY_ADD, [['alice']]); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('grouping policy elements do not meet role definition', $e->getMessage()); } $ast->buildIncrementalRoleLinks($rm, Policy::POLICY_ADD, [['alice', 'admin', 'root']]); $ast->buildIncrementalRoleLinks($rm, Policy::POLICY_REMOVE, [['alice', 'admin', 'root']]); } public function testBuildIncrementalConditionalRoleLinks() { $condRm = Mockery::mock(ConditionalRoleManager::class); $condRm->shouldReceive('addLink') ->once() ->with('alice', 'admin'); $condRm->shouldReceive('setLinkConditionFuncParams') ->once() ->with('alice', 'admin'); $condRm->shouldReceive('deleteLink') ->once() ->with('alice', 'admin'); $ast = new Assertion(); $ast->policy = [['alice']]; $ast->value = '_'; /** @var ConditionalRoleManager $condRm */ try { $ast->buildIncrementalConditionalRoleLinks($condRm, Policy::POLICY_ADD, [['alice']]); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('the number of "_" in role definition should be at least 2', $e->getMessage()); } $ast->value = '_, _'; try { $ast->buildIncrementalConditionalRoleLinks($condRm, Policy::POLICY_ADD, [['alice']]); $this->fail('Expected CasbinException to be thrown'); } catch (CasbinException $e) { $this->assertEquals('grouping policy elements do not meet role definition', $e->getMessage()); } $ast->tokens = ['alice', 'admin', 'root']; $ast->buildIncrementalConditionalRoleLinks($condRm, Policy::POLICY_ADD, [['alice', 'admin', 'root']]); $ast->buildIncrementalConditionalRoleLinks($condRm, Policy::POLICY_REMOVE, [['alice', 'admin', 'root']]); } } tests/Unit/Model/ModelTest.php000064400000036220152475271650012317 0ustar00name = $name; $r->owner = $owner; return $r; } public function testLoadModelFromText() { $text = <<<'EOT' # 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 EOT; $m = new Model(); $m->loadModelFromText($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')); } public function testABACNotUsingPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/abac_not_using_policy_model.conf', $this->modelAndPolicyPath . '/abac_rule_effect_policy.csv'); $data1 = self::newTestResource('data1', 'alice'); $data2 = self::newTestResource('data2', 'bob'); $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'), false); } public function testABACPolicy() { $e = new Enforcer($this->modelAndPolicyPath . '/abac_rule_model.conf', $this->modelAndPolicyPath . '/abac_rule_policy.csv'); $sub1 = new User('alice', 18); $sub2 = new User('alice', 20); $sub3 = new User('alice', 65); $this->assertEquals($e->enforce($sub1, '/data1', 'read'), false); $this->assertEquals($e->enforce($sub1, '/data2', 'read'), false); $this->assertEquals($e->enforce($sub1, '/data1', 'write'), false); $this->assertEquals($e->enforce($sub1, '/data2', 'write'), true); $this->assertEquals($e->enforce($sub2, '/data1', 'read'), true); $this->assertEquals($e->enforce($sub2, '/data2', 'read'), false); $this->assertEquals($e->enforce($sub2, '/data1', 'write'), false); $this->assertEquals($e->enforce($sub2, '/data2', 'write'), true); $this->assertEquals($e->enforce($sub3, '/data1', 'read'), true); $this->assertEquals($e->enforce($sub3, '/data2', 'read'), false); $this->assertEquals($e->enforce($sub3, '/data1', 'write'), false); $this->assertEquals($e->enforce($sub3, '/data2', 'write'), false); } public function testEvalFunctionException() { $this->expectException(EvalFunctionException::class); $this->expectExceptionMessage("please make sure rule exists in policy when using eval() in matcher"); $e = new Enforcer($this->modelAndPolicyPath . '/abac_rule_model.conf', ""); $sub1 = new User('alice', 18); $e->enforce($sub1, '/data1', 'read'); } public function testRBACModelWithPattern() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_pattern_policy.csv'); // Here's a little confusing: the matching function here is not the custom function used in matcher. // It is the matching function used by "g" (and "g2", "g3" if any..) // You can see in policy that: "g2, /book/:id, book_group", so in "g2()" function in the matcher, instead // of checking whether "/book/:id" equals the obj: "/book/1", it checks whether the pattern matches. // You can see it as normal RBAC: "/book/:id" == "/book/1" becomes KeyMatch2("/book/:id", "/book/1") $e->addNamedMatchingFunc('g2', 'keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $this->assertEquals($e->enforce('alice', '/book/1', 'GET'), true); $this->assertEquals($e->enforce('alice', '/book/2', 'GET'), true); $this->assertEquals($e->enforce('alice', '/pen/1', 'GET'), true); $this->assertEquals($e->enforce('alice', '/pen/2', 'GET'), false); $this->assertEquals($e->enforce('bob', '/book/1', 'GET'), false); $this->assertEquals($e->enforce('bob', '/book/2', 'GET'), false); $this->assertEquals($e->enforce('bob', '/pen/1', 'GET'), true); $this->assertEquals($e->enforce('bob', '/pen/2', 'GET'), true); // AddMatchingFunc() is actually setting a function because only one function is allowed, // so when we set "KeyMatch3", we are actually replacing "KeyMatch2" with "KeyMatch3". $e->addNamedMatchingFunc('g2', 'keyMatch2', fn (string $key1, string $key2) => BuiltinOperations::keyMatch3($key1, $key2)); $this->assertEquals($e->enforce('alice', '/book2/1', 'GET'), true); $this->assertEquals($e->enforce('alice', '/book2/2', 'GET'), true); $this->assertEquals($e->enforce('alice', '/pen2/1', 'GET'), true); $this->assertEquals($e->enforce('alice', '/pen2/2', 'GET'), false); $this->assertEquals($e->enforce('bob', '/book2/1', 'GET'), false); $this->assertEquals($e->enforce('bob', '/book2/2', 'GET'), false); $this->assertEquals($e->enforce('bob', '/pen2/1', 'GET'), true); $this->assertEquals($e->enforce('bob', '/pen2/2', 'GET'), true); } public function testRBACModelWithDifferentTypesOfRoles() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_different_types_of_roles_model.conf', $this->modelAndPolicyPath . '/rbac_with_different_types_of_roles_policy.csv'); $g = $e->getNamedGroupingPolicy('g'); foreach ($g as $gp) { if (count($gp) !== 5) { $this->fail("g parameters' num isn't 5"); return; } $e->addNamedDomainLinkConditionFunc('g', $gp[0], $gp[1], $gp[2], fn ($key1, $key2) => BuiltinOperations::timeMatch($key1, $key2)); } $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'), 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); $this->assertEquals($e->enforce('carol', 'data1', 'read'), false); $this->assertEquals($e->enforce('carol', 'data1', 'write'), false); $this->assertEquals($e->enforce('carol', 'data2', 'read'), false); $this->assertEquals($e->enforce('carol', 'data2', 'write'), false); } public function testDomainMatchModel() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domain_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_domain_pattern_policy.csv'); $e->addNamedDomainMatchingFunc('g', 'keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $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('alice', 'domain2', 'data2', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain2', 'data2', 'write'), true); $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 testAllMatchModel() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_all_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_all_pattern_policy.csv'); $e->addNamedMatchingFunc('g', 'keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $e->addNamedDomainMatchingFunc('g', 'keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $this->assertEquals($e->enforce('alice', 'domain1', '/book/1', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain1', '/book/1', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain2', '/book/1', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain2', '/book/1', 'write'), true); } public function testTemporalRolesModel() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_temporal_roles_model.conf', $this->modelAndPolicyPath . '/rbac_with_temporal_roles_policy.csv'); $fn = fn(string $key1, string $key2) => BuiltinOperations::timeMatch($key1, $key2); $e->addNamedLinkConditionFunc('g', 'alice', 'data2_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data3_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data4_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data5_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data6_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data7_admin', $fn); $e->addNamedLinkConditionFunc('g', 'alice', 'data8_admin', $fn); $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'), false); $this->assertEquals($e->enforce('alice', 'data3', 'read'), true); $this->assertEquals($e->enforce('alice', 'data3', 'write'), true); $this->assertEquals($e->enforce('alice', 'data4', 'read'), true); $this->assertEquals($e->enforce('alice', 'data4', 'write'), true); $this->assertEquals($e->enforce('alice', 'data5', 'read'), true); $this->assertEquals($e->enforce('alice', 'data5', 'write'), true); $this->assertEquals($e->enforce('alice', 'data6', 'read'), false); $this->assertEquals($e->enforce('alice', 'data6', 'write'), false); $this->assertEquals($e->enforce('alice', 'data7', 'read'), true); $this->assertEquals($e->enforce('alice', 'data7', 'write'), true); $this->assertEquals($e->enforce('alice', 'data8', 'read'), false); $this->assertEquals($e->enforce('alice', 'data8', 'write'), false); } public function testTemporalRolesModelWithDomain() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domain_temporal_roles_model.conf', $this->modelAndPolicyPath . '/rbac_with_domain_temporal_roles_policy.csv'); $fn = fn(string $key1, string $key2) => BuiltinOperations::timeMatch($key1, $key2); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data2_admin', 'domain2', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data3_admin', 'domain3', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data4_admin', 'domain4', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data5_admin', 'domain5', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data6_admin', 'domain6', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data7_admin', 'domain7', $fn); $e->addNamedDomainLinkConditionFunc('g', 'alice', 'data8_admin', 'domain8', $fn); $this->assertEquals($e->enforce('alice', 'domain1', 'data1', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain1', 'data1', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain2', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain2', 'data2', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain3', 'data3', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain3', 'data3', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain4', 'data4', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain4', 'data4', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain5', 'data5', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain5', 'data5', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain6', 'data6', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain6', 'data6', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain7', 'data7', 'read'), true); $this->assertEquals($e->enforce('alice', 'domain7', 'data7', 'write'), true); $this->assertEquals($e->enforce('alice', 'domain8', 'data8', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain8', 'data8', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data1', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data1', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data2', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data2', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data3', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data3', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data4', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data4', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data5', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data5', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data6', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data6', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data7', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data7', 'write'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data8', 'read'), false); $this->assertEquals($e->enforce('alice', 'domain_not_exist', 'data8', 'write'), false); } } tests/Unit/Model/PolicyTest.php000064400000013321152475271650012513 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 testHasPolicies() { $m = Model::newModelFromFile($this->modelAndPolicyPath . '/basic_model.conf'); $rules = [ ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data2', 'read'], ['bob', 'domain2', 'data1', 'write'], ['bob', 'domain2', 'data2', 'write'], ]; $m->addPolicies('p', 'p', $rules); $this->assertTrue($m->hasPolicies('p', 'p', [ ['alice', 'domain1', 'data1', 'read'], ['bob', 'domain2', 'data1', 'write'], ])); $this->assertFalse($m->hasPolicies('p', 'p', [ ['alice', 'domain1', 'data1', 'write'], ])); } public function testAddPolicy() { $m = new Model(); $m->loadModel($this->modelAndPolicyPath . '/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read']; $this->assertFalse($m->hasPolicy('p', 'p', $rule)); $m->addPolicy('p', 'p', $rule); $this->assertTrue($m->hasPolicy('p', 'p', $rule)); } public function testUpdatePolicy() { $m = Model::newModelFromFile($this->modelAndPolicyPath . '/basic_model.conf'); $rules = [ ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data2', 'read'], ['bob', 'domain2', 'data1', 'write'], ['bob', 'domain2', 'data2', 'write'], ]; $m->addPolicies('p', 'p', $rules); $this->assertEquals($rules, $m->getPolicy('p', 'p')); $this->assertFalse($m->hasPolicies('p', 'p', [ ['alice', 'domain1', 'data1', 'write'], ])); $m->updatePolicy('p', 'p', ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data1', 'write']); $this->assertEquals([ ['alice', 'domain1', 'data1', 'write'], ['alice', 'domain1', 'data2', 'read'], ['bob', 'domain2', 'data1', 'write'], ['bob', 'domain2', 'data2', 'write'], ], $m->getPolicy('p', 'p')); } public function testUpdatePolicies() { $m = Model::newModelFromFile($this->modelAndPolicyPath . '/basic_model.conf'); $rules = [ ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data2', 'read'], ['bob', 'domain2', 'data1', 'write'], ['bob', 'domain2', 'data2', 'write'], ]; $m->addPolicies('p', 'p', $rules); $this->assertEquals($rules, $m->getPolicy('p', 'p')); $this->assertFalse($m->hasPolicies('p', 'p', [ ['alice', 'domain1', 'data1', 'write'], ])); $oldRules = [ ['alice', 'domain1', 'data1', 'read'], ['alice', 'domain1', 'data2', 'read'] ]; $newRules = [ ['alice', 'domain1', 'data1', 'write'], ['alice', 'domain1', 'data2', 'write'] ]; $m->updatePolicies('p', 'p', $oldRules, $newRules); $this->assertEquals([ ['alice', 'domain1', 'data1', 'write'], ['alice', 'domain1', 'data2', 'write'], ['bob', 'domain2', 'data1', 'write'], ['bob', 'domain2', 'data2', 'write'], ], $m->getPolicy('p', 'p')); // trigger callback of addPolicies $oldRules = [ ['alice', 'domain1', 'data1', 'write'], ['alice', 'domain1', 'data2', 'read'] ]; $this->assertFalse($m->updatePolicies('p', 'p', $oldRules, $newRules)); } public function testRemovePolicy() { $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)); $m->removePolicy('p', 'p', $rule); $this->assertFalse($m->hasPolicy('p', 'p', $rule)); $this->assertFalse($m->removePolicy('p', 'p', $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('p1', 'p1', 1, 'domain1', 'data1'); $this->assertFalse($res); $res = $m->removeFilteredPolicy('p', 'p', 1, 'domain1', 'data1'); $this->assertNotFalse($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/Model/User.php000064400000000436152475271650011335 0ustar00Name = $Name; $this->Age = $Age; } } tests/Unit/Persist/Adapters/FileAdapterTest.php000064400000001326152475271650015572 0ustar00loadModel($this->modelAndPolicyPath . '/basic_model.conf'); $rule = ['admin', 'domain1', 'data1', 'read2']; $m->addPolicy('p', 'p', $rule); $res = $adapter->savePolicy($m); $this->assertFalse(false === $res); } } tests/Unit/Persist/Adapters/FileFilteredAdapterTest.php000075500000003772152475271650017263 0ustar00modelAndPolicyPath . '/rbac_with_domains_policy.csv'); $this->assertTrue($adapter->isFiltered()); $m = new Model(); $m->loadModel($this->modelAndPolicyPath . '/rbac_with_domains_model.conf'); $adapter->loadFilteredPolicy($m, null); $this->assertFalse($adapter->isFiltered()); $this->assertTrue($m->hasPolicy('p', 'p', ['admin', 'domain1', 'data1', 'read'])); $this->assertTrue($m->hasPolicy('p', 'p', ['admin', 'domain2', 'data2', 'read'])); $m->clearPolicy(); $filter = new Filter(); $filter->p = ['', 'domain1']; $filter->g = ['', '', 'domain1']; $adapter->loadFilteredPolicy($m, $filter); $this->assertTrue($adapter->isFiltered()); $this->assertTrue($m->hasPolicy('p', 'p', ['admin', 'domain1', 'data1', 'read'])); $this->assertFalse($m->hasPolicy('p', 'p', ['admin', 'domain2', 'data2', 'read'])); try { $adapter->savePolicy($m); } catch (\Throwable $th) { $this->assertInstanceOf(CasbinException::class, $th); } try { $adapter->loadFilteredPolicy($m, new \stdClass()); } catch (\Throwable $th) { $this->assertInstanceOf(CasbinException::class, $th); } try { $adapter = new FileFilteredAdapter(''); $adapter->loadFilteredPolicy($m, $filter); } catch (\Throwable $th) { $this->assertInstanceOf(CasbinException::class, $th); } } } tests/Unit/Persist/Adapters/basic_policy_test.csv000064400000000037152475271650016253 0ustar00p, admin, domain1, data1, read2tests/Unit/Persist/Adapters/rbac_policy_test.csv000064400000000000152475271650016067 0ustar00tests/Unit/Rbac/DefaultRoleManager/RoleManagerTest.php000064400000042534152475271650016770 0ustar00assertTrue(Util::setEquals($rm->getRoles($name), $res)); } protected function testPrintUsers(RoleManager $rm, string $name, array $res) { $this->assertTrue(Util::setEquals($rm->getUsers($name), $res)); } public function testMatch() { $rm = new RoleManager(3); $this->assertEquals($rm->match('u1', 'u1'), true); $this->assertEquals($rm->match('u1', 'u2'), false); $rm->addMatchingFunc('keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); $this->assertEquals($rm->match('u1', '*'), true); $this->assertEquals($rm->match('u1', 'u2'), false); $dm = new DomainManager(3); $this->assertEquals($dm->match('domain1', 'domain1'), true); $this->assertEquals($dm->match('domain1', 'domain2'), false); $dm->addDomainMatchingFunc('keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); $this->assertEquals($dm->match('domain1', '*'), true); $this->assertEquals($dm->match('domain1', 'domain2'), false); } public function testRole() { $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->hasLink('u1', 'g1'), true); $this->assertEquals($rm->hasLink('u1', 'g2'), false); $this->assertEquals($rm->hasLink('u1', 'g3'), true); $this->assertEquals($rm->hasLink('u2', 'g1'), true); $this->assertEquals($rm->hasLink('u2', 'g2'), false); $this->assertEquals($rm->hasLink('u2', 'g3'), true); $this->assertEquals($rm->hasLink('u3', 'g1'), false); $this->assertEquals($rm->hasLink('u3', 'g2'), true); $this->assertEquals($rm->hasLink('u3', 'g3'), false); $this->assertEquals($rm->hasLink('u4', 'g1'), false); $this->assertEquals($rm->hasLink('u4', 'g2'), true); $this->assertEquals($rm->hasLink('u4', 'g3'), true); $this->testPrintRoles($rm, 'u1', ['g1']); $this->testPrintRoles($rm, 'u2', ['g1']); $this->testPrintRoles($rm, 'u3', ['g2']); $this->testPrintRoles($rm, 'u4', ['g2', 'g3']); $this->testPrintRoles($rm, 'g1', ['g3']); $this->testPrintRoles($rm, 'g2', []); $this->testPrintRoles($rm, 'g3', []); $rm->deleteLink('g1', 'g3'); $rm->deleteLink('u4', 'g2'); // Current role inheritance tree after deleting the links: // g3 g2 // / \ \ // g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->hasLink('u1', 'g1'), true); $this->assertEquals($rm->hasLink('u1', 'g2'), false); $this->assertEquals($rm->hasLink('u1', 'g3'), false); $this->assertEquals($rm->hasLink('u2', 'g1'), true); $this->assertEquals($rm->hasLink('u2', 'g2'), false); $this->assertEquals($rm->hasLink('u2', 'g3'), false); $this->assertEquals($rm->hasLink('u3', 'g1'), false); $this->assertEquals($rm->hasLink('u3', 'g2'), true); $this->assertEquals($rm->hasLink('u3', 'g3'), false); $this->assertEquals($rm->hasLink('u4', 'g1'), false); $this->assertEquals($rm->hasLink('u4', 'g2'), false); $this->assertEquals($rm->hasLink('u4', 'g3'), true); $this->testPrintRoles($rm, 'u1', ['g1']); $this->testPrintRoles($rm, 'u2', ['g1']); $this->testPrintRoles($rm, 'u3', ['g2']); $this->testPrintRoles($rm, 'u4', ['g3']); $this->testPrintRoles($rm, 'g1', []); $this->testPrintRoles($rm, 'g2', []); $this->testPrintRoles($rm, 'g3', []); $rm = new RoleManager(3); $rm->addMatchingFunc('keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); $rm->addLink('u1', 'g1'); $rm->addLink('u1', '*'); $rm->addLink('u2', 'g2'); // Current role inheritance tree // g1 g2 // \ / \ // * u2 // | // u1 $this->assertEquals($rm->hasLink('u1', 'g1'), true); $this->assertEquals($rm->hasLink('u1', 'g2'), true); $this->assertEquals($rm->hasLink('u2', 'g2'), true); $this->assertEquals($rm->hasLink('u2', 'g1'), false); $this->testPrintRoles($rm, 'u1', ['*', 'u1', 'u2', 'g1', 'g2']); $this->testPrintUsers($rm, '*', ['u1']); } public function testDomainRole() { $rm = new DomainManager(3); $rm->addLink('u1', 'g1', 'domain1'); $rm->addLink('u2', 'g1', 'domain1'); $rm->addLink('u3', 'admin', 'domain2'); $rm->addLink('u4', 'admin', 'domain2'); $rm->addLink('u4', 'admin', 'domain1'); $rm->addLink('g1', 'admin', 'domain1'); // Current role inheritance tree: // domain1:admin domain2:admin // / \ / \ // domain1:g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->hasLink('u1', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u1', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u1', 'admin', 'domain1'), true); $this->assertEquals($rm->hasLink('u1', 'admin', 'domain2'), false); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u2', 'admin', 'domain1'), true); $this->assertEquals($rm->hasLink('u2', 'admin', 'domain2'), false); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u3', 'admin', 'domain1'), false); $this->assertEquals($rm->hasLink('u3', 'admin', 'domain2'), true); $this->assertEquals($rm->hasLink('u4', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u4', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u4', 'admin', 'domain1'), true); $this->assertEquals($rm->hasLink('u4', 'admin', 'domain2'), true); $rm->deleteLink('g1', 'admin', 'domain1'); $rm->deleteLink('u4', 'admin', 'domain2'); // Current role inheritance tree after deleting the links: // domain1:admin domain2:admin // \ \ // domain1:g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->hasLink('u1', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u1', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u1', 'admin', 'domain1'), false); $this->assertEquals($rm->hasLink('u1', 'admin', 'domain2'), false); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u2', 'admin', 'domain1'), false); $this->assertEquals($rm->hasLink('u2', 'admin', 'domain2'), false); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u3', 'admin', 'domain1'), false); $this->assertEquals($rm->hasLink('u3', 'admin', 'domain2'), true); $this->assertEquals($rm->hasLink('u4', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u4', 'g1', 'domain2'), false); $this->assertEquals($rm->hasLink('u4', 'admin', 'domain1'), true); $this->assertEquals($rm->hasLink('u4', 'admin', 'domain2'), false); } public function testDomainPatternRole() { $rm = new DomainManager(3); $rm->addDomainMatchingFunc('keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $rm->addLink('u1', 'g1', 'domain1'); $rm->addLink('u2', 'g1', 'domain2'); $rm->addLink('u3', 'g1', '*'); $rm->addLink('u4', 'g2', 'domain3'); // Current role inheritance tree after deleting the links: // domain1:g1 domain2:g1 domain3:g2 // / \ / \ | // domain1:u1 *:g1 domain2:u2 domain3:u4 // | // *:u3 $this->assertEquals($rm->hasLink('u1', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain2'), true); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain1'), true); $this->assertEquals($rm->hasLink('u3', 'g1', 'domain2'), true); $this->assertEquals($rm->hasLink('u1', 'g2', 'domain1'), false); $this->assertEquals($rm->hasLink('u4', 'g2', 'domain3'), true); $this->assertEquals($rm->hasLink('u3', 'g2', 'domain3'), false); $this->assertEquals($rm->getRoles('u3', 'domain1'), ['g1']); $this->assertEquals($rm->getRoles('u1', 'domain1'), ['g1']); $this->assertEquals($rm->getRoles('u3', 'domain2'), ['g1']); $this->assertEquals($rm->getRoles('u1', 'domain2'), []); $this->assertEquals($rm->getRoles('u4', 'domain3'), ['g2']); } public function testClear() { $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 $rm->clear(); // All data is cleared. // No role inheritance now. $this->assertEquals($rm->hasLink('u1', 'g1'), false); $this->assertEquals($rm->hasLink('u1', 'g2'), false); $this->assertEquals($rm->hasLink('u1', 'g3'), false); $this->assertEquals($rm->hasLink('u2', 'g1'), false); $this->assertEquals($rm->hasLink('u2', 'g2'), false); $this->assertEquals($rm->hasLink('u2', 'g3'), false); $this->assertEquals($rm->hasLink('u3', 'g1'), false); $this->assertEquals($rm->hasLink('u3', 'g2'), false); $this->assertEquals($rm->hasLink('u3', 'g3'), false); $this->assertEquals($rm->hasLink('u4', 'g1'), false); $this->assertEquals($rm->hasLink('u4', 'g2'), false); $this->assertEquals($rm->hasLink('u4', 'g3'), false); } public function testAllMatchingFunc() { $rm = new RoleManager(10); $rm->addMatchingFunc('keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $rm->addDomainMatchingFunc('keyMatch2', fn(string $key1, string $key2) => BuiltinOperations::keyMatch2($key1, $key2)); $rm->addLink('/book/:id', 'book_group', '*'); // Current role inheritance tree after deleting the links: // *:book_group // | // *:/book/:id $this->assertEquals($rm->hasLink('/book/1', 'book_group', 'domain1'), true); $this->assertEquals($rm->hasLink('/book/2', 'book_group', 'domain1'), true); } public function testMatchingFuncOrder() { $rm = new RoleManager(10); $rm->addMatchingFunc('regexMatch', fn(string $key1, string $key2) => BuiltinOperations::regexMatch($key1, $key2)); $rm->addLink('g\\d+', 'root'); $rm->addLink('u1', 'g1'); $this->assertEquals($rm->hasLink('u1', 'root'), true); $rm->clear(); $rm->AddLink('u1', 'g1'); $rm->AddLink('g\\d+', 'root'); $this->assertEquals($rm->hasLink('u1', 'root'), true); $rm->clear(); $rm->AddLink('u1', 'g\\d+'); $this->assertEquals($rm->hasLink('u1', 'g1'), true); $this->assertEquals($rm->hasLink('u1', 'g1'), true); } public function testDomainMatchingFuncWithDifferentDomain() { $rm = new DomainManager(10); $rm->addDomainMatchingFunc('keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); $rm->addLink('alice', 'editor', '*'); $rm->addLink('editor', 'admin', 'domain1'); $this->assertEquals($rm->hasLink('alice', 'admin', 'domain1'), true); $this->assertEquals($rm->hasLink('alice', 'admin', 'domain2'), false); } public function testTemporaryRole() { $rm = new RoleManager(10); $rm->addMatchingFunc('regexMatch', fn(string $key1, string $key2) => BuiltinOperations::regexMatch($key1, $key2)); $rm->addLink('u\d+', 'user'); for ($i = 0; $i < 10; $i++) { $this->assertEquals($rm->hasLink(sprintf('u%d', $i), 'user'), true); } $this->testPrintUsers($rm, 'user', ['u\d+']); $this->testPrintRoles($rm, 'u1', ['user']); $rm->addLink('u1', 'manager'); for ($i = 10; $i < 20; $i++) { $this->assertEquals($rm->hasLink(sprintf('u%d', $i), 'manager'), true); } $this->testPrintUsers($rm, 'user', ['u\d+', 'u1']); $this->testPrintRoles($rm, 'u1', ['user', 'manager']); } public function testMaxHierarchyLevel() { $rm = new RoleManager(1); $rm->addLink("level0", "level1"); $rm->addLink("level1", "level2"); $rm->addLink("level2", "level3"); $this->assertTrue($rm->hasLink("level0", "level0")); $this->assertTrue($rm->hasLink("level0", "level1")); $this->assertFalse($rm->hasLink("level0", "level2")); $this->assertFalse($rm->hasLink("level0", "level3")); $this->assertTrue($rm->hasLink("level1", "level2")); $this->assertFalse($rm->hasLink("level1", "level3")); $rm = new RoleManager(2); $rm->addLink("level0", "level1"); $rm->addLink("level1", "level2"); $rm->addLink("level2", "level3"); $this->assertTrue($rm->hasLink("level0", "level0")); $this->assertTrue($rm->hasLink("level0", "level1")); $this->assertTrue($rm->hasLink("level0", "level2")); $this->assertFalse($rm->hasLink("level0", "level3")); $this->assertTrue($rm->hasLink("level1", "level2")); $this->assertTrue($rm->hasLink("level1", "level3")); } public function testConditionalRoleManager() { $rm = new ConditionalRoleManager(10); $rm->addLink('u1', 'g1'); $rm->addLink('u2', 'g1'); $rm->addLink('u3', 'g2'); $rm->addLinkConditionFunc('u1', 'g1', fn() => true); $rm->addLinkConditionFunc('u2', 'g1', fn() => false); $rm->addLinkConditionFunc('u3', 'g2', function () { throw new CasbinException('error func'); }); $rm->setLinkConditionFuncParams('u1', 'g1'); $rm->setLinkConditionFuncParams('u2', 'g1'); $rm->setLinkConditionFuncParams('u3', 'g2'); $this->assertEquals($rm->hasLink('u1', 'g1'), true); $this->assertEquals($rm->hasLink('u2', 'g1'), false); $this->assertEquals($rm->hasLink('u3', 'g2'), false); $rm->deleteLink('u1', 'g1'); $this->assertEquals($rm->hasLink('u1', 'g1'), false); } public function testConditionalDomainManager() { $rm = new ConditionalDomainManager(10); $rm->addDomainMatchingFunc('keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2)); $rm->addLink('u1', 'g1', '*'); $rm->addLink('u2', 'g1', 'domain1'); $rm->addLink('u3', 'g2', 'domain2'); $rm->addLink('g1', 'root', 'domain1'); $rm->addDomainLinkConditionFunc('u1', 'g1', '*', fn() => true); $rm->addDomainLinkConditionFunc('u2', 'g1', 'domain1', fn() => false); $rm->addDomainLinkConditionFunc('u3', 'g2', 'domain2', function () { throw new CasbinException('error func'); }); $rm->setDomainLinkConditionFuncParams('u1', 'g1', '*'); $rm->setDomainLinkConditionFuncParams('u2', 'g1', 'domain1'); $rm->setDomainLinkConditionFuncParams('u3', 'g2', 'domain2'); $this->assertEquals($rm->hasLink('u1', 'root', 'domain1'), true); $this->assertEquals($rm->hasLink('u1', 'root', 'domain2'), false); $this->assertEquals($rm->hasLink('u2', 'g1', 'domain1'), false); $this->assertEquals($rm->hasLink('u3', 'g2', 'domain2'), false); $rm->deleteLink('u1', 'g1', '*'); $this->assertEquals($rm->hasLink('u1', 'root', 'domain1'), false); } } tests/Unit/Util/BuiltinOperationsTest.php000064400000031523152475271650014607 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->assertFalse($this->keyMatch2Func('/foo/bar', '/foo')); $this->assertFalse($this->keyMatch2Func('/foo/bar', '/foo*')); $this->assertTrue($this->keyMatch2Func('/foo/bar', '/foo/*')); $this->assertFalse($this->keyMatch2Func('/foobar', '/foo')); $this->assertFalse($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')); $this->assertFalse($this->keyMatch2Func('/alice/all', '/:/all')); } public function testKeyMatch3Func() { $this->assertTrue($this->keyMatch3Func('/foo', '/foo')); $this->assertTrue($this->keyMatch3Func('/foo', '/foo*')); $this->assertFalse($this->keyMatch3Func('/foo', '/foo/*')); $this->assertFalse($this->keyMatch3Func('/foo/bar', '/foo')); $this->assertFalse($this->keyMatch3Func('/foo/bar', '/foo*')); $this->assertTrue($this->keyMatch3Func('/foo/bar', '/foo/*')); $this->assertFalse($this->keyMatch3Func('/foobar', '/foo')); $this->assertFalse($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}/*')); $this->assertFalse($this->keyMatch3Func('/myid/using/myresid', '/{id/using/{resId}')); } public function testKeyMatch4Func() { $this->assertTrue($this->keyMatch4Func('/parent/123/child/123', '/parent/{id}/child/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{id}')); $this->assertTrue($this->keyMatch4Func('/parent/123/child/123', '/parent/{id}/child/{another_id}')); $this->assertTrue($this->keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{another_id}')); $this->assertTrue($this->keyMatch4Func('/parent/123/child/123/book/123', '/parent/{id}/child/{id}/book/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/123/book/456', '/parent/{id}/child/{id}/book/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/456/book/123', '/parent/{id}/child/{id}/book/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/456/book/', '/parent/{id}/child/{id}/book/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/456', '/parent/{id}/child/{id}/book/{id}')); $this->assertFalse($this->keyMatch4Func('/parent/123/child/123', '/parent/{i/d}/child/{i/d}')); } public function testKeyMatch5Func() { $this->assertTrue($this->keyMatch5Func('/parent/child', '/parent/child')); $this->assertTrue($this->keyMatch5Func('/parent/child?status=1&type=2', '/parent/child')); $this->assertFalse($this->keyMatch5Func('/parent?status=1&type=2', '/parent/child')); $this->assertTrue($this->keyMatch5Func('/parent/child/?status=1&type=2', '/parent/child/')); $this->assertFalse($this->keyMatch5Func('/parent/child/?status=1&type=2', '/parent/child')); $this->assertFalse($this->keyMatch5Func('/parent/child?status=1&type=2', '/parent/child/')); } public function testGlobMatchFunc() { $this->assertTrue($this->globMatchFunc('/foo', '/foo')); $this->assertTrue($this->globMatchFunc('/foo', '/foo*')); $this->assertFalse($this->globMatchFunc('/foo', '/foo/*')); $this->assertFalse($this->globMatchFunc('/prefix/foo', '*/foo')); $this->assertTrue($this->globMatchFunc('/foo/bar', '/foo/*')); } public function testKeyGetFunc() { $this->assertEquals('', $this->keyGetFunc('/foo', '/foo')); $this->assertEquals('', $this->keyGetFunc('/foo', '/foo*')); $this->assertEquals('', $this->keyGetFunc('/foo', '/foo/*')); $this->assertEquals('', $this->keyGetFunc('/foo/bar', '/foo')); $this->assertEquals('/bar', $this->keyGetFunc('/foo/bar', '/foo*')); $this->assertEquals('bar', $this->keyGetFunc('/foo/bar', '/foo/*')); $this->assertEquals('', $this->keyGetFunc('/foobar', '/foo')); $this->assertEquals('bar', $this->keyGetFunc('/foobar', '/foo*')); $this->assertEquals('', $this->keyGetFunc('/foobar', '/foo/*')); } public function testKeyGet2Func() { $this->assertEquals('', $this->keyGet2Func("/foo", "/foo", "id")); $this->assertEquals('', $this->keyGet2Func("/foo", "/foo*", "id")); $this->assertEquals('', $this->keyGet2Func("/foo", "/foo/*", "id")); $this->assertEquals('', $this->keyGet2Func("/foo/bar", "/foo", "id")); // different with KeyMatch. $this->assertEquals('', $this->keyGet2Func("/foo/bar", "/foo*", "id")); $this->assertEquals('', $this->keyGet2Func("/foo/bar", "/foo/*", "id")); $this->assertEquals('', $this->keyGet2Func("/foobar", "/foo", "id")); // different with KeyMatch. $this->assertEquals('', $this->keyGet2Func("/foobar", "/foo*", "id")); $this->assertEquals('', $this->keyGet2Func("/foobar", "/foo/*", "id")); $this->assertEquals('', $this->keyGet2Func("/", "/:resource", "resource")); $this->assertEquals('resource1', $this->keyGet2Func("/resource1", "/:resource", "resource")); $this->assertEquals('', $this->keyGet2Func("/myid", "/:id/using/:resId", "id")); $this->assertEquals('myid', $this->keyGet2Func("/myid/using/myresid", "/:id/using/:resId", "id")); $this->assertEquals('myresid', $this->keyGet2Func("/myid/using/myresid", "/:id/using/:resId", "resId")); $this->assertEquals('', $this->keyGet2Func("/proxy/myid", "/proxy/:id/*", "id")); $this->assertEquals('myid', $this->keyGet2Func("/proxy/myid/", "/proxy/:id/*", "id")); $this->assertEquals('myid', $this->keyGet2Func("/proxy/myid/res", "/proxy/:id/*", "id")); $this->assertEquals('myid', $this->keyGet2Func("/proxy/myid/res/res2", "/proxy/:id/*", "id")); $this->assertEquals('myid', $this->keyGet2Func("/proxy/myid/res/res2/res3", "/proxy/:id/*", "id")); $this->assertEquals('myid', $this->keyGet2Func("/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id")); $this->assertEquals('', $this->keyGet2Func('/proxy/', "/proxy/:id/*", "id")); $this->assertEquals('alice', $this->keyGet2Func("/alice", "/:id", "id")); $this->assertEquals('alice', $this->keyGet2Func("/alice/all", "/:id/all", "id")); $this->assertEquals('', $this->keyGet2Func("/alice", "/:id/all", "id")); $this->assertEquals('', $this->keyGet2Func("/alice/all", "/:id", "id")); $this->assertEquals('', $this->keyGet2Func("/alice/all", "/:/all", "")); } public function testRegexMatchFunc() { $this->assertTrue($this->regexMatchFunc("/topic/create", "/topic/create")); $this->assertTrue($this->regexMatchFunc("/topic/create/123", "/topic/create")); $this->assertFalse($this->regexMatchFunc("/topic/delete", "/topic/create")); $this->assertFalse($this->regexMatchFunc("/topic/edit", "/topic/edit/[0-9]+")); $this->assertTrue($this->regexMatchFunc("/topic/edit/123", "/topic/edit/[0-9]+")); $this->assertFalse($this->regexMatchFunc("/topic/edit/abc", "/topic/edit/[0-9]+")); $this->assertFalse($this->regexMatchFunc("/foo/delete/123", "/topic/delete/[0-9]+")); $this->assertTrue($this->regexMatchFunc("/topic/delete/0", "/topic/delete/[0-9]+")); $this->assertFalse($this->regexMatchFunc("/topic/edit/123s", "/topic/delete/[0-9]+")); } public function testIPMatchFunc() { // ipv4 $this->assertTrue($this->ipMatchFunc("192.168.2.123", "192.168.2.0/24")); $this->assertFalse($this->ipMatchFunc("192.168.2.123", "192.168.3.0/24")); $this->assertTrue($this->ipMatchFunc("192.168.2.123", "192.168.2.0/16")); $this->assertTrue($this->ipMatchFunc("192.168.2.123", "192.168.2.123")); $this->assertTrue($this->ipMatchFunc("192.168.2.123", "192.168.2.123/32")); $this->assertTrue($this->ipMatchFunc("10.0.0.11", "10.0.0.0/8")); $this->assertFalse($this->ipMatchFunc("11.0.0.123", "10.0.0.0/8")); // ipv6 $this->assertTrue($this->ipMatchFunc('2001:db8::ffff', '2001:db8::/64')); $this->assertTrue($this->ipMatchFunc('2a00:1450:400c:c04::6a', '2a00:1450::/32')); $this->assertFalse($this->ipMatchFunc('2001:db8:ffff::', '2001:db8::/64')); $this->assertTrue($this->ipMatchFunc('2001:0db8:85a3:08d3:1319:8a2e:0370:7347', '2001:0db8:85a3:08d3::/64')); $this->assertTrue($this->ipMatchFunc('2001:0db8:85a3:08d3:1319:8a2e:0370:7347', '2001:0db8:85a3:08d3:1319:8a2e:0370:7347')); $this->assertFalse($this->ipMatchFunc('2001:0db8:85a3:08d3:1319:8a2e:0370:7347', '2001:0db8:85a3:08d3::')); } } tests/Unit/Util/UtilTest.php000064400000011760152475271650012053 0ustar00assertEquals(Util::escapeAssertion('p.attr.value == p.attr'), 'p_attr.value == p_attr'); $this->assertEquals(Util::escapeAssertion('r.attr.value == p.attr'), 'r_attr.value == p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value || p.attr'), 'r_attp.value || p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value &&p.attr'), 'r_attp.value &&p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value >p.attr'), 'r_attp.value >p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value assertEquals(Util::escapeAssertion('r.attp.value +p.attr'), 'r_attp.value +p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value -p.attr'), 'r_attp.value -p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value *p.attr'), 'r_attp.value *p_attr'); $this->assertEquals(Util::escapeAssertion('r.attp.value /p.attr'), 'r_attp.value /p_attr'); $this->assertEquals(Util::escapeAssertion('!r.attp.value /p.attr'), '!r_attp.value /p_attr'); $this->assertEquals(Util::escapeAssertion('g(r.sub, p.sub) == p.attr'), 'g(r_sub, p_sub) == p_attr'); $this->assertEquals(Util::escapeAssertion('g(r.sub,p.sub) == p.attr'), 'g(r_sub,p_sub) == p_attr'); $this->assertEquals(Util::escapeAssertion('(r.attp.value || p.attr)p.u'), '(r_attp.value || p_attr)p_u'); } public function testArrayRemoveDuplicates() { $a = ['green', 'red', 'green', 'blue', 'red']; Util::arrayRemoveDuplicates($a); $this->assertEquals($a, ['green', 'red', 'blue']); } public function testSetEquals() { $this->assertEquals(Util::setEquals(['a', 'b', 'c'], ['a', 'b', 'c']), true); $this->assertEquals(Util::setEquals(['a', 'b', 'c'], ['a', 'b']), false); $this->assertEquals(Util::setEquals(['a', 'b', 'c'], ['a', 'c', 'b']), true); $this->assertEquals(Util::setEquals(['a', 'b', 'c'], []), false); } public function testContainEval() { $this->assertEquals(Util::hasEval('eval() && a && b &&c'), true); $this->assertEquals(Util::hasEval('eval) && a && b &&c'), false); $this->assertEquals(Util::hasEval('eval)( && a && b &&c'), false); $this->assertEquals(Util::hasEval('eval() && a && b &&c'), true); $this->assertEquals(Util::hasEval('eval(c * (a + b)) && a && b &&c'), true); $this->assertEquals(Util::hasEval('xeval() && a && b &&c'), false); } public function testReplaceEval() { $this->assertEquals(Util::replaceEval('eval() && a && b && c', 'a'), '(a) && a && b && c'); $this->assertEquals(Util::replaceEval('eval() && a && b && c', '(a)'), '((a)) && a && b && c'); } public function testGetEvalValue() { $this->assertEquals(Util::getEvalValue('eval(a) && a && b && c'), ['a']); $this->assertEquals(Util::getEvalValue('a && eval(a) && b && c'), ['a']); $this->assertEquals(Util::getEvalValue('eval(a) && eval(b) && a && b && c'), ['a', 'b']); $this->assertEquals(Util::getEvalValue('a && eval(a) && eval(b) && b && c'), ['a', 'b']); } public function testReplaceEvalWithMap() { $this->assertEquals(Util::replaceEvalWithMap('eval(rule1)', ['rule1' => 'a == b']), '(a == b)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) && c && d', ['rule1' => 'a == b']), '(a == b) && c && d'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1)', []), 'eval(rule1)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) && c && d', []), 'eval(rule1) && c && d'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2)', ['rule1' => 'a == b', 'rule2' => 'a == c']), '(a == b) || (a == c)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2) && c && d', ['rule1' => 'a == b', 'rule2' => 'a == c']), '(a == b) || (a == c) && c && d'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2)', ['rule1' => 'a == b']), '(a == b) || eval(rule2)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2) && c && d', ['rule1' => 'a == b']), '(a == b) || eval(rule2) && c && d'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2)', ['rule2' => 'a == b']), 'eval(rule1) || (a == b)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2) && c && d', ['rule2' => 'a == b']), 'eval(rule1) || (a == b) && c && d'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2)', []), 'eval(rule1) || eval(rule2)'); $this->assertEquals(Util::replaceEvalWithMap('eval(rule1) || eval(rule2) && c && d', []), 'eval(rule1) || eval(rule2) && c && d'); } } tests/Watcher/SampleWatcher.php000064400000001740152475271650012573 0ustar00callback = $func; } /** * update calls the update callback of other instances to synchronize their policy. * It is usually called after changing the policy in DB, like savePolicy() method of Enforcer class, * addPolicy(), removePolicy(), etc. */ public function update(): void { call_user_func($this->callback); } /** * Close stops and releases the watcher, the callback function will not be called any more. */ public function close(): void { } } tests/Watcher/SampleWatcherEx.php000064400000003655152475271650013077 0ustar00callback); } /** * updateForRemovePolicy calls the update callback of other instances to synchronize their policy. * It is called after removePolicy() method of Enforcer class * * @param string $sec * @param string $ptype * @param string ...$params * @return void */ public function updateForRemovePolicy(string $sec, string $ptype, string ...$params): void { call_user_func($this->callback); } /** * updateForRemoveFilteredPolicy calls the update callback of other instances to synchronize their policy. * It is called after removeFilteredNamedGroupingPolicy() method of Enforcer class * * @param string $sec * @param string $ptype * @param integer $fieldIndex * @param string ...$fieldValues * @return void */ public function updateForRemoveFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void { call_user_func($this->callback); } /** * updateForSavePolicy calls the update callback of other instances to synchronize their policy. * It is called after removeFilteredNamedGroupingPolicy() method of Enforcer class * * @param Model $model * @return void */ public function updateForSavePolicy(Model $model): void { call_user_func($this->callback); } } tests/Watcher/SampleWatcherUpdatable.php000064400000001756152475271650014424 0ustar00callback); } /** * updateForUpdatePolicies calls the update callback of other instances to synchronize their policy. * It is called after updatePolicies() method of Enforcer class * * @param array $oldRules * @param array $newRules * @return void */ public function updateForUpdatePolicies(array $oldRules, array $newRules): void { call_user_func($this->callback); } } tests/Watcher/WatcherExTest.php000064400000003334152475271650012567 0ustar00isCalled = false; $this->watcher = new SampleWatcherEx(); $this->enforcer = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); $this->enforcer->setWatcher($this->watcher); } public function testUpdateForSavePolicy() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForSavePolicy(new Model()); $this->assertTrue($this->isCalled); } public function testUpdateForAddPolicy() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForAddPolicy('p', 'p'); $this->assertTrue($this->isCalled); } public function testUpdateForRemovePolicy() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForRemovePolicy('p', 'p'); $this->assertTrue($this->isCalled); } public function testUpdateForRemoveFilteredPolicy() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForRemoveFilteredPolicy('p', 'p', 1); $this->assertTrue($this->isCalled); } } tests/Watcher/WatcherTest.php000064400000003445152475271650012275 0ustar00isCalled = false; $this->watcher = new SampleWatcher(); $this->enforcer = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); $this->enforcer->setWatcher($this->watcher); } public function testUpdate() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->update(); $this->assertTrue($this->isCalled); } public function testSelfModify() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->enforcer->addPolicy('eva', 'data', 'read'); $this->assertTrue($this->isCalled); $this->isCalled = false; $this->enforcer->selfAddPolicy('p', 'p', ['eva', 'data', 'write']); $this->assertFalse($this->isCalled); } public function testSelfModifyEx() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->enforcer->selfAddPolices('p', 'p', [['user1', 'data1', 'read']]); $this->assertFalse($this->isCalled); $this->enforcer->selfAddPolicesEx('p', 'p', [['user1', 'data1', 'read'], ['user2', 'data2', 'read']]); $this->assertFalse($this->isCalled); } } tests/Watcher/WatcherUpdatableTest.php000064400000002166152475271650014116 0ustar00isCalled = false; $this->watcher = new SampleWatcherUpdatable(); $this->enforcer = new Enforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); $this->enforcer->setWatcher($this->watcher); } public function testUpdateForUpdatePolicy() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForUpdatePolicy([], []); $this->assertTrue($this->isCalled); } public function testUpdateForUpdatePolicies() { $this->initWatcher(); $this->watcher->setUpdateCallback(function () { $this->isCalled = true; }); $this->watcher->updateForUpdatePolicies([], []); $this->assertTrue($this->isCalled); } }