.github/FUNDING.yml000064400000001235152475272120007731 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.yml000064400000000000152475272120010427 0ustar00.github/workflows/build.yml000064400000005267152475272120012004 0ustar00name: build on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: fail-fast: true matrix: php: [7.1, 7.2, 7.3, 7.4, 8.0, 8.1] 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: [7.1, 8.0] 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 .gitignore000064400000000113152475272120006536 0ustar00/vendor/ composer.lock .idea/ *.iml # coverage report /build .phpunit.*.releaserc.yml000064400000000202152475272120007313 0ustar00plugins: - "@semantic-release/commit-analyzer" - "@semantic-release/release-notes-generator" - "@semantic-release/github"LICENSE000064400000026135152475272120005567 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.md000064400000032727152475272120006045 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) [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 | beta-test | 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/en/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/en/middlewares ## Our adopters https://casbin.org/docs/en/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.md000064400000033452152475272120006421 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 | beta-test | 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/docs/zh-CN/overview ## 在线编辑器 你也可以使用在线编辑器(https://casbin.org/editor/) 在你的浏览器里编写Casbin模型和策略。 它提供了一些比如 `语法高亮`以及`代码补全`这样的功能,就像编程语言的IDE一样。 ## 教程 https://casbin.org/docs/zh-CN/tutorials ## Policy管理 Casbin 提供两组 API 来管理权限: - [管理API](https://github.com/php-casbin/php-casbin/blob/master/src/ManagementApi.php): Casbin的底层原生API,支持全部的策略管理功能。点击 [这里](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/ManagementApiTest.php) 查看更多例子。 - [RBAC API](https://github.com/php-casbin/php-casbin/blob/master/src/RbacApi.php): 对于RBAC, 是一个更加友好的 API。 此 API 是管理 API 中的一个子集。 RBAC 用户可以使用此 API 来简化代码。 点击 [这里](https://github.com/php-casbin/php-casbin/blob/master/tests/Unit/RbacApiTest.php) 查看更多例子。 同时也提供了一个简单的前端页面来管理`Model`和`Policy`: ![model editor](https://hsluoyz.github.io/casbin/ui_model_editor.png) ![policy editor](https://hsluoyz.github.io/casbin/ui_policy_editor.png) ## Policy持久化 在`Casbin`中,适配器(`adapter`,`Casbin`的中间件)实现了`policy`规则写入持久层的细节。 `Casbin`的用户可以调用`adapter`的`loadPolicy()`方法从持久层中加载`policy`规则, 同样也可以调用`savePolicy()`方法将`Policy`规则保存到持久层中。 为了保持代码轻量, 我们没有将`adapter`的代码放在主库中。 以下是`PHP-Casbin`支持的适配器:(欢迎更多新的第三方贡献的适配器,可以联系我们添加在下面) Adapter | Type | Author | Description ----|------|----|---- [File Adapter (内置)](https://casbin.org/docs/zh-CN/policy-storage#file-adapter-built-in) | File | php-casbin | 存储到[.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) 文件中 [Database Adapter](https://github.com/php-casbin/database-adapter) | Database | php-casbin | 支持存储到MySQL, PostgreSQL, SQLite, Microsoft SQL Server数据库的适配器 更多适配器的内容,请参考文档: https://casbin.org/docs/zh-CN/policy-storage ## Role管理 角色管理器用于在`Casbin`中管理`RBAC`多层角色继承(用户-角色的关系)。角色管理器可以从Casbin的`Policy`规则或者外部数据源(如LDAP, Okta, Auth0, Azure AD等)获取角色数据。我们支持多种角色管理器,为了保持代码轻量,我们没有将除了内置的默认的角色管理器以外的角色管理器放在主库中。以下是支持的角色管理器:(欢迎更多新的第三方贡献的角色管理器,可以联系我们添加在下面) Role manager | Author | Description ----|----|---- [Default Role Manager (内置)](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) | php-casbin | 支持多层角色继承 提示: 所有的角色管理器必须实现[RoleManager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/RoleManager.php) 接口。 可以参考[Default Role Manager](https://github.com/php-casbin/php-casbin/blob/master/src/Rbac/DefaultRoleManager/RoleManager.php) 。 ## 例子 Model | Model file | Policy file ----|------|---- ACL | [basic_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL with superuser | [basic_model_with_root.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_policy.csv) ACL without users | [basic_model_without_users.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_users_policy.csv) ACL without resources | [basic_model_without_resources.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/basic_without_resources_policy.csv) RBAC | [rbac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_policy.csv) RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_domains_policy.csv) ABAC | [abac_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/abac_model.conf) | N/A RESTful | [keymatch_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/keymatch_policy.csv) Deny-override | [rbac_model_with_deny.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/rbac_with_deny_policy.csv) Priority | [priority_model.conf](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/php-casbin/php-casbin/blob/master/examples/priority_policy.csv) ## 我们的采用者 ### Web框架 - [Laravel](https://laravel.com/): 为WEB艺术家创造的PHP框架, 通过这个扩展: [laravel-casbin](https://github.com/php-casbin/laravel-casbin) - [Yii PHP Framework](https://www.yiiframework.com/): 一个高性能的,适用于开发WEB2.0应用的PHP框架, 通过这个扩展: [yii-casbin](https://github.com/php-casbin/yii-casbin) - [CakePHP](https://cakephp.org/): 快速、稳定的PHP框架, 通过这个扩展: [cake-casbin](https://github.com/php-casbin/cake-casbin) - [ThinkPHP](http://www.thinkphp.cn/): 一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架, 通过这个扩展: [think-casbin](https://github.com/php-casbin/think-casbin) ## 协议 `PHP-Casbin` 采用 [Apache 2.0 license](LICENSE) 开源协议发布。 ## 联系 有问题或者功能建议,请联系我们或者提交PR: - https://github.com/php-casbin/php-casbin/issues - techlee@qq.com - QQ群: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885) composer.json000064400000002236152475272120007300 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": ">=7.1.0", "symfony/expression-language": "^3.4|^4.0|^5.0|^6.0", "s1lentium/iptools": "^1.1" }, "autoload": { "psr-4": { "Casbin\\": "src/" } }, "require-dev": { "phpunit/phpunit": "~7.0|~8.0|~9.0", "php-coveralls/php-coveralls": "^2.1", "phpstan/phpstan": "^1.2", "mockery/mockery": "^1.2" }, "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.conf000064400000000244152475272120011306 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.conf000064400000000311152475272120014745 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.csv000064400000000163152475272120013716 0ustar00p, alice, /data1, read, deny p, alice, /data1, write, allow p, bob, /data2, write, deny p, bob, /data2, read, allowexamples/abac_rule_model.conf000064400000000312152475272120012331 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.csv000064400000000101152475272120012372 0ustar00p, r.sub.Age > 18, /data1, read p, r.sub.Age < 60, /data2, write examples/basic_inverse_policy.csv000064400000000052152475272120013276 0ustar00p, alice, data1, write p, bob, data2, readexamples/basic_model.conf000064400000000302152475272120011474 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.csv000064400000000052152475272120011543 0ustar00p, alice, data1, read p, bob, data2, writeexamples/basic_with_root_model.conf000064400000000325152475272120013577 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.conf000064400000000246152475272120015360 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.csv000064400000000034152475272120015420 0ustar00p, alice, read p, bob, writeexamples/basic_without_users_model.conf000064400000000246152475272120014507 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.csv000064400000000036152475272120014551 0ustar00p, data1, read p, data2, writeexamples/error/error_model.conf000064400000000301152475272120012674 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.csv000064400000000047152475272120012750 0ustar00p, alice, data1, read bob, data2, writeexamples/ipmatch_model.conf000064400000000311152475272120012040 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.csv000064400000000073152475272120012112 0ustar00p, 192.168.2.0/24, data1, read p, 10.0.0.0/16, data2, writeexamples/keyget2_model.conf000064400000000362152475272120011773 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.conf000064400000000371152475272120011711 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.conf000064400000000325152475272120012307 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.csv000064400000000121152475272120012346 0ustar00p, alice, /alice_data/:resource, GET p, alice, /alice_data2/:id/using/:resId, GETexamples/keymatch_custom_model.conf000064400000000332152475272120013615 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.conf000064400000000324152475272120012224 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.csv000064400000000245152475272120012273 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.conf000064400000000532152475272120016225 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.csv000064400000000252152475272120016271 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/performance/rbac_with_pattern_large_scale_model.conf000064400000000411152475272120020735 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.csv000064400000531204152475272120021013 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.csv000064400000000045152475272120015255 0ustar00p, alice, data1, read, intdeterminateexamples/priority_model.conf000064400000000337152475272120012304 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.conf000064400000000351152475272120014201 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_policy.csv000064400000000437152475272120012352 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.csv000064400000000507152475272120014251 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/rbac_model.conf000064400000000337152475272120011332 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.conf000064400000000377152475272120017125 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.csv000064400000000172152475272120011374 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.conf000064400000000406152475272120014747 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.csv000064400000000146152475272120015015 0ustar00p, alice, domain1, book_group, read p, alice, domain2, book_group, write g, /book/:id, book_group, * examples/rbac_with_deny_model.conf000064400000000404152475272120013377 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.csv000064400000000263152475272120013447 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_domain_pattern_model.conf000064400000000406152475272120015446 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_policy.csv000064400000000251152475272120015511 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_domains_model.conf000064400000000405152475272120014073 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.csv000064400000000256152475272120014144 0ustar00p, admin, domain1, data1, read p, admin, domain1, data1, write p, admin, domain2, data2, read p, admin, domain2, data2, write g, alice, admin, domain1 g, bob, admin, domain2examples/rbac_with_hierarchy_policy.csv000064400000000331152475272120014462 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.csv000064400000000417152475272120017234 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.conf000064400000000345152475272120014263 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.conf000064400000000366152475272120014124 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.csv000064400000000502152475272120014161 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.conf000064400000000353152475272120015476 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.csv000064400000000231152475272120015536 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/subject_priority_model.conf000064400000000346152475272120014023 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.conf000064400000000477152475272120016412 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.csv000064400000000414152475272120014064 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.csv000064400000000310152475272120016441 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.neon000064400000000233152475272120007107 0ustar00parameters: reportUnmatchedIgnoredErrors: false checkMissingIterableValueType: false tipsOfTheDay: false level: 7 paths: - src phpunit.xml000064400000001724152475272120006770 0ustar00 ./tests/ ./tests/Benchmark ./tests/Benchmark/ ./src src/CachedEnforcer.php000064400000003660152475272120010713 0ustar00enableCache = true; } /** * Determines whether to enable cache on Enforce(). When enableCache is enabled, cached result (true | false) will be returned for previous decisions. * * @param bool $enableCache */ public function enableCache(bool $enableCache): void { $this->enableCache = $enableCache; } /** * Enforce decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). * If rvals is not string , ingore the cache. * * @param mixed ...$rvals * * @return bool * * @throws Exceptions\CasbinException */ public function enforce(...$rvals): bool { if (!$this->enableCache) { return parent::enforce(...$rvals); } $key = ''; foreach ($rvals as $rval) { if (is_string($rval)) { $key .= $rval.'$$'; } else { return parent::enforce(...$rvals); } } if (isset(self::$m[$key])) { return self::$m[$key]; } else { $res = parent::enforce(...$rvals); self::$m[$key] = $res; return $res; } } /** * Deletes all the existing cached decisions. */ public function invalidateCache(): void { self::$m = []; } } src/Config/Config.php000064400000014020152475272120010462 0ustar00> */ public $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 == null ? "" : $buf); for ($i = 0, $len = \count($buf); $i <= $len; ++$i) { if ($canWrite) { $this->write($section, $lineNum, $buffer); $canWrite = false; } ++$lineNum; $line = isset($buf[$i]) ? $buf[$i] : ''; if ($i == \count($buf)) { if (\strlen($buffer) > 0) { $this->write($section, $lineNum, $buffer); } break; } $line = trim($line); if ('' == $line || self::DEFAULT_COMMENT == substr($line, 0, 1) || self::DEFAULT_COMMENT_SEM == substr($line, 0, 1)) { $canWrite = true; continue; } elseif ('[' == substr($line, 0, 1) && ']' == substr($line, -1)) { if (\strlen($buffer) > 0) { $this->write($section, $lineNum, $buffer); $canWrite = false; } $section = substr($line, 1, -1); } else { $p = ''; if (self::DEFAULT_MULTI_LINE_SEPARATOR == substr($line, -1)) { $p = trim(substr($line, 0, -1)); } else { $p = $line; $canWrite = true; } $buffer .= $p; } } return true; } /** * @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 isset($this->data[$section][$option]) ? $this->data[$section][$option] : ''; } } src/Config/ConfigContract.php000064400000002115152475272120012162 0ustar00 'mysql', // mysql,pgsql,sqlite,sqlsrv * 'hostname' => '127.0.0.1', * 'database' => 'test', * 'username' => 'root', * 'password' => '123456', * 'hostport' => '3306', * ]); * $e = new Enforcer("path/to/basic_model.conf", $a). * * @param mixed ...$params * * @throws CasbinException */ public function __construct(...$params) { $parsedParamLen = 0; $paramLen = \count($params); if ($paramLen >= 1) { if (\is_bool($enableLog = $params[$paramLen - 1])) { $this->enableLog($enableLog); ++$parsedParamLen; } } if (2 == $paramLen - $parsedParamLen) { $p0 = $params[0]; if (\is_string($p0)) { $p1 = $params[1]; if (\is_string($p1)) { $this->initWithFile($p0, $p1); } else { $this->initWithAdapter($p0, $p1); } } else { if (\is_string($params[1])) { throw new CasbinException('Invalid parameters for enforcer.'); } else { $this->initWithModelAndAdapter($p0, $params[1]); } } } elseif (1 == $paramLen - $parsedParamLen) { $p0 = $params[0]; if (\is_string($p0)) { $this->initWithFile($p0, ''); } else { $this->initWithModelAndAdapter($p0, null); } } elseif (0 == $paramLen - $parsedParamLen) { // pass } 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 = null): void { $this->adapter = $adapter; $this->model = $m; $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(); } } /** * Initializes an enforcer with a database adapter. */ protected function initialize(): void { $this->rmMap = []; $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(function () { $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 { $flag = false; $needToRebuild = false; $newModel = clone $this->model; $newModel->clearPolicy(); try { if (!is_null($this->adapter)){ $this->adapter->loadPolicy($newModel); } $newModel->printPolicy(); $newModel->sortPoliciesBySubjectHierarchy(); $newModel->sortPoliciesByPriority(); if ($this->autoBuildRoleLinks) { $needToRebuild = true; foreach ($this->rmMap as $rm) { $rm->clear(); } $newModel->buildRoleLinks($this->rmMap); } $this->model = $newModel; } catch (InvalidFilePathException $e) { // Ignore throw $e; } catch (\Throwable $e) { $flag = true; throw $e; } finally { if ($flag) { if ($this->autoBuildRoleLinks && $needToRebuild) { $this->buildRoleLinks(); } } } } /** * 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->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'); } if (!is_null($this->adapter)){ $this->adapter->savePolicy($this->model); } if ($this->watcher !== null && $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(); } else { $this->rmMap[$ptype] = new DefaultRoleManager(10); } } } } /** * 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 { Log::getLogger()->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) { $rm = $ast->rm; $functions[$key] = BuiltinOperations::generateGFunction($rm); } } 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 && (strpos($expString, $pType . '_') !== false)) { 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; } list($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; } list($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; if (Log::getLogger()->isEnabled()) { $reqStr = 'Request: '; $reqStr .= implode(', ', array_values($rvals)); $reqStr .= sprintf(" ---> %s\n", var_export($result, true)); $reqStr = 'Hit Policy: '; if (count($explains) == count($explains, COUNT_RECURSIVE)) { // if $explains is not multidimensional $reqStr .= sprintf("%s \n", '[' . implode(', ', $explains) . ']'); } else { // if $explains is multidimensional foreach ($explains as $i => $pval) { $reqStr .= sprintf("%s \n", '[' . implode(', ', $pval) . ']'); } } Log::logPrint($reqStr); } return $result; } /** * @param array $functions * * @return ExpressionLanguage */ protected function getExpressionLanguage(array $functions): ExpressionLanguage { $expressionLanguage = new ExpressionLanguage(); foreach ($functions as $key => $func) { $expressionLanguage->register($key, function (...$args) use ($key) { return sprintf($key . '(%1$s)', implode(',', $args)); }, function ($arguments, ...$args) use ($func) { return $func(...$args); }); } 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]*)/', function ($m) { return $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); } /** * BatchEnforce enforce in batches * * @param string[][] $requests * @return bool[] */ public function batchEnforce(array $requests): array { return array_map(function (array $request) { return $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(function (array $request) use ($matcher) { return $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; } } src/Effector/DefaultEffector.php000064400000007367152475272120012667 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 "priority(p_eft) || deny": case "subjectPriority(p_eft) || deny": // 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.php000064400000001050152475272120011341 0ustar00rType = "r" . $suffix; $this->pType = "p" . $suffix; $this->eType = "e" . $suffix; $this->mType = "m" . $suffix; } } src/Enforcer.php000064400000043221152475272120007620 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); $res2 = $this->removeFilteredPolicy(0, $user); return $res1 || $res2; } /** * Deletes a role. * * @param string $role * @return bool */ public function deleteRole(string $role): bool { $res1 = $this->removeFilteredGroupingPolicy(1, $role); $res2 = $this->removeFilteredPolicy(0, $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 { return $this->removeFilteredPolicy(0, $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 = []; $args[0] = $user; foreach ($assertion->tokens as $i => $token) { if ($token == sprintf('%s_dom', $ptype)) { $args[$i] = $domain[0]; break; } } $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; } /** * 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; } /** * 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->getDomainIndex('p'); $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->getDomainIndex('p'); $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; } } src/Exceptions/BatchOperationException.php000064400000000303152475272120014751 0ustar00adapter) && $this->autoSave; } /** * 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->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]); } if ($this->watcher !== null && $this->autoNotifyWatcher) { 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 { 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); } if ($this->watcher !== null && $this->autoNotifyWatcher) { $this->watcher->update(); } return true; } /** * @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->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]); } if ($this->watcher !== null && $this->autoNotifyWatcher) { try { if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicy($oldRule, $newRule); } else { $this->watcher->update(); } } catch (\Exception $e) { Log::logPrint("An exception occurred:" . $e->getMessage()); return false; } } return true; } protected function updatePoliciesInternal(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); } if ($this->watcher !== null && $this->autoNotifyWatcher) { try { if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicies($oldRules, $newRules); } else { $this->watcher->update(); } } catch (\Exception $e) { Log::logPrint("An exception occurred:" . $e->getMessage()); 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->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]); } if ($this->watcher !== null && $this->autoNotifyWatcher) { 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->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); } if ($this->watcher !== null && $this->autoNotifyWatcher) { // 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->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); } if ($this->watcher !== null && $this->autoNotifyWatcher) { // error intentionally ignored if ($this->watcher instanceof WatcherEx) { $this->watcher->updateForRemoveFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues); } else { $this->watcher->update(); } } return true; } protected function updateFilteredPoliciesInternal(string $sec, string $ptype, array $newRules, int $fieldIndex, string ...$fieldValues): bool { $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 $ruleChanged; } 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); } if ($this->watcher !== null && $this->autoNotifyWatcher) { // error intentionally ignored if ($this->watcher instanceof WatcherUpdatable) { $this->watcher->updateForUpdatePolicies($oldRules, $newRules); } else { $this->watcher->update(); } return $ruleChanged; } return $ruleChanged; } /** * Undocumented function * * @param string $ptype * @return int */ protected function getDomainIndex(string $ptype): int { $p = $this->model['p'][$ptype]; $pattern = sprintf("%s_dom", $ptype); $index = count($p->tokens); $tempIndex = array_search($pattern, $p->tokens); if ($tempIndex !== false) { $index = intval($tempIndex); } return $index; } } src/Log/Log.php000064400000002021152475272120007310 0ustar00write(...$v); } /** * Prints the log with the format. * * @param string $format * @param mixed ...$v */ public static function logPrintf(string $format, ...$v): void { self::$logger->writef($format, ...$v); } } Log::setLogger(new DefaultLogger()); src/Log/Logger.php000064400000001407152475272120010015 0ustar00path = sys_get_temp_dir(); } /** * enableLog. * * @param bool $enable */ public function enableLog(bool $enable): void { $this->enable = $enable; } /** * @return bool */ public function isEnabled(): bool { return $this->enable; } /** * @param mixed ...$v */ public function write(...$v): void { if ($this->enable) { $content = date('Y-m-d H:i:s '); foreach ($v as $value) { if (\is_array($value)) { $value = json_encode($value); } elseif (\is_object($value)) { $value = json_encode($value); } $content .= $value; } $content .= PHP_EOL; $this->save($content); } } /** * @param string $format * @param mixed ...$v */ public function writef(string $format, ...$v): void { if ($this->enable) { $content = date('Y-m-d H:i:s '); $content .= sprintf($format, ...$v); $content .= PHP_EOL; $this->save($content); } } /** * @param string $content */ public function save(string $content): void { $file = $this->path.DIRECTORY_SEPARATOR.$this->name; file_put_contents($file, $content, FILE_APPEND | LOCK_EX); } } src/ManagementEnforcer.php000064400000043022152475272120011614 0ustar00model->getValuesForFieldInPolicyAllTypes('p', 0); } /** * Gets the list of subjects that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedSubjects(string $ptype): array { return $this->model->getValuesForFieldInPolicy('p', $ptype, 0); } /** * Gets the list of objects that show up in the current policy. * * @return array */ public function getAllObjects(): array { return $this->model->getValuesForFieldInPolicyAllTypes('p', 1); } /** * Gets the list of objects that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedObjects(string $ptype): array { return $this->model->getValuesForFieldInPolicy('p', $ptype, 1); } /** * Gets the list of actions that show up in the current policy. * * @return array */ public function getAllActions(): array { return $this->model->getValuesForFieldInPolicyAllTypes('p', 2); } /** * Gets the list of actions that show up in the current named policy. * * @param string $ptype * * @return array */ public function getAllNamedActions(string $ptype): array { return $this->model->getValuesForFieldInPolicy('p', $ptype, 2); } /** * 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); } /** * 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); } /** * 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); } /** * 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); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } 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 { $ruleAdded = $this->addPoliciesInternal('g', $ptype, $rules); if ($this->autoBuildRoleLinks) { $this->buildRoleLinks(); } return $ruleAdded; } /** * 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); } } src/Model/Assertion.php000064400000005777152475272120011102 0ustar00 */ public $policyMap = []; /** * $rm. * * @var RoleManager */ public $rm; /** * $priorityIndex. * * @var int|bool */ public $priorityIndex; public function initPriorityIndex(): void { $this->priorityIndex = false; } /** * @param RoleManager $rm * * @throws CasbinException */ public function buildRoleLinks(RoleManager $rm): void { $this->rm = $rm; $count = substr_count($this->value, '_'); foreach ($this->policy as $rule) { if ($count < 2) { throw new CasbinException('the number of "_" in role definition should be at least 2'); } if (\count($rule) < $count) { throw new CasbinException('grouping policy elements do not meet role definition'); } if (2 == $count) { $this->rm->addLink($rule[0], $rule[1]); } elseif (3 == $count) { $this->rm->addLink($rule[0], $rule[1], $rule[2]); } elseif (4 == $count) { $this->rm->addLink($rule[0], $rule[1], $rule[2], $rule[3]); } } Log::logPrint('Role links for: ' . $this->key); $this->rm->printRoles(); } /** * @param RoleManager $rm * @param integer $op * @param string[][] $rules * @return void */ 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); } switch ($op) { case Policy::POLICY_ADD: $rm->addLink($rule[0], $rule[1], ...array_slice($rule, 2)); break; case Policy::POLICY_REMOVE: $rm->deleteLink($rule[0], $rule[1], ...array_slice($rule, 2)); break; } } } } src/Model/FunctionMap.php000064400000004112152475272120011334 0ustar00 */ private $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', function (...$args) { return BuiltinOperations::keyMatchFunc(...$args); }); $fm->addFunction('keyGet', function (...$args) { return BuiltinOperations::keyGetFunc(...$args); }); $fm->addFunction('keyMatch2', function (...$args) { return BuiltinOperations::keyMatch2Func(...$args); }); $fm->addFunction('keyGet2', function (...$args) { return BuiltinOperations::keyGet2Func(...$args); }); $fm->addFunction('keyMatch3', function (...$args) { return BuiltinOperations::keyMatch3Func(...$args); }); $fm->addFunction('keyMatch4', function (...$args) { return BuiltinOperations::keyMatch4Func(...$args); }); $fm->addFunction('keyMatch5', function (...$args) { return BuiltinOperations::keyMatch5Func(...$args); }); $fm->addFunction('regexMatch', function (...$args) { return BuiltinOperations::regexMatchFunc(...$args); }); $fm->addFunction('ipMatch', function (...$args) { return BuiltinOperations::ipMatchFunc(...$args); }); $fm->addFunction('globMatch', function (...$args) { return BuiltinOperations::globMatchFunc(...$args); }); return $fm; } /** * @return array */ public function getFunctions(): array { return $this->functions; } } src/Model/Model.php000064400000022617152475272120010163 0ustar00 */ protected $sectionNameMap = [ 'r' => 'request_definition', 'p' => 'policy_definition', 'g' => 'role_definition', 'e' => 'policy_effect', 'm' => 'matchers', ]; public function __construct() { } 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); } /** * 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; $ast->initPriorityIndex(); if ('r' == $sec || 'p' == $sec) { $ast->tokens = explode(',', $ast->value); foreach ($ast->tokens as $i => $token) { $ast->tokens[$i] = $key . '_' . trim($token); } } 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 { Log::logPrint('Model:'); foreach ($this->items as $k => $v) { foreach ($v as $i => $j) { Log::logPrintf('%s.%s: %s', $k, $i, $j->value); } } } /** * 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 != 'subjectPriority(p_eft) || deny') { return; } $subIndex = 0; $domainIndex = -1; foreach ($this->items['p'] as $ptype => $assertion) { foreach ($assertion->tokens as $index => $token) { if ($token == sprintf('%s_dom', $ptype)) { $domainIndex = $index; break; } } $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]; $p2 = $subjectHierarchyMap[$name2]; 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) { $index = array_search(sprintf("%s_priority", $ptype), $assertion->tokens); if ($index !== false) { $assertion->priorityIndex = intval($index); } else { continue; } $policies = &$assertion->policy; usort($policies, function ($i, $j) use ($assertion): int { $p1 = $i[$assertion->priorityIndex]; $p2 = $j[$assertion->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.php000064400000032477152475272120010367 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 $items = []; /** * 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") { $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 { if (!isset($this->items['g'])) { return; } foreach ($this->items['g'] as $ptype => $ast) { $rm = $rmMap[$ptype]; $ast->buildRoleLinks($rm); } } /** * Prints the policy to log. */ public function printPolicy(): void { Log::logPrint('Policy:'); foreach (['p', 'g'] as $sec) { if (!isset($this->items[$sec])) { return; } foreach ($this->items[$sec] as $key => $ast) { Log::logPrint($key, ': ', $ast->value, ': ', $ast->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; if ($sec == 'p' && $assertion->priorityIndex !== false && $assertion->priorityIndex >= 0) { $idxInsert = $rule[$assertion->priorityIndex]; for ($i = count($assertion->policy) - 1; $i > 0; $i--) { $idx = $assertion->policy[$i - 1][$assertion->priorityIndex]; 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 { foreach ($rules as $rule) { $hashKey = implode(self::DEFAULT_SEP, $rule); if (isset($this->items[$sec][$ptype]->policyMap[$hashKey])) { continue; } $this->addPolicy($sec, $ptype, $rule); } } /** * 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; } /** * 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.php000064400000002556152475272120011074 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.php000064400000014110152475272120013424 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.php000075500000010471152475272120015114 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 = []; switch (trim($p[0])) { case 'p': $filterSlice = $filter->p; break; case 'g': $filterSlice = $filter->g; break; } 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.php000075500000001216152475272120012477 0ustar00p = $p; $this->g = $g; } } src/Persist/BatchAdapter.php000064400000001413152475272120012025 0ustar00name = $name; } /** * @param self $role */ public function addRole(self $role): void { // determine whether this role has been added foreach ($this->roles as $rr) { if ($rr->name == $role->name) { return; } } $this->roles[] = $role; } /** * @param self $role */ public function deleteRole(self $role): void { foreach ($this->roles as $key => $rr) { if ($rr->name == $role->name) { unset($this->roles[$key]); return; } } } /** * @param string $name * @param int $hierarchyLevel * * @return bool */ public function hasRole(string $name, int $hierarchyLevel): bool { if ($this->hasDirectRole($name)) { return true; } if ($hierarchyLevel <= 0) { return false; } foreach ($this->roles as $role) { if ($role->hasRole($name, $hierarchyLevel - 1)) { return true; } } return false; } /** * @param string $name * @param int $hierarchyLevel * @param Closure $matchingFunc * * @return bool */ public function hasRoleWithMatchingFunc(string $name, int $hierarchyLevel, Closure $matchingFunc): bool { if ($this->hasDirectRoleWithMatchingFunc($name, $matchingFunc)) { return true; } if ($hierarchyLevel <= 0) { return false; } foreach ($this->roles as $role) { if ($role->hasRoleWithMatchingFunc($name, $hierarchyLevel - 1, $matchingFunc)) { return true; } } return false; } /** * @param string $name * * @return bool */ public function hasDirectRole(string $name): bool { foreach ($this->roles as $role) { if ($role->name == $name) { return true; } } return false; } /** * @param string $name * @param Closure $matchingFunc * * @return bool */ public function hasDirectRoleWithMatchingFunc(string $name, Closure $matchingFunc): bool { foreach ($this->roles as $role) { if ($role->name == $name || $matchingFunc($name, $role->name)) { return true; } } return false; } /** * @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 { return array_map(function (Role $role) { return $role->name; }, $this->roles); } } src/Rbac/DefaultRoleManager/RoleManager.php000064400000024011152475272120014615 0ustar00 */ protected $allDomains; /** * @var int */ protected $maxHierarchyLevel; /** * @var bool */ protected $hasPattern; /** * @var Closure */ protected $matchingFunc; /** * @var bool */ protected $hasDomainPattern; /** * @var Closure */ protected $domainMatchingFunc; /** * RoleManager constructor. * * @param int $maxHierarchyLevel */ public function __construct(int $maxHierarchyLevel) { $this->allDomains[self::DEFAULT_DOMAIN] = new Roles(); $this->maxHierarchyLevel = $maxHierarchyLevel; $this->hasPattern = false; $this->hasDomainPattern = false; } /** * Support use pattern in g. * * @param string $name * @param Closure $fn */ public function addMatchingFunc(string $name, Closure $fn): void { $this->hasPattern = true; $this->matchingFunc = $fn; } /** * Support use domain pattern in g. * * @param string $name * @param Closure $fn */ public function addDomainMatchingFunc(string $name, Closure $fn): void { $this->hasDomainPattern = true; $this->domainMatchingFunc = $fn; } /** * Clears all stored data and resets the role manager to the initial state. */ public function clear(): void { $this->allDomains = []; $this->loadOrStoreRoles(self::DEFAULT_DOMAIN, new Roles()); } /** * 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 * * @throws CasbinException */ public function addLink(string $name1, string $name2, string ...$domain): void { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $domain = count($domain) == 0 ? [self::DEFAULT_DOMAIN] : $domain; $patternDomain = $this->getPatternDomain($domain[0]); foreach ($patternDomain as $domain) { $allRoles = &$this->loadOrStoreRoles($domain, new Roles()); $role1 = &$allRoles->loadOrStore($name1, new Role($name1)); $role2 = &$allRoles->loadOrStore($name2, new Role($name2)); $role1->addRole($role2); if ($this->hasPattern) { foreach ($allRoles->toArray() as $key => $value) { $matchingFunc = $this->matchingFunc; if ($matchingFunc($key . '', $name1) && $name1 != $key) { $valueRole = &$allRoles->loadOrStore($key . '', new Role($key . '')); $valueRole->addRole($role1); } if ($matchingFunc($key . '', $name2) && $name2 != $key) { $role2->addRole($value); } if ($matchingFunc($name1, $key . '') && $name1 != $key) { $valueRole = &$allRoles->loadOrStore($key . '', new Role($key . '')); $valueRole->addRole($role1); } if ($matchingFunc($name2, $key . '') && $name2 != $key) { $role2->addRole($value); } } } } } /** * Undocumented function * * @param string $domain * @return string[] */ public function getPatternDomain(string $domain): array { $patternDomain = [$domain]; if ($this->hasDomainPattern) { foreach ($this->allDomains as $key => $value) { $domainMatchingFunc = $this->domainMatchingFunc; if ($domain != $key . '' && $domainMatchingFunc($domain, $key . '')) { $patternDomain[] = $key . ''; } } } return $patternDomain; } /** * 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 * * @throws CasbinException */ public function deleteLink(string $name1, string $name2, string ...$domain): void { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $domain = count($domain) == 0 ? [self::DEFAULT_DOMAIN] : $domain; $allRoles = &$this->loadOrStoreRoles($domain[0], new Roles()); if (is_null($allRoles->load($name1)) || is_null($allRoles->load($name2))) { throw new CasbinException('error: name1 or name2 does not exist'); } $role1 = &$allRoles->loadOrStore($name1, new Role($name1)); $role2 = &$allRoles->loadOrStore($name2, new Role($name2)); $role1->deleteRole($role2); } /** * 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 * @throws CasbinException */ public function hasLink(string $name1, string $name2, string ...$domain): bool { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $domain = count($domain) == 0 ? [self::DEFAULT_DOMAIN] : $domain; if ($name1 == $name2) { return true; } $patternDomain = $this->getPatternDomain($domain[0]); foreach ($patternDomain as $domain) { $allRoles = &$this->loadOrStoreRoles($domain, new Roles()); if (!$allRoles->hasRole($name1, $this->matchingFunc) || !$allRoles->hasRole($name2, $this->matchingFunc)) { continue; } if ($this->hasPattern) { $flag = false; foreach ($allRoles->toArray() as $key => $value) { $matchingFunc = $this->matchingFunc; if ($matchingFunc($name1, $key . '') && $value->hasRoleWithMatchingFunc($name2, $this->maxHierarchyLevel, $matchingFunc)) { $flag = true; break; } } if ($flag) { return true; } continue; } $role1 = &$allRoles->createRole($name1); $result = $role1->hasRole($name2, $this->maxHierarchyLevel); if ($result) { return true; } continue; } return false; } /** * Gets the roles that a subject inherits. * domain is a prefix to the roles. * * @param string $name * @param string ...$domain * * @return string[] * @throws CasbinException */ public function getRoles(string $name, string ...$domain): array { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $domain = count($domain) == 0 ? [self::DEFAULT_DOMAIN] : $domain; $patternDomain = $this->getPatternDomain($domain[0]); $gottenRoles = []; foreach ($patternDomain as $domain) { $allRoles = &$this->loadOrStoreRoles($domain, new Roles()); if (!$allRoles->hasRole($name, $this->matchingFunc)) { continue; } $gottenRoles = array_merge($gottenRoles, $allRoles->createRole($name)->getRoles()); } $gottenRoles = array_unique($gottenRoles); return $gottenRoles; } /** * 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[] * @throws CasbinException */ public function getUsers(string $name, string ...$domain): array { if (count($domain) > 1) { throw new CasbinException('error: domain should be 1 parameter'); } $domain = count($domain) == 0 ? [self::DEFAULT_DOMAIN] : $domain; $patternDomain = $this->getPatternDomain($domain[0]); $names = []; foreach ($patternDomain as $domain) { $allRoles = &$this->loadOrStoreRoles($domain, new Roles()); if (!$allRoles->hasRole($name, $this->matchingFunc)) { return []; } foreach ($allRoles->toArray() as $role) { if ($role->hasDirectRole($name)) { $names[] = $role->name; } } } return $names; } /** * Prints all the roles to log. */ public function printRoles(): void { $line = []; array_map(function (Roles $roles) use (&$line) { array_map(function (Role $role) use (&$line) { if ($text = $role->toString()) { $line[] = $text; } }, $roles->toArray()); }, $this->allDomains); Log::logPrint(implode(', ', $line)); } /** * @param string $domain * @param Roles $roles * * @return Roles */ protected function &loadOrStoreRoles(string $domain, Roles $roles): Roles { if (!isset($this->allDomains[$domain])) { $this->allDomains[$domain] = $roles; } return $this->allDomains[$domain]; } } src/Rbac/DefaultRoleManager/Roles.php000064400000003110152475272120013502 0ustar00 */ private $roles = []; /** * @param string $name * @param Closure|null $matchingFunc * * @return bool */ public function hasRole(string $name, ?Closure $matchingFunc): bool { $ok = false; if ($matchingFunc instanceof Closure) { foreach ($this->roles as $key => $role) { if ($matchingFunc($name, $key)) { $ok = true; } } } else { $ok = isset($this->roles[$name]); } return $ok; } /** * @param string $name * * @return Role */ public function &createRole(string $name): Role { $role = &$this->loadOrStore($name, new Role($name)); return $role; } /** * @param string $name * * @return Role|null */ public function load(string $name): ?Role { if (!isset($this->roles[$name])) { return null; } return $this->roles[$name]; } /** * @param string $name * @param Role $role * * @return Role */ public function &loadOrStore(string $name, Role $role): Role { if (!isset($this->roles[$name])) { $this->roles[$name] = $role; } return $this->roles[$name]; } /** * @return array */ public function toArray(): array { return $this->roles; } } src/Rbac/RoleManager.php000064400000004702152475272120011121 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 = $args[0]; $name2 = $args[1]; 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, function ($m) { return '[^\/]+'; }, $key2 ); return self::regexMatch($key1, '^' . $key2 . '$'); } /** * The wrapper for KeyMatch2. * * @param mixed ...$args * * @return bool */ public static function keyMatch2Func(...$args): bool { $name1 = $args[0]; $name2 = $args[1]; 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, function ($m) { return '([^\/]+)'; }, $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 = $args[0]; $name2 = $args[1]; $key = $args[2]; 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, function ($m) { return '[^\/]+'; }, $key2 ); return self::regexMatch($key1, '^' . $key2 . '$'); } /** * The wrapper for KeyMatch3. * * @param mixed ...$args * * @return bool */ public static function keyMatch3Func(...$args): bool { $name1 = $args[0]; $name2 = $args[1]; 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 = $args[0]; $name2 = $args[1]; 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 = $args[0]; $name2 = $args[1]; 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 = $args[0]; $name2 = $args[1]; 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 { $objIP1 = IP::parse($ip1); $objIP2 = Range::parse($ip2); return $objIP2->contains($objIP1); } /** * The wrapper for IPMatch. * * @param mixed ...$args * * @return bool * * @throws Exception */ public static function ipMatchFunc(...$args): bool { $ip1 = $args[0]; $ip2 = $args[1]; 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 = $args[0]; $name2 = $args[1]; 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; }; } } src/Util/Util.php000064400000006474152475272120007720 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 (0 === strpos($s, "r") || 0 === strpos($s, "p")) { $pos = strpos($s, '.'); if ($pos !== false) { $s[$pos] = '_'; } } $ss = preg_replace_callback("~(\|| |=|\)|\(|&|<|>|,|\+|-|!|\*|\/)((r|p)[0-9]*)(\.)~", function ($m) { return $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 ($s) use ($sets): string { $s = $s[0]; preg_match(self::REGEXP, $s, $subs); if (count($subs) == 0) { return $s; } $key = $subs[1]; if (isset($sets[$key])) { $found = true; $value = $sets[$key]; } else { $found = false; } if (!$found) { return $s; } return preg_replace(self::REGEXP, $s, $value); }, $src); } } tests/Benchmark/ModelBenchmarkTest.php000064400000015537152475272120014046 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", false); $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", false); $this->benchmark(function () use ($e) { $e->enforce("alice", "data2", "read"); }, 10000); } public function testRBACModelSmall(): void { $e = new Enforcer($this->modelAndPolicyPath . "/rbac_model.conf", false); // 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", false); // 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", false); // 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", false); $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", false); $this->benchmark(function () use ($e) { $e->enforce("alice", "domain1", "data1", "read"); }, 1000); } public function testABACModel(): void { $e = new Enforcer($this->modelAndPolicyPath . "/abac_model.conf", false); $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", false); $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", function (...$args) { return 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 = isset(debug_backtrace()[1]['function']) ? 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.php000064400000053317152475272120011042 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')); } } tests/Unit/CachedEnforcerTest.php000064400000003317152475272120013044 0ustar00modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $e->removePolicy('alice', 'data1', 'read'); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); $e->invalidateCache(); $this->assertFalse($e->enforce('alice', 'data1', 'read')); $this->assertFalse($e->enforce('alice', 'data1', 'write')); $this->assertFalse($e->enforce('alice', 'data2', 'read')); $this->assertFalse($e->enforce('alice', 'data2', 'write')); } public function testEnableCache() { $e = new CachedEnforcer($this->modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv'); $e->enableCache(false); $this->assertTrue($e->enforce('alice', 'data1', 'read')); $e->removePolicy('alice', 'data1', 'read'); $this->assertFalse($e->enforce('alice', 'data1', 'read')); } } tests/Unit/Config/ConfigTest.php000064400000004314152475272120012621 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.ini000064400000001273152475272120011524 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.php000064400000026723152475272120012573 0ustar00modelAndPolicyPath . '/basic_model.conf', $this->modelAndPolicyPath . '/basic_policy.csv', 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')); } 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(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')); } } tests/Unit/EnforcerTest.php000064400000060063152475272120011755 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']]); } 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', function (string $key1, string $key2) { return 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 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', function (string $key1, string $key2) { return 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', function (string $key1, string $key2) { return 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()); } } tests/Unit/Log/LogTest.php000064400000001110152475272120011440 0ustar00enableLog(true); $enable = Log::getLogger()->isEnabled(); $this->assertTrue($enable); Log::getLogger()->enableLog(false); $enable = Log::getLogger()->isEnabled(); $this->assertFalse($enable); } } tests/Unit/Log/Logger/DefaultLoggerTest.php000064400000001470152475272120014673 0ustar00enableLog(true); $enable = $logger->isEnabled(); $this->assertTrue($enable); $path = $logger->path; $name = $logger->name; $logfile = $logger->path . DIRECTORY_SEPARATOR . $logger->name; if (file_exists($logfile)) { unlink($logfile); } $logger->write('testing logger'); $logger->write(['testing', 'logger']); $logger->writef('testing %s', 'DefaultLogger'); $this->assertTrue(file_exists($logfile)); } } tests/Unit/ManagementEnforcerTest.php000064400000037713152475272120013760 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']); } 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'], ]); } 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']); } 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/ModelTest.php000064400000020155152475272120012310 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', function (string $key1, string $key2) { return 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', function (string $key1, string $key2) { return 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 testDomainMatchModel() { $e = new Enforcer($this->modelAndPolicyPath . '/rbac_with_domain_pattern_model.conf', $this->modelAndPolicyPath . '/rbac_with_domain_pattern_policy.csv'); $e->addNamedDomainMatchingFunc('g', 'keyMatch2', function (string $key1, string $key2) { return 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', function (string $key1, string $key2) { return BuiltinOperations::keyMatch2($key1, $key2); }); $e->addNamedDomainMatchingFunc('g', 'keyMatch2', function (string $key1, string $key2) { return 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); } } tests/Unit/Model/PolicyTest.php000064400000013321152475272120012504 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.php000064400000000436152475272120011326 0ustar00Name = $Name; $this->Age = $Age; } } tests/Unit/Persist/Adapters/FileAdapterTest.php000064400000001326152475272120015563 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.php000075500000003772152475272120017254 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.csv000064400000000037152475272120016244 0ustar00p, admin, domain1, data1, read2tests/Unit/Persist/Adapters/rbac_policy_test.csv000064400000000000152475272120016060 0ustar00tests/Unit/Rbac/DefaultRoleManager/RoleManagerTest.php000064400000012363152475272120016756 0ustar00addLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertTrue($res); } public function testDeleteLink() { $rm = new RoleManager(3); try { $rm->deleteLink('u1', 'g1'); } catch (\Exception $e) { $this->assertTrue($e instanceof CasbinException); } $rm->addLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertTrue($res); $rm->deleteLink('u1', 'g1'); $res = $rm->hasLink('u1', 'g1'); $this->assertFalse($res); } public function testGetRoles() { $rm = new RoleManager(3); $rm->addLink('u1', 'g1'); $rm->addLink('u2', 'g1'); $rm->addLink('u3', 'g2'); $rm->addLink('u4', 'g2'); $rm->addLink('u4', 'g3'); $rm->addLink('g1', 'g3'); // Current role inheritance tree: // g3 g2 // / \ / \ // g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->getRoles('u1'), ['g1']); $this->assertEquals($rm->getRoles('u4'), ['g2', 'g3']); } public function testGetUsers() { $rm = new RoleManager(3); $rm->addLink('u1', 'g1'); $rm->addLink('u2', 'g1'); $rm->addLink('u3', 'g2'); $rm->addLink('u4', 'g2'); $rm->addLink('u4', 'g3'); $rm->addLink('g1', 'g3'); // Current role inheritance tree: // g3 g2 // / \ / \ // g1 u4 u3 // / \ // u1 u2 $this->assertEquals($rm->getUsers('g1'), ['u1', 'u2']); $this->assertEquals($rm->getUsers('g3'), ['g1', 'u4']); } public function testDomainPatternRole() { $rm = new RoleManager(10); $rm->addDomainMatchingFunc('keyMatch2', function (string $key1, string $key2) { return 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 testAllMatchingFunc() { $rm = new RoleManager(10); $rm->addMatchingFunc('keyMatch2', function (string $key1, string $key2) { return BuiltinOperations::keyMatch2($key1, $key2); }); $rm->addDomainMatchingFunc('keyMatch2', function (string $key1, string $key2) { return 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', function (string $key1, string $key2) { return 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); } } tests/Unit/Util/BuiltinOperationsTest.php000064400000025004152475272120014575 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", "")); } } tests/Unit/Util/UtilTest.php000064400000011143152475272120012037 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 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.php000064400000001740152475272120012564 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.php000064400000003655152475272120013070 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.php000064400000001756152475272120014415 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.php000064400000003334152475272120012560 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.php000064400000001445152475272120012264 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); } } tests/Watcher/WatcherUpdatableTest.php000064400000002166152475272120014107 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); } }