From 2afc12713cc28225b75a9b6331489de49e299a1a Mon Sep 17 00:00:00 2001 From: TechLee Date: Fri, 1 Mar 2019 17:26:47 +0800 Subject: [PATCH 001/349] Released version 0.2 --- README.md | 2 +- setup.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 63d6dc41..8a38230f 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ PyCasbin [![Build Status](https://www.travis-ci.org/casbin/pycasbin.svg?branch=master)](https://www.travis-ci.org/casbin/pycasbin) [![Coverage Status](https://coveralls.io/repos/github/casbin/pycasbin/badge.svg)](https://coveralls.io/github/casbin/pycasbin) [![Version](https://img.shields.io/pypi/v/casbin.svg)](https://pypi.org/project/casbin/) +[![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) [![Pyversions](https://img.shields.io/pypi/pyversions/casbin.svg)](https://pypi.org/project/casbin/) [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) [![License](https://img.shields.io/pypi/l/casbin.svg)](https://pypi.org/project/casbin/) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) -[![Patreon](https://img.shields.io/badge/patreon-donate-yellow.svg)](http://www.patreon.com/yangluo) **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ diff --git a/setup.py b/setup.py index 0dbc1da3..d29893d9 100644 --- a/setup.py +++ b/setup.py @@ -1,11 +1,13 @@ import setuptools -with open("README.md", "r") as fh: +desc_file = "README.md" + +with open(desc_file, "r") as fh: long_description = fh.read() setuptools.setup( name="casbin", - version="0.1.1", + version="0.2", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", @@ -15,11 +17,16 @@ keywords=["casbin", "rbac", "access control", "abac", "acl", "permission"], packages=setuptools.find_packages(), install_requires=['simpleeval>=0.9.8'], - python_requires=">=3.6", + python_requires=">=3.3", + license="Apache 2.0", classifiers=[ + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], + data_files=[desc_file], ) From d476a5f10c96b853f5e37c1bb3b6d77c44a9e29d Mon Sep 17 00:00:00 2001 From: Foufou Date: Tue, 12 Mar 2019 20:02:07 +0800 Subject: [PATCH 002/349] added SQLAlchemy Adapter --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 8a38230f..19c6a3e7 100644 --- a/README.md +++ b/README.md @@ -208,6 +208,7 @@ Adapter | Type | Author | Description ----|------|----|---- [File Adapter (built-in)](https://casbin.org/docs/en/policy-storage#file-adapter-built-in) | File | Casbin | Persistence for [.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) files [Filtered File Adapter (built-in)](https://github.com/casbin/casbin#policy-enforcement-at-scale) | File | [@faceless-saint](https://github.com/faceless-saint) | Persistence for [.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) files with policy subset loading support +[SQLAlchemy Adapter](https://github.com/pycasbin/sqlalchemy-adapter) | database | Casbin | PostgreSQL,MySQL,SQLite,Oracle,Microsoft SQL Server,Firebird,Sybase are supported by [SQLAlchemy](https://docs.sqlalchemy.org/en/latest/dialects/index.html) For details of adapters, please refer to the documentation: https://casbin.org/docs/en/policy-storage From c5f5be0a73f519436d835d60be92a460b3ffbc96 Mon Sep 17 00:00:00 2001 From: Puru <40428133+tuladhar-p@users.noreply.github.com> Date: Wed, 27 Mar 2019 16:06:10 +0545 Subject: [PATCH 003/349] Update README: It's for Python projects! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19c6a3e7..97619b3b 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ PyCasbin **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ -Casbin is a powerful and efficient open-source access control library for Golang projects. It provides support for enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model). +Casbin is a powerful and efficient open-source access control library for Python 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: From 2b5476bb25890255b89d67cbb14210109713794d Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Thu, 28 Mar 2019 00:07:33 +0800 Subject: [PATCH 004/349] Move adapter section to website. --- README.md | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/README.md b/README.md index 97619b3b..425cb49f 100644 --- a/README.md +++ b/README.md @@ -202,15 +202,7 @@ We also provide a web-based UI for model management and policy management: ## Policy persistence -In Casbin, the policy storage is implemented as an adapter (aka middleware for Casbin). To keep light-weight, we don't put adapter code in the main library (except the default file adapter). A complete list of Casbin adapters is provided as below. Any 3rd-party contribution on a new adapter is welcomed, please inform us and I will put it in this list:) - -Adapter | Type | Author | Description -----|------|----|---- -[File Adapter (built-in)](https://casbin.org/docs/en/policy-storage#file-adapter-built-in) | File | Casbin | Persistence for [.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) files -[Filtered File Adapter (built-in)](https://github.com/casbin/casbin#policy-enforcement-at-scale) | File | [@faceless-saint](https://github.com/faceless-saint) | Persistence for [.CSV (Comma-Separated Values)](https://en.wikipedia.org/wiki/Comma-separated_values) files with policy subset loading support -[SQLAlchemy Adapter](https://github.com/pycasbin/sqlalchemy-adapter) | database | Casbin | PostgreSQL,MySQL,SQLite,Oracle,Microsoft SQL Server,Firebird,Sybase are supported by [SQLAlchemy](https://docs.sqlalchemy.org/en/latest/dialects/index.html) - -For details of adapters, please refer to the documentation: https://casbin.org/docs/en/policy-storage +https://casbin.org/docs/en/adapters ## Policy enforcement at scale From 757e36d2839cd9f7a90c26fcc3c3a0267c59967a Mon Sep 17 00:00:00 2001 From: Simon Christian Date: Fri, 29 Mar 2019 14:16:13 +0000 Subject: [PATCH 005/349] Cast parameters to a string before attempting to concatenate them --- casbin/enforcer.py | 2 +- tests/test_enforcer.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 68df8f93..30f4cd96 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -286,7 +286,7 @@ def enforce(self, *rvals): # Log request. if log.get_logger().is_enabled(): req_str = "Request: " - req_str = req_str + ", ".join(rvals) + req_str = req_str + ", ".join([str(v) for v in rvals]) req_str = req_str + " ---> %s" % result log.log_print(req_str) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 7afc3403..045c19d7 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -121,3 +121,11 @@ def test_enforce_rbac_with_resource_roles(self): self.assertFalse(e.enforce('bob', 'data1', 'write')) self.assertFalse(e.enforce('bob', 'data2', 'read')) self.assertTrue(e.enforce('bob', 'data2', 'write')) + + def test_enforce_abac_log_enabled(self): + e = get_enforcer(get_examples("abac_model.conf"), enable_log=True) + e.enable_log(True) + + sub = 'alice' + obj = {'Owner': 'alice', 'id': 'data1'} + self.assertTrue(e.enforce(sub, obj, 'write')) From ebf709c31f349da5ff5459f7084a8486727a698b Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Thu, 4 Apr 2019 22:28:24 +0800 Subject: [PATCH 006/349] Move role manager section to website. --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 425cb49f..974d9e1a 100644 --- a/README.md +++ b/README.md @@ -222,13 +222,7 @@ Watcher | Type | Author | Description ## Role manager -The role manager is used to manage the RBAC role hierarchy (user-role mapping) in Casbin. A role manager can retrieve the role data from Casbin policy rules or external sources such as LDAP, Okta, Auth0, Azure AD, etc. We support different implementations of a role manager. To keep light-weight, we don't put role manager code in the main library (except the default role manager). A complete list of Casbin role managers is provided as below. Any 3rd-party contribution on a new role manager is welcomed, please inform us and I will put it in this list:) - -Role manager | Author | Description -----|----|---- -[Default Role Manager (built-in)](https://github.com/casbin/casbin/blob/master/casbin/rbac/default_role_manager/role_manager.py) | Casbin | Supports role hierarchy stored in Casbin policy - -For developers: all role managers must implement the [RoleManager](https://github.com/casbin/casbin/blob/master/casbin/rbac/role_manager.py) interface. +https://casbin.org/docs/en/role-managers ## Multi-threading From ed3a3db8864dc248c31470818f1de0d142a41cee Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 5 Apr 2019 00:52:43 +0800 Subject: [PATCH 007/349] Add Middlewares link to README. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 974d9e1a..82ac882c 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ production-ready | experimental | WIP | WIP - [Benchmarks](#benchmarks) - [Examples](#examples) - [How to use Casbin as a service?](#how-to-use-casbin-as-a-service) +- [Middlewares](#middlewares) - [Our adopters](#our-adopters) ## Supported models @@ -276,12 +277,13 @@ Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/ex - [Go-Simple-API-Gateway](https://github.com/Soontao/go-simple-api-gateway): A simple API gateway written by golang, supports for authentication and authorization. - [middleware-acl](https://github.com/luk4z7/middleware-acl): RESTful access control middleware based on Casbin. -## Our adopters +## Middlewares -### Web frameworks +Authz middlewares for web frameworks: https://casbin.org/docs/en/middlewares -... +## Our adopters +https://casbin.org/docs/en/adopters ## License From d8e5a8ead0591aaeaf35ee3fe44a74a61923130a Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 5 Apr 2019 00:53:12 +0800 Subject: [PATCH 008/349] Remove Casbin service in README. --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 82ac882c..1e676fcb 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ production-ready | experimental | WIP | WIP - [Multi-threading](#multi-threading) - [Benchmarks](#benchmarks) - [Examples](#examples) -- [How to use Casbin as a service?](#how-to-use-casbin-as-a-service) - [Middlewares](#middlewares) - [Our adopters](#our-adopters) @@ -271,12 +270,6 @@ RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/exa Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv) Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv) -## How to use Casbin as a service? - -- [Casbin Server](https://github.com/casbin/casbin-server): The official ``Casbin as a Service`` solution based on [gRPC](https://grpc.io/), both Management API and RBAC API are provided. -- [Go-Simple-API-Gateway](https://github.com/Soontao/go-simple-api-gateway): A simple API gateway written by golang, supports for authentication and authorization. -- [middleware-acl](https://github.com/luk4z7/middleware-acl): RESTful access control middleware based on Casbin. - ## Middlewares Authz middlewares for web frameworks: https://casbin.org/docs/en/middlewares From 41504d5a138d7783630a16866c3e84c8cab0427c Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 5 Apr 2019 00:56:59 +0800 Subject: [PATCH 009/349] Remove useless sections in README. --- README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/README.md b/README.md index 1e676fcb..164ef2a5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,6 @@ production-ready | experimental | WIP | WIP - [Get started](#get-started) - [Policy management](#policy-management) - [Policy persistence](#policy-persistence) -- [Policy consistence between multiple nodes](#policy-consistence-between-multiple-nodes) - [Role manager](#role-manager) - [Multi-threading](#multi-threading) - [Benchmarks](#benchmarks) @@ -204,22 +203,6 @@ We also provide a web-based UI for model management and policy management: https://casbin.org/docs/en/adapters -## Policy enforcement at scale - -Some adapters support filtered policy management. This means that the policy loaded by Casbin is a subset of the policy in storage based on a given filter. This allows for efficient policy enforcement in large, multi-tenant environments when parsing the entire policy becomes a performance bottleneck. - -To use filtered policies with a supported adapter, simply call the `LoadFilteredPolicy` method. The valid format for the filter parameter depends on the adapter used. To prevent accidental data loss, the `SavePolicy` method is disabled when a filtered policy is loaded. - - -## Policy consistence between multiple nodes - -We support to use distributed messaging systems like [etcd](https://github.com/coreos/etcd) to keep consistence between multiple Casbin enforcer instances. So our users can concurrently use multiple Casbin enforcers to handle large number of permission checking requests. - -Similar to policy storage adapters, we don't put watcher code in the main library. Any support for a new messaging system should be implemented as a watcher. A complete list of Casbin watchers is provided as below. Any 3rd-party contribution on a new watcher is welcomed, please inform us and I will put it in this list:) - -Watcher | Type | Author | Description -----|------|----|---- - ## Role manager https://casbin.org/docs/en/role-managers From fba55f8bf7874d1288a0d31eacad5ae4cc9e5b8f Mon Sep 17 00:00:00 2001 From: nosanity Date: Mon, 15 Apr 2019 17:17:13 +0300 Subject: [PATCH 010/349] fix for empty policy list Empty policy list causes error NameNotDefined 'p_sub' is not defined for expression ... because the token is represented by tuple in this string --- casbin/enforcer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 30f4cd96..87f62428 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -271,7 +271,7 @@ def enforce(self, *rvals): for j, token in enumerate(self.model.model["r"]["r"].tokens): parameters[token] = rvals[j] - for token in enumerate(self.model.model["p"]["p"].tokens): + for token in self.model.model["p"]["p"].tokens: parameters[token] = "" result = expression.evaluate(exp_string, parameters, functions) From bbf776b29f339380b0ebd5196512897a3bc461ef Mon Sep 17 00:00:00 2001 From: Foufou Date: Wed, 24 Apr 2019 14:33:37 +0800 Subject: [PATCH 011/349] Specifying source files --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 00000000..244de612 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +include = casbin/* From 1a9c3a6ae1b409eb1337a399058378c9c9bfd737 Mon Sep 17 00:00:00 2001 From: Calvin Behling Date: Tue, 23 Apr 2019 17:38:23 -0500 Subject: [PATCH 012/349] Add tests for rbac and empty policies Add tests related to #10 --- examples/empty_policy.csv | 0 tests/test_enforcer.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 examples/empty_policy.csv diff --git a/examples/empty_policy.csv b/examples/empty_policy.csv new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 045c19d7..b1dab828 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -86,6 +86,23 @@ def test_enforce_priority_indeterminate(self): e = get_enforcer(get_examples("priority_model.conf"), get_examples("priority_indeterminate_policy.csv")) self.assertFalse(e.enforce('alice', 'data1', 'read')) + def test_enforce_rbac(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + self.assertTrue(e.enforce('alice', 'data1', 'read')) + self.assertFalse(e.enforce('bob', 'data1', 'read')) + self.assertTrue(e.enforce('bob', 'data2', 'write')) + self.assertTrue(e.enforce('alice', 'data2', 'read')) + self.assertTrue(e.enforce('alice', 'data2', 'write')) + self.assertFalse(e.enforce('bogus', 'data2', 'write')) # test non-existant subject + + def test_enforce_rbac__empty_policy(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) + self.assertFalse(e.enforce('alice', 'data1', 'read')) + self.assertFalse(e.enforce('bob', 'data1', 'read')) + self.assertFalse(e.enforce('bob', 'data2', 'write')) + self.assertFalse(e.enforce('alice', 'data2', 'read')) + self.assertFalse(e.enforce('alice', 'data2', 'write')) + def test_enforce_rbac_with_deny(self): e = get_enforcer(get_examples("rbac_with_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) From f537dcf49a427644d8c634ef8911df5699b08a5e Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Wed, 24 Apr 2019 21:09:05 +0800 Subject: [PATCH 013/349] Remove "Benchmarks" in README. --- README.md | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/README.md b/README.md index 164ef2a5..87e9b03f 100644 --- a/README.md +++ b/README.md @@ -215,27 +215,7 @@ It also supports the ``AutoLoad`` feature, which means the Casbin enforcer will ## Benchmarks -The overhead of policy enforcement is benchmarked in [model_b_test.go](https://github.com/casbin/casbin/blob/master/model_b_test.go). The testbed is: - -``` -Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz, 2601 Mhz, 4 Core(s), 8 Logical Processor(s) -``` - -The benchmarking result of ``go test -bench=. -benchmem`` is as follows (op = an ``Enforce()`` call, ms = millisecond, KB = kilo bytes): - -Test case | Size | Time overhead | Memory overhead -----|------|------|---- -ACL | 2 rules (2 users) | 0.015493 ms/op | 5.649 KB -RBAC | 5 rules (2 users, 1 role) | 0.021738 ms/op | 7.522 KB -RBAC (small) | 1100 rules (1000 users, 100 roles) | 0.164309 ms/op | 80.620 KB -RBAC (medium) | 11000 rules (10000 users, 1000 roles) | 2.258262 ms/op | 765.152 KB -RBAC (large) | 110000 rules (100000 users, 10000 roles) | 23.916776 ms/op | 7.606 MB -RBAC with resource roles | 6 rules (2 users, 2 roles) | 0.021146 ms/op | 7.906 KB -RBAC with domains/tenants | 6 rules (2 users, 1 role, 2 domains) | 0.032696 ms/op | 10.755 KB -ABAC | 0 rule (0 user) | 0.007510 ms/op | 2.328 KB -RESTful | 5 rules (3 users) | 0.045398 ms/op | 91.774 KB -Deny-override | 6 rules (2 users, 1 role) | 0.023281 ms/op | 8.370 KB -Priority | 9 rules (2 users, 2 roles) | 0.016389 ms/op | 5.313 KB +https://casbin.org/docs/en/benchmark ## Examples From 43b368717fcfdffdd0e6dc931c4a164849d02e85 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Wed, 24 Apr 2019 21:09:59 +0800 Subject: [PATCH 014/349] Remove "Multi-threading" in README. --- README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/README.md b/README.md index 87e9b03f..6cf6c7f2 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ production-ready | experimental | WIP | WIP - [Policy management](#policy-management) - [Policy persistence](#policy-persistence) - [Role manager](#role-manager) -- [Multi-threading](#multi-threading) - [Benchmarks](#benchmarks) - [Examples](#examples) - [Middlewares](#middlewares) @@ -207,12 +206,6 @@ https://casbin.org/docs/en/adapters https://casbin.org/docs/en/role-managers -## Multi-threading - -If you use Casbin in a multi-threading manner, you can use the synchronized wrapper of the Casbin enforcer: https://github.com/casbin/casbin/blob/master/enforcer_synced.go. - -It also supports the ``AutoLoad`` feature, which means the Casbin enforcer will automatically load the latest policy rules from DB if it has changed. Call ``StartAutoLoadPolicy()`` to start automatically loading policy periodically and call ``StopAutoLoadPolicy()`` to stop it. - ## Benchmarks https://casbin.org/docs/en/benchmark From 0e1b8f95e91c9949581d2aad4bc08b586ecd4ade Mon Sep 17 00:00:00 2001 From: TechLee Date: Mon, 29 Apr 2019 14:18:55 +0800 Subject: [PATCH 015/349] Added ci so it can automatically release to PyPI a successful build. --- .travis.yml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3dba9d1b..2d8c49d8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,20 +1,21 @@ language: python python: - - "3.3" - - "3.4" - - "3.5" - - "3.6" +- '3.3' +- '3.4' +- '3.5' +- '3.6' install: - - pip install -r requirements.txt - - pip install coveralls +- pip install -r requirements.txt +- pip install coveralls script: - - coverage run -m unittest discover -s tests -t tests +- coverage run -m unittest discover -s tests -t tests after_success: - - coveralls +- coveralls deploy: provider: pypi - user: "techlee" + user: techlee password: - secure: "Your encrypted password" + secure: qoBLYrYCxU3qe9SP3tCdgqVEmqEQAdCFfC/h8ENeY3dD0cyBV/yaitjY92bQxLgialouDUojiyOpvEl4apl4XQSvVhywtIqBaY5w8h1SuRUNMOkCP8LiWQt5NbsVI6Liad9IRviknsiIkW3LTqOrS+zRWmhZ6Yz/wvc+QMFY4JX51EWLPwtFTSbuiG0VtJgN9s7LgX8Q6GvNW0Alp3mfJnVTqk/HCtJuHkQwd1EK5Zr2ePO+KwlLaTpvRrAAcQVEWuHysieriTNgz9GTXvVEoGAuP/meq8DIRGDQmqnMz4vjj7oxg+gaVioUfnhN35ZrTF0wggJaCeytkSjbbDCspIc47/zaK7041ZQO9r0A1eeErkSi1dUo3WH4yO3HWfWxg5HOLN+40CYPStlmC77/jG0vvYBdmdcD8h+cRmY0W426G8xf9aqnYNzI7VP8tVceO9ySVHgeKCdVPcGRht8nHQdsNHFxUijViThtzyhf+RKF1UXbIxlZUuWirwR2+rfGSY6oYdXIb0MUBXxLqwVVIPBrQJOuaMEZG4tAVxaa3K/YiRIN5rvTZp8Ru5YrPsAyGhINg3t2JrPoyU236JIlNKliQHPVvOUftGryD/kvhpDIl+kOKFkNoMCQnolbGMGEx+ZL7g4IBpR+iNkvJNEyV1KProPdGJR5ajUuGEAmdkM= + distributions: "sdist bdist_wheel" on: tags: true From feefccc00a710bd92e5c1e2f7ea1637ace261d54 Mon Sep 17 00:00:00 2001 From: Foufou Date: Mon, 29 Apr 2019 14:40:44 +0800 Subject: [PATCH 016/349] Update .travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 2d8c49d8..9a49cbf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,5 +17,6 @@ deploy: password: secure: qoBLYrYCxU3qe9SP3tCdgqVEmqEQAdCFfC/h8ENeY3dD0cyBV/yaitjY92bQxLgialouDUojiyOpvEl4apl4XQSvVhywtIqBaY5w8h1SuRUNMOkCP8LiWQt5NbsVI6Liad9IRviknsiIkW3LTqOrS+zRWmhZ6Yz/wvc+QMFY4JX51EWLPwtFTSbuiG0VtJgN9s7LgX8Q6GvNW0Alp3mfJnVTqk/HCtJuHkQwd1EK5Zr2ePO+KwlLaTpvRrAAcQVEWuHysieriTNgz9GTXvVEoGAuP/meq8DIRGDQmqnMz4vjj7oxg+gaVioUfnhN35ZrTF0wggJaCeytkSjbbDCspIc47/zaK7041ZQO9r0A1eeErkSi1dUo3WH4yO3HWfWxg5HOLN+40CYPStlmC77/jG0vvYBdmdcD8h+cRmY0W426G8xf9aqnYNzI7VP8tVceO9ySVHgeKCdVPcGRht8nHQdsNHFxUijViThtzyhf+RKF1UXbIxlZUuWirwR2+rfGSY6oYdXIb0MUBXxLqwVVIPBrQJOuaMEZG4tAVxaa3K/YiRIN5rvTZp8Ru5YrPsAyGhINg3t2JrPoyU236JIlNKliQHPVvOUftGryD/kvhpDIl+kOKFkNoMCQnolbGMGEx+ZL7g4IBpR+iNkvJNEyV1KProPdGJR5ajUuGEAmdkM= distributions: "sdist bdist_wheel" + skip_existing: true on: tags: true From 6c213e2a1f86234eb96b8ae04a6d0f0dc2b56b4d Mon Sep 17 00:00:00 2001 From: TechLee Date: Mon, 29 Apr 2019 15:00:14 +0800 Subject: [PATCH 017/349] Released version 0.3 --- .travis.yml | 1 + setup.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9a49cbf6..5b7d4136 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,3 +20,4 @@ deploy: skip_existing: true on: tags: true + python: '3.6' \ No newline at end of file diff --git a/setup.py b/setup.py index d29893d9..14a60ff6 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.2", + version="0.3", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From bd91a193546fcd78c1713e91ff0597fa0b8ab70e Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Fri, 24 May 2019 21:17:42 +0800 Subject: [PATCH 018/349] Create FUNDING.yml --- .github/FUNDING.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..96603a39 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,8 @@ +# 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 +custom: # Replace with a single custom sponsorship URL From 4586da2cba562d0104b07224ca430fc12960655c Mon Sep 17 00:00:00 2001 From: TechLee Date: Sun, 26 May 2019 23:28:34 +0800 Subject: [PATCH 019/349] Improve the api part code --- casbin/enforcer.py | 5 +- casbin/internal_api.py | 46 ++++++ casbin/management_api.py | 131 ++++++++++++++++++ casbin/model/policy.py | 45 +++++- .../rbac/default_role_manager/role_manager.py | 4 +- casbin/rbac_api.py | 11 ++ tests/test_management_api.py | 62 +++++++++ tests/test_rbac_api.py | 17 +++ 8 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 casbin/internal_api.py create mode 100644 casbin/management_api.py create mode 100644 casbin/rbac_api.py create mode 100644 tests/test_management_api.py create mode 100644 tests/test_rbac_api.py diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 87f62428..d0ede360 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -4,9 +4,10 @@ from casbin.rbac import default_role_manager from casbin.util import generate_g_function, expression from casbin.effect import DefaultEffector, Effector +from casbin.rbac_api import RbacApi -class Enforcer: +class Enforcer(RbacApi): """creates an enforcer via file or DB. Uses: @@ -14,7 +15,7 @@ class Enforcer: e = casbin.Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") MySQL DB: a = mysqladapter.DBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") - e = casbin.NewEnforcer("path/to/basic_model.conf", a) + e = casbin.Enforcer("path/to/basic_model.conf", a) """ model_path = "" diff --git a/casbin/internal_api.py b/casbin/internal_api.py new file mode 100644 index 00000000..89f6ecb5 --- /dev/null +++ b/casbin/internal_api.py @@ -0,0 +1,46 @@ +class InternalApi: + + def _add_policy(self, sec, ptype, rule): + """adds a rule to the current policy.""" + rule_added = self.model.add_policy(sec, ptype, rule) + if not rule_added: + return rule_added + + if self.adapter and self.auto_save: + if self.adapter.add_policy(sec, ptype, rule) is False: + return False + + if self.watcher: + self.watcher.update() + + return rule_added + + def _remove_policy(self, sec, ptype, rule): + """removes a rule from the current policy.""" + rule_removed = self.model.remove_policy(sec, ptype, rule) + if not rule_removed: + return rule_removed + + if self.adapter and self.auto_save: + if self.adapter.remove_policy(sec, ptype, rule) is False: + return False + + if self.watcher: + self.watcher.update() + + return rule_removed + + def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): + """removes rules based on field filters from the current policy.""" + rule_removed = self.model.remove_filtered_policy(sec, ptype, field_index, *field_values) + if not rule_removed: + return rule_removed + + if self.adapter and self.auto_save: + if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: + return False + + if self.watcher: + self.watcher.update() + + return rule_removed diff --git a/casbin/management_api.py b/casbin/management_api.py new file mode 100644 index 00000000..127fa707 --- /dev/null +++ b/casbin/management_api.py @@ -0,0 +1,131 @@ +from casbin.internal_api import InternalApi + + +class ManagementApi(InternalApi): + def get_all_subjects(self): + """gets the list of subjects that show up in the current policy.""" + return self.get_all_named_subjects('p') + + def get_all_named_subjects(self, ptype): + """gets the list of subjects that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy('p', ptype, 0) + + def get_all_objects(self): + """gets the list of objects that show up in the current policy.""" + return self.get_all_named_objects('p') + + def get_all_named_objects(self, ptype): + """gets the list of objects that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy('p', ptype, 1) + + def get_all_actions(self): + """gets the list of actions that show up in the current policy.""" + return self.get_all_named_actions('p') + + def get_all_named_actions(self, ptype): + """gets the list of actions that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy('p', ptype, 2) + + def get_all_roles(self): + """gets the list of roles that show up in the current named policy.""" + return self.get_all_named_roles('g') + + def get_all_named_roles(self, ptype): + """gets all the authorization rules in the policy.""" + return self.model.get_values_for_field_in_policy('g', ptype, 1) + + def get_policy(self): + """gets all the authorization rules in the policy.""" + return self.get_named_policy('p') + + def get_filtered_policy(self, field_index, *field_values): + """gets all the authorization rules in the policy, field filters can be specified.""" + return self.get_filtered_named_policy('p', field_index, *field_values) + + def get_named_policy(self, ptype): + """gets all the authorization rules in the named policy.""" + return self.model.get_policy('p', ptype) + + def get_filtered_named_policy(self, ptype, field_index, *field_values): + """gets all the authorization rules in the named policy, field filters can be specified.""" + return self.model.get_filtered_policy('p', ptype, field_index, *field_values) + + def get_grouping_policy(self): + """gets all the role inheritance rules in the policy.""" + return self.get_named_grouping_policy('g') + + def get_filtered_grouping_policy(self, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + return self.get_filtered_named_grouping_policy("g", field_index, *field_values) + + def get_named_grouping_policy(self, ptype): + """gets all the role inheritance rules in the policy.""" + return self.model.get_policy('g', ptype) + + def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + return self.model.get_filtered_policy('g', ptype, field_index, *field_values) + + def has_policy(self, *params): + """determines whether an authorization rule exists.""" + return self.has_named_policy('p', *params) + + def has_named_policy(self, ptype, *params): + """determines whether a named authorization rule exists.""" + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + return self.model.has_policy('p', ptype, str_slice) + + policy = [] + + for param in params: + policy.append(param) + + return self.model.has_policy('p', ptype, policy) + + def add_policy(self, *params): + """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. + """ + return self.add_named_policy('p', *params) + + def add_named_policy(self, ptype, *params): + """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. + """ + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_added = self._add_policy('p', ptype, str_slice) + else: + policy = [] + + for param in params: + policy.append(param) + + rule_added = self._add_policy('p', ptype, policy) + + return rule_added + + def has_grouping_policy(self, *params): + """determines whether a role inheritance rule exists.""" + + return self.has_named_grouping_policy("g", *params) + + def has_named_grouping_policy(self, ptype, *params): + """determines whether a named role inheritance rule exists.""" + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + return self.model.has_policy('g', ptype, str_slice) + + policy = [] + + for param in params: + policy.append(param) + + return self.model.has_policy('g', ptype, policy) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 8ffdc9b3..3fc16d29 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,4 +1,4 @@ -from casbin import log +from casbin import util, log class Policy: @@ -31,3 +31,46 @@ def clear_policy(self): def get_policy(self, sec, ptype): return self.model[sec][ptype].policy + + def get_filtered_policy(self, sec, ptype, field_index, *field_values): + """gets rules based on field filters from a policy.""" + res = [] + + for rule in self.model[sec][ptype].policy: + matched = True + for i, field_value in enumerate(field_values): + if field_value != '' and rule[field_index + i] != field_value: + matched = False + break + + if matched: + res.append(rule) + + return res + + def has_policy(self, sec, ptype, rule): + """determines whether a model has the specified policy rule.""" + if sec not in self.model.keys(): + return False + if ptype not in self.model[sec]: + return False + + for r in self.model[sec][ptype].policy: + if rule == r: + return True + + return False + + def get_values_for_field_in_policy(self, sec, ptype, field_index): + """gets all values for a field for all rules in a policy, duplicated values are removed.""" + + values = [] + if sec not in self.model.keys(): + return values + if ptype not in self.model[sec]: + return values + + for rule in self.model[sec][ptype].policy: + values.append(rule[field_index]) + + return util.array_remove_duplicates(values) diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index b3e4bd6d..b811e644 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -73,7 +73,7 @@ def get_roles(self, name, *domain): return RuntimeError("error: domain should be 1 parameter") if not self.has_role(name): - return dict() + return [] roles = self.create_role(name).get_roles() if len(domain) == 1: @@ -89,7 +89,7 @@ def get_users(self, name, *domain): names = [] for role in self.all_roles.values(): if role.has_direct_role(name): - names.append(name) + names.append(role.name) return names diff --git a/casbin/rbac_api.py b/casbin/rbac_api.py new file mode 100644 index 00000000..7cf68c41 --- /dev/null +++ b/casbin/rbac_api.py @@ -0,0 +1,11 @@ +from casbin.management_api import ManagementApi + + +class RbacApi(ManagementApi): + def get_roles_for_user(self, name): + """gets the roles that a user has.""" + return self.model.model['g']['g'].rm.get_roles(name) + + def get_users_for_role(self, name): + """gets the users that has a role.""" + return self.model.model['g']['g'].rm.get_users(name) diff --git a/tests/test_management_api.py b/tests/test_management_api.py new file mode 100644 index 00000000..8a43937c --- /dev/null +++ b/tests/test_management_api.py @@ -0,0 +1,62 @@ +from tests.test_enforcer import get_examples, get_enforcer +from unittest import TestCase + + +class TestManagementApi(TestCase): + def test_get_list(self): + e = get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + # True, + ) + + self.assertEqual(e.get_all_subjects(), ['alice', 'bob', 'data2_admin']) + self.assertEqual(e.get_all_objects(), ['data1', 'data2']) + self.assertEqual(e.get_all_actions(), ['read', 'write']) + self.assertEqual(e.get_all_roles(), ['data2_admin']) + + def test_get_policy_api(self): + e = get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + self.assertEqual(e.get_policy(), [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ]) + + self.assertEqual(e.get_filtered_policy(0, 'alice'), [['alice', 'data1', 'read']]) + self.assertEqual(e.get_filtered_policy(0, 'bob'), [['bob', 'data2', 'write']]) + self.assertEqual(e.get_filtered_policy(0, 'data2_admin'), + [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]) + self.assertEqual(e.get_filtered_policy(1, 'data1'), [['alice', 'data1', 'read']]) + self.assertEqual(e.get_filtered_policy(1, 'data2'), + [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write']]) + self.assertEqual(e.get_filtered_policy(2, 'read'), + [['alice', 'data1', 'read'], ['data2_admin', 'data2', 'read']]) + self.assertEqual(e.get_filtered_policy(2, 'write'), + [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]) + self.assertEqual(e.get_filtered_policy(0, 'data2_admin', 'data2'), + [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]) + + # Note: "" (empty string) in fieldValues means matching all values. + self.assertEqual(e.get_filtered_policy(0, 'data2_admin', '', 'read'), [['data2_admin', 'data2', 'read']]) + self.assertEqual(e.get_filtered_policy(1, 'data2', 'write'), + [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]) + + self.assertTrue(e.has_policy(['alice', 'data1', 'read'])) + self.assertTrue(e.has_policy(['bob', 'data2', 'write'])) + self.assertFalse(e.has_policy(['alice', 'data2', 'read'])) + self.assertFalse(e.has_policy(['bob', 'data3', 'write'])) + self.assertEqual(e.get_grouping_policy(), [['alice', 'data2_admin']]) + self.assertEqual(e.get_filtered_grouping_policy(0, 'alice'), [['alice', 'data2_admin']]) + self.assertEqual(e.get_filtered_grouping_policy(0, 'bob'), []) + self.assertEqual(e.get_filtered_grouping_policy(1, 'data1_admin'), []) + self.assertEqual(e.get_filtered_grouping_policy(1, 'data2_admin'), [['alice', 'data2_admin']]) + # Note: "" (empty string) in fieldValues means matching all values. + self.assertEqual(e.get_filtered_grouping_policy(0, '', 'data2_admin'), [['alice', 'data2_admin']]) + self.assertTrue(e.has_grouping_policy(['alice', 'data2_admin'])) + self.assertFalse(e.has_grouping_policy(['bob', 'data2_admin'])) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py new file mode 100644 index 00000000..0617a115 --- /dev/null +++ b/tests/test_rbac_api.py @@ -0,0 +1,17 @@ +from tests.test_enforcer import get_examples, get_enforcer +from unittest import TestCase + + +class TestRbacApi(TestCase): + def test_get_roles_for_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + + self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) + self.assertEqual(e.get_roles_for_user('bob'), []) + self.assertEqual(e.get_roles_for_user('data2_admin'), []) + self.assertEqual(e.get_roles_for_user('non_exist'), []) + + def test_get_users_for_role(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + + self.assertEqual(e.get_users_for_role('data2_admin'), ['alice']) From 73ba5eb76799cd6d8a80591158ee4a009b614646 Mon Sep 17 00:00:00 2001 From: TechLee Date: Mon, 27 May 2019 23:55:31 +0800 Subject: [PATCH 020/349] Released version 0.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 14a60ff6..b0080ff1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.3", + version="0.4", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From dd635c6d09efc3aa5567f0a258a2af4be3594afb Mon Sep 17 00:00:00 2001 From: TechLee Date: Wed, 5 Jun 2019 23:58:47 +0800 Subject: [PATCH 021/349] Improve add_policy api --- casbin/model/policy.py | 9 +++++++++ setup.py | 2 +- tests/test_management_api.py | 25 +++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 3fc16d29..438c75bc 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -61,6 +61,15 @@ def has_policy(self, sec, ptype, rule): return False + def add_policy(self, sec, ptype, rule): + """adds a policy rule to the model.""" + + if not self.has_policy(sec, ptype, rule): + self.model[sec][ptype].policy.append(rule) + return True + + return False + def get_values_for_field_in_policy(self, sec, ptype, field_index): """gets all values for a field for all rules in a policy, duplicated values are removed.""" diff --git a/setup.py b/setup.py index b0080ff1..83b007bf 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.4", + version="0.5", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 8a43937c..8d4fa563 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -60,3 +60,28 @@ def test_get_policy_api(self): self.assertEqual(e.get_filtered_grouping_policy(0, '', 'data2_admin'), [['alice', 'data2_admin']]) self.assertTrue(e.has_grouping_policy(['alice', 'data2_admin'])) self.assertFalse(e.has_grouping_policy(['bob', 'data2_admin'])) + + def test_modify_policy_api(self): + e = get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + # True, + ) + + self.assertEqual(e.get_policy(), [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ]) + + e.add_policy('eve', 'data3', 'read') + e.add_named_policy('p', ['eve', 'data3', 'write']) + self.assertEqual(e.get_policy(), [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ['eve', 'data3', 'read'], + ['eve', 'data3', 'write'], + ]) From 5c5de05c401a45e4626a9bf52a48474753fa3d76 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Mon, 10 Jun 2019 22:28:56 +0800 Subject: [PATCH 022/349] Update Casbin.NET in the language table. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6cf6c7f2..fe9a672a 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,10 @@ Casbin is a powerful and efficient open-source access control library for Python [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) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![dotnet](https://casbin.org/img/langs/dotnet.png)](https://github.com/Devolutions/casbin-net) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/Devolutions/casbin-rs) +[![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-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/Devolutions/casbin-rs) ----|----|----|---- -[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-Net](https://github.com/Devolutions/casbin-net) | [Casbin-RS](https://github.com/Devolutions/casbin-rs) -production-ready | experimental | WIP | WIP +[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/Devolutions/casbin-rs) +production-ready | production-ready | experimental | WIP ## Table of contents From 55b1b502de36669bab9d7c3e2a0f90bbf7deed71 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sat, 15 Jun 2019 21:29:56 +0800 Subject: [PATCH 023/349] Remove License badge in README. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index fe9a672a..d5348039 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ PyCasbin [![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) [![Pyversions](https://img.shields.io/pypi/pyversions/casbin.svg)](https://pypi.org/project/casbin/) [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) -[![License](https://img.shields.io/pypi/l/casbin.svg)](https://pypi.org/project/casbin/) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ From 618a3860fff64c4100280fbbbd520b4703f5e962 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 25 Jun 2019 21:12:36 +0800 Subject: [PATCH 024/349] Finish management API, refactor enforcer hierarchy. --- casbin/core_enforcer.py | 286 +++++++++++++++++ casbin/enforcer.py | 301 +----------------- .../{internal_api.py => internal_enforcer.py} | 9 +- ...nagement_api.py => management_enforcer.py} | 104 +++++- casbin/rbac_api.py | 11 - 5 files changed, 411 insertions(+), 300 deletions(-) create mode 100644 casbin/core_enforcer.py rename casbin/{internal_api.py => internal_enforcer.py} (90%) rename casbin/{management_api.py => management_enforcer.py} (56%) delete mode 100644 casbin/rbac_api.py diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py new file mode 100644 index 00000000..f3b42037 --- /dev/null +++ b/casbin/core_enforcer.py @@ -0,0 +1,286 @@ +from casbin import log +from casbin.persist.adapters import FileAdapter +from casbin.model import Model, FunctionMap +from casbin.rbac import default_role_manager +from casbin.util import generate_g_function, expression +from casbin.effect import DefaultEffector, Effector + + +class CoreEnforcer: + """CoreEnforcer defines the core functionality of an enforcer.""" + + model_path = "" + model = None + fm = None + eft = None + + adapter = None + watcher = None + rm = None + + enabled = False + auto_save = False + auto_build_role_links = False + + def __init__(self, model=None, adapter=None, enable_log=False): + self.enable_log(enable_log) + if isinstance(model, str): + if isinstance(adapter, str): + self.init_with_file(model, adapter) + else: + self.init_with_adapter(model, adapter) + pass + else: + if isinstance(adapter, str): + return RuntimeError("Invalid parameters for enforcer.") + else: + self.init_with_model_and_adapter(model, adapter) + + def init_with_file(self, model_path, policy_path): + """initializes an enforcer with a model file and a policy file.""" + a = FileAdapter(policy_path) + self.init_with_adapter(model_path, a) + + def init_with_adapter(self, model_path, adapter=None): + """initializes an enforcer with a database adapter.""" + m = self.new_model(model_path) + self.init_with_model_and_adapter(m, adapter) + + self.model_path = model_path + + def init_with_model_and_adapter(self, m, adapter=None): + """initializes an enforcer with a model and a database adapter.""" + self.adapter = adapter + + self.model = m + self.model.print_model() + self.fm = FunctionMap.load_function_map() + + self._initialize() + + # Do not initialize the full policy when using a filtered adapter + if self.adapter: + self.load_policy() + + def _initialize(self): + self.rm = default_role_manager.RoleManager(10) + self.eft = DefaultEffector() + self.watcher = None + + self.enabled = True + self.auto_save = True + self.auto_build_role_links = True + + @staticmethod + def new_model(path="", text=""): + """creates a model.""" + + m = Model() + if len(path) > 0: + m.load_model(path) + else: + m.load_model_from_text(text) + + return m + + def load_model(self): + """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(). + """ + + self.model = self.new_model() + self.model.load_model(self.model_path) + self.model.print_model() + self.fm = FunctionMap.load_function_map() + + def get_model(self): + """gets the current model.""" + + return self.model + + def set_model(self, m): + """sets the current model.""" + + self.model = m + self.fm = FunctionMap.load_function_map() + + def get_adapter(self): + """gets the current adapter.""" + + return self.adapter + + def set_adapter(self, adapter): + """sets the current adapter.""" + + self.adapter = adapter + + def set_watcher(self, watcher): + """sets the current watcher.""" + + self.watcher = watcher + pass + + def set_role_manager(self, rm): + """sets the current role manager.""" + + self.rm = rm + + def set_effector(self, eft): + """sets the current effector.""" + + self.eft = eft + + def clear_policy(self): + """ clears all policy.""" + + self.model.clear_policy() + + def load_policy(self): + """reloads the policy from file/database.""" + + self.model.clear_policy() + self.adapter.load_policy(self.model) + + self.model.print_policy() + if self.auto_build_role_links: + self.build_role_links() + + def load_filtered_policy(self, filter): + """reloads a filtered policy from file/database.""" + + pass + + def is_filtered(self): + """returns true if the loaded policy has been filtered.""" + + pass + + def save_policy(self): + if self.is_filtered(): + return RuntimeError("cannot save a filtered policy") + + self.adapter.save_policy(self.model) + + if self.watcher: + self.watcher.update() + + def enable_enforce(self, enabled=True): + """changes the enforcing state of Casbin, + when Casbin is disabled, all access will be allowed by the Enforce() function. + """ + + self.enabled = enabled + + def enable_log(self, enable): + """changes whether Casbin will log messages to the Logger.""" + + log.get_logger().enable_log(enable) + + def enable_auto_save(self, auto_save): + """controls whether to save a policy rule automatically to the adapter when it is added or removed.""" + self.auto_save = auto_save + + def enable_auto_build_role_links(self, auto_build_role_links): + """controls whether to rebuild the role inheritance relations when a role is added or deleted.""" + self.auto_build_role_links = auto_build_role_links + + def build_role_links(self): + """manually rebuild the role inheritance relations.""" + + self.rm.clear() + self.model.build_role_links(self.rm) + + def enforce(self, *rvals): + """decides whether a "subject" can access a "object" with the operation "action", + input parameters are usually: (sub, obj, act). + """ + + if not self.enabled: + return False + + functions = {} + for key, val in self.fm.get_functions().items(): + functions[key] = val + + if "g" in self.model.model.keys(): + for key, ast in self.model.model["g"].items(): + rm = ast.rm + functions[key] = generate_g_function(rm) + + if "m" not in self.model.model.keys(): + return RuntimeError("model is undefined") + + if "m" not in self.model.model["m"].keys(): + return RuntimeError("model is undefined") + + exp_string = self.model.model["m"]["m"].value + + policy_effects = [] + matcher_results = [] + + policy_len = len(self.model.model["p"]["p"].policy) + + if not 0 == policy_len: + for i, pvals in enumerate(self.model.model["p"]["p"].policy): + parameters = dict() + for j, token in enumerate(self.model.model["r"]["r"].tokens): + parameters[token] = rvals[j] + + for j, token in enumerate(self.model.model["p"]["p"].tokens): + parameters[token] = pvals[j] + + result = expression.evaluate(exp_string, parameters, functions) + + if isinstance(result, bool): + if not result: + policy_effects.append(Effector.INDETERMINATE) + continue + elif isinstance(result, float): + if 0 == result: + policy_effects.append(Effector.INDETERMINATE) + continue + else: + matcher_results.append(result) + else: + raise RuntimeError("matcher result should be bool, int or float") + + if "p_eft" in parameters.keys(): + eft = parameters["p_eft"] + if "allow" == eft: + policy_effects.append(Effector.ALLOW) + elif "deny" == eft: + policy_effects.append(Effector.DENY) + else: + policy_effects.append(Effector.INDETERMINATE) + else: + policy_effects.append(Effector.ALLOW) + + if "priority(p_eft) || deny" == self.model.model["e"]["e"].value: + break + + else: + parameters = dict() + for j, token in enumerate(self.model.model["r"]["r"].tokens): + parameters[token] = rvals[j] + + for token in self.model.model["p"]["p"].tokens: + parameters[token] = "" + + result = expression.evaluate(exp_string, parameters, functions) + + if result: + policy_effects.append(Effector.ALLOW) + else: + policy_effects.append(Effector.INDETERMINATE) + + result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results) + + # Log request. + if log.get_logger().is_enabled(): + req_str = "Request: " + req_str = req_str + ", ".join([str(v) for v in rvals]) + + req_str = req_str + " ---> %s" % result + log.log_print(req_str) + + return result diff --git a/casbin/enforcer.py b/casbin/enforcer.py index d0ede360..41913121 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,295 +1,24 @@ -from casbin import log -from casbin.persist.adapters import FileAdapter -from casbin.model import Model, FunctionMap -from casbin.rbac import default_role_manager -from casbin.util import generate_g_function, expression -from casbin.effect import DefaultEffector, Effector -from casbin.rbac_api import RbacApi +from casbin.management_enforcer import ManagementEnforcer -class Enforcer(RbacApi): +class Enforcer(ManagementEnforcer): + """ + Enforcer = ManagementEnforcer + RBAC + """ + """creates an enforcer via file or DB. - Uses: File: - e = casbin.Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") + e = casbin.Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv") MySQL DB: - a = mysqladapter.DBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") - e = casbin.Enforcer("path/to/basic_model.conf", a) + a = mysqladapter.DBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") + e = casbin.Enforcer("path/to/basic_model.conf", a) """ - model_path = "" - model = None - fm = None - eft = None - - adapter = None - watcher = None - rm = None - - enabled = False - auto_save = False - auto_build_role_links = False - - def __init__(self, model=None, adapter=None, enable_log=False): - self.enable_log(enable_log) - if isinstance(model, str): - if isinstance(adapter, str): - self.init_with_file(model, adapter) - else: - self.init_with_adapter(model, adapter) - pass - else: - if isinstance(adapter, str): - return RuntimeError("Invalid parameters for enforcer.") - else: - self.init_with_model_and_adapter(model, adapter) - - def init_with_file(self, model_path, policy_path): - """initializes an enforcer with a model file and a policy file.""" - a = FileAdapter(policy_path) - self.init_with_adapter(model_path, a) - - def init_with_adapter(self, model_path, adapter=None): - """initializes an enforcer with a database adapter.""" - m = self.new_model(model_path) - self.init_with_model_and_adapter(m, adapter) - - self.model_path = model_path - - def init_with_model_and_adapter(self, m, adapter=None): - """initializes an enforcer with a model and a database adapter.""" - self.adapter = adapter - - self.model = m - self.model.print_model() - self.fm = FunctionMap.load_function_map() - - self._initialize() - - # Do not initialize the full policy when using a filtered adapter - if self.adapter: - self.load_policy() - - def _initialize(self): - self.rm = default_role_manager.RoleManager(10) - self.eft = DefaultEffector() - self.watcher = None - - self.enabled = True - self.auto_save = True - self.auto_build_role_links = True - - @staticmethod - def new_model(path="", text=""): - """creates a model.""" - - m = Model() - if len(path) > 0: - m.load_model(path) - else: - m.load_model_from_text(text) - - return m - - def load_model(self): - """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(). - """ - - self.model = self.new_model() - self.model.load_model(self.model_path) - self.model.print_model() - self.fm = FunctionMap.load_function_map() - - def get_model(self): - """gets the current model.""" - - return self.model - - def set_model(self, m): - """sets the current model.""" - - self.model = m - self.fm = FunctionMap.load_function_map() - - def get_adapter(self): - """gets the current adapter.""" - - return self.adapter - - def set_adapter(self, adapter): - """sets the current adapter.""" - - self.adapter = adapter - - def set_watcher(self, watcher): - """sets the current watcher.""" - - self.watcher = watcher - pass - - def set_role_manager(self, rm): - """sets the current role manager.""" - - self.rm = rm - - def set_effector(self, eft): - """sets the current effector.""" - - self.eft = eft - - def clear_policy(self): - """ clears all policy.""" - - self.model.clear_policy() - - def load_policy(self): - """reloads the policy from file/database.""" - - self.model.clear_policy() - self.adapter.load_policy(self.model) - - self.model.print_policy() - if self.auto_build_role_links: - self.build_role_links() - - def load_filtered_policy(self, filter): - """reloads a filtered policy from file/database.""" - - pass - - def is_filtered(self): - """returns true if the loaded policy has been filtered.""" - - pass - - def save_policy(self): - if self.is_filtered(): - return RuntimeError("cannot save a filtered policy") - - self.adapter.save_policy(self.model) - - if self.watcher: - self.watcher.update() - - def enable_enforce(self, enabled=True): - """changes the enforcing state of Casbin, - when Casbin is disabled, all access will be allowed by the Enforce() function. - """ - - self.enabled = enabled - - def enable_log(self, enable): - """changes whether Casbin will log messages to the Logger.""" - - log.get_logger().enable_log(enable) - - def enable_auto_save(self, auto_save): - """controls whether to save a policy rule automatically to the adapter when it is added or removed.""" - self.auto_save = auto_save - - def enable_auto_build_role_links(self, auto_build_role_links): - """controls whether to rebuild the role inheritance relations when a role is added or deleted.""" - self.auto_build_role_links = auto_build_role_links - - def build_role_links(self): - """manually rebuild the role inheritance relations.""" - - self.rm.clear() - self.model.build_role_links(self.rm) - - def enforce(self, *rvals): - """decides whether a "subject" can access a "object" with the operation "action", - input parameters are usually: (sub, obj, act). - """ - - if not self.enabled: - return False - - functions = {} - for key, val in self.fm.get_functions().items(): - functions[key] = val - - if "g" in self.model.model.keys(): - for key, ast in self.model.model["g"].items(): - rm = ast.rm - functions[key] = generate_g_function(rm) - - if "m" not in self.model.model.keys(): - return RuntimeError("model is undefined") - - if "m" not in self.model.model["m"].keys(): - return RuntimeError("model is undefined") - - exp_string = self.model.model["m"]["m"].value - - policy_effects = [] - matcher_results = [] - - policy_len = len(self.model.model["p"]["p"].policy) - - if not 0 == policy_len: - for i, pvals in enumerate(self.model.model["p"]["p"].policy): - parameters = dict() - for j, token in enumerate(self.model.model["r"]["r"].tokens): - parameters[token] = rvals[j] - - for j, token in enumerate(self.model.model["p"]["p"].tokens): - parameters[token] = pvals[j] - - result = expression.evaluate(exp_string, parameters, functions) - - if isinstance(result, bool): - if not result: - policy_effects.append(Effector.INDETERMINATE) - continue - elif isinstance(result, float): - if 0 == result: - policy_effects.append(Effector.INDETERMINATE) - continue - else: - matcher_results.append(result) - else: - raise RuntimeError("matcher result should be bool, int or float") - - if "p_eft" in parameters.keys(): - eft = parameters["p_eft"] - if "allow" == eft: - policy_effects.append(Effector.ALLOW) - elif "deny" == eft: - policy_effects.append(Effector.DENY) - else: - policy_effects.append(Effector.INDETERMINATE) - else: - policy_effects.append(Effector.ALLOW) - - if "priority(p_eft) || deny" == self.model.model["e"]["e"].value: - break - - else: - parameters = dict() - for j, token in enumerate(self.model.model["r"]["r"].tokens): - parameters[token] = rvals[j] - - for token in self.model.model["p"]["p"].tokens: - parameters[token] = "" - - result = expression.evaluate(exp_string, parameters, functions) - - if result: - policy_effects.append(Effector.ALLOW) - else: - policy_effects.append(Effector.INDETERMINATE) - - result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results) - - # Log request. - if log.get_logger().is_enabled(): - req_str = "Request: " - req_str = req_str + ", ".join([str(v) for v in rvals]) - - req_str = req_str + " ---> %s" % result - log.log_print(req_str) + def get_roles_for_user(self, name): + """gets the roles that a user has.""" + return self.model.model['g']['g'].rm.get_roles(name) - return result + def get_users_for_role(self, name): + """gets the users that has a role.""" + return self.model.model['g']['g'].rm.get_users(name) diff --git a/casbin/internal_api.py b/casbin/internal_enforcer.py similarity index 90% rename from casbin/internal_api.py rename to casbin/internal_enforcer.py index 89f6ecb5..ac87b3b1 100644 --- a/casbin/internal_api.py +++ b/casbin/internal_enforcer.py @@ -1,4 +1,11 @@ -class InternalApi: + +from casbin.core_enforcer import CoreEnforcer + + +class InternalEnforcer(CoreEnforcer): + """ + InternalEnforcer = CoreEnforcer + Internal API. + """ def _add_policy(self, sec, ptype, rule): """adds a rule to the current policy.""" diff --git a/casbin/management_api.py b/casbin/management_enforcer.py similarity index 56% rename from casbin/management_api.py rename to casbin/management_enforcer.py index 127fa707..4dbd58dd 100644 --- a/casbin/management_api.py +++ b/casbin/management_enforcer.py @@ -1,7 +1,11 @@ -from casbin.internal_api import InternalApi +from casbin.internal_enforcer import InternalEnforcer -class ManagementApi(InternalApi): +class ManagementEnforcer(InternalEnforcer): + """ + ManagementEnforcer = InternalEnforcer + Management API. + """ + def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" return self.get_all_named_subjects('p') @@ -111,6 +115,34 @@ def add_named_policy(self, ptype, *params): return rule_added + def remove_policy(self, *params): + """removes an authorization rule from the current policy.""" + return self.remove_named_policy('p', *params) + + def remove_filtered_policy(self, field_index, *field_values): + """removes an authorization rule from the current policy, field filters can be specified.""" + return self.remove_filtered_named_policy('p', field_index, *field_values) + + def remove_named_policy(self, ptype, *params): + """removes an authorization rule from the current named policy.""" + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_removed = self._remove_policy('p', ptype, str_slice) + else: + policy = [] + + for param in params: + policy.append(param) + + rule_removed = self._remove_policy('p', ptype, policy) + + return rule_removed + + def remove_filtered_named_policy(self, ptype, field_index, *field_values): + """removes an authorization rule from the current named policy, field filters can be specified.""" + return self._remove_filtered_policy('p', ptype, field_index, *field_values) + def has_grouping_policy(self, *params): """determines whether a role inheritance rule exists.""" @@ -129,3 +161,71 @@ def has_named_grouping_policy(self, ptype, *params): policy.append(param) return self.model.has_policy('g', ptype, policy) + + def add_grouping_policy(self, *params): + """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. + """ + return self.add_named_grouping_policy('p', *params) + + def add_named_grouping_policy(self, ptype, *params): + """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. + """ + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_added = self._add_policy('g', ptype, str_slice) + else: + policy = [] + + for param in params: + policy.append(param) + + rule_added = self._add_policy('g', ptype, policy) + + if self.auto_build_role_links: + self.build_role_links() + return rule_added + + def remove_grouping_policy(self, *params): + """removes a role inheritance rule from the current policy.""" + return self.remove_named_grouping_policy('g', *params) + + def remove_filtered_grouping_policy(self, field_index, *field_values): + """removes a role inheritance rule from the current policy, field filters can be specified.""" + return self.remove_filtered_named_grouping_policy('g', field_index, *field_values) + + def remove_named_grouping_policy(self, ptype, *params): + """removes a role inheritance rule from the current named policy.""" + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_removed = self._remove_policy('g', ptype, str_slice) + else: + policy = [] + + for param in params: + policy.append(param) + + rule_removed = self._remove_policy('g', ptype, policy) + + if self.auto_build_role_links: + self.build_role_links() + return rule_removed + + def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """removes a role inheritance rule from the current named policy, field filters can be specified.""" + rule_removed = self._remove_filtered_policy('g', ptype, field_index, *field_values) + + if self.auto_build_role_links: + self.build_role_links() + return rule_removed + + def add_function(self, name, func): + """adds a customized function.""" + self.fm.add_function(name, func) diff --git a/casbin/rbac_api.py b/casbin/rbac_api.py deleted file mode 100644 index 7cf68c41..00000000 --- a/casbin/rbac_api.py +++ /dev/null @@ -1,11 +0,0 @@ -from casbin.management_api import ManagementApi - - -class RbacApi(ManagementApi): - def get_roles_for_user(self, name): - """gets the roles that a user has.""" - return self.model.model['g']['g'].rm.get_roles(name) - - def get_users_for_role(self, name): - """gets the users that has a role.""" - return self.model.model['g']['g'].rm.get_users(name) From 110d14310fca63fa6995444db45209e9d6979de1 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 25 Jun 2019 21:32:59 +0800 Subject: [PATCH 025/349] Add some RBAC API functions. --- casbin/enforcer.py | 71 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 41913121..5c812b1b 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -15,10 +15,73 @@ class Enforcer(ManagementEnforcer): e = casbin.Enforcer("path/to/basic_model.conf", a) """ - def get_roles_for_user(self, name): + def get_roles_for_user(self, user): """gets the roles that a user has.""" - return self.model.model['g']['g'].rm.get_roles(name) + return self.model.model['g']['g'].rm.get_roles(user) - def get_users_for_role(self, name): + def get_users_for_role(self, role): """gets the users that has a role.""" - return self.model.model['g']['g'].rm.get_users(name) + return self.model.model['g']['g'].rm.get_users(role) + + def has_role_for_user(self, user, role): + """determines whether a user has a role.""" + roles = self.get_roles_for_user(user) + + for r in roles: + if r == role: + return True + + return False + + def add_role_for_user(self, user, role): + """adds a role for a user.""" + """Returns false if the user already has the role (aka not affected).""" + return self.add_grouping_policy(user, role) + + def delete_role_for_user(self, user, role): + """deletes a role for a user.""" + """Returns false if the user does not have the role (aka not affected).""" + return self.remove_grouping_policy(user, role) + + def delete_roles_for_user(self, user): + """deletes all roles for a user.""" + """Returns false if the user does not have any roles (aka not affected).""" + return self.remove_filtered_grouping_policy(0, user) + + def delete_user(self, user): + """deletes a user.""" + """Returns false if the user does not exist (aka not affected).""" + return self.remove_filtered_grouping_policy(0, user) + + def delete_role(self, role): + """deletes a role.""" + self.remove_filtered_grouping_policy(1, role) + self.remove_filtered_policy(0, role) + + def delete_permission(self, *permission): + """deletes a permission.""" + """Returns false if the permission does not exist (aka not affected).""" + return self.remove_filtered_policy(1, *permission) + + def add_permission_for_user(self, user, *permission): + """adds a permission for a user or role.""" + """Returns false if the user or role already has the permission (aka not affected).""" + return self.add_policy(*permission) + + def delete_permission_for_user(self, user, *permission): + """adds a permission for a user or role.""" + """Returns false if the user or role already has the permission (aka not affected).""" + return self.remove_policy(*permission) + + def delete_permissions_for_user(self, user): + """deletes permissions for a user or role.""" + """Returns false if the user or role does not have any permissions (aka not affected).""" + return self.remove_filtered_policy(0, user) + + def get_permissions_for_user(self, user): + """gets permissions for a user or role.""" + return self.get_filtered_policy(0, user) + + def has_permission_for_user(self, user, *permission): + """determines whether a user has a permission.""" + return self.has_policy(*permission) From 0e4c30f8c12b242114a40b75b9f860ec7898e9d9 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sat, 29 Jun 2019 23:02:25 +0800 Subject: [PATCH 026/349] Add Open Collective to README. --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index d5348039..c4a47359 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,32 @@ Authz middlewares for web frameworks: https://casbin.org/docs/en/middlewares 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). From 89c6a8393e9339db9f20e673e00df44b68a5fe9f Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Wed, 3 Jul 2019 13:39:43 +0800 Subject: [PATCH 027/349] Bump to version 0.6.0 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 83b007bf..e7915d6e 100644 --- a/setup.py +++ b/setup.py @@ -7,14 +7,14 @@ setuptools.setup( name="casbin", - version="0.5", + version="0.6.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/casbin/pycasbin", - keywords=["casbin", "rbac", "access control", "abac", "acl", "permission"], + keywords=["casbin", "acl", "rbac", "abac", "auth", "authz", "authorization", "access control", "permission"], packages=setuptools.find_packages(), install_requires=['simpleeval>=0.9.8'], python_requires=">=3.3", From 9348bfede296b5550ea534026ba5e95dec6c7e4f Mon Sep 17 00:00:00 2001 From: Huang Yan Date: Wed, 3 Jul 2019 14:47:47 +0800 Subject: [PATCH 028/349] Fix global logging settings. --- casbin/log/default_logger.py | 14 ++++++++++---- casbin/log/log.py | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/casbin/log/default_logger.py b/casbin/log/default_logger.py index ad421565..d9ce7ceb 100644 --- a/casbin/log/default_logger.py +++ b/casbin/log/default_logger.py @@ -1,13 +1,19 @@ from .logger import Logger import logging -logging.basicConfig(level=logging.NOTSET, format="%(asctime)s - %(levelname)s - %(message)s") - class DefaultLogger(Logger): """the implementation for a Logger using logging.""" enable = False + def __init__(self): + self.logger = logging.getLogger('casbin') + self.logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + fmt = "%(asctime)s - %(levelname)s - %(message)s" + formatter = logging.Formatter(fmt) + handler.setFormatter(formatter) + self.logger.addHandler(handler) def enable_log(self, enable): """controls whether print the message.""" @@ -23,9 +29,9 @@ def write(self, *v): s = "" for vv in v: s = s + str(vv) - logging.info(s) + self.logger.info(s) def writef(self, fmt, *v): """formats according to a format specifier and logs the message.""" if self.enable: - logging.info(fmt, *v) + self.logger.info(fmt, *v) diff --git a/casbin/log/log.py b/casbin/log/log.py index 9acb70a6..b029a22e 100644 --- a/casbin/log/log.py +++ b/casbin/log/log.py @@ -5,6 +5,7 @@ def set_logger(l): """sets the current logger.""" + global logger logger = l From 56b1c559ddfc4381ad3a76b7f7d299ef2f90626f Mon Sep 17 00:00:00 2001 From: Foufou Date: Thu, 4 Jul 2019 11:38:56 +0800 Subject: [PATCH 029/349] Bump to version 0.6.1 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e7915d6e..2a18ce9b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.6.0", + version="0.6.1", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From b6cff0f5ac670b8fd9a383d2a73a263c815e09be Mon Sep 17 00:00:00 2001 From: Stanislav Lobanov Date: Thu, 11 Jul 2019 14:48:59 +0300 Subject: [PATCH 030/349] Efficient implementation for array_remove_duplicates utility function. --- casbin/util/util.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/casbin/util/util.py b/casbin/util/util.py index e9b13726..eb001b20 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -1,3 +1,6 @@ +from collections import OrderedDict + + def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" @@ -19,15 +22,7 @@ def remove_comments(s): def array_remove_duplicates(s): """removes any duplicated elements in a string array.""" - found = dict() - j = 0 - for x in s: - if x not in found.keys(): - found[x] = True - s[j] = x - j = j + 1 - - return s[:j] + return list(OrderedDict.fromkeys(s)) def array_to_string(s): From 74521098eb2bf4b50d71785c8943083dcce4fb3e Mon Sep 17 00:00:00 2001 From: Stanislav Lobanov Date: Thu, 11 Jul 2019 15:00:03 +0300 Subject: [PATCH 031/349] Pre-compile regular expressions to save performance. Regexp compilation is cpu intensive task. --- casbin/util/builtin_operators.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 3c7a861e..694e9e67 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -2,6 +2,10 @@ import ipaddress +KEY_MATCH2_PATTERN = re.compile(r'(.*):[^\/]+(.*)') +KEY_MATCH3_PATTERN = re.compile(r'(.*){[^\/]+}(.*)') + + def key_match(key1, key2): """determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. For example, "/foo/bar" matches "/foo/*" @@ -32,12 +36,11 @@ def key_match2(key1, key2): key2 = key2.replace("/*", "/.*") - pattern = re.compile(r'(.*):[^\/]+(.*)') while True: if "/:" not in key2: break - key2 = "^" + pattern.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + "$" + key2 = "^" + KEY_MATCH2_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + "$" return regex_match(key1, key2) @@ -56,12 +59,11 @@ def key_match3(key1, key2): key2 = key2.replace("/*", "/.*") - pattern = re.compile(r'(.*){[^\/]+}(.*)') while True: if "{" not in key2: break - key2 = pattern.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + key2 = KEY_MATCH3_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) return regex_match(key1, key2) From 3ab69825541498b03cb38712deab776a9499bb0a Mon Sep 17 00:00:00 2001 From: TechLee Date: Mon, 15 Jul 2019 11:38:25 +0800 Subject: [PATCH 032/349] Added the remove_policy function and optimized policy related code. --- casbin/model/policy.py | 27 ++++++++++++++++++---- tests/model/test_policy.py | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 tests/model/test_policy.py diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 438c75bc..920bafe7 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -6,6 +6,8 @@ def __init__(self): self.model = {} def build_role_links(self, rm): + """initializes the roles in RBAC.""" + if "g" not in self.model.keys(): return @@ -13,6 +15,8 @@ def build_role_links(self, rm): ast.build_role_links(rm) def print_policy(self): + """prints the policy to log.""" + log.log_print("Policy:") for sec in ["p", "g"]: if sec not in self.model.keys(): @@ -22,6 +26,8 @@ def print_policy(self): log.log_print(key, ": ", ast.value, ": ", ast.policy) def clear_policy(self): + """clears all current policy.""" + for sec in ["p", "g"]: if sec not in self.model.keys(): continue @@ -30,6 +36,8 @@ def clear_policy(self): self.model[sec][key].policy = [] def get_policy(self, sec, ptype): + """gets all rules in a policy.""" + return self.model[sec][ptype].policy def get_filtered_policy(self, sec, ptype, field_index, *field_values): @@ -55,11 +63,7 @@ def has_policy(self, sec, ptype, rule): if ptype not in self.model[sec]: return False - for r in self.model[sec][ptype].policy: - if rule == r: - return True - - return False + return rule in self.model[sec][ptype].policy def add_policy(self, sec, ptype, rule): """adds a policy rule to the model.""" @@ -70,6 +74,19 @@ def add_policy(self, sec, ptype, rule): return False + def remove_policy(self, sec, ptype, rule): + """removePolicy removes a policy rule from the model.""" + + if sec not in self.model.keys(): + return False + if ptype not in self.model[sec]: + return False + + if not self.has_policy(sec, ptype, rule): + return False + + return self.model[sec][ptype].policy.remove(rule) + def get_values_for_field_in_policy(self, sec, ptype, field_index): """gets all values for a field for all rules in a policy, duplicated values are removed.""" diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py new file mode 100644 index 00000000..c46629b9 --- /dev/null +++ b/tests/model/test_policy.py @@ -0,0 +1,47 @@ +from unittest import TestCase +from casbin.model import Model +from tests.test_enforcer import get_examples + + +class TestPolicy(TestCase): + def test_get_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + + m.add_policy('p', 'p', rule) + + self.assertTrue(m.get_policy('p', 'p') == [rule]) + + def test_has_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + m.add_policy('p', 'p', rule) + + self.assertTrue(m.has_policy('p', 'p', rule)) + + def test_add_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + + self.assertFalse(m.has_policy('p', 'p', rule)) + + m.add_policy('p', 'p', rule) + self.assertTrue(m.has_policy('p', 'p', rule)) + + def test_remove_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + m.add_policy('p', 'p', rule) + self.assertTrue(m.has_policy('p', 'p', rule)) + + m.remove_policy('p', 'p', rule) + self.assertFalse(m.has_policy('p', 'p', rule)) + self.assertFalse(m.remove_policy('p', 'p', rule)) From ff31bb38b0ebe32da654ea7018510e84acb6d7f6 Mon Sep 17 00:00:00 2001 From: Foufou Date: Mon, 15 Jul 2019 11:57:59 +0800 Subject: [PATCH 033/349] Bump to version 0.7.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2a18ce9b..0690c6b2 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.6.1", + version="0.7.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 9159b240463f26ac48701d7525573e258aef6750 Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Nam Date: Thu, 18 Jul 2019 23:37:48 +0700 Subject: [PATCH 034/349] Change class attribute to instance attribute --- casbin/model/assertion.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index f7253a30..77aac9c8 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -2,11 +2,12 @@ class Assertion: - key = "" - value = "" - tokens = [] - policy = [] - rm = None + def __init__(self): + self.key = "" + self.value = "" + self.tokens = [] + self.policy = [] + self.rm = None def build_role_links(self, rm): self.rm = rm From 40ab1af9738a2eeed1f387fac1bd948f51a376cc Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Nam Date: Fri, 19 Jul 2019 10:49:17 +0700 Subject: [PATCH 035/349] Modularize tests code --- tests/__init__.py | 0 tests/config/__init__.py | 0 tests/log/__init__.py | 0 tests/model/__init__.py | 0 tests/util/__init__.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/config/__init__.py create mode 100644 tests/log/__init__.py create mode 100644 tests/model/__init__.py create mode 100644 tests/util/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/__init__.py b/tests/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/log/__init__.py b/tests/log/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/model/__init__.py b/tests/model/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/util/__init__.py b/tests/util/__init__.py new file mode 100644 index 00000000..e69de29b From a6d2df8e9c944e50a3111ecd1787994bda781391 Mon Sep 17 00:00:00 2001 From: Nguyen Hoang Nam Date: Fri, 19 Jul 2019 10:56:07 +0700 Subject: [PATCH 036/349] Add test role policy --- tests/model/test_policy.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index c46629b9..30282adf 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -45,3 +45,22 @@ def test_remove_policy(self): m.remove_policy('p', 'p', rule) self.assertFalse(m.has_policy('p', 'p', rule)) self.assertFalse(m.remove_policy('p', 'p', rule)) + + def test_add_role_policy(self): + m = Model() + m.load_model(get_examples("rbac_model.conf")) + + p_rule1 = ['alice', 'data1', 'read'] + m.add_policy('p', 'p', p_rule1) + self.assertTrue(m.has_policy('p', 'p', p_rule1)) + + p_rule2 = ['data2_admin', 'data2', 'read'] + m.add_policy('p', 'p', p_rule2) + self.assertTrue(m.has_policy('p', 'p', p_rule2)) + + g_rule = ['alice', 'data2_admin'] + m.add_policy('g', 'g', g_rule) + self.assertTrue(m.has_policy('g', 'g', g_rule)) + + self.assertTrue(m.get_policy('p', 'p') == [p_rule1, p_rule2]) + self.assertTrue(m.get_policy('g', 'g') == [g_rule]) \ No newline at end of file From cc31523fdc52c11085b0f76fffd8c559e9d58a03 Mon Sep 17 00:00:00 2001 From: Foufou Date: Fri, 19 Jul 2019 19:11:10 +0800 Subject: [PATCH 037/349] Bump to version 0.7.1. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 0690c6b2..33a66bb4 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.7.0", + version="0.7.1", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 7220237f6601e41df741d16be6273b36b96b1c25 Mon Sep 17 00:00:00 2001 From: Huang Yan Date: Sat, 20 Jul 2019 11:25:30 +0800 Subject: [PATCH 038/349] Implicit GetImplicitRolesForUser and GetImplicitPermissionsForUser --- casbin/enforcer.py | 69 +++++++++++++++++++ ...bac_with_hierarchy_with_domains_policy.csv | 11 +++ tests/test_enforcer.py | 57 +++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 examples/rbac_with_hierarchy_with_domains_policy.csv diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 5c812b1b..a4fccbdd 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,4 +1,6 @@ +import copy from casbin.management_enforcer import ManagementEnforcer +from functools import reduce class Enforcer(ManagementEnforcer): @@ -19,10 +21,21 @@ def get_roles_for_user(self, user): """gets the roles that a user has.""" return self.model.model['g']['g'].rm.get_roles(user) + def get_roles_for_user_in_domain(self, user, domain): + """gets the roles that a user has inside a domain.""" + res = self.model.model['g']['g'].rm.get_roles(user, domain) + return [] if isinstance(res, RuntimeError) else [r.replace(domain + '::', '') for r in res] + def get_users_for_role(self, role): """gets the users that has a role.""" return self.model.model['g']['g'].rm.get_users(role) + def get_users_for_role_in_domain(self, role, domain): + """gets the users that has a role inside a domain.""" + _role = domain + '::' + role + res = self.model.model['g']['g'].rm.get_users(_role, domain) + return [] if isinstance(res, RuntimeError) else [r.replace(domain + '::', '') for r in res] + def has_role_for_user(self, user, role): """determines whether a user has a role.""" roles = self.get_roles_for_user(user) @@ -38,6 +51,12 @@ def add_role_for_user(self, user, role): """Returns false if the user already has the role (aka not affected).""" return self.add_grouping_policy(user, role) + def add_role_for_user_in_domain(self, user, role, domain): + """adds a role for a user inside a domain.""" + """Returns false if the user already has the role (aka not affected).""" + raise NotImplementedError + return self.add_grouping_policy(user, role, domain) + def delete_role_for_user(self, user, role): """deletes a role for a user.""" """Returns false if the user does not have the role (aka not affected).""" @@ -48,6 +67,12 @@ def delete_roles_for_user(self, user): """Returns false if the user does not have any roles (aka not affected).""" return self.remove_filtered_grouping_policy(0, user) + def delete_roles_for_user_in_domain(self, user, role, domain): + """deletes a role for a user inside a domain.""" + """Returns false if the user does not have any roles (aka not affected).""" + raise NotImplementedError + return self.remove_filtered_grouping_policy(0, user, role, domain) + def delete_user(self, user): """deletes a user.""" """Returns false if the user does not exist (aka not affected).""" @@ -82,6 +107,50 @@ def get_permissions_for_user(self, user): """gets permissions for a user or role.""" return self.get_filtered_policy(0, user) + def get_permissions_for_user_in_domain(self, user, domain): + """gets permissions for a user or role inside domain.""" + return self.get_filtered_policy(0, user, domain) + def has_permission_for_user(self, user, *permission): """determines whether a user has a permission.""" return self.has_policy(*permission) + + def get_implicit_roles_for_user(self, user, domain=None): + """ + get_implicit_roles_for_user gets implicit roles that a user has. + Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. + For example: + g, alice, role:admin + g, role:admin, role:user + + get_roles_for_user("alice") can only get: ["role:admin"]. + But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. + """ + roles = self.get_roles_for_user_in_domain(user, domain) if domain else self.get_roles_for_user(user) + res = copy.copy(roles) + for r in roles: + _roles = self.get_roles_for_user_in_domain(r, domain) if domain else self.get_roles_for_user(r) + res.extend(_roles) + return res + + + def get_implicit_permissions_for_user(self, user, domain=None): + """ + gets implicit permissions for a user or role. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + """ + roles = self.get_implicit_roles_for_user(user, domain) + permissions = self.get_permissions_for_user_in_domain(user, domain) if domain else self.get_permissions_for_user(user) + for role in roles: + _permissions = self.get_permissions_for_user_in_domain(role, domain) if domain else self.get_permissions_for_user(role) + for item in _permissions: + if item not in permissions: + permissions.append(item) + return permissions diff --git a/examples/rbac_with_hierarchy_with_domains_policy.csv b/examples/rbac_with_hierarchy_with_domains_policy.csv new file mode 100644 index 00000000..45d91739 --- /dev/null +++ b/examples/rbac_with_hierarchy_with_domains_policy.csv @@ -0,0 +1,11 @@ +p, 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, domain1 + diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index b1dab828..71e377f2 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -146,3 +146,60 @@ def test_enforce_abac_log_enabled(self): sub = 'alice' obj = {'Owner': 'alice', 'id': 'data1'} self.assertTrue(e.enforce(sub, obj, 'write')) + + def test_enforce_implicit_roles_api(self): + e = get_enforcer(get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv")) + + self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) + self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) + + self.assertTrue(e.get_implicit_roles_for_user('alice') == ['admin', 'data1_admin', 'data2_admin']) + self.assertTrue(e.get_implicit_roles_for_user('bob') == []) + + def test_enforce_implicit_roles_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv")) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) + self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) + + def test_enforce_implicit_permissions_api(self): + e = get_enforcer(get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv")) + self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) + self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) + self.assertTrue(e.get_implicit_permissions_for_user('alice') == [ + ['alice', 'data1', 'read'], + ['data1_admin', 'data1', 'read'], + ['data1_admin', 'data1', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write']]) + self.assertTrue(e.get_implicit_permissions_for_user('bob') == [["bob", "data2", "write"]]) + + def test_enforce_implicit_permissions_api_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv")) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) + self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == + ['role:global_admin', 'role:reader', 'role:writer']) + self.assertTrue(e.get_implicit_permissions_for_user('alice', 'domain1') == [ + ['alice', 'domain1', 'data2', 'read'], + ["role:reader", "domain1", "data1", "read"], + ["role:writer", "domain1", "data1", "write"]]) + self.assertTrue(e.get_implicit_permissions_for_user('bob', 'domain1') == []) + + def test_enforce_get_users_in_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + # e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + # e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + # self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) + # self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + # self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + # self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) From ad38121c7e6ed19479857a286ed6887e755cf32c Mon Sep 17 00:00:00 2001 From: TechLee Date: Wed, 7 Aug 2019 13:22:58 +0800 Subject: [PATCH 039/349] Specify the encoding as utf-8, when reading the configuration file. --- casbin/config/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/config/config.py b/casbin/config/config.py index c477cbfc..8bf638c3 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -41,7 +41,7 @@ def add_config(self, section, option, value): self._data[section][option] = value def _parse(self, fname): - with open(fname, 'r') as f: + with open(fname, 'r', encoding='utf-8') as f: self._parse_buffer(f) def _parse_buffer(self, f): From 700344539fe0708d92892ac9ce7da4e55091a58a Mon Sep 17 00:00:00 2001 From: TechLee Date: Wed, 7 Aug 2019 14:04:40 +0800 Subject: [PATCH 040/349] Added remove_filtered_policy() to Policy. --- casbin/model/policy.py | 28 +++++++++++++++++++++++++- tests/model/test_policy.py | 41 +++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 920bafe7..b82040a4 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -75,7 +75,7 @@ def add_policy(self, sec, ptype, rule): return False def remove_policy(self, sec, ptype, rule): - """removePolicy removes a policy rule from the model.""" + """removes a policy rule from the model.""" if sec not in self.model.keys(): return False @@ -87,6 +87,32 @@ def remove_policy(self, sec, ptype, rule): return self.model[sec][ptype].policy.remove(rule) + def remove_filtered_policy(self, sec, ptype, field_index, *field_values): + """removes policy rules based on field filters from the model.""" + tmp = [] + res = False + + if sec not in self.model.keys(): + return res + if ptype not in self.model[sec]: + return res + + for rule in self.model[sec][ptype].policy: + matched = True + for i, field_value in enumerate(field_values): + if field_value != '' and rule[field_index + i] != field_value: + matched = False + break + + if matched: + res = True + else: + tmp.append(rule) + + self.model[sec][ptype].policy = tmp + + return res + def get_values_for_field_in_policy(self, sec, ptype, field_index): """gets all values for a field for all rules in a policy, duplicated values are removed.""" diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index 30282adf..a39b7db2 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -34,18 +34,6 @@ def test_add_policy(self): m.add_policy('p', 'p', rule) self.assertTrue(m.has_policy('p', 'p', rule)) - def test_remove_policy(self): - m = Model() - m.load_model(get_examples("basic_model.conf")) - - rule = ['admin', 'domain1', 'data1', 'read'] - m.add_policy('p', 'p', rule) - self.assertTrue(m.has_policy('p', 'p', rule)) - - m.remove_policy('p', 'p', rule) - self.assertFalse(m.has_policy('p', 'p', rule)) - self.assertFalse(m.remove_policy('p', 'p', rule)) - def test_add_role_policy(self): m = Model() m.load_model(get_examples("rbac_model.conf")) @@ -53,7 +41,7 @@ def test_add_role_policy(self): p_rule1 = ['alice', 'data1', 'read'] m.add_policy('p', 'p', p_rule1) self.assertTrue(m.has_policy('p', 'p', p_rule1)) - + p_rule2 = ['data2_admin', 'data2', 'read'] m.add_policy('p', 'p', p_rule2) self.assertTrue(m.has_policy('p', 'p', p_rule2)) @@ -63,4 +51,29 @@ def test_add_role_policy(self): self.assertTrue(m.has_policy('g', 'g', g_rule)) self.assertTrue(m.get_policy('p', 'p') == [p_rule1, p_rule2]) - self.assertTrue(m.get_policy('g', 'g') == [g_rule]) \ No newline at end of file + self.assertTrue(m.get_policy('g', 'g') == [g_rule]) + + def test_remove_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + m.add_policy('p', 'p', rule) + self.assertTrue(m.has_policy('p', 'p', rule)) + + m.remove_policy('p', 'p', rule) + self.assertFalse(m.has_policy('p', 'p', rule)) + self.assertFalse(m.remove_policy('p', 'p', rule)) + + def test_remove_filtered_policy(self): + m = Model() + m.load_model(get_examples("rbac_with_domains_model.conf")) + + rule = ['admin', 'domain1', 'data1', 'read'] + m.add_policy('p', 'p', rule) + + res = m.remove_filtered_policy('p', 'p', 1, 'domain1', 'data1') + self.assertTrue(res) + + res = m.remove_filtered_policy('p', 'p', 1, 'domain1', 'data1') + self.assertFalse(res) From de063acdf88a3475455c78cc1999a5efcca22ef1 Mon Sep 17 00:00:00 2001 From: Huang Yan Date: Sun, 11 Aug 2019 12:13:11 +0800 Subject: [PATCH 041/349] Implicit add_role_for_user_in_domain and delete_roles_for_user_in_domain --- casbin/enforcer.py | 2 -- casbin/management_enforcer.py | 2 +- tests/test_enforcer.py | 57 ++++++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index a4fccbdd..7a39c9ea 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -54,7 +54,6 @@ def add_role_for_user(self, user, role): def add_role_for_user_in_domain(self, user, role, domain): """adds a role for a user inside a domain.""" """Returns false if the user already has the role (aka not affected).""" - raise NotImplementedError return self.add_grouping_policy(user, role, domain) def delete_role_for_user(self, user, role): @@ -70,7 +69,6 @@ def delete_roles_for_user(self, user): def delete_roles_for_user_in_domain(self, user, role, domain): """deletes a role for a user inside a domain.""" """Returns false if the user does not have any roles (aka not affected).""" - raise NotImplementedError return self.remove_filtered_grouping_policy(0, user, role, domain) def delete_user(self, user): diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 4dbd58dd..e1f91d4c 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -168,7 +168,7 @@ def add_grouping_policy(self, *params): 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. """ - return self.add_named_grouping_policy('p', *params) + return self.add_named_grouping_policy('g', *params) def add_named_grouping_policy(self, ptype, *params): """adds a named role inheritance rule to the current policy. diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 71e377f2..165278e9 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -162,7 +162,8 @@ def test_enforce_implicit_roles_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv")) self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) - self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) + self.assertTrue( + e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) def test_enforce_implicit_permissions_api(self): e = get_enforcer(get_examples("rbac_model.conf"), @@ -197,9 +198,51 @@ def test_enforce_get_users_in_domain(self): self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - # e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - # e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - # self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) - # self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - # self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - # self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + + def test_enforce_user_api_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + + def test_enforce_get_roles_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['admin']) + self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain1') == []) + self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain1') == []) + self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain1') == []) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain2') == []) + self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain2') == ['admin']) + self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain2') == []) + self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain2') == []) + + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == []) + self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain1') == ['admin']) + self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain1') == []) + self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain1') == []) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain2') == []) + self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain2') == ['admin']) + self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain2') == []) + self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain2') == []) From bc80af78883b802335d46b1b96908fda84b278bd Mon Sep 17 00:00:00 2001 From: TechLee Date: Thu, 15 Aug 2019 17:40:30 +0800 Subject: [PATCH 042/349] Improve the test code for api. --- tests/test_rbac_api.py | 50 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 0617a115..7483bae2 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -15,3 +15,53 @@ def test_get_users_for_role(self): e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertEqual(e.get_users_for_role('data2_admin'), ['alice']) + + def test_has_role_for_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + + self.assertTrue(e.has_role_for_user('alice', 'data2_admin')) + self.assertFalse(e.has_role_for_user('alice', 'data1_admin')) + + def test_add_role_for_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e.add_role_for_user('alice', 'data1_admin') + self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) + + def test_delete_role_for_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e.add_role_for_user('alice', 'data1_admin') + self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) + + e.delete_role_for_user('alice', 'data1_admin') + self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) + + def test_delete_roles_for_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e.delete_roles_for_user('alice') + self.assertEqual(e.get_roles_for_user('alice'), []) + + def test_delete_user(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e.delete_user('alice') + self.assertEqual(e.get_roles_for_user('alice'), []) + + def test_delete_role(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e.delete_role('data2_admin') + self.assertTrue(e.enforce('alice', 'data1', 'read')) + self.assertFalse(e.enforce('alice', 'data1', 'write')) + self.assertFalse(e.enforce('alice', 'data2', 'read')) + self.assertFalse(e.enforce('alice', 'data2', 'write')) + self.assertFalse(e.enforce('bob', 'data1', 'read')) + self.assertFalse(e.enforce('bob', 'data1', 'write')) + self.assertFalse(e.enforce('bob', 'data2', 'read')) + self.assertTrue(e.enforce('bob', 'data2', 'write')) + + def test_delete_permission(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + e.delete_permission('read') + self.assertFalse(e.enforce('alice', 'read')) + self.assertFalse(e.enforce('alice', 'write')) + self.assertFalse(e.enforce('bob', 'read')) + self.assertTrue(e.enforce('bob', 'write')) From 039d7766a7f65c68246f4d8f7e73ca442dda0eee Mon Sep 17 00:00:00 2001 From: TechLee Date: Thu, 15 Aug 2019 18:57:51 +0800 Subject: [PATCH 043/349] Fixed bugs in add/delete/has permission for user api. --- casbin/enforcer.py | 20 ++++++++++++++------ tests/test_rbac_api.py | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 7a39c9ea..462e66fc 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -89,12 +89,16 @@ def delete_permission(self, *permission): def add_permission_for_user(self, user, *permission): """adds a permission for a user or role.""" """Returns false if the user or role already has the permission (aka not affected).""" - return self.add_policy(*permission) + params = [user, *permission] + + return self.add_policy(*params) def delete_permission_for_user(self, user, *permission): """adds a permission for a user or role.""" """Returns false if the user or role already has the permission (aka not affected).""" - return self.remove_policy(*permission) + params = [user, *permission] + + return self.remove_policy(*params) def delete_permissions_for_user(self, user): """deletes permissions for a user or role.""" @@ -111,7 +115,9 @@ def get_permissions_for_user_in_domain(self, user, domain): def has_permission_for_user(self, user, *permission): """determines whether a user has a permission.""" - return self.has_policy(*permission) + params = [user, *permission] + + return self.has_policy(*params) def get_implicit_roles_for_user(self, user, domain=None): """ @@ -131,7 +137,6 @@ def get_implicit_roles_for_user(self, user, domain=None): res.extend(_roles) return res - def get_implicit_permissions_for_user(self, user, domain=None): """ gets implicit permissions for a user or role. @@ -145,9 +150,12 @@ def get_implicit_permissions_for_user(self, user, domain=None): But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. """ roles = self.get_implicit_roles_for_user(user, domain) - permissions = self.get_permissions_for_user_in_domain(user, domain) if domain else self.get_permissions_for_user(user) + permissions = self.get_permissions_for_user_in_domain(user, + domain) if domain else self.get_permissions_for_user(user) for role in roles: - _permissions = self.get_permissions_for_user_in_domain(role, domain) if domain else self.get_permissions_for_user(role) + _permissions = self.get_permissions_for_user_in_domain(role, + domain) if domain else self.get_permissions_for_user( + role) for item in _permissions: if item not in permissions: permissions.append(item) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 7483bae2..50c7cb11 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -65,3 +65,43 @@ def test_delete_permission(self): self.assertFalse(e.enforce('alice', 'write')) self.assertFalse(e.enforce('bob', 'read')) self.assertTrue(e.enforce('bob', 'write')) + + def test_add_permission_for_user(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + e.delete_permission('read') + e.add_permission_for_user('bob', 'read') + self.assertTrue(e.enforce('bob', 'read')) + self.assertTrue(e.enforce('bob', 'write')) + + def test_delete_permission_for_user(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + e.add_permission_for_user('bob', 'read') + + self.assertTrue(e.enforce('bob', 'read')) + e.delete_permission_for_user('bob', 'read') + self.assertFalse(e.enforce('bob', 'read')) + self.assertTrue(e.enforce('bob', 'write')) + + def test_delete_permissions_for_user(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + e.delete_permissions_for_user('bob') + + self.assertTrue(e.enforce('alice', 'read')) + self.assertFalse(e.enforce('bob', 'read')) + self.assertFalse(e.enforce('bob', 'write')) + + def test_get_permissions_for_user(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + self.assertEqual(e.get_permissions_for_user('alice'), [['alice', 'read']]) + + def test_has_permission_for_user(self): + e = get_enforcer(get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv")) + self.assertTrue(e.has_permission_for_user('alice', *['read'])) + self.assertFalse(e.has_permission_for_user('alice', *['write'])) + self.assertFalse(e.has_permission_for_user('bob', *['read'])) + self.assertTrue(e.has_permission_for_user('bob', *['write'])) From 690c1c94e9035413ed88c83276787ebcd89ab828 Mon Sep 17 00:00:00 2001 From: Foufou Date: Thu, 15 Aug 2019 22:22:53 +0800 Subject: [PATCH 044/349] Bump to version 0.8.0. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 33a66bb4..2f6a2275 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.7.1", + version="0.8.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From e7aa0f1520456d087e1a9444d28b7e974694e605 Mon Sep 17 00:00:00 2001 From: TechLee Date: Fri, 16 Aug 2019 14:29:34 +0800 Subject: [PATCH 045/349] Fixed the error in the Policy that remove_policy() returns a value of None . --- casbin/model/policy.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index b82040a4..ddd0efcb 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -85,7 +85,9 @@ def remove_policy(self, sec, ptype, rule): if not self.has_policy(sec, ptype, rule): return False - return self.model[sec][ptype].policy.remove(rule) + self.model[sec][ptype].policy.remove(rule) + + return rule not in self.model[sec][ptype].policy def remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes policy rules based on field filters from the model.""" From a80068352e09c3b373fc60a4d0b6460279fedf8d Mon Sep 17 00:00:00 2001 From: TechLee Date: Fri, 16 Aug 2019 16:05:45 +0800 Subject: [PATCH 046/349] Compatible with python3.4/3.3. --- casbin/enforcer.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 462e66fc..e81c0800 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -89,14 +89,16 @@ def delete_permission(self, *permission): def add_permission_for_user(self, user, *permission): """adds a permission for a user or role.""" """Returns false if the user or role already has the permission (aka not affected).""" - params = [user, *permission] + params = [user] + params.extend(permission) return self.add_policy(*params) def delete_permission_for_user(self, user, *permission): """adds a permission for a user or role.""" """Returns false if the user or role already has the permission (aka not affected).""" - params = [user, *permission] + params = [user] + params.extend(permission) return self.remove_policy(*params) @@ -115,7 +117,8 @@ def get_permissions_for_user_in_domain(self, user, domain): def has_permission_for_user(self, user, *permission): """determines whether a user has a permission.""" - params = [user, *permission] + params = [user] + params.extend(permission) return self.has_policy(*params) From de68177e704fb6409504c17a7924fd5ad5ea5403 Mon Sep 17 00:00:00 2001 From: TechLee Date: Fri, 16 Aug 2019 17:25:09 +0800 Subject: [PATCH 047/349] Support domain parameters in get_roles() and get_users(). --- casbin/enforcer.py | 17 +-- .../rbac/default_role_manager/role_manager.py | 24 ++++- tests/test_enforcer.py | 100 ------------------ tests/test_rbac_api.py | 100 ++++++++++++++++++ 4 files changed, 125 insertions(+), 116 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index e81c0800..deec1517 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -21,30 +21,23 @@ def get_roles_for_user(self, user): """gets the roles that a user has.""" return self.model.model['g']['g'].rm.get_roles(user) - def get_roles_for_user_in_domain(self, user, domain): + def get_roles_for_user_in_domain(self, name, domain): """gets the roles that a user has inside a domain.""" - res = self.model.model['g']['g'].rm.get_roles(user, domain) - return [] if isinstance(res, RuntimeError) else [r.replace(domain + '::', '') for r in res] + return self.model.model['g']['g'].rm.get_roles(name, domain) def get_users_for_role(self, role): """gets the users that has a role.""" return self.model.model['g']['g'].rm.get_users(role) - def get_users_for_role_in_domain(self, role, domain): + def get_users_for_role_in_domain(self, name, domain): """gets the users that has a role inside a domain.""" - _role = domain + '::' + role - res = self.model.model['g']['g'].rm.get_users(_role, domain) - return [] if isinstance(res, RuntimeError) else [r.replace(domain + '::', '') for r in res] + return self.model.model['g']['g'].rm.get_users(name, domain) def has_role_for_user(self, user, role): """determines whether a user has a role.""" roles = self.get_roles_for_user(user) - for r in roles: - if r == role: - return True - - return False + return role in roles def add_role_for_user(self, user, role): """adds a role for a user.""" diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index b811e644..c75db137 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -67,6 +67,10 @@ def has_link(self, name1, name2, *domain): return role1.has_role(name2, self.max_hierarchy_level) def get_roles(self, name, *domain): + """ + gets the roles that a subject inherits. + domain is a prefix to the roles. + """ if len(domain) == 1: name = domain[0] + "::" + name elif len(domain) > 1: @@ -77,19 +81,31 @@ def get_roles(self, name, *domain): roles = self.create_role(name).get_roles() if len(domain) == 1: - for value in roles: - value = value[len(domain[0]) + 2:] + for key, value in enumerate(roles): + roles[key] = value[len(domain[0]) + 2:] return roles def get_users(self, name, *domain): + """ + gets the users that inherits a subject. + domain is an unreferenced parameter here, may be used in other implementations. + """ + if len(domain) == 1: + name = domain[0] + "::" + name + elif len(domain) > 1: + return RuntimeError("error: domain should be 1 parameter") + if not self.has_role(name): - return RuntimeError("error: name does not exist") + return [] names = [] for role in self.all_roles.values(): if role.has_direct_role(name): - names.append(role.name) + if len(domain) == 1: + names.append(role.name[len(domain[0]) + 2:]) + else: + names.append(role.name) return names diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 165278e9..b1dab828 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -146,103 +146,3 @@ def test_enforce_abac_log_enabled(self): sub = 'alice' obj = {'Owner': 'alice', 'id': 'data1'} self.assertTrue(e.enforce(sub, obj, 'write')) - - def test_enforce_implicit_roles_api(self): - e = get_enforcer(get_examples("rbac_model.conf"), - get_examples("rbac_with_hierarchy_policy.csv")) - - self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) - self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) - - self.assertTrue(e.get_implicit_roles_for_user('alice') == ['admin', 'data1_admin', 'data2_admin']) - self.assertTrue(e.get_implicit_roles_for_user('bob') == []) - - def test_enforce_implicit_roles_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_hierarchy_with_domains_policy.csv")) - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) - self.assertTrue( - e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) - - def test_enforce_implicit_permissions_api(self): - e = get_enforcer(get_examples("rbac_model.conf"), - get_examples("rbac_with_hierarchy_policy.csv")) - self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) - self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) - self.assertTrue(e.get_implicit_permissions_for_user('alice') == [ - ['alice', 'data1', 'read'], - ['data1_admin', 'data1', 'read'], - ['data1_admin', 'data1', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write']]) - self.assertTrue(e.get_implicit_permissions_for_user('bob') == [["bob", "data2", "write"]]) - - def test_enforce_implicit_permissions_api_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_hierarchy_with_domains_policy.csv")) - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) - self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == - ['role:global_admin', 'role:reader', 'role:writer']) - self.assertTrue(e.get_implicit_permissions_for_user('alice', 'domain1') == [ - ['alice', 'domain1', 'data2', 'read'], - ["role:reader", "domain1", "data1", "read"], - ["role:writer", "domain1", "data1", "write"]]) - self.assertTrue(e.get_implicit_permissions_for_user('bob', 'domain1') == []) - - def test_enforce_get_users_in_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - - def test_enforce_user_api_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - - def test_enforce_get_roles_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['admin']) - self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain1') == []) - self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain1') == []) - self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain1') == []) - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain2') == []) - self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain2') == ['admin']) - self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain2') == []) - self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain2') == []) - - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == []) - self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain1') == ['admin']) - self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain1') == []) - self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain1') == []) - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain2') == []) - self.assertTrue(e.get_roles_for_user_in_domain('bob', 'domain2') == ['admin']) - self.assertTrue(e.get_roles_for_user_in_domain('admin', 'domain2') == []) - self.assertTrue(e.get_roles_for_user_in_domain('non_exist', 'domain2') == []) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 50c7cb11..5a829842 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -105,3 +105,103 @@ def test_has_permission_for_user(self): self.assertFalse(e.has_permission_for_user('alice', *['write'])) self.assertFalse(e.has_permission_for_user('bob', *['read'])) self.assertTrue(e.has_permission_for_user('bob', *['write'])) + + def test_enforce_implicit_roles_api(self): + e = get_enforcer(get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv")) + + self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) + self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) + + self.assertTrue(e.get_implicit_roles_for_user('alice') == ['admin', 'data1_admin', 'data2_admin']) + self.assertTrue(e.get_implicit_roles_for_user('bob') == []) + + def test_enforce_implicit_roles_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv")) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) + self.assertTrue( + e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) + + def test_enforce_implicit_permissions_api(self): + e = get_enforcer(get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv")) + self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) + self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) + self.assertTrue(e.get_implicit_permissions_for_user('alice') == [ + ['alice', 'data1', 'read'], + ['data1_admin', 'data1', 'read'], + ['data1_admin', 'data1', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write']]) + self.assertTrue(e.get_implicit_permissions_for_user('bob') == [["bob", "data2", "write"]]) + + def test_enforce_implicit_permissions_api_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv")) + + self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) + self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == + ['role:global_admin', 'role:reader', 'role:writer']) + self.assertTrue(e.get_implicit_permissions_for_user('alice', 'domain1') == [ + ['alice', 'domain1', 'data2', 'read'], + ["role:reader", "domain1", "data1", "read"], + ["role:writer", "domain1", "data1", "write"]]) + self.assertTrue(e.get_implicit_permissions_for_user('bob', 'domain1') == []) + + def test_enforce_get_users_in_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) + self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) + self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + + def test_enforce_user_api_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain1'), ['alice']) + self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain1'), []) + self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain2'), ['bob']) + self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain2'), []) + + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + + self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain1'), ['bob']) + self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain1'), []) + self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain2'), ['bob']) + self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain2'), []) + + def test_enforce_get_roles_with_domain(self): + e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) + self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain1'), ['admin']) + self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain1'), []) + self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain1'), []) + self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain1'), []) + + self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain2'), []) + self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin']) + self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), []) + self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) + + e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') + e.add_role_for_user_in_domain('bob', 'admin', 'domain1') + + self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain1'), []) + self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain1'), ['admin']) + self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain1'), []) + self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain1'), []) + + self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain2'), []) + self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin']) + self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), []) + self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) From d61eca2312b75a9fae45c367ee853dd57c3f818c Mon Sep 17 00:00:00 2001 From: Foufou Date: Mon, 19 Aug 2019 01:26:18 +0800 Subject: [PATCH 048/349] Bump to version 0.8.1. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2f6a2275..0ffdd9aa 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.8.0", + version="0.8.1", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 55c9c451ba222002196390e09d8585efbb482224 Mon Sep 17 00:00:00 2001 From: TechLee Date: Thu, 22 Aug 2019 16:34:56 +0800 Subject: [PATCH 049/349] Optimize code to improve performance. --- casbin/core_enforcer.py | 66 ++++++++++++++++++------------- casbin/effect/default_effector.py | 23 +++++------ casbin/util/expression.py | 32 ++++++++++++--- 3 files changed, 74 insertions(+), 47 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index f3b42037..2ccaf0f5 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -2,7 +2,7 @@ from casbin.persist.adapters import FileAdapter from casbin.model import Model, FunctionMap from casbin.rbac import default_role_manager -from casbin.util import generate_g_function, expression +from casbin.util import generate_g_function, SimpleEval from casbin.effect import DefaultEffector, Effector @@ -198,9 +198,7 @@ def enforce(self, *rvals): if not self.enabled: return False - functions = {} - for key, val in self.fm.get_functions().items(): - functions[key] = val + functions = self.fm.get_functions() if "g" in self.model.model.keys(): for key, ast in self.model.model["g"].items(): @@ -208,70 +206,75 @@ def enforce(self, *rvals): functions[key] = generate_g_function(rm) if "m" not in self.model.model.keys(): - return RuntimeError("model is undefined") + raise RuntimeError("model is undefined") if "m" not in self.model.model["m"].keys(): - return RuntimeError("model is undefined") + raise RuntimeError("model is undefined") + + r_tokens = self.model.model["r"]["r"].tokens + p_tokens = self.model.model["p"]["p"].tokens + + if len(r_tokens) != len(rvals): + raise RuntimeError("invalid request size") exp_string = self.model.model["m"]["m"].value + expression = self._get_expression(exp_string, functions) - policy_effects = [] - matcher_results = [] + policy_effects = set() + matcher_results = set() + + r_parameters = dict(zip(r_tokens, rvals)) policy_len = len(self.model.model["p"]["p"].policy) if not 0 == policy_len: for i, pvals in enumerate(self.model.model["p"]["p"].policy): - parameters = dict() - for j, token in enumerate(self.model.model["r"]["r"].tokens): - parameters[token] = rvals[j] - - for j, token in enumerate(self.model.model["p"]["p"].tokens): - parameters[token] = pvals[j] + if len(p_tokens) != len(pvals): + raise RuntimeError("invalid policy size") - result = expression.evaluate(exp_string, parameters, functions) + parameters = dict(r_parameters, **dict(zip(p_tokens, pvals))) + result = expression.eval(parameters) if isinstance(result, bool): if not result: - policy_effects.append(Effector.INDETERMINATE) + policy_effects.add(Effector.INDETERMINATE) continue elif isinstance(result, float): if 0 == result: - policy_effects.append(Effector.INDETERMINATE) + policy_effects.add(Effector.INDETERMINATE) continue else: - matcher_results.append(result) + matcher_results.add(result) else: raise RuntimeError("matcher result should be bool, int or float") if "p_eft" in parameters.keys(): eft = parameters["p_eft"] if "allow" == eft: - policy_effects.append(Effector.ALLOW) + policy_effects.add(Effector.ALLOW) elif "deny" == eft: - policy_effects.append(Effector.DENY) + policy_effects.add(Effector.DENY) else: - policy_effects.append(Effector.INDETERMINATE) + policy_effects.add(Effector.INDETERMINATE) else: - policy_effects.append(Effector.ALLOW) + policy_effects.add(Effector.ALLOW) if "priority(p_eft) || deny" == self.model.model["e"]["e"].value: break else: - parameters = dict() - for j, token in enumerate(self.model.model["r"]["r"].tokens): - parameters[token] = rvals[j] + + parameters = r_parameters.copy() for token in self.model.model["p"]["p"].tokens: parameters[token] = "" - result = expression.evaluate(exp_string, parameters, functions) + result = expression.eval(parameters) if result: - policy_effects.append(Effector.ALLOW) + policy_effects.add(Effector.ALLOW) else: - policy_effects.append(Effector.INDETERMINATE) + policy_effects.add(Effector.INDETERMINATE) result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results) @@ -284,3 +287,10 @@ def enforce(self, *rvals): log.log_print(req_str) return result + + @staticmethod + def _get_expression(expr, functions=None): + expr = expr.replace("&&", "and") + expr = expr.replace("||", "or") + + return SimpleEval(expr, functions) diff --git a/casbin/effect/default_effector.py b/casbin/effect/default_effector.py index edc7a634..47ee9765 100644 --- a/casbin/effect/default_effector.py +++ b/casbin/effect/default_effector.py @@ -7,28 +7,23 @@ class DefaultEffector(Effector): def merge_effects(self, expr, effects, results): """merges all matching results collected by the enforcer into a single decision.""" + effects = set(effects) result = False if expr == "some(where (p_eft == allow))": - for eft in effects: - if eft == self.ALLOW: - result = True - break + if self.ALLOW in effects: + result = True elif expr == "!some(where (p_eft == deny))": result = True - for eft in effects: - if eft == self.DENY: - result = False - break + if self.DENY in effects: + result = False elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": - for eft in effects: - if eft == self.ALLOW: - result = True - elif eft == self.DENY: - result = False - break + if self.DENY in effects: + result = False + elif self.ALLOW in effects: + result = True elif expr == "priority(p_eft) || deny": for eft in effects: diff --git a/casbin/util/expression.py b/casbin/util/expression.py index 99130ef0..cd9a20db 100644 --- a/casbin/util/expression.py +++ b/casbin/util/expression.py @@ -1,7 +1,29 @@ -from simpleeval import simple_eval +from simpleeval import SimpleEval +import ast -def evaluate(exp_string, parameters=None, functions=None): - exp_string = exp_string.replace("&&", "and") - exp_string = exp_string.replace("||", "or") - return simple_eval(exp_string, functions=functions, names=parameters) +class SimpleEval(SimpleEval): + """ Rewrite SimpleEval. + >>> s = SimpleEval("20 + 30 - ( 10 * 5)") + >>> s.eval() + 0 + """ + + ast_parsed_value = None + + def __init__(self, expr, functions=None): + """Create the evaluator instance. Set up valid operators (+,-, etc) + functions (add, random, get_val, whatever) and names. """ + super(SimpleEval, self).__init__(functions=functions) + if expr != "": + self.expr = expr + self.expr_parsed_value = ast.parse(expr.strip()).body[0].value + + def eval(self, names=None): + """ evaluate an expresssion, using the operators, functions and + names previously set up. """ + + if names: + self.names = names + + return self._eval(self.expr_parsed_value) From dced4782735c719d6cdd6cf3b3ce5c22627fe168 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 10 Sep 2019 15:16:45 +0800 Subject: [PATCH 050/349] Remove Python 3.3 for travis-ci often failing, add Python3.7 and pypy3. --- .travis.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) mode change 100644 => 100755 .travis.yml diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index 5b7d4136..e9b6b47d --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,17 @@ language: python python: -- '3.3' -- '3.4' -- '3.5' -- '3.6' + - "3.4" + - "3.5" + - "3.6" + - "3.7" + - "pypy3" install: -- pip install -r requirements.txt -- pip install coveralls + - pip install -r requirements.txt + - pip install coveralls script: -- coverage run -m unittest discover -s tests -t tests + - coverage run -m unittest discover -s tests -t tests after_success: -- coveralls + - coveralls deploy: provider: pypi user: techlee From 24485e2392ad98a20fb1c2e12a0e46670959fe4b Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Wed, 2 Oct 2019 01:13:13 +0200 Subject: [PATCH 051/349] Fix the add_policy method def in the Adapter intf It's an obvious copypasta. --- casbin/persist/adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 6588866d..b290be2c 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -31,7 +31,7 @@ def save_policy(self, model): """saves all policy rules to the storage.""" pass - def save_policy(self, model): + def add_policy(self, sec, ptype, rule): """adds a policy rule to the storage.""" pass From de35f37c39d1a6d9165bea9c1fbca7d5a27916d2 Mon Sep 17 00:00:00 2001 From: Joris Geysens Date: Wed, 13 Nov 2019 15:38:02 +0100 Subject: [PATCH 052/349] Fix: raise instead of returning exceptions. --- casbin/core_enforcer.py | 4 ++-- casbin/rbac/default_role_manager/role_manager.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 2ccaf0f5..d705f68b 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -32,7 +32,7 @@ def __init__(self, model=None, adapter=None, enable_log=False): pass else: if isinstance(adapter, str): - return RuntimeError("Invalid parameters for enforcer.") + raise RuntimeError("Invalid parameters for enforcer.") else: self.init_with_model_and_adapter(model, adapter) @@ -157,7 +157,7 @@ def is_filtered(self): def save_policy(self): if self.is_filtered(): - return RuntimeError("cannot save a filtered policy") + raise RuntimeError("cannot save a filtered policy") self.adapter.save_policy(self.model) diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index c75db137..7e0abc9a 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -29,7 +29,7 @@ def add_link(self, name1, name2, *domain): name1 = domain[0] + "::" + name1 name2 = domain[0] + "::" + name2 elif len(domain) > 1: - return RuntimeError("error: domain should be 1 parameter") + raise RuntimeError("error: domain should be 1 parameter") role1 = self.create_role(name1) role2 = self.create_role(name2) @@ -40,10 +40,10 @@ def delete_link(self, name1, name2, *domain): name1 = domain[0] + "::" + name1 name2 = domain[0] + "::" + name2 elif len(domain) > 1: - return RuntimeError("error: domain should be 1 parameter") + raise RuntimeError("error: domain should be 1 parameter") if not self.has_role(name1) or not self.has_role(name2): - return RuntimeError("error: name1 or name2 does not exist") + raise RuntimeError("error: name1 or name2 does not exist") role1 = self.create_role(name1) role2 = self.create_role(name2) @@ -54,7 +54,7 @@ def has_link(self, name1, name2, *domain): name1 = domain[0] + "::" + name1 name2 = domain[0] + "::" + name2 elif len(domain) > 1: - return RuntimeError("error: domain should be 1 parameter") + raise RuntimeError("error: domain should be 1 parameter") if name1 == name2: return True From b5818be7680b3f027186df3171910afe0f03d9cc Mon Sep 17 00:00:00 2001 From: techlee Date: Sat, 30 Nov 2019 14:28:39 +0800 Subject: [PATCH 053/349] add python3.8 support --- .travis.yml | 1 + requirements.txt | 2 +- setup.py | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index e9b6b47d..ea48290e 100755 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ python: - "3.5" - "3.6" - "3.7" + - "3.8" - "pypy3" install: - pip install -r requirements.txt diff --git a/requirements.txt b/requirements.txt index e8a78a24..52aeb858 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -simpleeval==0.9.8 +simpleeval>=0.9.10 diff --git a/setup.py b/setup.py index 0ffdd9aa..ba644502 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ url="https://github.com/casbin/pycasbin", keywords=["casbin", "acl", "rbac", "abac", "auth", "authz", "authorization", "access control", "permission"], packages=setuptools.find_packages(), - install_requires=['simpleeval>=0.9.8'], + install_requires=['simpleeval>=0.9.10'], python_requires=">=3.3", license="Apache 2.0", classifiers=[ @@ -25,6 +25,7 @@ "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], From 9aca2c8e0e99a6c75400254eef72958ba8046ea4 Mon Sep 17 00:00:00 2001 From: techlee Date: Sat, 30 Nov 2019 22:45:08 +0800 Subject: [PATCH 054/349] Bump to version 0.8.2 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ba644502..1580a99f 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.8.1", + version="0.8.2", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 77668dece4bbc028a3f8067c900e34f97ffeb28a Mon Sep 17 00:00:00 2001 From: Kyle McCarthy Date: Sun, 8 Dec 2019 19:19:54 -0600 Subject: [PATCH 055/349] Update casbin-rs link in README Update README with correct link to Rust's Casbin repo -- https://github.com/casbin/casbin-rs/issues/23 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4a47359..13ab1334 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,9 @@ Casbin is a powerful and efficient open-source access control library for Python [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-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/Devolutions/casbin-rs) +[![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-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![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-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/Devolutions/casbin-rs) +[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/casbin/casbin-rs) production-ready | production-ready | experimental | WIP ## Table of contents From 3e9996c65c38abf584278b995b1b610fde3ad3a0 Mon Sep 17 00:00:00 2001 From: Benjamin Sims Date: Wed, 11 Dec 2019 18:44:11 +0000 Subject: [PATCH 056/349] Remove tests from setup.py packaging to avoid conflict --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 1580a99f..75ca3592 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.8.2", + version="0.8.3", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", @@ -15,7 +15,7 @@ long_description_content_type="text/markdown", url="https://github.com/casbin/pycasbin", keywords=["casbin", "acl", "rbac", "abac", "auth", "authz", "authorization", "access control", "permission"], - packages=setuptools.find_packages(), + packages=setuptools.find_packages(exclude=("tests",)), install_requires=['simpleeval>=0.9.10'], python_requires=">=3.3", license="Apache 2.0", From f7f0a216c72ea23c88b3f10e32d84c27ec0700aa Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Mon, 27 Jan 2020 16:16:24 +0800 Subject: [PATCH 057/349] Built-in file adapter supports save_policy() now. --- casbin/persist/adapters/file_adapter.py | 26 ++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index d2a2079b..29514e91 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -19,7 +19,10 @@ def load_policy(self, model): self._load_policy_file(model) def save_policy(self, model): - pass + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + + self._save_policy_file(model) def _load_policy_file(self, model): with open(self._file_path, "rb") as file: @@ -28,8 +31,25 @@ def _load_policy_file(self, model): persist.load_policy_line(line.decode().strip(), model) line = file.readline() - def _save_policy_file(self, text): - pass + def _save_policy_file(self, model): + with open(self._file_path, "w") as file: + lines = [] + + if "p" in model.model.keys(): + for key, ast in model.model["p"].items(): + for pvals in ast.policy: + lines.append(key + ', ' + ', '.join(pvals)) + + if "g" in model.model.keys(): + for key, ast in model.model["g"].items(): + for pvals in ast.policy: + lines.append(key + ', ' + ', '.join(pvals)) + + for i, line in enumerate(lines): + if i != len(lines) - 1: + lines[i] += "\n" + + file.writelines(lines) def add_policy(self, sec, ptype, rule): pass From 5d748f3cb5953d5346d912dce05f33f452866496 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Mon, 27 Jan 2020 16:22:09 +0800 Subject: [PATCH 058/349] Bump to version 0.8.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 75ca3592..2faa389b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.8.3", + version="0.8.4", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 2ceaae9a96f307d4873b8dfc748a1fe889955ed8 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 28 Jan 2020 17:05:17 +0800 Subject: [PATCH 059/349] Add some benchmark tests. --- tests/test_model_benchmark.py | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_model_benchmark.py diff --git a/tests/test_model_benchmark.py b/tests/test_model_benchmark.py new file mode 100644 index 00000000..9ff47b45 --- /dev/null +++ b/tests/test_model_benchmark.py @@ -0,0 +1,40 @@ +import datetime +import logging +import sys +from tests.test_enforcer import get_examples, get_enforcer +from unittest import TestCase + +log = logging.getLogger(__name__) +loglevel = logging.DEBUG +logging.basicConfig(level=loglevel) + + +def get_function_name(): + return sys._getframe(2).f_code.co_name + + +def print_time_diff(start, end, time): + ms = (end - start).total_seconds() * 1000 / time + log.debug("%s %f ms" % (get_function_name(), ms)) + + +class TestModelBenchmark(TestCase): + def test_benchmark_basic_model(self): + e = get_enforcer(get_examples("basic_model.conf"), get_examples("basic_policy.csv")) + + time = 10000 + start = datetime.datetime.now() + for i in range(0, time): + e.enforce("alice", "data1", "read") + end = datetime.datetime.now() + print_time_diff(start, end, time) + + def test_benchmark_rbac_model(self): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + + time = 10000 + start = datetime.datetime.now() + for i in range(0, time): + e.enforce("alice", "data2", "read") + end = datetime.datetime.now() + print_time_diff(start, end, time) From ae403f3136ea86f76fd87f68aca2261fe5f96487 Mon Sep 17 00:00:00 2001 From: Andres Roldan Date: Tue, 11 Feb 2020 08:44:28 -0500 Subject: [PATCH 060/349] Fix syntax errors on multiline config sentences - The actual code strips the multiline char but sometimes it leads to syntax errors because not compatible chars may result appended. --- casbin/config/config.py | 1 + tests/config/test.ini | 10 +++++----- tests/config/test_config.py | 8 ++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/casbin/config/config.py b/casbin/config/config.py index 8bf638c3..149a2dde 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -75,6 +75,7 @@ def _parse_buffer(self, f): p = '' if self.DEFAULT_MULTI_LINE_SEPARATOR == line[-1]: p = line[0:-1].strip() + p = p + ' ' else: p = line can_write = True diff --git a/tests/config/test.ini b/tests/config/test.ini index 15276c3e..0e9b8e15 100644 --- a/tests/config/test.ini +++ b/tests/config/test.ini @@ -31,15 +31,15 @@ key1 = test key # multi-line test [multi1] name = r.sub==p.sub \ - &&r.obj==p.obj\ + && r.obj==p.obj\ \ [multi2] name = r.sub==p.sub \ - &&r.obj==p.obj + && r.obj==p.obj [multi3] name = r.sub==p.sub \ - &&r.obj==p.obj + && r.obj==p.obj [multi4] name = \ @@ -48,5 +48,5 @@ name = \ [multi5] name = r.sub==p.sub \ - &&r.obj==p.obj\ - \ \ No newline at end of file + && r.obj==p.obj\ + \ diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 8cfd97ac..81e526d5 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -33,8 +33,8 @@ def test_new_config(self): config.set("other::key1", "test key") - self.assertEqual(config.get("multi1::name"), "r.sub==p.sub&&r.obj==p.obj") - self.assertEqual(config.get("multi2::name"), "r.sub==p.sub&&r.obj==p.obj") - self.assertEqual(config.get("multi3::name"), "r.sub==p.sub&&r.obj==p.obj") + self.assertEqual(config.get("multi1::name"), "r.sub==p.sub && r.obj==p.obj") + self.assertEqual(config.get("multi2::name"), "r.sub==p.sub && r.obj==p.obj") + self.assertEqual(config.get("multi3::name"), "r.sub==p.sub && r.obj==p.obj") self.assertEqual(config.get("multi4::name"), "") - self.assertEqual(config.get("multi5::name"), "r.sub==p.sub&&r.obj==p.obj") + self.assertEqual(config.get("multi5::name"), "r.sub==p.sub && r.obj==p.obj") From 16b9aaee1c4160e44198f156d27c2ed68bb7f757 Mon Sep 17 00:00:00 2001 From: Cheng JIANG Date: Fri, 17 Apr 2020 11:20:53 +0200 Subject: [PATCH 061/349] casbin-rs is now production-ready --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 13ab1334..4e329fd1 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ 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-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![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-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/casbin/casbin-rs) -production-ready | production-ready | experimental | WIP +production-ready | production-ready | experimental | production-ready ## Table of contents From f27df2e938a02d1e8cb7c1e60d3f2dc5314d422d Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Wed, 15 Apr 2020 19:44:32 +0530 Subject: [PATCH 062/349] Added RBAC API. --- casbin/rbac_enforcer.py | 200 ++++++++++++++++++++++++++++++++++++++++ casbin/util/util.py | 22 +++++ 2 files changed, 222 insertions(+) create mode 100644 casbin/rbac_enforcer.py diff --git a/casbin/rbac_enforcer.py b/casbin/rbac_enforcer.py new file mode 100644 index 00000000..09bb9086 --- /dev/null +++ b/casbin/rbac_enforcer.py @@ -0,0 +1,200 @@ +from casbin.management_enforcer import ManagementEnforcer +from casbin.util import join_slice, set_subtract + +class RBACEnforcer(ManagementEnforcer): + """ + RBACEnforcer = RBAC_API + ManagementEnforcer. + """ + + def get_roles_for_user(self, name): + ''' gets the roles that a user has. ''' + res = self.model["g"]["g"].rm.get_roles(name) + return res + + def get_users_for_role(self, name): + ''' gets the users that has a role. ''' + res = self.model["g"]["g"].rm.get_roles(name) + return res + + def has_role_for_user(self, name, role): + ''' determines whether a user has a role. ''' + roles = self.get_roles_for_user(name) + + hasRole = False + for r in roles: + if r == role: + hasRole = True + break + + return hasRole + + def add_role_for_user(self, user, role): + ''' + adds a role for a user. + Returns false if the user already has the role (aka not affected). + ''' + return self.add_grouping_policy(user, role) + + def delete_role_for_user(self, user, role): + ''' + deletes a role for a user. + Returns false if the user does not have the role (aka not affected). + ''' + return self.remove_grouping_policy(user, role) + + def delete_roles_for_user(self, user): + ''' + deletes all roles for a user. + Returns false if the user does not have any roles (aka not affected). + ''' + return self.remove_filtered_grouping_policy(0, user) + + def delete_user(self, user): + ''' + deletes a user. + Returns false if the user does not exist (aka not affected). + ''' + res1 = self.remove_filtered_grouping_policy(0, user) + + res2 = self.remove_filtered_policy(0, user) + return res1 or res2 + + def delete_role(self, role): + ''' + deletes a role. + Returns false if the role does not exist (aka not affected). + ''' + res1 = self.remove_filtered_grouping_policy(1, role) + + res2 = self.remove_filtered_policy(0, role) + return res1 or res2 + + def delete_permission(self, *permission): + ''' + deletes a permission. + Returns false if the permission does not exist (aka not affected). + ''' + return self.remove_filtered_policy(1, *permission) + + def add_permission_for_user(self, user, *permission): + ''' + adds a permission for a user or role. + Returns false if the user or role already has the permission (aka not affected). + ''' + return self.add_policy(join_slice(user, *permission)) + + def delete_permission_for_user(self, user, *permission): + ''' + deletes a permission for a user or role. + Returns false if the user or role does not have the permission (aka not affected). + ''' + return self.remove_policy(join_slice(user, *permission)) + + def delete_permission_for_user(self, user): + ''' + deletes permissions for a user or role. + Returns false if the user or role does not have any permissions (aka not affected). + ''' + return self.remove_filtered_policy(0, user) + + def get_permissions_for_user(self, user): + ''' + gets permissions for a user or role. + ''' + return self.get_filtered_policy(0, user) + + def HasPermissionForUser(self, user, *permission): + ''' + determines whether a user has a permission. + ''' + return self.has_policy(join_slice(user, *permission)) + + def get_implicit_roles_for_user(self, name, *domain): + ''' + gets implicit roles that a user has. + Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. + For example: + g, alice, role:admin + g, role:admin, role:user + + get_roles_for_user("alice") can only get: ["role:admin"]. + But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. + ''' + res = list() + roleSet = dict() + roleSet[name] = True + + q = list() + q.append(name) + + while len(q) > 0: + name = q[0] + q = q[1:] + + roles = self.rm.get_roles(name, *domain) + for r in roles: + if r not in roleSet: + res.append(r) + q.append(r) + roleSet[r] = True + + return res + + def get_implicit_permissions_for_user(self, user, *domain): + ''' + gets implicit permissions for a user or role. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + ''' + roles = self.get_implicit_roles_for_user(user, *domain) + + roles.append(user, *roles) + + withDomain = False + if len(domain) == 1: + withDomain = True + elif len(domain) > 1: + return None + + res = [list()] + permissions = [list()] + for role in roles: + if withDomain: + permissions = self.get_permissions_for_user_in_domain(role, domain[0]) + else: + permissions = self.get_permissions_for_user(role) + res.append(*permissions) + + return res + + def get_implicit_users_for_per(self, *permission): + ''' + gets implicit users for a permission. + For example: + p, admin, data1, read + p, bob, data1, read + g, alice, admin + + get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. + Note: only users will be returned, roles (2nd arg in "g") will be excluded. + ''' + subjects = self.get_all_subjects() + roles = self.get_all_roles() + + users = set_subtract(subjects, roles) + + res = list() + for user in users: + req = join_slice(user, *permission) + allowed = self.enforce(*req) + + if allowed: + res.append(user) + + return res \ No newline at end of file diff --git a/casbin/util/util.py b/casbin/util/util.py index eb001b20..bb23b86f 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -35,3 +35,25 @@ def params_to_string(*s): """gets a printable string for variable number of parameters.""" return ", ".join(s) + +def join_slice(a, *b): + ''' joins a string and a slice into a new slice.''' + res = [] + + res.append(a, *b) + + return res + +def set_subtract(a, b): + ''' returns the elements in `a` that aren't in `b`. ''' + mb = dict() + + for x in b: + mb[x] = True + + diff = list() + for x in a: + if x in mb: + diff.append(x) + + return diff From 785e42bbda2863fe7f02b8697dfe6988947dcc58 Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Wed, 15 Apr 2020 19:50:52 +0530 Subject: [PATCH 063/349] Fix bugs. --- casbin/util/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/util/util.py b/casbin/util/util.py index bb23b86f..d561eb7f 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -56,4 +56,4 @@ def set_subtract(a, b): if x in mb: diff.append(x) - return diff + return diff From c432ebe27a8d66fbb514c78dc9b4a677b57e487d Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Sat, 18 Apr 2020 16:14:57 +0530 Subject: [PATCH 064/349] Added test support and added rbac_domain functions. --- casbin/enforcer.py | 149 +-------------------------- casbin/rbac_enforcer.py | 16 +-- casbin/rbac_with_domains_enforcer.py | 33 ++++++ casbin/util/util.py | 4 +- 4 files changed, 47 insertions(+), 155 deletions(-) create mode 100644 casbin/rbac_with_domains_enforcer.py diff --git a/casbin/enforcer.py b/casbin/enforcer.py index deec1517..d4bdfc45 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,11 +1,9 @@ -import copy -from casbin.management_enforcer import ManagementEnforcer -from functools import reduce +from casbin.rbac_with_domains_enforcer import RBACWithDomainsEnforcer -class Enforcer(ManagementEnforcer): +class Enforcer(RBACWithDomainsEnforcer): """ - Enforcer = ManagementEnforcer + RBAC + Enforcer = RBACWithDomainsEnforcer """ """creates an enforcer via file or DB. @@ -16,143 +14,4 @@ class Enforcer(ManagementEnforcer): a = mysqladapter.DBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") e = casbin.Enforcer("path/to/basic_model.conf", a) """ - - def get_roles_for_user(self, user): - """gets the roles that a user has.""" - return self.model.model['g']['g'].rm.get_roles(user) - - def get_roles_for_user_in_domain(self, name, domain): - """gets the roles that a user has inside a domain.""" - return self.model.model['g']['g'].rm.get_roles(name, domain) - - def get_users_for_role(self, role): - """gets the users that has a role.""" - return self.model.model['g']['g'].rm.get_users(role) - - def get_users_for_role_in_domain(self, name, domain): - """gets the users that has a role inside a domain.""" - return self.model.model['g']['g'].rm.get_users(name, domain) - - def has_role_for_user(self, user, role): - """determines whether a user has a role.""" - roles = self.get_roles_for_user(user) - - return role in roles - - def add_role_for_user(self, user, role): - """adds a role for a user.""" - """Returns false if the user already has the role (aka not affected).""" - return self.add_grouping_policy(user, role) - - def add_role_for_user_in_domain(self, user, role, domain): - """adds a role for a user inside a domain.""" - """Returns false if the user already has the role (aka not affected).""" - return self.add_grouping_policy(user, role, domain) - - def delete_role_for_user(self, user, role): - """deletes a role for a user.""" - """Returns false if the user does not have the role (aka not affected).""" - return self.remove_grouping_policy(user, role) - - def delete_roles_for_user(self, user): - """deletes all roles for a user.""" - """Returns false if the user does not have any roles (aka not affected).""" - return self.remove_filtered_grouping_policy(0, user) - - def delete_roles_for_user_in_domain(self, user, role, domain): - """deletes a role for a user inside a domain.""" - """Returns false if the user does not have any roles (aka not affected).""" - return self.remove_filtered_grouping_policy(0, user, role, domain) - - def delete_user(self, user): - """deletes a user.""" - """Returns false if the user does not exist (aka not affected).""" - return self.remove_filtered_grouping_policy(0, user) - - def delete_role(self, role): - """deletes a role.""" - self.remove_filtered_grouping_policy(1, role) - self.remove_filtered_policy(0, role) - - def delete_permission(self, *permission): - """deletes a permission.""" - """Returns false if the permission does not exist (aka not affected).""" - return self.remove_filtered_policy(1, *permission) - - def add_permission_for_user(self, user, *permission): - """adds a permission for a user or role.""" - """Returns false if the user or role already has the permission (aka not affected).""" - params = [user] - params.extend(permission) - - return self.add_policy(*params) - - def delete_permission_for_user(self, user, *permission): - """adds a permission for a user or role.""" - """Returns false if the user or role already has the permission (aka not affected).""" - params = [user] - params.extend(permission) - - return self.remove_policy(*params) - - def delete_permissions_for_user(self, user): - """deletes permissions for a user or role.""" - """Returns false if the user or role does not have any permissions (aka not affected).""" - return self.remove_filtered_policy(0, user) - - def get_permissions_for_user(self, user): - """gets permissions for a user or role.""" - return self.get_filtered_policy(0, user) - - def get_permissions_for_user_in_domain(self, user, domain): - """gets permissions for a user or role inside domain.""" - return self.get_filtered_policy(0, user, domain) - - def has_permission_for_user(self, user, *permission): - """determines whether a user has a permission.""" - params = [user] - params.extend(permission) - - return self.has_policy(*params) - - def get_implicit_roles_for_user(self, user, domain=None): - """ - get_implicit_roles_for_user gets implicit roles that a user has. - Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. - For example: - g, alice, role:admin - g, role:admin, role:user - - get_roles_for_user("alice") can only get: ["role:admin"]. - But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. - """ - roles = self.get_roles_for_user_in_domain(user, domain) if domain else self.get_roles_for_user(user) - res = copy.copy(roles) - for r in roles: - _roles = self.get_roles_for_user_in_domain(r, domain) if domain else self.get_roles_for_user(r) - res.extend(_roles) - return res - - def get_implicit_permissions_for_user(self, user, domain=None): - """ - gets implicit permissions for a user or role. - Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. - For example: - p, admin, data1, read - p, alice, data2, read - g, alice, admin - - get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. - But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. - """ - roles = self.get_implicit_roles_for_user(user, domain) - permissions = self.get_permissions_for_user_in_domain(user, - domain) if domain else self.get_permissions_for_user(user) - for role in roles: - _permissions = self.get_permissions_for_user_in_domain(role, - domain) if domain else self.get_permissions_for_user( - role) - for item in _permissions: - if item not in permissions: - permissions.append(item) - return permissions + pass \ No newline at end of file diff --git a/casbin/rbac_enforcer.py b/casbin/rbac_enforcer.py index 09bb9086..4d16f7e6 100644 --- a/casbin/rbac_enforcer.py +++ b/casbin/rbac_enforcer.py @@ -8,12 +8,12 @@ class RBACEnforcer(ManagementEnforcer): def get_roles_for_user(self, name): ''' gets the roles that a user has. ''' - res = self.model["g"]["g"].rm.get_roles(name) + res = self.model.model["g"]["g"].rm.get_roles(name) return res def get_users_for_role(self, name): ''' gets the users that has a role. ''' - res = self.model["g"]["g"].rm.get_roles(name) + res = self.model.model["g"]["g"].rm.get_users(name) return res def has_role_for_user(self, name, role): @@ -90,7 +90,7 @@ def delete_permission_for_user(self, user, *permission): ''' return self.remove_policy(join_slice(user, *permission)) - def delete_permission_for_user(self, user): + def delete_permissions_for_user(self, user): ''' deletes permissions for a user or role. Returns false if the user or role does not have any permissions (aka not affected). @@ -103,7 +103,7 @@ def get_permissions_for_user(self, user): ''' return self.get_filtered_policy(0, user) - def HasPermissionForUser(self, user, *permission): + def has_permission_for_user(self, user, *permission): ''' determines whether a user has a permission. ''' @@ -154,7 +154,7 @@ def get_implicit_permissions_for_user(self, user, *domain): ''' roles = self.get_implicit_roles_for_user(user, *domain) - roles.append(user, *roles) + roles.insert(0, user) withDomain = False if len(domain) == 1: @@ -162,18 +162,18 @@ def get_implicit_permissions_for_user(self, user, *domain): elif len(domain) > 1: return None - res = [list()] + res = [] permissions = [list()] for role in roles: if withDomain: permissions = self.get_permissions_for_user_in_domain(role, domain[0]) else: permissions = self.get_permissions_for_user(role) - res.append(*permissions) + res.extend(permissions) return res - def get_implicit_users_for_per(self, *permission): + def get_implicit_users_for_permission(self, *permission): ''' gets implicit users for a permission. For example: diff --git a/casbin/rbac_with_domains_enforcer.py b/casbin/rbac_with_domains_enforcer.py new file mode 100644 index 00000000..2d139138 --- /dev/null +++ b/casbin/rbac_with_domains_enforcer.py @@ -0,0 +1,33 @@ +from casbin.rbac_enforcer import RBACEnforcer + +class RBACWithDomainsEnforcer(RBACEnforcer): + """ + RBACWithDomainsEnforcer = RBAC_With_Domains_API + RBACEnforcer. + """ + + def get_roles_for_user_in_domain(self, name, domain): + """gets the roles that a user has inside a domain.""" + res = self.model.model['g']['g'].rm.get_roles(name, domain) + return res + + def get_users_for_role_in_domain(self, name, domain): + """gets the users that has a role inside a domain.""" + res = self.model.model['g']['g'].rm.get_users(name, domain) + return res + + def add_role_for_user_in_domain(self, user, role, domain): + """adds a role for a user inside a domain.""" + """Returns false if the user already has the role (aka not affected).""" + res = self.add_grouping_policy(user, role, domain) + return res + + def delete_roles_for_user_in_domain(self, user, role, domain): + """deletes a role for a user inside a domain.""" + """Returns false if the user does not have any roles (aka not affected).""" + res = self.remove_filtered_grouping_policy(0, user, role, domain) + return res + + def get_permissions_for_user_in_domain(self, user, domain): + """gets permissions for a user or role inside domain.""" + res = self.get_filtered_policy(0, user, domain) + return res \ No newline at end of file diff --git a/casbin/util/util.py b/casbin/util/util.py index d561eb7f..bf2c4926 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -38,9 +38,9 @@ def params_to_string(*s): def join_slice(a, *b): ''' joins a string and a slice into a new slice.''' - res = [] + res = [a] - res.append(a, *b) + res.extend(b) return res From 19ddfef1b2f1f2ee3f430c66ad9c7878c0ac747d Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Tue, 21 Apr 2020 16:38:46 +0530 Subject: [PATCH 065/349] Code structure changes. --- casbin/enforcer.py | 229 ++++++++++++++++++++++++++- casbin/rbac_enforcer.py | 200 ----------------------- casbin/rbac_with_domains_enforcer.py | 33 ---- 3 files changed, 224 insertions(+), 238 deletions(-) delete mode 100644 casbin/rbac_enforcer.py delete mode 100644 casbin/rbac_with_domains_enforcer.py diff --git a/casbin/enforcer.py b/casbin/enforcer.py index d4bdfc45..aedd2bc1 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,9 +1,9 @@ -from casbin.rbac_with_domains_enforcer import RBACWithDomainsEnforcer +from casbin.management_enforcer import ManagementEnforcer +from casbin.util import join_slice, set_subtract - -class Enforcer(RBACWithDomainsEnforcer): +class Enforcer(ManagementEnforcer): """ - Enforcer = RBACWithDomainsEnforcer + Enforcer = ManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API """ """creates an enforcer via file or DB. @@ -14,4 +14,223 @@ class Enforcer(RBACWithDomainsEnforcer): a = mysqladapter.DBAdapter("mysql", "mysql_username:mysql_password@tcp(127.0.0.1:3306)/") e = casbin.Enforcer("path/to/basic_model.conf", a) """ - pass \ No newline at end of file + + def get_roles_for_user(self, name): + ''' gets the roles that a user has. ''' + res = self.model.model["g"]["g"].rm.get_roles(name) + return res + + def get_users_for_role(self, name): + ''' gets the users that has a role. ''' + res = self.model.model["g"]["g"].rm.get_users(name) + return res + + def has_role_for_user(self, name, role): + ''' determines whether a user has a role. ''' + roles = self.get_roles_for_user(name) + + hasRole = False + for r in roles: + if r == role: + hasRole = True + break + + return hasRole + + def add_role_for_user(self, user, role): + ''' + adds a role for a user. + Returns false if the user already has the role (aka not affected). + ''' + return self.add_grouping_policy(user, role) + + def delete_role_for_user(self, user, role): + ''' + deletes a role for a user. + Returns false if the user does not have the role (aka not affected). + ''' + return self.remove_grouping_policy(user, role) + + def delete_roles_for_user(self, user): + ''' + deletes all roles for a user. + Returns false if the user does not have any roles (aka not affected). + ''' + return self.remove_filtered_grouping_policy(0, user) + + def delete_user(self, user): + ''' + deletes a user. + Returns false if the user does not exist (aka not affected). + ''' + res1 = self.remove_filtered_grouping_policy(0, user) + + res2 = self.remove_filtered_policy(0, user) + return res1 or res2 + + def delete_role(self, role): + ''' + deletes a role. + Returns false if the role does not exist (aka not affected). + ''' + res1 = self.remove_filtered_grouping_policy(1, role) + + res2 = self.remove_filtered_policy(0, role) + return res1 or res2 + + def delete_permission(self, *permission): + ''' + deletes a permission. + Returns false if the permission does not exist (aka not affected). + ''' + return self.remove_filtered_policy(1, *permission) + + def add_permission_for_user(self, user, *permission): + ''' + adds a permission for a user or role. + Returns false if the user or role already has the permission (aka not affected). + ''' + return self.add_policy(join_slice(user, *permission)) + + def delete_permission_for_user(self, user, *permission): + ''' + deletes a permission for a user or role. + Returns false if the user or role does not have the permission (aka not affected). + ''' + return self.remove_policy(join_slice(user, *permission)) + + def delete_permissions_for_user(self, user): + ''' + deletes permissions for a user or role. + Returns false if the user or role does not have any permissions (aka not affected). + ''' + return self.remove_filtered_policy(0, user) + + def get_permissions_for_user(self, user): + ''' + gets permissions for a user or role. + ''' + return self.get_filtered_policy(0, user) + + def has_permission_for_user(self, user, *permission): + ''' + determines whether a user has a permission. + ''' + return self.has_policy(join_slice(user, *permission)) + + def get_implicit_roles_for_user(self, name, *domain): + ''' + gets implicit roles that a user has. + Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. + For example: + g, alice, role:admin + g, role:admin, role:user + + get_roles_for_user("alice") can only get: ["role:admin"]. + But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. + ''' + res = list() + roleSet = dict() + roleSet[name] = True + + q = list() + q.append(name) + + while len(q) > 0: + name = q[0] + q = q[1:] + + roles = self.rm.get_roles(name, *domain) + for r in roles: + if r not in roleSet: + res.append(r) + q.append(r) + roleSet[r] = True + + return res + + def get_implicit_permissions_for_user(self, user, *domain): + ''' + gets implicit permissions for a user or role. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + ''' + roles = self.get_implicit_roles_for_user(user, *domain) + + roles.insert(0, user) + + withDomain = False + if len(domain) == 1: + withDomain = True + elif len(domain) > 1: + return None + + res = [] + permissions = [list()] + for role in roles: + if withDomain: + permissions = self.get_permissions_for_user_in_domain(role, domain[0]) + else: + permissions = self.get_permissions_for_user(role) + res.extend(permissions) + + return res + + def get_implicit_users_for_permission(self, *permission): + ''' + gets implicit users for a permission. + For example: + p, admin, data1, read + p, bob, data1, read + g, alice, admin + + get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. + Note: only users will be returned, roles (2nd arg in "g") will be excluded. + ''' + subjects = self.get_all_subjects() + roles = self.get_all_roles() + + users = set_subtract(subjects, roles) + + res = list() + for user in users: + req = join_slice(user, *permission) + allowed = self.enforce(*req) + + if allowed: + res.append(user) + + return res + + def get_roles_for_user_in_domain(self, name, domain): + """gets the roles that a user has inside a domain.""" + res = self.model.model['g']['g'].rm.get_roles(name, domain) + return res + + def get_users_for_role_in_domain(self, name, domain): + """gets the users that has a role inside a domain.""" + res = self.model.model['g']['g'].rm.get_users(name, domain) + return res + + def add_role_for_user_in_domain(self, user, role, domain): + """adds a role for a user inside a domain.""" + """Returns false if the user already has the role (aka not affected).""" + res = self.add_grouping_policy(user, role, domain) + return res + + def delete_roles_for_user_in_domain(self, user, role, domain): + """deletes a role for a user inside a domain.""" + """Returns false if the user does not have any roles (aka not affected).""" + res = self.remove_filtered_grouping_policy(0, user, role, domain) + return res + + def get_permissions_for_user_in_domain(self, user, domain): + """gets permissions for a user or role inside domain.""" + res = self.get_filtered_policy(0, user, domain) + return res \ No newline at end of file diff --git a/casbin/rbac_enforcer.py b/casbin/rbac_enforcer.py deleted file mode 100644 index 4d16f7e6..00000000 --- a/casbin/rbac_enforcer.py +++ /dev/null @@ -1,200 +0,0 @@ -from casbin.management_enforcer import ManagementEnforcer -from casbin.util import join_slice, set_subtract - -class RBACEnforcer(ManagementEnforcer): - """ - RBACEnforcer = RBAC_API + ManagementEnforcer. - """ - - def get_roles_for_user(self, name): - ''' gets the roles that a user has. ''' - res = self.model.model["g"]["g"].rm.get_roles(name) - return res - - def get_users_for_role(self, name): - ''' gets the users that has a role. ''' - res = self.model.model["g"]["g"].rm.get_users(name) - return res - - def has_role_for_user(self, name, role): - ''' determines whether a user has a role. ''' - roles = self.get_roles_for_user(name) - - hasRole = False - for r in roles: - if r == role: - hasRole = True - break - - return hasRole - - def add_role_for_user(self, user, role): - ''' - adds a role for a user. - Returns false if the user already has the role (aka not affected). - ''' - return self.add_grouping_policy(user, role) - - def delete_role_for_user(self, user, role): - ''' - deletes a role for a user. - Returns false if the user does not have the role (aka not affected). - ''' - return self.remove_grouping_policy(user, role) - - def delete_roles_for_user(self, user): - ''' - deletes all roles for a user. - Returns false if the user does not have any roles (aka not affected). - ''' - return self.remove_filtered_grouping_policy(0, user) - - def delete_user(self, user): - ''' - deletes a user. - Returns false if the user does not exist (aka not affected). - ''' - res1 = self.remove_filtered_grouping_policy(0, user) - - res2 = self.remove_filtered_policy(0, user) - return res1 or res2 - - def delete_role(self, role): - ''' - deletes a role. - Returns false if the role does not exist (aka not affected). - ''' - res1 = self.remove_filtered_grouping_policy(1, role) - - res2 = self.remove_filtered_policy(0, role) - return res1 or res2 - - def delete_permission(self, *permission): - ''' - deletes a permission. - Returns false if the permission does not exist (aka not affected). - ''' - return self.remove_filtered_policy(1, *permission) - - def add_permission_for_user(self, user, *permission): - ''' - adds a permission for a user or role. - Returns false if the user or role already has the permission (aka not affected). - ''' - return self.add_policy(join_slice(user, *permission)) - - def delete_permission_for_user(self, user, *permission): - ''' - deletes a permission for a user or role. - Returns false if the user or role does not have the permission (aka not affected). - ''' - return self.remove_policy(join_slice(user, *permission)) - - def delete_permissions_for_user(self, user): - ''' - deletes permissions for a user or role. - Returns false if the user or role does not have any permissions (aka not affected). - ''' - return self.remove_filtered_policy(0, user) - - def get_permissions_for_user(self, user): - ''' - gets permissions for a user or role. - ''' - return self.get_filtered_policy(0, user) - - def has_permission_for_user(self, user, *permission): - ''' - determines whether a user has a permission. - ''' - return self.has_policy(join_slice(user, *permission)) - - def get_implicit_roles_for_user(self, name, *domain): - ''' - gets implicit roles that a user has. - Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. - For example: - g, alice, role:admin - g, role:admin, role:user - - get_roles_for_user("alice") can only get: ["role:admin"]. - But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. - ''' - res = list() - roleSet = dict() - roleSet[name] = True - - q = list() - q.append(name) - - while len(q) > 0: - name = q[0] - q = q[1:] - - roles = self.rm.get_roles(name, *domain) - for r in roles: - if r not in roleSet: - res.append(r) - q.append(r) - roleSet[r] = True - - return res - - def get_implicit_permissions_for_user(self, user, *domain): - ''' - gets implicit permissions for a user or role. - Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. - For example: - p, admin, data1, read - p, alice, data2, read - g, alice, admin - - get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. - But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. - ''' - roles = self.get_implicit_roles_for_user(user, *domain) - - roles.insert(0, user) - - withDomain = False - if len(domain) == 1: - withDomain = True - elif len(domain) > 1: - return None - - res = [] - permissions = [list()] - for role in roles: - if withDomain: - permissions = self.get_permissions_for_user_in_domain(role, domain[0]) - else: - permissions = self.get_permissions_for_user(role) - res.extend(permissions) - - return res - - def get_implicit_users_for_permission(self, *permission): - ''' - gets implicit users for a permission. - For example: - p, admin, data1, read - p, bob, data1, read - g, alice, admin - - get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. - Note: only users will be returned, roles (2nd arg in "g") will be excluded. - ''' - subjects = self.get_all_subjects() - roles = self.get_all_roles() - - users = set_subtract(subjects, roles) - - res = list() - for user in users: - req = join_slice(user, *permission) - allowed = self.enforce(*req) - - if allowed: - res.append(user) - - return res \ No newline at end of file diff --git a/casbin/rbac_with_domains_enforcer.py b/casbin/rbac_with_domains_enforcer.py deleted file mode 100644 index 2d139138..00000000 --- a/casbin/rbac_with_domains_enforcer.py +++ /dev/null @@ -1,33 +0,0 @@ -from casbin.rbac_enforcer import RBACEnforcer - -class RBACWithDomainsEnforcer(RBACEnforcer): - """ - RBACWithDomainsEnforcer = RBAC_With_Domains_API + RBACEnforcer. - """ - - def get_roles_for_user_in_domain(self, name, domain): - """gets the roles that a user has inside a domain.""" - res = self.model.model['g']['g'].rm.get_roles(name, domain) - return res - - def get_users_for_role_in_domain(self, name, domain): - """gets the users that has a role inside a domain.""" - res = self.model.model['g']['g'].rm.get_users(name, domain) - return res - - def add_role_for_user_in_domain(self, user, role, domain): - """adds a role for a user inside a domain.""" - """Returns false if the user already has the role (aka not affected).""" - res = self.add_grouping_policy(user, role, domain) - return res - - def delete_roles_for_user_in_domain(self, user, role, domain): - """deletes a role for a user inside a domain.""" - """Returns false if the user does not have any roles (aka not affected).""" - res = self.remove_filtered_grouping_policy(0, user, role, domain) - return res - - def get_permissions_for_user_in_domain(self, user, domain): - """gets permissions for a user or role inside domain.""" - res = self.get_filtered_policy(0, user, domain) - return res \ No newline at end of file From 18af83a11170175224ee7272464a8bc031a023c3 Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Wed, 22 Apr 2020 11:45:51 +0530 Subject: [PATCH 066/349] Comment formatting changes. --- casbin/enforcer.py | 83 +++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 45 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index aedd2bc1..ee8d0e7e 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -16,17 +16,15 @@ class Enforcer(ManagementEnforcer): """ def get_roles_for_user(self, name): - ''' gets the roles that a user has. ''' - res = self.model.model["g"]["g"].rm.get_roles(name) - return res + """ gets the roles that a user has. """ + return self.model.model["g"]["g"].rm.get_roles(name) def get_users_for_role(self, name): - ''' gets the users that has a role. ''' - res = self.model.model["g"]["g"].rm.get_users(name) - return res + """ gets the users that has a role. """ + return self.model.model["g"]["g"].rm.get_users(name) def has_role_for_user(self, name, role): - ''' determines whether a user has a role. ''' + """ determines whether a user has a role. """ roles = self.get_roles_for_user(name) hasRole = False @@ -38,88 +36,88 @@ def has_role_for_user(self, name, role): return hasRole def add_role_for_user(self, user, role): - ''' + """ adds a role for a user. Returns false if the user already has the role (aka not affected). - ''' + """ return self.add_grouping_policy(user, role) def delete_role_for_user(self, user, role): - ''' + """ deletes a role for a user. Returns false if the user does not have the role (aka not affected). - ''' + """ return self.remove_grouping_policy(user, role) def delete_roles_for_user(self, user): - ''' + """ deletes all roles for a user. Returns false if the user does not have any roles (aka not affected). - ''' + """ return self.remove_filtered_grouping_policy(0, user) def delete_user(self, user): - ''' + """ deletes a user. Returns false if the user does not exist (aka not affected). - ''' + """ res1 = self.remove_filtered_grouping_policy(0, user) res2 = self.remove_filtered_policy(0, user) return res1 or res2 def delete_role(self, role): - ''' + """ deletes a role. Returns false if the role does not exist (aka not affected). - ''' + """ res1 = self.remove_filtered_grouping_policy(1, role) res2 = self.remove_filtered_policy(0, role) return res1 or res2 def delete_permission(self, *permission): - ''' + """ deletes a permission. Returns false if the permission does not exist (aka not affected). - ''' + """ return self.remove_filtered_policy(1, *permission) def add_permission_for_user(self, user, *permission): - ''' + """ adds a permission for a user or role. Returns false if the user or role already has the permission (aka not affected). - ''' + """ return self.add_policy(join_slice(user, *permission)) def delete_permission_for_user(self, user, *permission): - ''' + """ deletes a permission for a user or role. Returns false if the user or role does not have the permission (aka not affected). - ''' + """ return self.remove_policy(join_slice(user, *permission)) def delete_permissions_for_user(self, user): - ''' + """ deletes permissions for a user or role. Returns false if the user or role does not have any permissions (aka not affected). - ''' + """ return self.remove_filtered_policy(0, user) def get_permissions_for_user(self, user): - ''' + """ gets permissions for a user or role. - ''' + """ return self.get_filtered_policy(0, user) def has_permission_for_user(self, user, *permission): - ''' + """ determines whether a user has a permission. - ''' + """ return self.has_policy(join_slice(user, *permission)) def get_implicit_roles_for_user(self, name, *domain): - ''' + """ gets implicit roles that a user has. Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. For example: @@ -128,7 +126,7 @@ def get_implicit_roles_for_user(self, name, *domain): get_roles_for_user("alice") can only get: ["role:admin"]. But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. - ''' + """ res = list() roleSet = dict() roleSet[name] = True @@ -150,7 +148,7 @@ def get_implicit_roles_for_user(self, name, *domain): return res def get_implicit_permissions_for_user(self, user, *domain): - ''' + """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. For example: @@ -160,7 +158,7 @@ def get_implicit_permissions_for_user(self, user, *domain): get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. - ''' + """ roles = self.get_implicit_roles_for_user(user, *domain) roles.insert(0, user) @@ -183,7 +181,7 @@ def get_implicit_permissions_for_user(self, user, *domain): return res def get_implicit_users_for_permission(self, *permission): - ''' + """ gets implicit users for a permission. For example: p, admin, data1, read @@ -192,7 +190,7 @@ def get_implicit_users_for_permission(self, *permission): get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. Note: only users will be returned, roles (2nd arg in "g") will be excluded. - ''' + """ subjects = self.get_all_subjects() roles = self.get_all_roles() @@ -210,27 +208,22 @@ def get_implicit_users_for_permission(self, *permission): def get_roles_for_user_in_domain(self, name, domain): """gets the roles that a user has inside a domain.""" - res = self.model.model['g']['g'].rm.get_roles(name, domain) - return res + return self.model.model['g']['g'].rm.get_roles(name, domain) def get_users_for_role_in_domain(self, name, domain): """gets the users that has a role inside a domain.""" - res = self.model.model['g']['g'].rm.get_users(name, domain) - return res + return self.model.model['g']['g'].rm.get_users(name, domain) def add_role_for_user_in_domain(self, user, role, domain): """adds a role for a user inside a domain.""" """Returns false if the user already has the role (aka not affected).""" - res = self.add_grouping_policy(user, role, domain) - return res + return self.add_grouping_policy(user, role, domain) def delete_roles_for_user_in_domain(self, user, role, domain): """deletes a role for a user inside a domain.""" """Returns false if the user does not have any roles (aka not affected).""" - res = self.remove_filtered_grouping_policy(0, user, role, domain) - return res + return self.remove_filtered_grouping_policy(0, user, role, domain) def get_permissions_for_user_in_domain(self, user, domain): """gets permissions for a user or role inside domain.""" - res = self.get_filtered_policy(0, user, domain) - return res \ No newline at end of file + return self.get_filtered_policy(0, user, domain) \ No newline at end of file From 3b1b38e1d360b55c33381c9992924a05eda6b8ed Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Sat, 25 Apr 2020 12:33:53 +0530 Subject: [PATCH 067/349] Added test for get_implicit_users_for_permission --- casbin/util/util.py | 2 +- tests/test_rbac_api.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/casbin/util/util.py b/casbin/util/util.py index bf2c4926..96aefc54 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -53,7 +53,7 @@ def set_subtract(a, b): diff = list() for x in a: - if x in mb: + if x not in mb: diff.append(x) return diff diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 5a829842..90bdf9fe 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -205,3 +205,11 @@ def test_enforce_get_roles_with_domain(self): self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin']) self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), []) self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) + + def test_implicit_user_api(self): + e = get_enforcer(get_examples("rbac_model.conf"),get_examples("rbac_with_hierarchy_policy.csv")) + + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "read")) + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "write")) + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read")) + self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) \ No newline at end of file From 5f1775cb9f60e3fdde3e33b132f79981ea5cebf7 Mon Sep 17 00:00:00 2001 From: DivyPatel9881 Date: Tue, 28 Apr 2020 11:28:49 +0530 Subject: [PATCH 068/349] Added support for without space CONF model. --- casbin/model/model.py | 4 ++-- examples/basic_model_without_spaces.conf | 11 +++++++++++ tests/test_enforcer.py | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 examples/basic_model_without_spaces.conf diff --git a/casbin/model/model.py b/casbin/model/model.py index 4e55502b..6424cd22 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -27,9 +27,9 @@ def add_def(self, sec, key, value): ast.value = value if "r" == sec or "p" == sec: - ast.tokens = ast.value.split(", ") + ast.tokens = ast.value.split(",") for i,token in enumerate(ast.tokens): - ast.tokens[i] = key + "_" + token + ast.tokens[i] = key + "_" + token.strip() else: ast.value = util.remove_comments(util.escape_assertion(ast.value)) diff --git a/examples/basic_model_without_spaces.conf b/examples/basic_model_without_spaces.conf new file mode 100644 index 00000000..5452f954 --- /dev/null +++ b/examples/basic_model_without_spaces.conf @@ -0,0 +1,11 @@ +[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 \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index b1dab828..78dde265 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -29,6 +29,22 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertFalse(e.enforce('bob', 'data1', 'write')) + def test_enforcer_basic_without_spaces(self): + e = get_enforcer( + get_examples("basic_model_without_spaces.conf"), + get_examples("basic_policy.csv"), + # True, + ) + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + def test_enforce_basic_with_root(self): e = get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) self.assertTrue(e.enforce('root', 'any', 'any')) From d2609fa7dc739410f2f3c5b55739ebc41b647b78 Mon Sep 17 00:00:00 2001 From: divy9881 Date: Thu, 16 Jul 2020 12:45:16 +0530 Subject: [PATCH 069/349] fix: add casbin-cpp to supported languages. Signed-off-by: divy9881 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4e329fd1..35a3242f 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ Casbin is a powerful and efficient open-source access control library for Python [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-net/Casbin.NET) | [![delphi](https://casbin.org/img/langs/delphi.png)](https://github.com/casbin4d/Casbin4D) | [![rust](https://casbin.org/img/langs/rust.png)](https://github.com/casbin/casbin-rs) +[![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-net/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-net/Casbin.NET) | [Casbin4D](https://github.com/casbin4d/Casbin4D) | [Casbin-RS](https://github.com/casbin/casbin-rs) -production-ready | production-ready | experimental | production-ready +[PyCasbin](https://github.com/casbin/pycasbin) | [Casbin.NET](https://github.com/casbin-net/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 ## Table of contents From d7b9c66da871b1d39548769e025f01cc5a04d632 Mon Sep 17 00:00:00 2001 From: Cameron Hurst Date: Tue, 11 Aug 2020 07:05:55 -0400 Subject: [PATCH 070/349] feat: added support for filtered policies Signed-off-by: Cameron Hurst --- casbin/core_enforcer.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index d705f68b..fd2efe93 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -59,7 +59,7 @@ def init_with_model_and_adapter(self, m, adapter=None): self._initialize() # Do not initialize the full policy when using a filtered adapter - if self.adapter: + if self.adapter and not self.is_filtered(): self.load_policy() def _initialize(self): @@ -147,13 +147,20 @@ def load_policy(self): def load_filtered_policy(self, filter): """reloads a filtered policy from file/database.""" + self.model.clear_policy() - pass + if not hasattr(self.adapter, "is_filtered"): + raise ValueError("filtered policies are not supported by this adapter") + + self.adapter.load_filtered_policy(self.model, filter) + self.model.print_policy() + if self.auto_build_role_links: + self.build_role_links() def is_filtered(self): """returns true if the loaded policy has been filtered.""" - pass + return hasattr(self.adapter, "is_filtered") and self.adapter.is_filtered() def save_policy(self): if self.is_filtered(): From 06c54b5420fb74542221e4a6c474669ce18ebd18 Mon Sep 17 00:00:00 2001 From: Cameron Hurst Date: Sat, 3 Oct 2020 14:11:24 -0400 Subject: [PATCH 071/349] fix: added filtered policy functions to defaults Resolves #73 Signed-off-by: Cameron Hurst --- casbin/persist/adapter.py | 8 ++++++++ casbin/persist/adapters/file_adapter.py | 10 ++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index b290be2c..6ecb7d9a 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -44,3 +44,11 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): This is part of the Auto-Save feature. """ pass + + def is_filtered(self): + """Marks if the loaded policy is filtered or not""" + pass + + def load_filtered_policy(self, model, filter): + """Loads policy rules that match the filter from the storage.""" + pass diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index 29514e91..805f34b8 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -38,12 +38,12 @@ def _save_policy_file(self, model): if "p" in model.model.keys(): for key, ast in model.model["p"].items(): for pvals in ast.policy: - lines.append(key + ', ' + ', '.join(pvals)) + lines.append(key + ", " + ", ".join(pvals)) if "g" in model.model.keys(): for key, ast in model.model["g"].items(): for pvals in ast.policy: - lines.append(key + ', ' + ', '.join(pvals)) + lines.append(key + ", " + ", ".join(pvals)) for i, line in enumerate(lines): if i != len(lines) - 1: @@ -59,3 +59,9 @@ def remove_policy(self, sec, ptype, rule): def remove_filtered_policy(self, sec, ptype, field_index, *field_values): pass + + def is_filtered(self): + return False + + def load_filtered_policy(self, model, filter): + pass From 38767ae73f8926a46ff3ce7758c9809da5cd41a9 Mon Sep 17 00:00:00 2001 From: Foufou Date: Fri, 9 Oct 2020 12:19:24 +0800 Subject: [PATCH 072/349] Bump to version 0.9.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2faa389b..10b0adf3 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.8.4", + version="0.9.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From ab7d1a6eb282ca56a94816696edb1fe632e12039 Mon Sep 17 00:00:00 2001 From: Elijah Wilson Date: Wed, 14 Oct 2020 15:10:42 -0400 Subject: [PATCH 073/349] Add support for N role link values Signed-off-by: Elijah Wilson --- casbin/model/assertion.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 77aac9c8..f2a49a63 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -20,12 +20,7 @@ def build_role_links(self, rm): if len(rule) < count: raise RuntimeError("grouping policy elements do not meet role definition") - if count == 2: - self.rm.add_link(rule[0], rule[1]) - elif count == 3: - self.rm.add_link(rule[0], rule[1], rule[2]) - elif count == 4: - self.rm.add_link(rule[0], rule[1], rule[2], rule[3]) + self.rm.add_link(*rule[:count]) log.log_print("Role links for: " + self.key) self.rm.print_roles() From fa3ed257638b97434e285d936d5a82290e09222f Mon Sep 17 00:00:00 2001 From: yyellowsun Date: Wed, 28 Oct 2020 10:51:57 +0800 Subject: [PATCH 074/349] add not operator Signed-off-by: yyellowsun --- casbin/core_enforcer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index fd2efe93..abf40286 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -299,5 +299,6 @@ def enforce(self, *rvals): def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") expr = expr.replace("||", "or") + expr = expr.replace("!","not") return SimpleEval(expr, functions) From 040c3fbde868d21a9340bd4ed73db01ba7f02358 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Wed, 4 Nov 2020 22:15:51 +0800 Subject: [PATCH 075/349] Fix code sample typo in README. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 35a3242f..02e41084 100644 --- a/README.md +++ b/README.md @@ -177,7 +177,7 @@ else: 3. Besides the static policy file, Casbin also provides API for permission management at run-time. For example, You can get all the roles assigned to a user as below: ```python -roles = e.get_roles("alice") +roles = e.get_roles_for_user("alice") ``` See [Policy management APIs](#policy-management) for more usage. From 0748dec309dc633b66f1a5eeefd6331fb07bbf07 Mon Sep 17 00:00:00 2001 From: Zixuan Liu Date: Thu, 5 Nov 2020 15:31:37 +0800 Subject: [PATCH 076/349] fix: improve KeyMatch and add tests Signed-off-by: Zixuan Liu --- casbin/util/builtin_operators.py | 25 ++++-------- tests/util/test_builtin_operators.py | 59 ++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 694e9e67..de151c8b 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -1,9 +1,8 @@ import re import ipaddress - -KEY_MATCH2_PATTERN = re.compile(r'(.*):[^\/]+(.*)') -KEY_MATCH3_PATTERN = re.compile(r'(.*){[^\/]+}(.*)') +KEY_MATCH2_PATTERN = re.compile(r'(.*?):[^\/]+(.*?)') +KEY_MATCH3_PATTERN = re.compile(r'(.*?){[^\/]+}(.*?)') def key_match(key1, key2): @@ -35,14 +34,9 @@ def key_match2(key1, key2): """ key2 = key2.replace("/*", "/.*") + key2 = KEY_MATCH2_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) - while True: - if "/:" not in key2: - break - - key2 = "^" + KEY_MATCH2_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + "$" - - return regex_match(key1, key2) + return regex_match(key1, "^" + key2 + "$") def key_match2_func(*args): @@ -58,14 +52,9 @@ def key_match3(key1, key2): """ key2 = key2.replace("/*", "/.*") + key2 = KEY_MATCH3_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) - while True: - if "{" not in key2: - break - - key2 = KEY_MATCH3_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) - - return regex_match(key1, key2) + return regex_match(key1, "^" + key2 + "$") def key_match3_func(*args): @@ -100,7 +89,7 @@ def ip_match(ip1, ip2): """ ip1 = ipaddress.ip_address(ip1) try: - network = ipaddress.ip_network(ip2, strict=True) + network = ipaddress.ip_network(ip2, strict=False) return ip1 in network except ValueError: return ip1 == ip2 diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index c57e2ecc..74e2a0aa 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -5,6 +5,7 @@ class TestBuiltinOperators(TestCase): def test_key_match(self): + self.assertFalse(util.key_match_func("/foo", "/")) self.assertTrue(util.key_match_func("/foo", "/foo")) self.assertTrue(util.key_match_func("/foo", "/foo*")) self.assertFalse(util.key_match_func("/foo", "/foo/*")) @@ -15,15 +16,18 @@ def test_key_match(self): self.assertTrue(util.key_match_func("/foobar", "/foo*")) self.assertFalse(util.key_match_func("/foobar", "/foo/*")) + self.assertFalse(util.key_match2_func("/alice/all", "/:/all")) + def test_key_match2(self): + self.assertFalse(util.key_match2_func("/foo", "/")) self.assertTrue(util.key_match2_func("/foo", "/foo")) self.assertTrue(util.key_match2_func("/foo", "/foo*")) self.assertFalse(util.key_match2_func("/foo", "/foo/*")) - self.assertTrue(util.key_match2_func("/foo/bar", "/foo")) # different with KeyMatch. - self.assertTrue(util.key_match2_func("/foo/bar", "/foo*")) + self.assertFalse(util.key_match2_func("/foo/bar", "/foo")) # different with KeyMatch. + self.assertFalse(util.key_match2_func("/foo/bar", "/foo*")) self.assertTrue(util.key_match2_func("/foo/bar", "/foo/*")) - self.assertTrue(util.key_match2_func("/foobar", "/foo")) # different with KeyMatch. - self.assertTrue(util.key_match2_func("/foobar", "/foo*")) + self.assertFalse(util.key_match2_func("/foobar", "/foo")) # different with KeyMatch. + self.assertFalse(util.key_match2_func("/foobar", "/foo*")) self.assertFalse(util.key_match2_func("/foobar", "/foo/*")) self.assertFalse(util.key_match2_func("/", "/:resource")) @@ -42,3 +46,50 @@ def test_key_match2(self): self.assertTrue(util.key_match2_func("/alice/all", "/:id/all")) self.assertFalse(util.key_match2_func("/alice", "/:id/all")) self.assertFalse(util.key_match2_func("/alice/all", "/:id")) + + self.assertFalse(util.key_match2_func("/alice/all", "/:/all")) + + def test_key_match3(self): + self.assertTrue(util.key_match3_func("/foo", "/foo")) + self.assertTrue(util.key_match3_func("/foo", "/foo*")) + self.assertFalse(util.key_match3_func("/foo", "/foo/*")) + self.assertFalse(util.key_match3_func("/foo/bar", "/foo")) + self.assertFalse(util.key_match3_func("/foo/bar", "/foo*")) + self.assertTrue(util.key_match3_func("/foo/bar", "/foo/*")) + self.assertFalse(util.key_match3_func("/foobar", "/foo")) + self.assertFalse(util.key_match3_func("/foobar", "/foo*")) + self.assertFalse(util.key_match3_func("/foobar", "/foo/*")) + + self.assertFalse(util.key_match3_func("/", "/{resource}")) + self.assertTrue(util.key_match3_func("/resource1", "/{resource}")) + self.assertFalse(util.key_match3_func("/myid", "/{id}/using/{resId}")) + self.assertTrue(util.key_match3_func("/myid/using/myresid", "/{id}/using/{resId}")) + + self.assertFalse(util.key_match3_func("/proxy/myid", "/proxy/{id}/*")) + self.assertTrue(util.key_match3_func("/proxy/myid/", "/proxy/{id}/*")) + self.assertTrue(util.key_match3_func("/proxy/myid/res", "/proxy/{id}/*")) + self.assertTrue(util.key_match3_func("/proxy/myid/res/res2", "/proxy/{id}/*")) + self.assertTrue(util.key_match3_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*")) + self.assertFalse(util.key_match3_func("/proxy/", "/proxy/{id}/*")) + + self.assertFalse(util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}")) + + def test_regex_match(self): + self.assertTrue(util.regex_match_func("/topic/create", "/topic/create")) + self.assertTrue(util.regex_match_func("/topic/create/123", "/topic/create")) + self.assertFalse(util.regex_match_func("/topic/delete", "/topic/create")) + self.assertFalse(util.regex_match_func("/topic/edit", "/topic/edit/[0-9]+")) + self.assertTrue(util.regex_match_func("/topic/edit/123", "/topic/edit/[0-9]+")) + self.assertFalse(util.regex_match_func("/topic/edit/abc", "/topic/edit/[0-9]+")) + self.assertFalse(util.regex_match_func("/foo/delete/123", "/topic/delete/[0-9]+")) + self.assertTrue(util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+")) + self.assertFalse(util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+")) + + def test_ip_match(self): + self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.0/24")) + self.assertFalse(util.ip_match_func("192.168.2.123", "192.168.3.0/24")) + self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.0/16")) + self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.123")) + self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.123/32")) + self.assertTrue(util.ip_match_func("10.0.0.11", "10.0.0.0/8")) + self.assertFalse(util.ip_match_func("11.0.0.123", "10.0.0.0/8")) From a34e160434975aff65be5dd00e705a28dfe35506 Mon Sep 17 00:00:00 2001 From: yyellowsun Date: Tue, 10 Nov 2020 00:41:56 +0800 Subject: [PATCH 077/349] feat: support semantic-release Signed-off-by: yyellowsun --- .github/workflows/release.yml | 26 +++++++++++++++++++++++++ .releaserc | 23 ++++++++++++++++++++++ package.json | 36 +++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 .github/workflows/release.yml create mode 100644 .releaserc create mode 100644 package.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..cc6a0bbc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: release +on: + push: + branches: + - master +jobs: + release: + name: Release + runs-on: ubuntu-18.04 + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v1 + + - name: Setup + run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/npm @semantic-release/git @semantic-release/release-notes-generator + + - name: Rlease + run: npx semantic-release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.releaserc b/.releaserc new file mode 100644 index 00000000..ab7f426a --- /dev/null +++ b/.releaserc @@ -0,0 +1,23 @@ +{ + "branch": "master", + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/npm", + "@semantic-release/github", + "@semantic-release/release-notes-generator", + [ + "@semantic-release/changelog", + { + "changelogFile": "CHANGELOG.md", + "changelogTitle": "# Semantic Versioning Changelog" + } + ], + [ + "@semantic-release/git", + { + "assets": ["package.json", "CHANGELOG.md"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + } + ] + ] +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..e02930bf --- /dev/null +++ b/package.json @@ -0,0 +1,36 @@ +{ + "name": "casbin", + "version": "0.0.0-development", + "description": "An authorization library that supports access control models like ACL, RBAC, ABAC in python", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "semantic-release": "semantic-release" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/casbin/pycasbin.git" + }, + "license": "ISC", + "bugs": { + "url": "https://github.com/casbin/pycasbin/issues" + }, + "homepage": "http://casbin.org", + "devDependencies": { + "semantic-release": "^17.2.2", + "@semantic-release/commit-analyzer": "^8.0.1", + "@semantic-release/release-notes-generator": "^9.0.1", + "@semantic-release/changelog": "^5.0.1", + "@semantic-release/git": "^9.0.0", + "@semantic-release/git": "^7.1.1" + }, + "release": { + "plugins": [ + "@semantic-release/commit-analyzer", + "@semantic-release/release-notes-generator", + "@semantic-release/changelog", + "@semantic-release/git", + "@semantic-release/github" + ] + } +} \ No newline at end of file From 76787fbfbbc1cc34112fc422b8381067e0352ffc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 10 Nov 2020 01:44:00 +0000 Subject: [PATCH 078/349] chore(release): 0.10.0 [skip ci] # [0.10.0](https://github.com/casbin/pycasbin/compare/v0.9.0...v0.10.0) (2020-11-10) ### Bug Fixes * improve KeyMatch and add tests ([f62a2b2](https://github.com/casbin/pycasbin/commit/f62a2b2920e870a879d004148117019137805c09)) ### Features * support semantic-release ([f346ab4](https://github.com/casbin/pycasbin/commit/f346ab45602d0820e88de5c3febfea4a6a5cc7b1)) --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..df898d32 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# [0.10.0](https://github.com/casbin/pycasbin/compare/v0.9.0...v0.10.0) (2020-11-10) + + +### Bug Fixes + +* improve KeyMatch and add tests ([f62a2b2](https://github.com/casbin/pycasbin/commit/f62a2b2920e870a879d004148117019137805c09)) + + +### Features + +* support semantic-release ([f346ab4](https://github.com/casbin/pycasbin/commit/f346ab45602d0820e88de5c3febfea4a6a5cc7b1)) From 43ec842bad00a93172be58a355ca4324d5615ea5 Mon Sep 17 00:00:00 2001 From: Foufou Date: Tue, 10 Nov 2020 14:03:20 +0800 Subject: [PATCH 079/349] Bump to version 0.10.0 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 10b0adf3..af7f6b3a 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ setuptools.setup( name="casbin", - version="0.9.0", + version="0.10.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", From 4ca24035e5a21848ea2f2ff7f3e91f33f726e3f9 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Mon, 16 Nov 2020 14:12:50 +0100 Subject: [PATCH 080/349] support matching function in default role manager Signed-off-by: Andreas Bichinger --- .../rbac/default_role_manager/role_manager.py | 17 +++++++++++- examples/rbac_with_pattern_model.conf | 15 +++++++++++ examples/rbac_with_pattern_policy.csv | 12 +++++++++ tests/test_enforcer.py | 27 +++++++++++++++++++ 4 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 examples/rbac_with_pattern_model.conf create mode 100644 examples/rbac_with_pattern_policy.csv diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 7e0abc9a..34cfbe11 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -11,14 +11,29 @@ class RoleManager(RoleManager): def __init__(self, max_hierarchy_level): self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level + self.matching_func = None + + def add_matching_func(self, fn): + self.matching_func = fn def has_role(self, name): - return name in self.all_roles.keys() + if self.matching_func is None: + return name in self.all_roles.keys() + else: + for key in self.all_roles.keys(): + if self.matching_func(name, key): + return True + return False def create_role(self, name): if name not in self.all_roles.keys(): self.all_roles[name] = Role(name) + if self.matching_func is not None: + for key, role in self.all_roles.items(): + if self.matching_func(name, key) and name != key: + self.all_roles[name].add_role(role) + return self.all_roles[name] def clear(self): diff --git a/examples/rbac_with_pattern_model.conf b/examples/rbac_with_pattern_model.conf new file mode 100644 index 00000000..84580d90 --- /dev/null +++ b/examples/rbac_with_pattern_model.conf @@ -0,0 +1,15 @@ +[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) \ No newline at end of file diff --git a/examples/rbac_with_pattern_policy.csv b/examples/rbac_with_pattern_policy.csv new file mode 100644 index 00000000..eff87b62 --- /dev/null +++ b/examples/rbac_with_pattern_policy.csv @@ -0,0 +1,12 @@ +p, 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 +g2, /book/:id, book_group +g2, /pen/:id, pen_group + +g2, /book2/{id}, book_group +g2, /pen2/{id}, pen_group \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 78dde265..fa9ede4f 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -155,6 +155,33 @@ def test_enforce_rbac_with_resource_roles(self): self.assertFalse(e.enforce('bob', 'data2', 'read')) self.assertTrue(e.enforce('bob', 'data2', 'write')) + def test_enforce_rbac_with_pattern(self): + e = get_enforcer(get_examples("rbac_with_pattern_model.conf"), + get_examples("rbac_with_pattern_policy.csv")) + + #set matching function to key_match2 + e.rm.add_matching_func(casbin.util.key_match2) + + self.assertTrue(e.enforce("alice", "/book/1", "GET")) + self.assertTrue(e.enforce("alice", "/book/2", "GET")) + self.assertTrue(e.enforce("alice", "/pen/1", "GET")) + self.assertFalse(e.enforce("alice", "/pen/2", "GET")) + self.assertFalse(e.enforce("bob", "/book/1", "GET")) + self.assertFalse(e.enforce("bob", "/book/2", "GET")) + self.assertTrue(e.enforce("bob", "/pen/1", "GET")) + self.assertTrue(e.enforce("bob", "/pen/2", "GET")) + + #replace key_match2 with key_match3 + e.rm.add_matching_func(casbin.util.key_match3) + self.assertTrue(e.enforce("alice", "/book2/1", "GET")) + self.assertTrue(e.enforce("alice", "/book2/2", "GET")) + self.assertTrue(e.enforce("alice", "/pen2/1", "GET")) + self.assertFalse(e.enforce("alice", "/pen2/2", "GET")) + self.assertFalse(e.enforce("bob", "/book2/1", "GET")) + self.assertFalse(e.enforce("bob", "/book2/2", "GET")) + self.assertTrue(e.enforce("bob", "/pen2/1", "GET")) + self.assertTrue(e.enforce("bob", "/pen2/2", "GET")) + def test_enforce_abac_log_enabled(self): e = get_enforcer(get_examples("abac_model.conf"), enable_log=True) e.enable_log(True) From 11778b6817501e4ac9bd71b0c92d489002cf641e Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Mon, 30 Nov 2020 19:06:29 +0100 Subject: [PATCH 081/349] feat: add eval function Signed-off-by: Andreas Bichinger --- casbin/core_enforcer.py | 18 ++++++-- casbin/util/util.py | 23 ++++++++++ examples/abac_multiple_rules_model.conf | 11 +++++ examples/abac_multiple_rules_policy.csv | 2 + examples/abac_rule_model.conf | 11 +++++ examples/abac_rule_policy.csv | 2 + tests/test_enforcer.py | 57 +++++++++++++++++++++++++ tests/util/test_util.py | 20 +++++++++ 8 files changed, 141 insertions(+), 3 deletions(-) create mode 100644 examples/abac_multiple_rules_model.conf create mode 100644 examples/abac_multiple_rules_policy.csv create mode 100644 examples/abac_rule_model.conf create mode 100644 examples/abac_rule_policy.csv diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index abf40286..5c6e9318 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -2,7 +2,7 @@ from casbin.persist.adapters import FileAdapter from casbin.model import Model, FunctionMap from casbin.rbac import default_role_manager -from casbin.util import generate_g_function, SimpleEval +from casbin.util import generate_g_function, SimpleEval, util from casbin.effect import DefaultEffector, Effector @@ -225,7 +225,9 @@ def enforce(self, *rvals): raise RuntimeError("invalid request size") exp_string = self.model.model["m"]["m"].value - expression = self._get_expression(exp_string, functions) + has_eval = util.has_eval(exp_string) + if not has_eval: + expression = self._get_expression(exp_string, functions) policy_effects = set() matcher_results = set() @@ -239,7 +241,15 @@ def enforce(self, *rvals): if len(p_tokens) != len(pvals): raise RuntimeError("invalid policy size") - parameters = dict(r_parameters, **dict(zip(p_tokens, pvals))) + p_parameters = dict(zip(p_tokens, pvals)) + parameters = dict(r_parameters, **p_parameters) + + if util.has_eval(exp_string): + rule_names = util.get_eval_value(exp_string) + rules = [util.escape_assertion(p_parameters[rule_name]) for rule_name in rule_names] + exp_with_rule = util.replace_eval(exp_string, rules) + expression = self._get_expression(exp_with_rule, functions) + result = expression.eval(parameters) if isinstance(result, bool): @@ -270,6 +280,8 @@ def enforce(self, *rvals): break else: + if has_eval: + raise RuntimeError("please make sure rule exists in policy when using eval() in matcher") parameters = r_parameters.copy() diff --git a/casbin/util/util.py b/casbin/util/util.py index 96aefc54..34a53e4a 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -1,5 +1,7 @@ from collections import OrderedDict +import re +eval_reg = re.compile(r'\beval\((?P[^)]*)\)') def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" @@ -57,3 +59,24 @@ def set_subtract(a, b): diff.append(x) return diff + +def has_eval(s): + '''determine whether matcher contains function eval''' + return eval_reg.search(s) + +def replace_eval(expr, rules): + ''' replace all occurences of function eval with rules ''' + pos = 0 + match = eval_reg.search(expr, pos) + while match: + rule = "(" + rules.pop(0) + ")" + expr = expr[:match.start()] + rule + expr[match.end():] + pos = match.start() + len(rule) + match = eval_reg.search(expr, pos) + + return expr + +def get_eval_value(s): + '''returns the parameters of function eval''' + sub_match = eval_reg.findall(s) + return sub_match.copy() diff --git a/examples/abac_multiple_rules_model.conf b/examples/abac_multiple_rules_model.conf new file mode 100644 index 00000000..58fe442f --- /dev/null +++ b/examples/abac_multiple_rules_model.conf @@ -0,0 +1,11 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub_rule, sub_rule2, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.obj == p.obj && r.act == p.act && eval(p.sub_rule) && eval(p.sub_rule2) \ No newline at end of file diff --git a/examples/abac_multiple_rules_policy.csv b/examples/abac_multiple_rules_policy.csv new file mode 100644 index 00000000..a6084083 --- /dev/null +++ b/examples/abac_multiple_rules_policy.csv @@ -0,0 +1,2 @@ +p, r.sub.age > 18, r.sub.name == "alice", /data1, read +p, r.sub.age < 60, r.sub.name == "bob", /data2, write \ No newline at end of file diff --git a/examples/abac_rule_model.conf b/examples/abac_rule_model.conf new file mode 100644 index 00000000..591dd3a6 --- /dev/null +++ b/examples/abac_rule_model.conf @@ -0,0 +1,11 @@ +[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 \ No newline at end of file diff --git a/examples/abac_rule_policy.csv b/examples/abac_rule_policy.csv new file mode 100644 index 00000000..4c248eff --- /dev/null +++ b/examples/abac_rule_policy.csv @@ -0,0 +1,2 @@ +p, r.sub.age > 18, /data1, read +p, r.sub.age < 60, /data2, write \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index fa9ede4f..2b11c0a2 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -15,6 +15,11 @@ def get_examples(path): examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/" return os.path.abspath(examples_path + path) +class TestSub(): + + def __init__(self, name, age): + self.name = name + self.age = age class TestConfig(TestCase): def test_enforcer_basic(self): @@ -189,3 +194,55 @@ def test_enforce_abac_log_enabled(self): sub = 'alice' obj = {'Owner': 'alice', 'id': 'data1'} self.assertTrue(e.enforce(sub, obj, 'write')) + + def test_abac_with_sub_rule(self): + e = get_enforcer(get_examples("abac_rule_model.conf"), + get_examples("abac_rule_policy.csv")) + + sub1 = TestSub("alice", 16) + sub2 = TestSub("bob", 20) + sub3 = TestSub("alice", 65) + + self.assertFalse(e.enforce(sub1, "/data1", "read")) + self.assertFalse(e.enforce(sub1, "/data2", "read")) + self.assertFalse(e.enforce(sub1, "/data1", "write")) + self.assertTrue(e.enforce(sub1, "/data2", "write")) + + self.assertTrue(e.enforce(sub2, "/data1", "read")) + self.assertFalse(e.enforce(sub2, "/data2", "read")) + self.assertFalse(e.enforce(sub2, "/data1", "write")) + self.assertTrue(e.enforce(sub2, "/data2", "write")) + + self.assertTrue(e.enforce(sub3, "/data1", "read")) + self.assertFalse(e.enforce(sub3, "/data2", "read")) + self.assertFalse(e.enforce(sub3, "/data1", "write")) + self.assertFalse(e.enforce(sub3, "/data2", "write")) + + def test_abac_with_multiple_sub_rules(self): + e = get_enforcer(get_examples("abac_multiple_rules_model.conf"), + get_examples("abac_multiple_rules_policy.csv")) + + sub1 = TestSub("alice", 16) + sub2 = TestSub("alice", 20) + sub3 = TestSub("bob", 65) + sub4 = TestSub("bob", 35) + + self.assertFalse(e.enforce(sub1, "/data1", "read")) + self.assertFalse(e.enforce(sub1, "/data2", "read")) + self.assertFalse(e.enforce(sub1, "/data1", "write")) + self.assertFalse(e.enforce(sub1, "/data2", "write")) + + self.assertTrue(e.enforce(sub2, "/data1", "read")) + self.assertFalse(e.enforce(sub2, "/data2", "read")) + self.assertFalse(e.enforce(sub2, "/data1", "write")) + self.assertFalse(e.enforce(sub2, "/data2", "write")) + + self.assertFalse(e.enforce(sub3, "/data1", "read")) + self.assertFalse(e.enforce(sub3, "/data2", "read")) + self.assertFalse(e.enforce(sub3, "/data1", "write")) + self.assertFalse(e.enforce(sub3, "/data2", "write")) + + self.assertFalse(e.enforce(sub4, "/data1", "read")) + self.assertFalse(e.enforce(sub4, "/data2", "read")) + self.assertFalse(e.enforce(sub4, "/data1", "write")) + self.assertTrue(e.enforce(sub4, "/data2", "write")) diff --git a/tests/util/test_util.py b/tests/util/test_util.py index 4f75ab09..3216e821 100644 --- a/tests/util/test_util.py +++ b/tests/util/test_util.py @@ -24,3 +24,23 @@ def test_array_to_string(self): def test_params_to_string(self): self.assertEqual(util.params_to_string('data', 'data1', 'data2', 'data3'), "data, data1, data2, data3") + + def test_has_eval(self): + self.assertTrue(util.has_eval("eval() && a && b && c")) + self.assertTrue(util.has_eval("a && b && eval(c)")) + self.assertFalse(util.has_eval("eval) && a && b && c")) + self.assertFalse(util.has_eval("eval)( && a && b && c")) + self.assertTrue(util.has_eval("eval(c * (a + b)) && a && b && c")) + self.assertFalse(util.has_eval("xeval() && a && b && c")) + self.assertTrue(util.has_eval("eval(a) && eval(b) && a && b && c")) + + def test_replace_eval(self): + self.assertEqual(util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c") + self.assertEqual(util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)") + + def test_get_eval_value(self): + self.assertEqual(util.get_eval_value("eval(a) && a && b && c"), ["a"]) + self.assertEqual(util.get_eval_value("a && eval(a) && b && c"), ["a"]) + self.assertEqual(util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"]) + self.assertEqual(util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"]) + self.assertEqual(util.get_eval_value("eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)"), ["p.sub_rule", "p.domain_rule"]) From f5ac332784edf34bea741e8ab21352719f6c1824 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 1 Dec 2020 10:39:45 +0000 Subject: [PATCH 082/349] chore(release): 0.11.0 [skip ci] # [0.11.0](https://github.com/casbin/pycasbin/compare/v0.10.0...v0.11.0) (2020-12-01) ### Features * add eval function ([9604fbf](https://github.com/casbin/pycasbin/commit/9604fbf91a22149dab531183576c8a4e7078a4d0)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index df898d32..8adbf939 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [0.11.0](https://github.com/casbin/pycasbin/compare/v0.10.0...v0.11.0) (2020-12-01) + + +### Features + +* add eval function ([9604fbf](https://github.com/casbin/pycasbin/commit/9604fbf91a22149dab531183576c8a4e7078a4d0)) + # [0.10.0](https://github.com/casbin/pycasbin/compare/v0.9.0...v0.10.0) (2020-11-10) From 5dc158b37f86aef8b1309fc4fe52dc5dc5f1ffe6 Mon Sep 17 00:00:00 2001 From: yyellowsun Date: Wed, 2 Dec 2020 23:27:35 +0800 Subject: [PATCH 083/349] feat: add semantic-release-pypi plugin Signed-off-by: yyellowsun --- .github/workflows/release.yml | 17 +++++++++++++---- .releaserc | 8 ++++---- casbin/core_enforcer.py | 1 + package.json | 36 ----------------------------------- setup.py | 3 +-- 5 files changed, 19 insertions(+), 46 deletions(-) delete mode 100644 package.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cc6a0bbc..4670d7b4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,18 @@ jobs: uses: actions/setup-node@v1 - name: Setup - run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/npm @semantic-release/git @semantic-release/release-notes-generator - + run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi + + - name: Set up python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install setuptools + run: python -m pip install --upgrade setuptools wheel twine + - name: Rlease - run: npx semantic-release env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + GH_TOKEN: ${{ secrets.GH_TOKEN }} + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: npx semantic-release \ No newline at end of file diff --git a/.releaserc b/.releaserc index ab7f426a..235dc037 100644 --- a/.releaserc +++ b/.releaserc @@ -1,10 +1,10 @@ { - "branch": "master", + "branches": "master", "plugins": [ "@semantic-release/commit-analyzer", - "@semantic-release/npm", - "@semantic-release/github", "@semantic-release/release-notes-generator", + "semantic-release-pypi", + "@semantic-release/github", [ "@semantic-release/changelog", { @@ -15,8 +15,8 @@ [ "@semantic-release/git", { - "assets": ["package.json", "CHANGELOG.md"], "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" + "assets": ["CHANGELOG.md", "setup.py", "setup.cfg"], } ] ] diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 5c6e9318..eab42331 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -24,6 +24,7 @@ class CoreEnforcer: def __init__(self, model=None, adapter=None, enable_log=False): self.enable_log(enable_log) + if isinstance(model, str): if isinstance(adapter, str): self.init_with_file(model, adapter) diff --git a/package.json b/package.json deleted file mode 100644 index e02930bf..00000000 --- a/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "casbin", - "version": "0.0.0-development", - "description": "An authorization library that supports access control models like ACL, RBAC, ABAC in python", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "semantic-release": "semantic-release" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/casbin/pycasbin.git" - }, - "license": "ISC", - "bugs": { - "url": "https://github.com/casbin/pycasbin/issues" - }, - "homepage": "http://casbin.org", - "devDependencies": { - "semantic-release": "^17.2.2", - "@semantic-release/commit-analyzer": "^8.0.1", - "@semantic-release/release-notes-generator": "^9.0.1", - "@semantic-release/changelog": "^5.0.1", - "@semantic-release/git": "^9.0.0", - "@semantic-release/git": "^7.1.1" - }, - "release": { - "plugins": [ - "@semantic-release/commit-analyzer", - "@semantic-release/release-notes-generator", - "@semantic-release/changelog", - "@semantic-release/git", - "@semantic-release/github" - ] - } -} \ No newline at end of file diff --git a/setup.py b/setup.py index af7f6b3a..95776195 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,6 @@ setuptools.setup( name="casbin", - version="0.10.0", author="TechLee", author_email="techlee@qq.com", description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", @@ -30,4 +29,4 @@ "Operating System :: OS Independent", ], data_files=[desc_file], -) +) \ No newline at end of file From ad637500b92cb09aadf792cee957d51c7c68c00c Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Fri, 4 Dec 2020 16:04:55 +0100 Subject: [PATCH 084/349] chore: fix releaserc --- .releaserc => .releaserc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .releaserc => .releaserc.json (84%) diff --git a/.releaserc b/.releaserc.json similarity index 84% rename from .releaserc rename to .releaserc.json index 235dc037..72f72984 100644 --- a/.releaserc +++ b/.releaserc.json @@ -15,8 +15,8 @@ [ "@semantic-release/git", { - "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" - "assets": ["CHANGELOG.md", "setup.py", "setup.cfg"], + "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}", + "assets": ["CHANGELOG.md", "setup.py", "setup.cfg"] } ] ] From feb9f6008c8441b0e304e9c922b6e28101bff4eb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 7 Dec 2020 14:54:52 +0000 Subject: [PATCH 085/349] chore(release): 0.12.0 [skip ci] # [0.12.0](https://github.com/casbin/pycasbin/compare/v0.11.0...v0.12.0) (2020-12-07) ### Features * add semantic-release-pypi plugin ([e53d842](https://github.com/casbin/pycasbin/commit/e53d84283952b3b66720e6d50ae91030880fe839)) --- CHANGELOG.md | 9 +++++++++ setup.cfg | 3 +++ 2 files changed, 12 insertions(+) create mode 100644 setup.cfg diff --git a/CHANGELOG.md b/CHANGELOG.md index 8adbf939..513091fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +# Semantic Versioning Changelog + +# [0.12.0](https://github.com/casbin/pycasbin/compare/v0.11.0...v0.12.0) (2020-12-07) + + +### Features + +* add semantic-release-pypi plugin ([e53d842](https://github.com/casbin/pycasbin/commit/e53d84283952b3b66720e6d50ae91030880fe839)) + # [0.11.0](https://github.com/casbin/pycasbin/compare/v0.10.0...v0.11.0) (2020-12-01) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..5d27862f --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[metadata] +version = 0.12.0 + From c652870aefabdab4abbdc4f2b687e287571c6ec5 Mon Sep 17 00:00:00 2001 From: Romain Arnal Date: Fri, 11 Dec 2020 17:56:53 +0100 Subject: [PATCH 086/349] feat: add glob pattern matching using fnmatch module Signed-off-by: Romain Arnal --- casbin/model/function.py | 1 + casbin/util/builtin_operators.py | 15 ++++++++++++++ tests/util/test_builtin_operators.py | 31 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+) diff --git a/casbin/model/function.py b/casbin/model/function.py index 50fd4b47..b116a441 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -14,6 +14,7 @@ def load_function_map(): fm.add_function("keyMatch2", util.key_match2_func) fm.add_function("regexMatch", util.regex_match_func) fm.add_function("ipMatch", util.ip_match_func) + fm.add_function("globMatch", util.glob_match_func) return fm diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index de151c8b..509eba42 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -1,3 +1,4 @@ +import fnmatch import re import ipaddress @@ -83,6 +84,20 @@ def regex_match_func(*args): return regex_match(name1, name2) +def glob_match(string, pattern): + """determines whether string matches the pattern in glob expression.""" + return fnmatch.fnmatch(string, pattern) + + +def glob_match_func(*args): + """the wrapper for globMatch.""" + + string = args[0] + pattern = args[1] + + return glob_match(string, pattern) + + def ip_match(ip1, ip2): """IPMatch determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR pattern. For example, "192.168.2.123" matches "192.168.2.0/24" diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 74e2a0aa..1bd3e2d6 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -85,6 +85,37 @@ def test_regex_match(self): self.assertTrue(util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+")) self.assertFalse(util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+")) + def test_glob_match(self): + self.assertTrue(util.glob_match_func("/foo", "/foo")) + self.assertTrue(util.glob_match_func("/foo", "/foo*")) + self.assertFalse(util.glob_match_func("/foo", "/foo/*")) + self.assertFalse(util.glob_match_func("/foo/bar", "/foo")) + self.assertTrue(util.glob_match_func("/foo/bar", "/foo*")) # differ from Casbin Go + self.assertTrue(util.glob_match_func("/foo/bar", "/foo/*")) + self.assertFalse(util.glob_match_func("/foobar", "/foo")) + self.assertTrue(util.glob_match_func("/foobar", "/foo*")) + self.assertFalse(util.glob_match_func("/foobar", "/foo/*")) + + self.assertTrue(util.glob_match_func("/prefix/foo", "*/foo")) # differ from Casbin Go + self.assertTrue(util.glob_match_func("/prefix/foo", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo/*")) + self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo")) + self.assertTrue(util.glob_match_func("/prefix/foo/bar", "*/foo*")) # differ from Casbin Go + self.assertTrue(util.glob_match_func("/prefix/foo/bar", "*/foo/*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo")) + self.assertTrue(util.glob_match_func("/prefix/foobar", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo/*")) + + self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "*/foo")) # differ from Casbin Go + self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo/*")) + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo")) + self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo*")) # differ from Casbin Go + self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo/*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo")) + self.assertTrue(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/*")) + def test_ip_match(self): self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.0/24")) self.assertFalse(util.ip_match_func("192.168.2.123", "192.168.3.0/24")) From ce012b209a687aade95a986610489f61a45bc361 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 14 Dec 2020 15:55:25 +0000 Subject: [PATCH 087/349] chore(release): 0.13.0 [skip ci] # [0.13.0](https://github.com/casbin/pycasbin/compare/v0.12.0...v0.13.0) (2020-12-14) ### Features * add glob pattern matching using fnmatch module ([41ba828](https://github.com/casbin/pycasbin/commit/41ba828b2cecd21df79a5ec905d9d4de9506b918)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 513091fb..95af49f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.13.0](https://github.com/casbin/pycasbin/compare/v0.12.0...v0.13.0) (2020-12-14) + + +### Features + +* add glob pattern matching using fnmatch module ([41ba828](https://github.com/casbin/pycasbin/commit/41ba828b2cecd21df79a5ec905d9d4de9506b918)) + # [0.12.0](https://github.com/casbin/pycasbin/compare/v0.11.0...v0.12.0) (2020-12-07) diff --git a/setup.cfg b/setup.cfg index 5d27862f..401d6390 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.12.0 +version = 0.13.0 From 1975d88c12ad6fbb4f0fd682743010284d318d84 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Sat, 19 Dec 2020 17:03:28 +0100 Subject: [PATCH 088/349] feat: add function get_role_manager Signed-off-by: Andreas Bichinger --- casbin/core_enforcer.py | 4 ++++ tests/test_enforcer.py | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index eab42331..26ddc671 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -121,6 +121,10 @@ def set_watcher(self, watcher): self.watcher = watcher pass + def get_role_manager(self): + """gets the current role manager.""" + return self.rm + def set_role_manager(self, rm): """sets the current role manager.""" diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 2b11c0a2..88d8b049 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -165,7 +165,7 @@ def test_enforce_rbac_with_pattern(self): get_examples("rbac_with_pattern_policy.csv")) #set matching function to key_match2 - e.rm.add_matching_func(casbin.util.key_match2) + e.get_role_manager().add_matching_func(casbin.util.key_match2) self.assertTrue(e.enforce("alice", "/book/1", "GET")) self.assertTrue(e.enforce("alice", "/book/2", "GET")) @@ -177,7 +177,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen/2", "GET")) #replace key_match2 with key_match3 - e.rm.add_matching_func(casbin.util.key_match3) + e.get_role_manager().add_matching_func(casbin.util.key_match3) self.assertTrue(e.enforce("alice", "/book2/1", "GET")) self.assertTrue(e.enforce("alice", "/book2/2", "GET")) self.assertTrue(e.enforce("alice", "/pen2/1", "GET")) From 58416c71d5cb8817e00f785b86ac2ef81583b104 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Sat, 19 Dec 2020 17:05:26 +0100 Subject: [PATCH 089/349] feat: add SyncedEnforcer Signed-off-by: Andreas Bichinger --- casbin/__init__.py | 1 + casbin/synced_enforcer.py | 508 ++++++++++++++++++++++++++++++++++ casbin/util/rwlock.py | 68 +++++ tests/test_enforcer.py | 85 ++++-- tests/test_management_api.py | 28 +- tests/test_model_benchmark.py | 18 +- tests/test_rbac_api.py | 61 ++-- tests/util/test_rwlock.py | 85 ++++++ 8 files changed, 788 insertions(+), 66 deletions(-) create mode 100644 casbin/synced_enforcer.py create mode 100644 casbin/util/rwlock.py create mode 100644 tests/util/test_rwlock.py diff --git a/casbin/__init__.py b/casbin/__init__.py index 0f51c07b..67694841 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -1 +1,2 @@ from .enforcer import * +from .synced_enforcer import SyncedEnforcer diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py new file mode 100644 index 00000000..55ec57e0 --- /dev/null +++ b/casbin/synced_enforcer.py @@ -0,0 +1,508 @@ +from casbin.enforcer import Enforcer +from casbin.util.rwlock import RWLockWrite +import threading +import time + +class AtomicBool(): + + def __init__(self, value): + self._lock = threading.Lock() + self._value = value + + @property + def value(self): + with self._lock: + return self._value + + @value.setter + def value(self, value): + with self._lock: + self._value = value + +class SyncedEnforcer(Enforcer): + + """SyncedEnforcer wraps Enforcer and provides synchronized access. + It's also a drop-in replacement for Enforcer""" + + def __init__(self, model=None, adapter=None, enable_log=False): + self._e = Enforcer(model, adapter, enable_log) + self._rwlock = RWLockWrite() + self._rl = self._rwlock.gen_rlock() + self._wl = self._rwlock.gen_wlock() + self._auto_loading = AtomicBool(False) + self._auto_loading_thread = None + + def is_auto_loading_running(self): + """check if SyncedEnforcer is auto loading policies""" + return self._auto_loading.value + + def _auto_load_policy(self, interval): + while self.is_auto_loading_running(): + time.sleep(interval) + self.load_policy() + + def start_auto_load_policy(self, interval): + """starts a thread that will call load_policy every interval seconds""" + if self.is_auto_loading_running(): + return + self._auto_loading.value = True + self._auto_loading_thread = threading.Thread(target=self._auto_load_policy, args=[interval], daemon=True) + + def stop_auto_load_policy(self): + """stops the thread started by start_auto_load_policy""" + if self.is_auto_loading_running(): + self._auto_loading.value = False + + def get_model(self): + """gets the current model.""" + with self._rl: + return self._e.get_model() + + def set_model(self, m): + """sets the current model.""" + with self._wl: + return self._e.set_model(m) + + def load_model(self): + """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(). + """ + with self._wl: + return self._e.load_model() + + def get_role_manager(self): + """gets the current role manager.""" + with self._rl: + return self._e.get_role_manager() + + def set_role_manager(self, rm): + with self._wl: + self._e.set_role_manager(rm) + + def get_adapter(self): + """gets the current adapter.""" + with self._rl: + self._e.get_adapter() + + def set_adapter(self, adapter): + """sets the current adapter.""" + with self._wl: + self._e.set_adapter(adapter) + + def set_watcher(self, watcher): + """sets the current watcher.""" + with self._wl: + self._e.set_watcher(watcher) + + def set_effector(self, eft): + """sets the current effector.""" + with self._wl: + self._e.set_effector(eft) + + def enable_log(self, enable): + """changes whether Casbin will log messages to the Logger.""" + with self._wl: + return self._e.enable_log(enable) + + def clear_policy(self): + """ clears all policy.""" + with self._wl: + return self._e.clear_policy() + + def load_policy(self): + """reloads the policy from file/database.""" + with self._wl: + return self._e.load_policy() + + def load_filtered_policy(self, filter): + """"reloads a filtered policy from file/database.""" + with self._wl: + return self._e.load_filtered_policy(filter) + + def save_policy(self): + with self._rl: + return self._e.save_policy() + + def build_role_links(self): + """manually rebuild the role inheritance relations.""" + with self._rl: + return self._e.build_role_links() + + def enforce(self, *rvals): + """decides whether a "subject" can access a "object" with the operation "action", + input parameters are usually: (sub, obj, act). + """ + with self._rl: + return self._e.enforce(*rvals) + + def get_all_subjects(self): + """gets the list of subjects that show up in the current policy.""" + with self._rl: + return self._e.get_all_subjects() + + def get_all_named_subjects(self, ptype): + """gets the list of subjects that show up in the current named policy.""" + with self._rl: + return self._e.get_all_named_subjects(ptype) + + def get_all_objects(self): + """gets the list of objects that show up in the current policy.""" + with self._rl: + return self._e.get_all_objects() + + def get_all_named_objects(self, ptype): + """gets the list of objects that show up in the current named policy.""" + with self._rl: + return self._e.get_all_named_objects(ptype) + + def get_all_actions(self): + """gets the list of actions that show up in the current policy.""" + with self._rl: + return self._e.get_all_actions() + + def get_all_named_actions(self, ptype): + """gets the list of actions that show up in the current named policy.""" + with self._rl: + return self._e.get_all_named_actions(ptype) + + def get_all_roles(self): + """gets the list of roles that show up in the current named policy.""" + with self._rl: + return self._e.get_all_roles() + + def get_all_named_roles(self, ptype): + """gets all the authorization rules in the policy.""" + with self._rl: + return self._e.get_all_named_roles(ptype) + + def get_policy(self): + """gets all the authorization rules in the policy.""" + with self._rl: + return self._e.get_policy() + + def get_filtered_policy(self, field_index, *field_values): + """gets all the authorization rules in the policy, field filters can be specified.""" + with self._rl: + return self._e.get_filtered_policy(field_index, *field_values) + + def get_named_policy(self, ptype): + """gets all the authorization rules in the named policy.""" + with self._rl: + return self._e.get_named_policy(ptype) + + def get_filtered_named_policy(self, ptype, field_index, *field_values): + """gets all the authorization rules in the named policy, field filters can be specified.""" + with self._rl: + return self._e.get_filtered_named_policy(ptype, field_index, *field_values) + + def get_grouping_policy(self): + """gets all the role inheritance rules in the policy.""" + with self._rl: + return self._e.get_grouping_policy() + + def get_filtered_grouping_policy(self, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + with self._rl: + return self._e.get_filtered_grouping_policy(field_index, *field_values) + + def get_named_grouping_policy(self, ptype): + """gets all the role inheritance rules in the policy.""" + with self._rl: + return self._e.get_named_grouping_policy(ptype) + + def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + with self._rl: + return self._e.get_filtered_named_grouping_policy(ptype, field_index, *field_values) + + def has_policy(self, *params): + """determines whether an authorization rule exists.""" + with self._rl: + return self._e.has_policy(*params) + + def has_named_policy(self, ptype, *params): + """determines whether a named authorization rule exists.""" + with self._rl: + return self._e.has_named_policy(ptype, *params) + + def add_policy(self, *params): + """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. + """ + with self._wl: + return self._e.add_policy(*params) + + def add_named_policy(self, ptype, *params): + """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. + """ + with self._wl: + return self._e.add_named_policy(ptype, *params) + + def remove_policy(self, *params): + """removes an authorization rule from the current policy.""" + with self._wl: + return self._e.remove_policy(*params) + + def remove_filtered_policy(self, field_index, *field_values): + """removes an authorization rule from the current policy, field filters can be specified.""" + with self._wl: + return self._e.remove_filtered_policy(field_index, *field_values) + + def remove_named_policy(self, ptype, *params): + """removes an authorization rule from the current named policy.""" + with self._wl: + return self._e.remove_named_policy(ptype, *params) + + def remove_filtered_named_policy(self, ptype, field_index, *field_values): + """removes an authorization rule from the current named policy, field filters can be specified.""" + with self._wl: + return self._e.remove_filtered_named_policy(ptype, field_index, *field_values) + + def has_grouping_policy(self, *params): + """determines whether a role inheritance rule exists.""" + with self._rl: + return self._e.has_grouping_policy(*params) + + def has_named_grouping_policy(self, ptype, *params): + """determines whether a named role inheritance rule exists.""" + with self._rl: + return self._e.has_named_grouping_policy(ptype, *params) + + def add_grouping_policy(self, *params): + """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. + """ + with self._wl: + return self._e.add_grouping_policy(*params) + + def add_named_grouping_policy(self, ptype, *params): + """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. + """ + with self._wl: + return self._e.add_named_grouping_policy(ptype, *params) + + def remove_grouping_policy(self, *params): + """removes a role inheritance rule from the current policy.""" + with self._wl: + return self._e.remove_grouping_policy(*params) + + def remove_filtered_grouping_policy(self, field_index, *field_values): + """removes a role inheritance rule from the current policy, field filters can be specified.""" + with self._wl: + return self._e.remove_filtered_grouping_policy(field_index, *field_values) + + def remove_named_grouping_policy(self, ptype, *params): + """removes a role inheritance rule from the current named policy.""" + with self._wl: + return self._e.remove_named_grouping_policy(ptype, *params) + + def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """removes a role inheritance rule from the current named policy, field filters can be specified.""" + with self._wl: + return self._e.remove_filtered_named_grouping_policy(ptype, field_index, *field_values) + + def add_function(self, name, func): + """adds a customized function.""" + with self._wl: + return self._e.add_function(name, func) + + # enforcer.py + + def get_roles_for_user(self, name): + """ gets the roles that a user has. """ + with self._rl: + return self._e.get_roles_for_user(name) + + def get_users_for_role(self, name): + """ gets the users that has a role. """ + with self._rl: + return self._e.get_users_for_role(name) + + def has_role_for_user(self, name, role): + """ determines whether a user has a role. """ + with self._rl: + return self._e.has_role_for_user(name, role) + + def add_role_for_user(self, user, role): + """ + adds a role for a user. + Returns false if the user already has the role (aka not affected). + """ + with self._wl: + return self._e.add_role_for_user(user, role) + + def delete_role_for_user(self, user, role): + """ + deletes a role for a user. + Returns false if the user does not have the role (aka not affected). + """ + with self._wl: + return self._e.delete_role_for_user(user, role) + + def delete_roles_for_user(self, user): + """ + deletes all roles for a user. + Returns false if the user does not have any roles (aka not affected). + """ + with self._wl: + return self._e.delete_roles_for_user(user) + + def delete_user(self, user): + """ + deletes a user. + Returns false if the user does not exist (aka not affected). + """ + with self._wl: + return self._e.delete_user(user) + + def delete_role(self, role): + """ + deletes a role. + Returns false if the role does not exist (aka not affected). + """ + with self._wl: + return self._e.delete_role(role) + + def delete_permission(self, *permission): + """ + deletes a permission. + Returns false if the permission does not exist (aka not affected). + """ + with self._wl: + return self._e.delete_permission(*permission) + + def add_permission_for_user(self, user, *permission): + """ + adds a permission for a user or role. + Returns false if the user or role already has the permission (aka not affected). + """ + with self._wl: + return self._e.add_permission_for_user(user, *permission) + + def delete_permission_for_user(self, user, *permission): + """ + deletes a permission for a user or role. + Returns false if the user or role does not have the permission (aka not affected). + """ + with self._wl: + return self._e.delete_permission_for_user(user, *permission) + + def delete_permissions_for_user(self, user): + """ + deletes permissions for a user or role. + Returns false if the user or role does not have any permissions (aka not affected). + """ + with self._wl: + return self._e.delete_permissions_for_user(user) + + def get_permissions_for_user(self, user): + """ + gets permissions for a user or role. + """ + with self._rl: + return self._e.get_permissions_for_user(user) + + def has_permission_for_user(self, user, *permission): + """ + determines whether a user has a permission. + """ + with self._rl: + return self._e.has_permission_for_user(user, *permission) + + def get_implicit_roles_for_user(self, name, *domain): + """ + gets implicit roles that a user has. + Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. + For example: + g, alice, role:admin + g, role:admin, role:user + + get_roles_for_user("alice") can only get: ["role:admin"]. + But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. + """ + with self._rl: + return self._e.get_implicit_roles_for_user(name, *domain) + + def get_implicit_permissions_for_user(self, user, *domain): + """ + gets implicit permissions for a user or role. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + """ + with self._rl: + return self._e.get_implicit_permissions_for_user(user, *domain) + + def get_implicit_users_for_permission(self, *permission): + """ + gets implicit users for a permission. + For example: + p, admin, data1, read + p, bob, data1, read + g, alice, admin + + get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. + Note: only users will be returned, roles (2nd arg in "g") will be excluded. + """ + with self._rl: + return self._e.get_implicit_users_for_permission(*permission) + + def get_roles_for_user_in_domain(self, name, domain): + """gets the roles that a user has inside a domain.""" + with self._rl: + return self._e.get_roles_for_user_in_domain(name, domain) + + def get_users_for_role_in_domain(self, name, domain): + """gets the users that has a role inside a domain.""" + with self._rl: + return self._e.get_users_for_role_in_domain(name, domain) + + def add_role_for_user_in_domain(self, user, role, domain): + """adds a role for a user inside a domain.""" + """Returns false if the user already has the role (aka not affected).""" + with self._wl: + return self._e.add_role_for_user_in_domain(user, role, domain) + + def delete_roles_for_user_in_domain(self, user, role, domain): + """deletes a role for a user inside a domain.""" + """Returns false if the user does not have any roles (aka not affected).""" + with self._wl: + return self._e.delete_roles_for_user_in_domain(user, role, domain) + + def get_permissions_for_user_in_domain(self, user, domain): + """gets permissions for a user or role inside domain.""" + with self._rl: + return self._e.get_permissions_for_user_in_domain(user, domain) + + def enable_auto_build_role_links(self, auto_build_role_links): + """controls whether to rebuild the role inheritance relations when a role is added or deleted.""" + with self._wl: + return self._e.enable_auto_build_role_links(auto_build_role_links) + + def enable_auto_save(self, auto_save): + """controls whether to save a policy rule automatically to the adapter when it is added or removed.""" + with self._wl: + return self._e.enable_auto_save(auto_save) + + def enable_enforce(self, enabled=True): + """changes the enforcing state of Casbin, + when Casbin is disabled, all access will be allowed by the Enforce() function. + """ + with self._wl: + return self._e.enable_enforce(enabled) + + def is_filtered(self): + """returns true if the loaded policy has been filtered.""" + with self._rl: + self._e.is_filtered() \ No newline at end of file diff --git a/casbin/util/rwlock.py b/casbin/util/rwlock.py new file mode 100644 index 00000000..2f54b905 --- /dev/null +++ b/casbin/util/rwlock.py @@ -0,0 +1,68 @@ +from threading import RLock, Condition + +# This implementation was adapted from https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + +class RWLockWrite(): + ''' write preferring readers-wirter lock ''' + + def __init__(self): + self._lock = RLock() + self._cond = Condition(self._lock) + self._active_readers = 0 + self._waiting_writers = 0 + self._writer_active = False + + def aquire_read(self): + with self._lock: + while self._waiting_writers > 0 or self._writer_active: + self._cond.wait() + self._active_readers += 1 + + def release_read(self): + with self._lock: + self._active_readers -= 1 + if self._active_readers == 0: + self._cond.notify_all() + + def aquire_write(self): + with self._lock: + self._waiting_writers += 1 + while self._active_readers > 0 or self._writer_active: + self._cond.wait() + self._waiting_writers -= 1 + self._writer_active = True + + def release_write(self): + with self._lock: + self._writer_active = False + self._cond.notify_all() + + def gen_rlock(self): + return ReadRWLock(self) + + def gen_wlock(self): + return WriteRWLock(self) + +class ReadRWLock(): + + def __init__(self, rwlock): + self.rwlock = rwlock + + def __enter__(self): + self.rwlock.aquire_read() + + def __exit__(self, exc_type, exc_value, traceback): + self.rwlock.release_read() + return False + +class WriteRWLock(): + + def __init__(self, rwlock): + self.rwlock = rwlock + + def __enter__(self): + self.rwlock.aquire_write() + + def __exit__(self, exc_type, exc_value, traceback): + self.rwlock.release_write() + return False diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 88d8b049..e78c678f 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -1,15 +1,7 @@ import casbin import os from unittest import TestCase - - -def get_enforcer(model=None, adapter=None, enable_log=False): - return casbin.Enforcer( - model, - adapter, - enable_log, - ) - +import time def get_examples(path): examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/" @@ -21,9 +13,18 @@ def __init__(self, name, age): self.name = name self.age = age -class TestConfig(TestCase): +class TestCaseBase(TestCase): + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.Enforcer( + model, + adapter, + enable_log, + ) + +class TestConfig(TestCaseBase): + def test_enforcer_basic(self): - e = get_enforcer( + e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), # True, @@ -35,7 +36,7 @@ def test_enforcer_basic(self): self.assertFalse(e.enforce('bob', 'data1', 'write')) def test_enforcer_basic_without_spaces(self): - e = get_enforcer( + e = self.get_enforcer( get_examples("basic_model_without_spaces.conf"), get_examples("basic_policy.csv"), # True, @@ -51,11 +52,11 @@ def test_enforcer_basic_without_spaces(self): self.assertTrue(e.enforce("bob", "data2", "write")) def test_enforce_basic_with_root(self): - e = get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) + e = self.get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) self.assertTrue(e.enforce('root', 'any', 'any')) def test_enforce_basic_without_resources(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) self.assertTrue(e.enforce('alice', 'read')) self.assertFalse(e.enforce('alice', 'write')) @@ -63,7 +64,7 @@ def test_enforce_basic_without_resources(self): self.assertFalse(e.enforce('bob', 'read')) def test_enforce_basic_without_users(self): - e = get_enforcer(get_examples("basic_without_users_model.conf"), + e = self.get_enforcer(get_examples("basic_without_users_model.conf"), get_examples("basic_without_users_policy.csv")) self.assertTrue(e.enforce('data1', 'read')) self.assertFalse(e.enforce('data1', 'write')) @@ -71,13 +72,13 @@ def test_enforce_basic_without_users(self): self.assertFalse(e.enforce('data2', 'read')) def test_enforce_ip_match(self): - e = get_enforcer(get_examples("ipmatch_model.conf"), + e = self.get_enforcer(get_examples("ipmatch_model.conf"), get_examples("ipmatch_policy.csv")) self.assertTrue(e.enforce('192.168.2.1', 'data1', 'read')) self.assertFalse(e.enforce('192.168.3.1', 'data1', 'read')) def test_enforce_key_match(self): - e = get_enforcer(get_examples("keymatch_model.conf"), + e = self.get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) self.assertTrue(e.enforce('alice', '/alice_data/test', 'GET')) self.assertFalse(e.enforce('alice', '/bob_data/test', 'GET')) @@ -86,13 +87,13 @@ def test_enforce_key_match(self): self.assertFalse(e.enforce('cathy', '/cathy_data/12', 'POST')) def test_enforce_key_match2(self): - e = get_enforcer(get_examples("keymatch2_model.conf"), + e = self.get_enforcer(get_examples("keymatch2_model.conf"), get_examples("keymatch2_policy.csv")) self.assertTrue(e.enforce('alice', '/alice_data/resource', 'GET')) self.assertTrue(e.enforce('alice', '/alice_data2/123/using/456', 'GET')) def test_enforce_priority(self): - e = get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) + e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertFalse(e.enforce('alice', 'data1', 'write')) self.assertFalse(e.enforce('alice', 'data2', 'read')) @@ -104,11 +105,11 @@ def test_enforce_priority(self): self.assertFalse(e.enforce('bob', 'data2', 'write')) def test_enforce_priority_indeterminate(self): - e = get_enforcer(get_examples("priority_model.conf"), get_examples("priority_indeterminate_policy.csv")) + e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_indeterminate_policy.csv")) self.assertFalse(e.enforce('alice', 'data1', 'read')) def test_enforce_rbac(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertFalse(e.enforce('bob', 'data1', 'read')) self.assertTrue(e.enforce('bob', 'data2', 'write')) @@ -117,7 +118,7 @@ def test_enforce_rbac(self): self.assertFalse(e.enforce('bogus', 'data2', 'write')) # test non-existant subject def test_enforce_rbac__empty_policy(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) self.assertFalse(e.enforce('alice', 'data1', 'read')) self.assertFalse(e.enforce('bob', 'data1', 'read')) self.assertFalse(e.enforce('bob', 'data2', 'write')) @@ -125,14 +126,14 @@ def test_enforce_rbac__empty_policy(self): self.assertFalse(e.enforce('alice', 'data2', 'write')) def test_enforce_rbac_with_deny(self): - e = get_enforcer(get_examples("rbac_with_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) + e = self.get_enforcer(get_examples("rbac_with_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertTrue(e.enforce('alice', 'data2', 'read')) self.assertFalse(e.enforce('alice', 'data2', 'write')) def test_enforce_rbac_with_domains(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'read')) self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'write')) self.assertFalse(e.enforce('alice', 'domain1', 'data2', 'read')) @@ -144,11 +145,11 @@ def test_enforce_rbac_with_domains(self): self.assertTrue(e.enforce('bob', 'domain2', 'data2', 'write')) def test_enforce_rbac_with_not_deny(self): - e = get_enforcer(get_examples("rbac_with_not_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) + e = self.get_enforcer(get_examples("rbac_with_not_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) self.assertFalse(e.enforce('alice', 'data2', 'write')) def test_enforce_rbac_with_resource_roles(self): - e = get_enforcer(get_examples("rbac_with_resource_roles_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_resource_roles_model.conf"), get_examples("rbac_with_resource_roles_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertTrue(e.enforce('alice', 'data1', 'write')) @@ -161,7 +162,7 @@ def test_enforce_rbac_with_resource_roles(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) def test_enforce_rbac_with_pattern(self): - e = get_enforcer(get_examples("rbac_with_pattern_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_pattern_model.conf"), get_examples("rbac_with_pattern_policy.csv")) #set matching function to key_match2 @@ -188,7 +189,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen2/2", "GET")) def test_enforce_abac_log_enabled(self): - e = get_enforcer(get_examples("abac_model.conf"), enable_log=True) + e = self.get_enforcer(get_examples("abac_model.conf"), enable_log=True) e.enable_log(True) sub = 'alice' @@ -196,7 +197,7 @@ def test_enforce_abac_log_enabled(self): self.assertTrue(e.enforce(sub, obj, 'write')) def test_abac_with_sub_rule(self): - e = get_enforcer(get_examples("abac_rule_model.conf"), + e = self.get_enforcer(get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv")) sub1 = TestSub("alice", 16) @@ -219,7 +220,7 @@ def test_abac_with_sub_rule(self): self.assertFalse(e.enforce(sub3, "/data2", "write")) def test_abac_with_multiple_sub_rules(self): - e = get_enforcer(get_examples("abac_multiple_rules_model.conf"), + e = self.get_enforcer(get_examples("abac_multiple_rules_model.conf"), get_examples("abac_multiple_rules_policy.csv")) sub1 = TestSub("alice", 16) @@ -246,3 +247,27 @@ def test_abac_with_multiple_sub_rules(self): self.assertFalse(e.enforce(sub4, "/data2", "read")) self.assertFalse(e.enforce(sub4, "/data1", "write")) self.assertTrue(e.enforce(sub4, "/data2", "write")) + +class TestConfigSynced(TestConfig): + + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.SyncedEnforcer( + model, + adapter, + enable_log, + ) + + def test_auto_loading_policy(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + # True, + ) + + e.start_auto_load_policy(5/1000) + self.assertTrue(e.is_auto_loading_running()) + e.stop_auto_load_policy() + #thread needs a moment to exit + time.sleep(10/1000) + self.assertFalse(e.is_auto_loading_running()) + diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 8d4fa563..11204e7f 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -1,10 +1,17 @@ -from tests.test_enforcer import get_examples, get_enforcer -from unittest import TestCase +import casbin +from tests.test_enforcer import get_examples, TestCaseBase +class TestManagementApi(TestCaseBase): + + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.Enforcer( + model, + adapter, + enable_log, + ) -class TestManagementApi(TestCase): def test_get_list(self): - e = get_enforcer( + e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv"), # True, @@ -16,7 +23,7 @@ def test_get_list(self): self.assertEqual(e.get_all_roles(), ['data2_admin']) def test_get_policy_api(self): - e = get_enforcer( + e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv"), ) @@ -62,7 +69,7 @@ def test_get_policy_api(self): self.assertFalse(e.has_grouping_policy(['bob', 'data2_admin'])) def test_modify_policy_api(self): - e = get_enforcer( + e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv"), # True, @@ -85,3 +92,12 @@ def test_modify_policy_api(self): ['eve', 'data3', 'read'], ['eve', 'data3', 'write'], ]) + +class TestManagementApiSynced(TestManagementApi): + + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.SyncedEnforcer( + model, + adapter, + enable_log, + ) \ No newline at end of file diff --git a/tests/test_model_benchmark.py b/tests/test_model_benchmark.py index 9ff47b45..8c48c256 100644 --- a/tests/test_model_benchmark.py +++ b/tests/test_model_benchmark.py @@ -1,7 +1,8 @@ +import casbin import datetime import logging import sys -from tests.test_enforcer import get_examples, get_enforcer +from tests.test_enforcer import get_examples, TestCaseBase from unittest import TestCase log = logging.getLogger(__name__) @@ -18,9 +19,9 @@ def print_time_diff(start, end, time): log.debug("%s %f ms" % (get_function_name(), ms)) -class TestModelBenchmark(TestCase): +class TestModelBenchmark(TestCaseBase): def test_benchmark_basic_model(self): - e = get_enforcer(get_examples("basic_model.conf"), get_examples("basic_policy.csv")) + e = self.get_enforcer(get_examples("basic_model.conf"), get_examples("basic_policy.csv")) time = 10000 start = datetime.datetime.now() @@ -30,7 +31,7 @@ def test_benchmark_basic_model(self): print_time_diff(start, end, time) def test_benchmark_rbac_model(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) time = 10000 start = datetime.datetime.now() @@ -38,3 +39,12 @@ def test_benchmark_rbac_model(self): e.enforce("alice", "data2", "read") end = datetime.datetime.now() print_time_diff(start, end, time) + +class TestModelBenchmarkSynced(TestModelBenchmark): + + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.SyncedEnforcer( + model, + adapter, + enable_log, + ) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 90bdf9fe..bca5b8c5 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -1,10 +1,10 @@ -from tests.test_enforcer import get_examples, get_enforcer -from unittest import TestCase +import casbin +from tests.test_enforcer import get_examples, TestCaseBase -class TestRbacApi(TestCase): +class TestRbacApi(TestCaseBase): def test_get_roles_for_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) self.assertEqual(e.get_roles_for_user('bob'), []) @@ -12,23 +12,23 @@ def test_get_roles_for_user(self): self.assertEqual(e.get_roles_for_user('non_exist'), []) def test_get_users_for_role(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertEqual(e.get_users_for_role('data2_admin'), ['alice']) def test_has_role_for_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertTrue(e.has_role_for_user('alice', 'data2_admin')) self.assertFalse(e.has_role_for_user('alice', 'data1_admin')) def test_add_role_for_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.add_role_for_user('alice', 'data1_admin') self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) def test_delete_role_for_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.add_role_for_user('alice', 'data1_admin') self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) @@ -36,17 +36,17 @@ def test_delete_role_for_user(self): self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) def test_delete_roles_for_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_roles_for_user('alice') self.assertEqual(e.get_roles_for_user('alice'), []) def test_delete_user(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_user('alice') self.assertEqual(e.get_roles_for_user('alice'), []) def test_delete_role(self): - e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_role('data2_admin') self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertFalse(e.enforce('alice', 'data1', 'write')) @@ -58,7 +58,7 @@ def test_delete_role(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) def test_delete_permission(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) e.delete_permission('read') self.assertFalse(e.enforce('alice', 'read')) @@ -67,7 +67,7 @@ def test_delete_permission(self): self.assertTrue(e.enforce('bob', 'write')) def test_add_permission_for_user(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) e.delete_permission('read') e.add_permission_for_user('bob', 'read') @@ -75,7 +75,7 @@ def test_add_permission_for_user(self): self.assertTrue(e.enforce('bob', 'write')) def test_delete_permission_for_user(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) e.add_permission_for_user('bob', 'read') @@ -85,7 +85,7 @@ def test_delete_permission_for_user(self): self.assertTrue(e.enforce('bob', 'write')) def test_delete_permissions_for_user(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) e.delete_permissions_for_user('bob') @@ -94,12 +94,12 @@ def test_delete_permissions_for_user(self): self.assertFalse(e.enforce('bob', 'write')) def test_get_permissions_for_user(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) self.assertEqual(e.get_permissions_for_user('alice'), [['alice', 'read']]) def test_has_permission_for_user(self): - e = get_enforcer(get_examples("basic_without_resources_model.conf"), + e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), get_examples("basic_without_resources_policy.csv")) self.assertTrue(e.has_permission_for_user('alice', *['read'])) self.assertFalse(e.has_permission_for_user('alice', *['write'])) @@ -107,7 +107,7 @@ def test_has_permission_for_user(self): self.assertTrue(e.has_permission_for_user('bob', *['write'])) def test_enforce_implicit_roles_api(self): - e = get_enforcer(get_examples("rbac_model.conf"), + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_with_hierarchy_policy.csv")) self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) @@ -117,7 +117,7 @@ def test_enforce_implicit_roles_api(self): self.assertTrue(e.get_implicit_roles_for_user('bob') == []) def test_enforce_implicit_roles_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_hierarchy_with_domains_policy.csv")) self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) @@ -125,7 +125,7 @@ def test_enforce_implicit_roles_with_domain(self): e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) def test_enforce_implicit_permissions_api(self): - e = get_enforcer(get_examples("rbac_model.conf"), + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_with_hierarchy_policy.csv")) self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) @@ -138,7 +138,7 @@ def test_enforce_implicit_permissions_api(self): self.assertTrue(e.get_implicit_permissions_for_user('bob') == [["bob", "data2", "write"]]) def test_enforce_implicit_permissions_api_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_hierarchy_with_domains_policy.csv")) self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) @@ -151,7 +151,7 @@ def test_enforce_implicit_permissions_api_with_domain(self): self.assertTrue(e.get_implicit_permissions_for_user('bob', 'domain1') == []) def test_enforce_get_users_in_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) @@ -165,7 +165,7 @@ def test_enforce_get_users_in_domain(self): self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) def test_enforce_user_api_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain1'), ['alice']) self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain1'), []) @@ -181,7 +181,7 @@ def test_enforce_user_api_with_domain(self): self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain2'), []) def test_enforce_get_roles_with_domain(self): - e = get_enforcer(get_examples("rbac_with_domains_model.conf"), + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain1'), ['admin']) self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain1'), []) @@ -207,9 +207,18 @@ def test_enforce_get_roles_with_domain(self): self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) def test_implicit_user_api(self): - e = get_enforcer(get_examples("rbac_model.conf"),get_examples("rbac_with_hierarchy_policy.csv")) + e = self.get_enforcer(get_examples("rbac_model.conf"),get_examples("rbac_with_hierarchy_policy.csv")) self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "read")) self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "write")) self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read")) - self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) \ No newline at end of file + self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) + +class TestRbacApiSynced(TestRbacApi): + + def get_enforcer(self, model=None, adapter=None, enable_log=False): + return casbin.SyncedEnforcer( + model, + adapter, + enable_log, + ) \ No newline at end of file diff --git a/tests/util/test_rwlock.py b/tests/util/test_rwlock.py new file mode 100644 index 00000000..aa91940e --- /dev/null +++ b/tests/util/test_rwlock.py @@ -0,0 +1,85 @@ +from unittest import TestCase +from casbin.util.rwlock import RWLockWrite +from concurrent.futures import ThreadPoolExecutor +import time +import queue + +class TestRWLock(TestCase): + + def gen_locks(self): + rw_lock = RWLockWrite() + rl = rw_lock.gen_rlock() + wl = rw_lock.gen_wlock() + return (rl, wl) + + def test_multiple_readers(self): + [rl, _] = self.gen_locks() + + delay = 5/1000 #5ms + num_readers = 10 + start = time.time() + + def read(): + with rl: + time.sleep(delay) + + executor = ThreadPoolExecutor(num_readers) + futures = [executor.submit(read) for i in range(num_readers)] + [future.result() for future in futures] + exec_time = time.time() - start + + self.assertLess(exec_time, delay*num_readers) + + def test_single_writer(self): + [_, wl] = self.gen_locks() + + delay = 5/1000 #5ms + num_writers = 10 + start = time.time() + + def write(): + with wl: + time.sleep(delay) + + executor = ThreadPoolExecutor(num_writers) + futures = [executor.submit(write) for i in range(num_writers)] + [future.result() for future in futures] + exec_time = time.time() - start + + self.assertGreaterEqual(exec_time, delay*num_writers) + + def test_writer_preference(self): + [rl, wl] = self.gen_locks() + + q = queue.Queue() + delay = 5/1000 #5ms + start = time.time() + + def read(): + with rl: + time.sleep(delay) + q.put('r') + + def write(): + with wl: + time.sleep(delay) + q.put('w') + + executor = ThreadPoolExecutor(10) + futures = [executor.submit(read) for i in range(3)] + time.sleep(1/1000) + futures += [executor.submit(write) for i in range(3)] + time.sleep(1/1000) + futures += [executor.submit(read) for i in range(3)] + [future.result() for future in futures] + + sequence = '' + while not q.empty(): + sequence += q.get() + + self.assertEqual(sequence, 'rrrwwwrrr') + + + + + From 53fa9d5365af8d641bf903c7df3de68c9014cd53 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 23 Dec 2020 14:20:25 +0000 Subject: [PATCH 090/349] chore(release): 0.14.0 [skip ci] # [0.14.0](https://github.com/casbin/pycasbin/compare/v0.13.0...v0.14.0) (2020-12-23) ### Features * add function get_role_manager ([7fb1879](https://github.com/casbin/pycasbin/commit/7fb1879e3bbdc77ea5e01ac46f96d81f917cb6fe)) * add SyncedEnforcer ([9439272](https://github.com/casbin/pycasbin/commit/94392722a1fd4680a7f4072ae20b176e8225be15)) --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95af49f8..cc9345b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Semantic Versioning Changelog +# [0.14.0](https://github.com/casbin/pycasbin/compare/v0.13.0...v0.14.0) (2020-12-23) + + +### Features + +* add function get_role_manager ([7fb1879](https://github.com/casbin/pycasbin/commit/7fb1879e3bbdc77ea5e01ac46f96d81f917cb6fe)) +* add SyncedEnforcer ([9439272](https://github.com/casbin/pycasbin/commit/94392722a1fd4680a7f4072ae20b176e8225be15)) + # [0.13.0](https://github.com/casbin/pycasbin/compare/v0.12.0...v0.13.0) (2020-12-14) diff --git a/setup.cfg b/setup.cfg index 401d6390..16423627 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.13.0 +version = 0.14.0 From a79222824b56d76886de2172e0955ab0b42cd4c3 Mon Sep 17 00:00:00 2001 From: yyellowsun Date: Mon, 21 Dec 2020 16:05:24 +0800 Subject: [PATCH 091/349] feat: add filtered_adapter Signed-off-by: yyellowsun --- casbin/core_enforcer.py | 10 ++ casbin/persist/__init__.py | 1 + casbin/persist/adapter.py | 8 -- casbin/persist/adapter_filtered.py | 13 ++ casbin/persist/adapters/__init__.py | 1 + casbin/persist/adapters/adapter_filtered.py | 89 ++++++++++++++ casbin/persist/adapters/file_adapter.py | 13 +- test_filter.py | 125 ++++++++++++++++++++ 8 files changed, 240 insertions(+), 20 deletions(-) create mode 100644 casbin/persist/adapter_filtered.py create mode 100644 casbin/persist/adapters/adapter_filtered.py create mode 100644 test_filter.py diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 26ddc671..40ba5ae9 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -161,6 +161,16 @@ def load_filtered_policy(self, filter): self.model.print_policy() if self.auto_build_role_links: self.build_role_links() + + def load_increment_filtered_policy(self,filter): + """LoadIncrementalFilteredPolicy append a filtered policy from file/database.""" + if not hasattr(self.adapter, "is_filtered"): + raise ValueError("filtered policies are not supported by this adapter") + + self.adapter.load_filtered_policy(self.model, filter) + self.model.print_policy() + if self.auto_build_role_links: + self.build_role_links() def is_filtered(self): """returns true if the loaded policy has been filtered.""" diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index 38fc8322..cee63db5 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -1 +1,2 @@ from .adapter import * +from .adapter_filtered import * \ No newline at end of file diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 6ecb7d9a..b290be2c 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -44,11 +44,3 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): This is part of the Auto-Save feature. """ pass - - def is_filtered(self): - """Marks if the loaded policy is filtered or not""" - pass - - def load_filtered_policy(self, model, filter): - """Loads policy rules that match the filter from the storage.""" - pass diff --git a/casbin/persist/adapter_filtered.py b/casbin/persist/adapter_filtered.py new file mode 100644 index 00000000..fbb4fefc --- /dev/null +++ b/casbin/persist/adapter_filtered.py @@ -0,0 +1,13 @@ +from .adapter import Adapter + +""" FilteredAdapter is the interface for Casbin adapters supporting filtered policies.""" +class FilteredAdapter(Adapter): + def is_filtered(self): + """IsFiltered returns true if the loaded policy has been filtered + Marks if the loaded policy is filtered or not + """ + pass + + def load_filtered_policy(self, model, filter): + """Loads policy rules that match the filter from the storage.""" + pass \ No newline at end of file diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index b82b676d..f1b9da13 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -1 +1,2 @@ from .file_adapter import FileAdapter +from .adapter_filtered import FilteredAdapter \ No newline at end of file diff --git a/casbin/persist/adapters/adapter_filtered.py b/casbin/persist/adapters/adapter_filtered.py new file mode 100644 index 00000000..08e500f5 --- /dev/null +++ b/casbin/persist/adapters/adapter_filtered.py @@ -0,0 +1,89 @@ +from casbin import persist +from casbin import model +from .file_adapter import FileAdapter +import os + +class Filter: + #P,G are string [] + P = [] + G = [] + +class FilteredAdapter (FileAdapter,persist.FilteredAdapter): + filtered = False + _file_path = "" + filter = Filter() + #new_filtered_adapte is the constructor for FilteredAdapter. + def __init__(self,file_path): + self.filtered = True + self._file_path = file_path + + def load_policy(self,model): + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + self.filtered=False + self._load_policy_file(model) + + #load_filtered_policy loads only policy rules that match the filter. + def load_filtered_policy(self,model,filter): + if filter == None: + return self.load_policy(model) + + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + + try: + filter_value = [filter.__dict__['P']]+[filter.__dict__['G']] + except: + raise RuntimeError("invalid filter type") + + self.load_filtered_policy_file(model,filter_value,persist.load_policy_line) + self.filtered = True + + def load_filtered_policy_file(self,model,filter,hanlder): + with open(self._file_path, "rb") as file: + while True: + line = file.readline() + line = line.decode().strip() + if line == '\n': + continue + if not line : + break + if filter_line(line,filter): + continue + + hanlder(line,model) + + #is_filtered returns true if the loaded policy has been filtered. + def is_filtered(self): + return self.filtered + def save_policy(self,model): + if self.filtered: + raise RuntimeError("cannot save a filtered policy") + + self._save_policy_file(model) + +def filter_line(line,filter): + if filter == None: + return False + + p = line.split(',') + if len(p) == 0: + return True + filter_slice = [] + + if p[0].strip()== 'p': + filter_slice = filter[0] + elif p[0].strip() == 'g': + filter_slice = filter[1] + return filter_words(p,filter_slice) + +def filter_words(line,filter): + if len(line) < len(filter)+1: + return True + skip_line=False + for i,v in enumerate(filter): + if(len(v) >0 and ( v.strip() != line[i+1].strip() ) ): + skip_line = True + break + + return skip_line \ No newline at end of file diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index 805f34b8..c62c9b2a 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -1,12 +1,10 @@ from casbin import persist import os - class FileAdapter(persist.Adapter): """the file adapter for Casbin. It can load policy from file or save policy to file. """ - _file_path = "" def __init__(self, file_path): @@ -55,13 +53,4 @@ def add_policy(self, sec, ptype, rule): pass def remove_policy(self, sec, ptype, rule): - pass - - def remove_filtered_policy(self, sec, ptype, field_index, *field_values): - pass - - def is_filtered(self): - return False - - def load_filtered_policy(self, model, filter): - pass + pass \ No newline at end of file diff --git a/test_filter.py b/test_filter.py new file mode 100644 index 00000000..fec4f942 --- /dev/null +++ b/test_filter.py @@ -0,0 +1,125 @@ +import casbin +import os +from unittest import TestCase +import unittest +class Filter: + #P,G are strings + P = [] + G = [] +class TestFilteredAdapter(TestCase): + def test_init_filtered_adapter(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + self.assertFalse(e.has_policy(['admin', 'domain1', 'data1','read'])) + + def test_load_filtered_policy(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + filter = Filter() + filter.P = ["", "domain1"] + filter.G = ["", "", "domain1"] + try: + e.load_policy() + except: + raise RuntimeError("nexpected error in LoadFilteredPolicy") + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) + try: + e.load_filtered_policy(filter) + except: + raise RuntimeError("unexpected error in LoadFilteredPolicy") + + if not e.is_filtered: + raise RuntimeError("adapter did not set the filtered flag correctly") + + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) + self.assertFalse(e.has_policy(['admin', 'domain2', 'data2','read'])) + + with self.assertRaises(RuntimeError): + e.save_policy() + + with self.assertRaises(RuntimeError): + e.get_adapter().save_policy(e.get_model()) + + def test_append_filtered_policy(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + filter = Filter() + filter.P = ["", "domain1"] + filter.G = ["", "", "domain1"] + try: + e.load_policy() + except: + raise RuntimeError("nexpected error in LoadFilteredPolicy") + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) + try: + e.load_filtered_policy(filter) + except: + raise RuntimeError("unexpected error in LoadFilteredPolicy") + + if not e.is_filtered: + raise RuntimeError("adapter did not set the filtered flag correctly") + + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) + self.assertFalse(e.has_policy(['admin', 'domain2', 'data2','read'])) + + filter.P = ["", "domain2"] + filter.G = ["", "", "domain2"] + try: + e.load_increment_filtered_policy(filter) + except: + raise RuntimeError("unexpected error in LoadFilteredPolicy") + + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) + + + def test_filtered_policy_invalid_filter(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + filter = ["","domain1"] + + with self.assertRaises(RuntimeError): + e.load_filtered_policy(filter) + + + def test_filtered_policy_empty_filter(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + + try: + e.load_filtered_policy(None) + except: + raise RuntimeError("unexpected error in LoadFilteredPolicy") + + if e.is_filtered(): + raise RuntimeError("adapter did not reset the filtered flag correctly") + + try: + e.save_policy() + except: + raise RuntimeError("unexpected error in SavePolicy") + + + def test_unsupported_filtered_policy(self): + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv") + filter = Filter() + filter.P = ["", "domain1"] + filter.G = ["", "", "domain1"] + with self.assertRaises(ValueError): + e.load_filtered_policy(filter) + + def test_filtered_adapter_empty_filepath(self): + adapter = casbin.persist.adapters.FilteredAdapter("") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf",adapter) + + with self.assertRaises(RuntimeError): + e.load_filtered_policy(None) + + def test_filtered_adapter_invalid_filepath(self): + adapter = casbin.persist.adapters.FilteredAdapter("examples/does_not_exist_policy.csv") + e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + + with self.assertRaises(FileNotFoundError): + e.load_filtered_policy(None) From 57783700ef1511a2f5abb17f7fbc682128b3b3d3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 26 Dec 2020 15:06:58 +0000 Subject: [PATCH 092/349] chore(release): 0.15.0 [skip ci] # [0.15.0](https://github.com/casbin/pycasbin/compare/v0.14.0...v0.15.0) (2020-12-26) ### Features * add filtered_adapter ([ee817b5](https://github.com/casbin/pycasbin/commit/ee817b521b94aaf90533906efd158c761b25f9f4)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9345b5..81c9de31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.15.0](https://github.com/casbin/pycasbin/compare/v0.14.0...v0.15.0) (2020-12-26) + + +### Features + +* add filtered_adapter ([ee817b5](https://github.com/casbin/pycasbin/commit/ee817b521b94aaf90533906efd158c761b25f9f4)) + # [0.14.0](https://github.com/casbin/pycasbin/compare/v0.13.0...v0.14.0) (2020-12-23) diff --git a/setup.cfg b/setup.cfg index 16423627..187fcf06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.14.0 +version = 0.15.0 From 038ad1cc1dd44b971639ea95e6f756c5cc2f12b5 Mon Sep 17 00:00:00 2001 From: yyellowsun Date: Fri, 1 Jan 2021 23:04:11 +0800 Subject: [PATCH 093/349] feat: add batch_adapter Signed-off-by: yyellowsun --- casbin/internal_enforcer.py | 32 +++++++++++++- casbin/management_enforcer.py | 57 ++++++++++++++++++++++++- casbin/model/policy.py | 29 +++++++++++++ casbin/persist/adapters/file_adapter.py | 6 +++ casbin/persist/batch_adapter.py | 11 +++++ tests/test_management_api.py | 50 +++++++++++++++++++++- 6 files changed, 180 insertions(+), 5 deletions(-) create mode 100644 casbin/persist/batch_adapter.py diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index ac87b3b1..09e7f806 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -1,7 +1,5 @@ - from casbin.core_enforcer import CoreEnforcer - class InternalEnforcer(CoreEnforcer): """ InternalEnforcer = CoreEnforcer + Internal API. @@ -21,7 +19,22 @@ def _add_policy(self, sec, ptype, rule): self.watcher.update() return rule_added + + def _add_policies(self,sec,ptype,rules): + """adds rules to the current policy.""" + rules_added = self.model.add_policies(sec, ptype, rules) + if not rules_added: + return rules_added + + if self.adapter and self.auto_save: + if self.adapter.add_policies(sec, ptype, rules) is False: + return False + + if self.watcher: + self.watcher.update() + return rules_added + def _remove_policy(self, sec, ptype, rule): """removes a rule from the current policy.""" rule_removed = self.model.remove_policy(sec, ptype, rule) @@ -37,6 +50,21 @@ def _remove_policy(self, sec, ptype, rule): return rule_removed + def _remove_policies(self, sec, ptype, rules): + """RemovePolicies removes policy rules from the model.""" + rules_removed = self.model.remove_policies(sec, ptype, rules) + if not rules_removed: + return rules_removed + + if self.adapter and self.auto_save: + if self.adapter.remove_policies(sec, ptype, rules) is False: + return False + + if self.watcher: + self.watcher.update() + + return rules_removed + def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes rules based on field filters from the current policy.""" rule_removed = self.model.remove_filtered_policy(sec, ptype, field_index, *field_values) diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index e1f91d4c..568e9fc9 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -94,6 +94,14 @@ def add_policy(self, *params): Otherwise the function returns true by adding the new rule. """ return self.add_named_policy('p', *params) + + def add_policies(self,rules): + """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. + """ + return self.add_named_policies('p',rules) def add_named_policy(self, ptype, *params): """adds an authorization rule to the current named policy. @@ -115,10 +123,21 @@ def add_named_policy(self, ptype, *params): return rule_added + def add_named_policies(self,ptype,rules): + """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.""" + return self._add_policies('p',ptype,rules) + def remove_policy(self, *params): """removes an authorization rule from the current policy.""" return self.remove_named_policy('p', *params) + def remove_policies(self,rules): + """removes authorization rules from the current policy.""" + return self.remove_named_policies('p',rules) + def remove_filtered_policy(self, field_index, *field_values): """removes an authorization rule from the current policy, field filters can be specified.""" return self.remove_filtered_named_policy('p', field_index, *field_values) @@ -139,6 +158,10 @@ def remove_named_policy(self, ptype, *params): return rule_removed + def remove_named_policies(self,ptype,rules): + """removes authorization rules from the current named policy.""" + return self._remove_policies('p',ptype,rules) + def remove_filtered_named_policy(self, ptype, field_index, *field_values): """removes an authorization rule from the current named policy, field filters can be specified.""" return self._remove_filtered_policy('p', ptype, field_index, *field_values) @@ -146,7 +169,7 @@ def remove_filtered_named_policy(self, ptype, field_index, *field_values): def has_grouping_policy(self, *params): """determines whether a role inheritance rule exists.""" - return self.has_named_grouping_policy("g", *params) + return self.has_named_grouping_policy('g', *params) def has_named_grouping_policy(self, ptype, *params): """determines whether a named role inheritance rule exists.""" @@ -170,6 +193,14 @@ def add_grouping_policy(self, *params): """ return self.add_named_grouping_policy('g', *params) + def add_grouping_policies(self,rules): + """adds role inheritance rulea to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise the function returns true for the corresponding policy rule by adding the new rule. + """ + return self.add_named_grouping_policies('g',rules) + def add_named_grouping_policy(self, ptype, *params): """adds a named role inheritance rule to the current policy. @@ -192,10 +223,25 @@ def add_named_grouping_policy(self, ptype, *params): self.build_role_links() return rule_added + def add_named_grouping_policies(self,ptype,rules): + """"adds named role inheritance rules to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" + rules_added = self._add_policies('g',ptype,rules) + if self.auto_build_role_links: + self.build_role_links() + + return rules_added + def remove_grouping_policy(self, *params): """removes a role inheritance rule from the current policy.""" return self.remove_named_grouping_policy('g', *params) + def remove_grouping_policies(self,rules): + """removes role inheritance rulea from the current policy.""" + return self.remove_named_grouping_policies('g',rules) + def remove_filtered_grouping_policy(self, field_index, *field_values): """removes a role inheritance rule from the current policy, field filters can be specified.""" return self.remove_filtered_named_grouping_policy('g', field_index, *field_values) @@ -217,6 +263,15 @@ def remove_named_grouping_policy(self, ptype, *params): if self.auto_build_role_links: self.build_role_links() return rule_removed + + def remove_named_grouping_policies(self,ptype,rules): + """ removes role inheritance rules from the current named policy.""" + rules_removed = self._remove_policies('g',ptype,rules) + + if self.auto_build_role_links: + self.build_role_links() + + return rules_removed def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" diff --git a/casbin/model/policy.py b/casbin/model/policy.py index ddd0efcb..b9e08c34 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -74,6 +74,18 @@ def add_policy(self, sec, ptype, rule): return False + def add_policies(self,sec,ptype,rules): + """adds policy rules to the model.""" + + for rule in rules: + if self.has_policy(sec,ptype,rule): + return False + + for rule in rules: + self.model[sec][ptype].policy.append(rule) + + return True + def remove_policy(self, sec, ptype, rule): """removes a policy rule from the model.""" @@ -89,6 +101,23 @@ def remove_policy(self, sec, ptype, rule): return rule not in self.model[sec][ptype].policy + def remove_policies(self, sec, ptype, rules): + """RemovePolicies removes policy rules from the model.""" + + if sec not in self.model.keys(): + return False + if ptype not in self.model[sec]: + return False + + for rule in rules: + if not self.has_policy(sec,ptype,rule): + return False + self.model[sec][ptype].policy.remove(rule) + if rule in self.model[sec][ptype].policy: + return False + + return True + def remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes policy rules based on field filters from the model.""" tmp = [] diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index c62c9b2a..7f2e86f6 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -51,6 +51,12 @@ def _save_policy_file(self, model): def add_policy(self, sec, ptype, rule): pass + + def add_policies(self,sec,ptype,rules): + pass def remove_policy(self, sec, ptype, rule): + pass + + def remove_policies(self,sec,ptype,rules): pass \ No newline at end of file diff --git a/casbin/persist/batch_adapter.py b/casbin/persist/batch_adapter.py new file mode 100644 index 00000000..6dbf88da --- /dev/null +++ b/casbin/persist/batch_adapter.py @@ -0,0 +1,11 @@ +from .adapter import Adapter + +"""BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.""" +class BatchAdapter(Adapter): + def add_policies(self,sec,ptype,rules): + """AddPolicies adds policy rules to the storage.""" + pass + + def remove_policies(self,sec,ptype,rules): + """RemovePolicies removes policy rules from the storage.""" + pass \ No newline at end of file diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 11204e7f..cf5b5bbc 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -74,7 +74,6 @@ def test_modify_policy_api(self): get_examples("rbac_policy.csv"), # True, ) - self.assertEqual(e.get_policy(), [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], @@ -92,7 +91,53 @@ def test_modify_policy_api(self): ['eve', 'data3', 'read'], ['eve', 'data3', 'write'], ]) + + rules = [ + ['jack', 'data4', 'read'], + ['katy', 'data4', 'write'], + ['leyo', 'data4', 'read'], + ['ham', 'data4', 'write'] + ] + + named_policies = [ + ['jack', 'data4', 'write'], + ['katy', 'data4', 'read'], + ['leyo', 'data4', 'write'], + ['ham', 'data4', 'read'] + ] + e.add_policies(rules) + e.add_named_policies('p',named_policies) + + self.assertEqual(e.get_policy(), [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ['eve', 'data3', 'read'], + ['eve', 'data3', 'write'], + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ['jack', 'data4', 'write'], + ['katy', 'data4', 'read'], + ['leyo', 'data4', 'write'], + ['ham', 'data4', 'read'] + ] + ) + e.remove_policies(rules) + e.remove_named_policies('p',named_policies) + + self.assertEqual(e.get_policy(), [ + ['alice', 'data1', 'read'], + ['bob', 'data2', 'write'], + ['data2_admin', 'data2', 'read'], + ['data2_admin', 'data2', 'write'], + ['eve', 'data3', 'read'], + ['eve', 'data3', 'write'], + ]) +""" class TestManagementApiSynced(TestManagementApi): def get_enforcer(self, model=None, adapter=None, enable_log=False): @@ -100,4 +145,5 @@ def get_enforcer(self, model=None, adapter=None, enable_log=False): model, adapter, enable_log, - ) \ No newline at end of file + ) +""" \ No newline at end of file From 296a7d4bf1d4cc8bc28dabb90bed477f7548c060 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Sun, 3 Jan 2021 14:17:23 +0100 Subject: [PATCH 094/349] feat: add batch api to SyncedEnforcer Signed-off-by: Andreas Bichinger --- casbin/internal_enforcer.py | 8 ++++- casbin/management_enforcer.py | 3 +- casbin/persist/__init__.py | 3 +- casbin/synced_enforcer.py | 58 +++++++++++++++++++++++++++++++++-- test_filter.py | 2 +- tests/test_management_api.py | 5 ++- 6 files changed, 69 insertions(+), 10 deletions(-) diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 09e7f806..628c68be 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -27,6 +27,9 @@ def _add_policies(self,sec,ptype,rules): return rules_added if self.adapter and self.auto_save: + if hasattr(self.adapter,'add_policies') is False: + return False + if self.adapter.add_policies(sec, ptype, rules) is False: return False @@ -57,6 +60,9 @@ def _remove_policies(self, sec, ptype, rules): return rules_removed if self.adapter and self.auto_save: + if hasattr(self.adapter,'remove_policies') is False: + return False + if self.adapter.remove_policies(sec, ptype, rules) is False: return False @@ -78,4 +84,4 @@ def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): if self.watcher: self.watcher.update() - return rule_removed + return rule_removed \ No newline at end of file diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 568e9fc9..7e62a760 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -1,6 +1,5 @@ from casbin.internal_enforcer import InternalEnforcer - class ManagementEnforcer(InternalEnforcer): """ ManagementEnforcer = InternalEnforcer + Management API. @@ -283,4 +282,4 @@ def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_value def add_function(self, name, func): """adds a customized function.""" - self.fm.add_function(name, func) + self.fm.add_function(name, func) \ No newline at end of file diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index cee63db5..d681a76f 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -1,2 +1,3 @@ from .adapter import * -from .adapter_filtered import * \ No newline at end of file +from .adapter_filtered import * +from .batch_adapter import * \ No newline at end of file diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 55ec57e0..8f815c05 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -19,7 +19,7 @@ def value(self, value): with self._lock: self._value = value -class SyncedEnforcer(Enforcer): +class SyncedEnforcer(): """SyncedEnforcer wraps Enforcer and provides synchronized access. It's also a drop-in replacement for Enforcer""" @@ -505,4 +505,58 @@ def enable_enforce(self, enabled=True): def is_filtered(self): """returns true if the loaded policy has been filtered.""" with self._rl: - self._e.is_filtered() \ No newline at end of file + self._e.is_filtered() + + def add_policies(self,rules): + """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. + """ + with self._wl: + return self._e.add_policies(rules) + + def add_named_policies(self,ptype,rules): + """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.""" + with self._wl: + return self._e.add_named_policies(ptype,rules) + + def remove_policies(self,rules): + """removes authorization rules from the current policy.""" + with self._wl: + return self._e.remove_policies(rules) + + def remove_named_policies(self,ptype,rules): + """removes authorization rules from the current named policy.""" + with self._wl: + return self._e.remove_named_policies(ptype,rules) + + def add_grouping_policies(self,rules): + """adds role inheritance rulea to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise the function returns true for the corresponding policy rule by adding the new rule. + """ + with self._wl: + return self._e.add_grouping_policies(rules) + + def add_named_grouping_policies(self,ptype,rules): + """"adds named role inheritance rules to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" + with self._wl: + return self._e.add_named_grouping_policies(ptype,rules) + + def remove_grouping_policies(self,rules): + """removes role inheritance rulea from the current policy.""" + with self._wl: + return self._e.addremove_grouping_policies_policies(rules) + + def remove_named_grouping_policies(self,ptype,rules): + """ removes role inheritance rules from the current named policy.""" + with self._wl: + return self._e.remove_named_grouping_policies(ptype,rules) \ No newline at end of file diff --git a/test_filter.py b/test_filter.py index fec4f942..22d91104 100644 --- a/test_filter.py +++ b/test_filter.py @@ -122,4 +122,4 @@ def test_filtered_adapter_invalid_filepath(self): e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) with self.assertRaises(FileNotFoundError): - e.load_filtered_policy(None) + e.load_filtered_policy(None) \ No newline at end of file diff --git a/tests/test_management_api.py b/tests/test_management_api.py index cf5b5bbc..4e445e4a 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -137,7 +137,7 @@ def test_modify_policy_api(self): ['eve', 'data3', 'read'], ['eve', 'data3', 'write'], ]) -""" + class TestManagementApiSynced(TestManagementApi): def get_enforcer(self, model=None, adapter=None, enable_log=False): @@ -145,5 +145,4 @@ def get_enforcer(self, model=None, adapter=None, enable_log=False): model, adapter, enable_log, - ) -""" \ No newline at end of file + ) \ No newline at end of file From 6b011f035c8aaac5a8fa43aa5300c2d9900c41be Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 9 Jan 2021 08:48:05 +0000 Subject: [PATCH 095/349] chore(release): 0.16.0 [skip ci] # [0.16.0](https://github.com/casbin/pycasbin/compare/v0.15.0...v0.16.0) (2021-01-09) ### Features * add batch api to SyncedEnforcer ([9191ccd](https://github.com/casbin/pycasbin/commit/9191ccd72cbeed6e36303d4cbf0407b626718421)) * add batch_adapter ([9470c3e](https://github.com/casbin/pycasbin/commit/9470c3ebf678ac4c64bffea623fb9a974f4f2e95)) --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c9de31..a9227cbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Semantic Versioning Changelog +# [0.16.0](https://github.com/casbin/pycasbin/compare/v0.15.0...v0.16.0) (2021-01-09) + + +### Features + +* add batch api to SyncedEnforcer ([9191ccd](https://github.com/casbin/pycasbin/commit/9191ccd72cbeed6e36303d4cbf0407b626718421)) +* add batch_adapter ([9470c3e](https://github.com/casbin/pycasbin/commit/9470c3ebf678ac4c64bffea623fb9a974f4f2e95)) + # [0.15.0](https://github.com/casbin/pycasbin/compare/v0.14.0...v0.15.0) (2020-12-26) diff --git a/setup.cfg b/setup.cfg index 187fcf06..1b88664f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.15.0 +version = 0.16.0 From 2af53d237cd97753e319d89498607ba12559260d Mon Sep 17 00:00:00 2001 From: "Carme, Pamy" Date: Mon, 26 Oct 2020 21:26:26 -0400 Subject: [PATCH 096/349] delete custom logs Signed-off-by: Carme, Pamy --- casbin/log/__init__.py | 3 --- casbin/log/default_logger.py | 37 ------------------------------------ casbin/log/log.py | 24 ----------------------- casbin/log/logger.py | 18 ------------------ tests/log/__init__.py | 0 tests/log/test_log.py | 12 ------------ 6 files changed, 94 deletions(-) delete mode 100644 casbin/log/__init__.py delete mode 100644 casbin/log/default_logger.py delete mode 100644 casbin/log/log.py delete mode 100644 casbin/log/logger.py delete mode 100644 tests/log/__init__.py delete mode 100644 tests/log/test_log.py diff --git a/casbin/log/__init__.py b/casbin/log/__init__.py deleted file mode 100644 index 498b1645..00000000 --- a/casbin/log/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .default_logger import DefaultLogger -from .logger import Logger -from .log import * diff --git a/casbin/log/default_logger.py b/casbin/log/default_logger.py deleted file mode 100644 index d9ce7ceb..00000000 --- a/casbin/log/default_logger.py +++ /dev/null @@ -1,37 +0,0 @@ -from .logger import Logger -import logging - - -class DefaultLogger(Logger): - """the implementation for a Logger using logging.""" - - enable = False - def __init__(self): - self.logger = logging.getLogger('casbin') - self.logger.setLevel(logging.INFO) - handler = logging.StreamHandler() - fmt = "%(asctime)s - %(levelname)s - %(message)s" - formatter = logging.Formatter(fmt) - handler.setFormatter(formatter) - self.logger.addHandler(handler) - - def enable_log(self, enable): - """controls whether print the message.""" - self.enable = enable - - def is_enabled(self): - """returns if logger is enabled.""" - return self.enable - - def write(self, *v): - """formats using the default formats for its operands and logs the message.""" - if self.enable: - s = "" - for vv in v: - s = s + str(vv) - self.logger.info(s) - - def writef(self, fmt, *v): - """formats according to a format specifier and logs the message.""" - if self.enable: - self.logger.info(fmt, *v) diff --git a/casbin/log/log.py b/casbin/log/log.py deleted file mode 100644 index b029a22e..00000000 --- a/casbin/log/log.py +++ /dev/null @@ -1,24 +0,0 @@ -from .default_logger import DefaultLogger - -logger = DefaultLogger() - - -def set_logger(l): - """sets the current logger.""" - global logger - logger = l - - -def get_logger(): - """returns the current logger.""" - return logger - - -def log_print(*v): - """prints the log.""" - logger.write(*v) - - -def log_printf(fmt, *v): - """prints the log with the format.""" - logger.writef(fmt, *v) diff --git a/casbin/log/logger.py b/casbin/log/logger.py deleted file mode 100644 index 2624ea80..00000000 --- a/casbin/log/logger.py +++ /dev/null @@ -1,18 +0,0 @@ -class Logger: - """Logger is the logging interface implementation.""" - - def enable_log(self, enable): - """controls whether print the message.""" - pass - - def is_enabled(self): - """returns if logger is enabled.""" - pass - - def write(self, *v): - """formats using the default formats for its operands and logs the message.""" - pass - - def writef(self, fmt, *v): - """formats according to a format specifier and logs the message.""" - pass diff --git a/tests/log/__init__.py b/tests/log/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/log/test_log.py b/tests/log/test_log.py deleted file mode 100644 index aad7afb0..00000000 --- a/tests/log/test_log.py +++ /dev/null @@ -1,12 +0,0 @@ -from unittest import TestCase -from casbin import log - - -class TestLog(TestCase): - - def test_log(self): - log.get_logger().enable_log(True) - log.log_print("test log", "print") - log.log_printf("test log %s", "print") - - self.assertTrue(log.get_logger().is_enabled()) From 27ceb7ed74af801150eb908c9dc03010a66ec460 Mon Sep 17 00:00:00 2001 From: "Carme, Pamy" Date: Mon, 26 Oct 2020 21:26:52 -0400 Subject: [PATCH 097/349] use root logger as default Signed-off-by: Carme, Pamy --- casbin/core_enforcer.py | 26 +++++++++---------- casbin/model/assertion.py | 5 ++-- casbin/model/model.py | 6 ++--- casbin/model/policy.py | 10 ++++--- .../rbac/default_role_manager/role_manager.py | 6 ++--- casbin/synced_enforcer.py | 9 ++----- tests/test_enforcer.py | 13 +++------- tests/test_management_api.py | 6 ++--- tests/test_model_benchmark.py | 7 +++-- tests/test_rbac_api.py | 3 +-- 10 files changed, 38 insertions(+), 53 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 40ba5ae9..8367f579 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -1,9 +1,9 @@ -from casbin import log from casbin.persist.adapters import FileAdapter from casbin.model import Model, FunctionMap from casbin.rbac import default_role_manager from casbin.util import generate_g_function, SimpleEval, util from casbin.effect import DefaultEffector, Effector +import logging class CoreEnforcer: @@ -22,9 +22,8 @@ class CoreEnforcer: auto_save = False auto_build_role_links = False - def __init__(self, model=None, adapter=None, enable_log=False): - self.enable_log(enable_log) - + def __init__(self, model=None, adapter=None): + self.logger = logging.getLogger() if isinstance(model, str): if isinstance(adapter, str): self.init_with_file(model, adapter) @@ -193,11 +192,6 @@ def enable_enforce(self, enabled=True): self.enabled = enabled - def enable_log(self, enable): - """changes whether Casbin will log messages to the Logger.""" - - log.get_logger().enable_log(enable) - def enable_auto_save(self, auto_save): """controls whether to save a policy rule automatically to the adapter when it is added or removed.""" self.auto_save = auto_save @@ -313,12 +307,16 @@ def enforce(self, *rvals): result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results) # Log request. - if log.get_logger().is_enabled(): - req_str = "Request: " - req_str = req_str + ", ".join([str(v) for v in rvals]) - req_str = req_str + " ---> %s" % result - log.log_print(req_str) + req_str = "Request: " + req_str = req_str + ", ".join([str(v) for v in rvals]) + + req_str = req_str + " ---> %s" % result + if result: + self.logger.info(req_str) + else: + # leaving this in error for now, if it's very noise this can be changed to info or debug + self.logger.error(req_str) return result diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index f2a49a63..7ad0cce5 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -1,8 +1,9 @@ -from casbin import log +import logging class Assertion: def __init__(self): + self.logger = logging.getLogger() self.key = "" self.value = "" self.tokens = [] @@ -22,5 +23,5 @@ def build_role_links(self, rm): self.rm.add_link(*rule[:count]) - log.log_print("Role links for: " + self.key) + self.logger.info("Role links for: {}".format(self.key)) self.rm.print_roles() diff --git a/casbin/model/model.py b/casbin/model/model.py index 6424cd22..f3e197d6 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -1,5 +1,5 @@ from . import Assertion -from casbin import util, config, log +from casbin import util, config from .policy import Policy @@ -75,7 +75,7 @@ def load_model_from_text(self, text): self._load_section(cfg, "g") def print_model(self): - log.log_print("Model:") + self.logger.info("Model:") for k, v in self.model.items(): for i, j in v.items(): - log.log_printf("%s.%s: %s", k, i, j.value) + self.logger.info("%s.%s: %s", k, i, j.value) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index b9e08c34..d6788e4c 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,8 +1,10 @@ -from casbin import util, log +from casbin import util +import logging class Policy: def __init__(self): + self.logger = logging.getLogger() self.model = {} def build_role_links(self, rm): @@ -15,15 +17,15 @@ def build_role_links(self, rm): ast.build_role_links(rm) def print_policy(self): - """prints the policy to log.""" + """Log using info""" - log.log_print("Policy:") + self.logger.info("Policy:") for sec in ["p", "g"]: if sec not in self.model.keys(): continue for key, ast in self.model[sec].items(): - log.log_print(key, ": ", ast.value, ": ", ast.policy) + self.logger.info(f"{key} : {ast.value} : {ast.policy}") def clear_policy(self): """clears all current policy.""" diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 34cfbe11..db5872d4 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -1,5 +1,5 @@ -from casbin import log from casbin.rbac import RoleManager +import logging class RoleManager(RoleManager): @@ -9,6 +9,7 @@ class RoleManager(RoleManager): max_hierarchy_level = 0 def __init__(self, max_hierarchy_level): + self.logger = logging.getLogger() self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level self.matching_func = None @@ -130,8 +131,7 @@ def print_roles(self): text = role.to_string() if text: line.append(text) - - log.log_print(", ".join(line)) + self.logger.info(", ".join(line)) class Role: diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 8f815c05..bce7720b 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -24,8 +24,8 @@ class SyncedEnforcer(): """SyncedEnforcer wraps Enforcer and provides synchronized access. It's also a drop-in replacement for Enforcer""" - def __init__(self, model=None, adapter=None, enable_log=False): - self._e = Enforcer(model, adapter, enable_log) + def __init__(self, model=None, adapter=None): + self._e = Enforcer(model, adapter) self._rwlock = RWLockWrite() self._rl = self._rwlock.gen_rlock() self._wl = self._rwlock.gen_wlock() @@ -99,11 +99,6 @@ def set_effector(self, eft): with self._wl: self._e.set_effector(eft) - def enable_log(self, enable): - """changes whether Casbin will log messages to the Logger.""" - with self._wl: - return self._e.enable_log(enable) - def clear_policy(self): """ clears all policy.""" with self._wl: diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index e78c678f..e01c3292 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -14,11 +14,10 @@ def __init__(self, name, age): self.age = age class TestCaseBase(TestCase): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.Enforcer( model, adapter, - enable_log, ) class TestConfig(TestCaseBase): @@ -27,7 +26,6 @@ def test_enforcer_basic(self): e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), - # True, ) self.assertTrue(e.enforce('alice', 'data1', 'read')) @@ -39,7 +37,6 @@ def test_enforcer_basic_without_spaces(self): e = self.get_enforcer( get_examples("basic_model_without_spaces.conf"), get_examples("basic_policy.csv"), - # True, ) self.assertTrue(e.enforce("alice", "data1", "read")) @@ -189,9 +186,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen2/2", "GET")) def test_enforce_abac_log_enabled(self): - e = self.get_enforcer(get_examples("abac_model.conf"), enable_log=True) - e.enable_log(True) - + e = self.get_enforcer(get_examples("abac_model.conf")) sub = 'alice' obj = {'Owner': 'alice', 'id': 'data1'} self.assertTrue(e.enforce(sub, obj, 'write')) @@ -250,18 +245,16 @@ def test_abac_with_multiple_sub_rules(self): class TestConfigSynced(TestConfig): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - enable_log, ) def test_auto_loading_policy(self): e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), - # True, ) e.start_auto_load_policy(5/1000) diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 4e445e4a..faa1593d 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -3,11 +3,10 @@ class TestManagementApi(TestCaseBase): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.Enforcer( model, adapter, - enable_log, ) def test_get_list(self): @@ -140,9 +139,8 @@ def test_modify_policy_api(self): class TestManagementApiSynced(TestManagementApi): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - enable_log, ) \ No newline at end of file diff --git a/tests/test_model_benchmark.py b/tests/test_model_benchmark.py index 8c48c256..895bcfed 100644 --- a/tests/test_model_benchmark.py +++ b/tests/test_model_benchmark.py @@ -6,7 +6,7 @@ from unittest import TestCase log = logging.getLogger(__name__) -loglevel = logging.DEBUG +loglevel = logging.WARNING logging.basicConfig(level=loglevel) @@ -16,7 +16,7 @@ def get_function_name(): def print_time_diff(start, end, time): ms = (end - start).total_seconds() * 1000 / time - log.debug("%s %f ms" % (get_function_name(), ms)) + log.warning("%s %f ms" % (get_function_name(), ms)) class TestModelBenchmark(TestCaseBase): @@ -42,9 +42,8 @@ def test_benchmark_rbac_model(self): class TestModelBenchmarkSynced(TestModelBenchmark): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - enable_log, ) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index bca5b8c5..d1d4889b 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -216,9 +216,8 @@ def test_implicit_user_api(self): class TestRbacApiSynced(TestRbacApi): - def get_enforcer(self, model=None, adapter=None, enable_log=False): + def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - enable_log, ) \ No newline at end of file From b505fb8282cb4fc4e5e6183d6aa306bce8a19251 Mon Sep 17 00:00:00 2001 From: "Carme, Pamy" Date: Mon, 26 Oct 2020 21:40:25 -0400 Subject: [PATCH 098/349] fix CI Signed-off-by: Carme, Pamy --- casbin/model/policy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index d6788e4c..c52c193c 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -25,7 +25,7 @@ def print_policy(self): continue for key, ast in self.model[sec].items(): - self.logger.info(f"{key} : {ast.value} : {ast.policy}") + self.logger.info("{} : {} : {}".format(key, ast.value, ast.policy)) def clear_policy(self): """clears all current policy.""" From 3a8c4b965a1e1343786a00903854f5e745f9bf6a Mon Sep 17 00:00:00 2001 From: "Carme, Pamy" Date: Tue, 27 Oct 2020 09:46:35 -0400 Subject: [PATCH 099/349] add some unit tests Signed-off-by: Carme, Pamy --- tests/test_enforcer.py | 17 +++++++++++++++++ tests/test_management_api.py | 2 ++ 2 files changed, 19 insertions(+) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index e01c3292..1de3625e 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -33,6 +33,23 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertFalse(e.enforce('bob', 'data1', 'write')) + def test_model_operations(self): + e = get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + # True, + ) + self.assertTrue(e.enforce('alice', 'data1', 'read')) + self.assertFalse(e.enforce('alice', 'data2', 'read')) + self.assertTrue(e.enforce('bob', 'data2', 'write')) + self.assertFalse(e.enforce('bob', 'data1', 'write')) + e.set_model(None) + self.assertTrue(e.model is None) + # creating new model + e.load_model() + self.assertTrue(e.model) + + def test_enforcer_basic_without_spaces(self): e = self.get_enforcer( get_examples("basic_model_without_spaces.conf"), diff --git a/tests/test_management_api.py b/tests/test_management_api.py index faa1593d..6330bd22 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -128,6 +128,7 @@ def test_modify_policy_api(self): e.remove_policies(rules) e.remove_named_policies('p',named_policies) + e.add_named_policy('p', 'testing') self.assertEqual(e.get_policy(), [ ['alice', 'data1', 'read'], ['bob', 'data2', 'write'], @@ -135,6 +136,7 @@ def test_modify_policy_api(self): ['data2_admin', 'data2', 'write'], ['eve', 'data3', 'read'], ['eve', 'data3', 'write'], + ['testing'] ]) class TestManagementApiSynced(TestManagementApi): From 28c36f79f3fb3422683e01394de2511d62164e43 Mon Sep 17 00:00:00 2001 From: "Carme, Pamy" Date: Sat, 23 Jan 2021 18:34:23 -0400 Subject: [PATCH 100/349] fix unit test that was added to pass the coverage test Signed-off-by: Carme, Pamy --- tests/test_enforcer.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 1de3625e..a10d36b8 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -33,21 +33,17 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertFalse(e.enforce('bob', 'data1', 'write')) - def test_model_operations(self): - e = get_enforcer( + def test_model_set_load(self): + e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), - # True, ) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) - self.assertFalse(e.enforce('bob', 'data1', 'write')) - e.set_model(None) - self.assertTrue(e.model is None) - # creating new model - e.load_model() - self.assertTrue(e.model) + if not isinstance(e, casbin.SyncedEnforcer): + e.set_model(None) + self.assertTrue(e.model is None) + # creating new model + e.load_model() + self.assertTrue(e.model) def test_enforcer_basic_without_spaces(self): @@ -280,4 +276,4 @@ def test_auto_loading_policy(self): #thread needs a moment to exit time.sleep(10/1000) self.assertFalse(e.is_auto_loading_running()) - + From f9fafe3113234d87bd2c46827a302a2aaefc5189 Mon Sep 17 00:00:00 2001 From: Jon Lee Date: Wed, 27 Jan 2021 00:08:27 +0800 Subject: [PATCH 101/349] chore: add senmantic.yml Signed-off-by: Jon Lee --- .github/semantic.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/semantic.yml diff --git a/.github/semantic.yml b/.github/semantic.yml new file mode 100644 index 00000000..96f92336 --- /dev/null +++ b/.github/semantic.yml @@ -0,0 +1,2 @@ +# Always validate the PR title AND all the commits +titleAndCommits: true From d732f9c743cb8b7bf26056e9ab0298c37bd29812 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Tue, 26 Jan 2021 17:13:19 +0100 Subject: [PATCH 102/349] fix: role manager with matching_func Signed-off-by: Andreas Bichinger --- .../rbac/default_role_manager/role_manager.py | 47 ++-- tests/rbac/__init__.py | 0 tests/rbac/test_role_manager.py | 234 ++++++++++++++++++ 3 files changed, 265 insertions(+), 16 deletions(-) create mode 100644 tests/rbac/__init__.py create mode 100644 tests/rbac/test_role_manager.py diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index db5872d4..bfb8b1e4 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -30,11 +30,6 @@ def create_role(self, name): if name not in self.all_roles.keys(): self.all_roles[name] = Role(name) - if self.matching_func is not None: - for key, role in self.all_roles.items(): - if self.matching_func(name, key) and name != key: - self.all_roles[name].add_role(role) - return self.all_roles[name] def clear(self): @@ -51,6 +46,17 @@ def add_link(self, name1, name2, *domain): role2 = self.create_role(name2) role1.add_role(role2) + if self.matching_func is not None: + for key, role in self.all_roles.items(): + if self.matching_func(key, name1) and name1 != key: + self.all_roles[key].add_role(role1) + if self.matching_func(key, name2) and name2 != key: + self.all_roles[name2].add_role(role) + if self.matching_func(name1, key) and name1 != key: + self.all_roles[key].add_role(role1) + if self.matching_func(name2, key) and name2 != key: + self.all_roles[name2].add_role(role) + def delete_link(self, name1, name2, *domain): if len(domain) == 1: name1 = domain[0] + "::" + name1 @@ -78,9 +84,14 @@ def has_link(self, name1, name2, *domain): if not self.has_role(name1) or not self.has_role(name2): return False - role1 = self.create_role(name1) - - return role1.has_role(name2, self.max_hierarchy_level) + if self.matching_func is None: + role1 = self.create_role(name1) + return role1.has_role(name2, self.max_hierarchy_level) + else: + for key, role in self.all_roles.items(): + if self.matching_func(name1, key) and role.has_role(name2, self.max_hierarchy_level, self.matching_func): + return True + return False def get_roles(self, name, *domain): """ @@ -158,23 +169,27 @@ def delete_role(self, role): self.roles.remove(rr) return - def has_role(self, name, hierarchy_level): - if name == self.name: + def has_role(self, name, hierarchy_level, matching_func=None): + if self.has_direct_role(name, matching_func): return True if hierarchy_level <= 0: return False for role in self.roles: - if role.has_role(name, hierarchy_level - 1): + if role.has_role(name, hierarchy_level - 1, matching_func): return True return False - def has_direct_role(self, name): - for role in self.roles: - if role.name == name: - return True - + def has_direct_role(self, name, matching_func=None): + if matching_func is None: + for role in self.roles: + if role.name == name: + return True + else: + for role in self.roles: + if matching_func(name, role.name): + return True return False def to_string(self): diff --git a/tests/rbac/__init__.py b/tests/rbac/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py new file mode 100644 index 00000000..c1bfe0b9 --- /dev/null +++ b/tests/rbac/test_role_manager.py @@ -0,0 +1,234 @@ +from unittest import TestCase +from casbin.rbac import default_role_manager +from casbin.util import regex_match_func +import time +from concurrent.futures import ThreadPoolExecutor + +def get_role_manager(): + return default_role_manager.RoleManager(max_hierarchy_level=10) + +class TestDefaultRoleManager(TestCase): + + def test_role(self): + rm = get_role_manager() + rm.add_link("u1", "g1") + rm.add_link("u2", "g1") + rm.add_link("u3", "g2") + rm.add_link("u4", "g2") + rm.add_link("u4", "g3") + rm.add_link("g1", "g3") + + # Current role inheritance tree: + # g3 g2 + # / \ / \ + # g1 u4 u3 + # / \ + # u1 u2 + + self.assertTrue(rm.has_link("u1", "g1")) + self.assertFalse(rm.has_link("u1", "g2")) + self.assertTrue(rm.has_link("u1", "g3")) + self.assertTrue(rm.has_link("u2", "g1")) + self.assertFalse(rm.has_link("u2", "g2")) + self.assertTrue(rm.has_link("u2", "g3")) + self.assertFalse(rm.has_link("u3", "g1")) + self.assertTrue(rm.has_link("u3", "g2")) + self.assertFalse(rm.has_link("u3", "g3")) + self.assertFalse(rm.has_link("u4", "g1")) + self.assertTrue(rm.has_link("u4", "g2")) + self.assertTrue(rm.has_link("u4", "g3")) + + self.assertCountEqual(rm.get_roles("u1"), ["g1"]) + self.assertCountEqual(rm.get_roles("u2"), ["g1"]) + self.assertCountEqual(rm.get_roles("u3"), ["g2"]) + self.assertCountEqual(rm.get_roles("u4"), ["g2", "g3"]) + self.assertCountEqual(rm.get_roles("g1"), ["g3"]) + self.assertCountEqual(rm.get_roles("g2"), []) + self.assertCountEqual(rm.get_roles("g3"), []) + + rm.delete_link("g1", "g3") + rm.delete_link("u4", "g2") + + # Current role inheritance tree after deleting the links: + # g3 g2 + # \ \ + # g1 u4 u3 + # / \ + # u1 u2 + + self.assertTrue(rm.has_link("u1", "g1")) + self.assertFalse(rm.has_link("u1", "g2")) + self.assertFalse(rm.has_link("u1", "g3")) + self.assertTrue(rm.has_link("u2", "g1")) + self.assertFalse(rm.has_link("u2", "g2")) + self.assertFalse(rm.has_link("u2", "g3")) + self.assertFalse(rm.has_link("u3", "g1")) + self.assertTrue(rm.has_link("u3", "g2")) + self.assertFalse(rm.has_link("u3", "g3")) + self.assertFalse(rm.has_link("u4", "g1")) + self.assertFalse(rm.has_link("u4", "g2")) + self.assertTrue(rm.has_link("u4", "g3")) + + self.assertCountEqual(rm.get_roles("u1"), ["g1"]) + self.assertCountEqual(rm.get_roles("u2"), ["g1"]) + self.assertCountEqual(rm.get_roles("u3"), ["g2"]) + self.assertCountEqual(rm.get_roles("u4"), ["g3"]) + self.assertCountEqual(rm.get_roles("g1"), []) + self.assertCountEqual(rm.get_roles("g2"), []) + self.assertCountEqual(rm.get_roles("g3"), []) + + def test_domain_role(self): + rm = get_role_manager() + rm.add_link("u1", "g1", "domain1") + rm.add_link("u2", "g1", "domain1") + rm.add_link("u3", "admin", "domain2") + rm.add_link("u4", "admin", "domain2") + rm.add_link("u4", "admin", "domain1") + rm.add_link("g1", "admin", "domain1") + + # Current role inheritance tree: + # domain1:admin domain2:admin + # / \ / \ + # domain1:g1 u4 u3 + # / \ + # u1 u2 + + self.assertTrue(rm.has_link("u1", "g1", "domain1")) + self.assertFalse(rm.has_link("u1", "g1", "domain2")) + self.assertTrue(rm.has_link("u1", "admin", "domain1")) + self.assertFalse(rm.has_link("u1", "admin", "domain2")) + + self.assertTrue(rm.has_link("u2", "g1", "domain1")) + self.assertFalse(rm.has_link("u2", "g1", "domain2")) + self.assertTrue(rm.has_link("u2", "admin", "domain1")) + self.assertFalse(rm.has_link("u2", "admin", "domain2")) + + self.assertFalse(rm.has_link("u3", "g1", "domain1")) + self.assertFalse(rm.has_link("u3", "g1", "domain2")) + self.assertFalse(rm.has_link("u3", "admin", "domain1")) + self.assertTrue(rm.has_link("u3", "admin", "domain2")) + + self.assertFalse(rm.has_link("u4", "g1", "domain1")) + self.assertFalse(rm.has_link("u4", "g1", "domain2")) + self.assertTrue(rm.has_link("u4", "admin", "domain1")) + self.assertTrue(rm.has_link("u4", "admin", "domain2")) + + def test_clear(self): + rm = get_role_manager() + rm.add_link("u1", "g1") + rm.add_link("u2", "g1") + rm.add_link("u3", "g2") + rm.add_link("u4", "g2") + rm.add_link("u4", "g3") + rm.add_link("g1", "g3") + + # Current role inheritance tree: + # g3 g2 + # / \ / \ + # g1 u4 u3 + # / \ + # u1 u2 + + rm.clear() + + # All data is cleared. + # No role inheritance now. + + self.assertFalse(rm.has_link("u1", "g1")) + self.assertFalse(rm.has_link("u1", "g2")) + self.assertFalse(rm.has_link("u1", "g3")) + self.assertFalse(rm.has_link("u2", "g1")) + self.assertFalse(rm.has_link("u2", "g2")) + self.assertFalse(rm.has_link("u2", "g3")) + self.assertFalse(rm.has_link("u3", "g1")) + self.assertFalse(rm.has_link("u3", "g2")) + self.assertFalse(rm.has_link("u3", "g3")) + self.assertFalse(rm.has_link("u4", "g1")) + self.assertFalse(rm.has_link("u4", "g2")) + self.assertFalse(rm.has_link("u4", "g3")) + + def test_matching_func(self): + rm = get_role_manager() + rm.add_matching_func(regex_match_func) + + rm.add_link("u1", "g1") + rm.add_link("u3", "g2") + rm.add_link("u3", "g3") + rm.add_link(r"u\d+", "g2") + + self.assertTrue(rm.has_link("u1", "g1")) + self.assertTrue(rm.has_link("u1", "g2")) + self.assertFalse(rm.has_link("u1", "g3")) + + self.assertFalse(rm.has_link("u2", "g1")) + self.assertTrue(rm.has_link("u2", "g2")) + self.assertFalse(rm.has_link("u2", "g3")) + + self.assertFalse(rm.has_link("u3", "g1")) + self.assertTrue(rm.has_link("u3", "g2")) + self.assertTrue(rm.has_link("u3", "g3")) + + def test_one_to_many(self): + rm = get_role_manager() + rm.add_matching_func(regex_match_func) + + rm.add_link("u1", r"g\d+") + self.assertTrue(rm.has_link("u1", "g1")) + self.assertTrue(rm.has_link("u1", "g2")) + self.assertFalse(rm.has_link("u2", "g1")) + self.assertFalse(rm.has_link("u2", "g2")) + + def test_many_to_one(self): + rm = get_role_manager() + rm.add_matching_func(regex_match_func) + + rm.add_link(r"u\d+", "g1") + self.assertTrue(rm.has_link("u1", "g1")) + self.assertFalse(rm.has_link("u1", "g2")) + self.assertTrue(rm.has_link("u2", "g1")) + self.assertFalse(rm.has_link("u2", "g2")) + + def test_matching_func_order(self): + rm = get_role_manager() + rm.add_matching_func(regex_match_func) + + rm.add_link(r"g\d+", "root") + rm.add_link("u1", "g1") + self.assertTrue(rm.has_link("u1", "root")) + + rm.clear() + + rm.add_link("u1", "g1") + rm.add_link(r"g\d+", "root") + self.assertTrue(rm.has_link("u1", "root")) + + rm.clear() + + rm.add_link("u1", r"g\d+") + rm.add_link("g1", "root") + self.assertTrue(rm.has_link("u1", "root")) + + rm.clear() + + rm.add_link("g1", "root") + rm.add_link("u1", r"g\d+") + self.assertTrue(rm.has_link("u1", "root")) + + def test_concurrent_has_link_with_matching_func(self): + + def matching_func(*args): + time.sleep(0.01) + return regex_match_func(*args) + + rm = get_role_manager() + rm.add_matching_func(matching_func) + rm.add_link(r"u\d+", "users") + + def test_has_link(role): + return rm.has_link(role, "users") + + executor = ThreadPoolExecutor(10) + futures = [executor.submit(test_has_link, "u"+str(i)) for i in range(10)] + for future in futures: + self.assertTrue(future.result()) + \ No newline at end of file From 95bf3c3e0981f3de232df675a65d57c0e893e248 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 29 Jan 2021 15:58:26 +0000 Subject: [PATCH 103/349] chore(release): 0.16.1 [skip ci] ## [0.16.1](https://github.com/casbin/pycasbin/compare/v0.16.0...v0.16.1) (2021-01-29) ### Bug Fixes * role manager with matching_func ([8079cda](https://github.com/casbin/pycasbin/commit/8079cda8a28409e9502508fe2bdf9b0bddac3f98)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9227cbe..24ab09d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.16.1](https://github.com/casbin/pycasbin/compare/v0.16.0...v0.16.1) (2021-01-29) + + +### Bug Fixes + +* role manager with matching_func ([8079cda](https://github.com/casbin/pycasbin/commit/8079cda8a28409e9502508fe2bdf9b0bddac3f98)) + # [0.16.0](https://github.com/casbin/pycasbin/compare/v0.15.0...v0.16.0) (2021-01-09) diff --git a/setup.cfg b/setup.cfg index 1b88664f..546f284b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.16.0 +version = 0.16.1 From 5c0062eec5ae5e55d956afbe45467cfa46677e3d Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 2 Feb 2021 23:10:18 +0800 Subject: [PATCH 104/349] docs: switch CI to travis-ci.com --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 02e41084..3ed0ffc1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ PyCasbin ==== -[![Build Status](https://www.travis-ci.org/casbin/pycasbin.svg?branch=master)](https://www.travis-ci.org/casbin/pycasbin) +[![Build Status](https://www.travis-ci.com/casbin/pycasbin.svg?branch=master)](https://www.travis-ci.com/casbin/pycasbin) [![Coverage Status](https://coveralls.io/repos/github/casbin/pycasbin/badge.svg)](https://coveralls.io/github/casbin/pycasbin) [![Version](https://img.shields.io/pypi/v/casbin.svg)](https://pypi.org/project/casbin/) [![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) From 54bfbb9ae2bace2b8b204a626166fa094e10776f Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 3 Feb 2021 20:32:41 +0800 Subject: [PATCH 105/349] perf: remove duplicated check (#117) * perf: remove duplicated check Signed-off-by: Zxilly * perf: remove duplicated check Signed-off-by: Zxilly --- casbin/model/policy.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index c52c193c..a414ecc9 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -91,11 +91,6 @@ def add_policies(self,sec,ptype,rules): def remove_policy(self, sec, ptype, rule): """removes a policy rule from the model.""" - if sec not in self.model.keys(): - return False - if ptype not in self.model[sec]: - return False - if not self.has_policy(sec, ptype, rule): return False @@ -106,11 +101,6 @@ def remove_policy(self, sec, ptype, rule): def remove_policies(self, sec, ptype, rules): """RemovePolicies removes policy rules from the model.""" - if sec not in self.model.keys(): - return False - if ptype not in self.model[sec]: - return False - for rule in rules: if not self.has_policy(sec,ptype,rule): return False From 1998f034ee0919a7b2a4ce4ce4a60839016a5e42 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Feb 2021 12:33:38 +0000 Subject: [PATCH 106/349] chore(release): 0.16.2 [skip ci] ## [0.16.2](https://github.com/casbin/pycasbin/compare/v0.16.1...v0.16.2) (2021-02-03) ### Performance Improvements * remove duplicated check ([#117](https://github.com/casbin/pycasbin/issues/117)) ([6aebadf](https://github.com/casbin/pycasbin/commit/6aebadf0a344e453ca2a1e9c0ae3b0d2f1c3d2c7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ab09d0..7d6ea681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.16.2](https://github.com/casbin/pycasbin/compare/v0.16.1...v0.16.2) (2021-02-03) + + +### Performance Improvements + +* remove duplicated check ([#117](https://github.com/casbin/pycasbin/issues/117)) ([6aebadf](https://github.com/casbin/pycasbin/commit/6aebadf0a344e453ca2a1e9c0ae3b0d2f1c3d2c7)) + ## [0.16.1](https://github.com/casbin/pycasbin/compare/v0.16.0...v0.16.1) (2021-01-29) diff --git a/setup.cfg b/setup.cfg index 546f284b..135da325 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.16.1 +version = 0.16.2 From b113417014fbb98b49b651acb24987b59dc9e96f Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 2 Feb 2021 17:45:34 +0800 Subject: [PATCH 107/349] feat: add update_policy() Signed-off-by: Zxilly feat: further development on update_policy() Signed-off-by: Zxilly refactor: adjust to Python-style variable naming Signed-off-by: Zxilly feat: add update_policies() Signed-off-by: Zxilly feat: add unittest for update_policies() Signed-off-by: Zxilly refactor: remove adapter check Signed-off-by: Zxilly refactor: remove duplicated check Signed-off-by: Zxilly --- casbin/internal_enforcer.py | 34 +++++++++++++++++++++++++++++++ casbin/management_enforcer.py | 16 +++++++++++++++ casbin/model/policy.py | 20 +++++++++++++++++- tests/model/test_policy.py | 38 +++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 1 deletion(-) diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 628c68be..871a2fdd 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -37,6 +37,40 @@ def _add_policies(self,sec,ptype,rules): self.watcher.update() return rules_added + + def _update_policy(self, sec, ptype, old_rule, new_rule): + """updates a rule from the current policy.""" + rule_updated = self.model.update_policy(sec, ptype, old_rule, new_rule) + + if not rule_updated: + return rule_updated + + if self.adapter and self.auto_save: + + if self.adapter.update_policy(sec, ptype, old_rule, new_rule) is False: + return False + + if self.watcher: + self.watcher.update() + + return rule_updated + + def _update_policies(self, sec, ptype, old_rules, new_rules): + """updates rules from the current policy.""" + rules_updated = self.model.update_policies(sec, ptype, old_rules, new_rules) + + if not rules_updated: + return rules_updated + + if self.adapter and self.auto_save: + + if self.adapter.update_policies(sec, ptype, old_rules, new_rules) is False: + return False + + if self.watcher: + self.watcher.update() + + return rules_updated def _remove_policy(self, sec, ptype, rule): """removes a rule from the current policy.""" diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 7e62a760..02ebd7cd 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -129,6 +129,22 @@ def add_named_policies(self,ptype,rules): Otherwise the function returns true for the corresponding by adding the new rule.""" return self._add_policies('p',ptype,rules) + def update_policy(self, old_rule, new_rule): + """updates an authorization rule from the current policy.""" + return self.update_named_policy('p', old_rule, new_rule) + + def update_policies(self, old_rules, new_rules): + """updates authorization rules from the current policy.""" + return self.update_named_policies('p', old_rules, new_rules) + + def update_named_policy(self, ptype, old_rule, new_rule): + """updates an authorization rule from the current named policy.""" + return self._update_policy('p', ptype, old_rule, new_rule) + + def update_named_policies(self, ptype, old_rules, new_rules): + """updates authorization rules from the current named policy.""" + return self._update_policies('p', ptype, old_rules, new_rules) + def remove_policy(self, *params): """removes an authorization rule from the current policy.""" return self.remove_named_policy('p', *params) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index a414ecc9..d0323cfa 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,6 +1,7 @@ -from casbin import util import logging +from casbin import util + class Policy: def __init__(self): @@ -88,6 +89,23 @@ def add_policies(self,sec,ptype,rules): return True + def update_policy(self, sec, ptype, old_rule, new_rule): + """update a policy rule from the model.""" + + if not self.has_policy(sec, ptype, old_rule): + return False + + return self.remove_policy(sec, ptype, old_rule) and self.add_policy(sec, ptype, new_rule) + + def update_policies(self, sec, ptype, old_rules, new_rules): + """update policy rules from the model.""" + + for rule in old_rules: + if not self.has_policy(sec, ptype, rule): + return False + + return self.remove_policies(sec, ptype, old_rules) and self.add_policies(sec, ptype, new_rules) + def remove_policy(self, sec, ptype, rule): """removes a policy rule from the model.""" diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index a39b7db2..65bd6507 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -1,4 +1,5 @@ from unittest import TestCase + from casbin.model import Model from tests.test_enforcer import get_examples @@ -53,6 +54,43 @@ def test_add_role_policy(self): self.assertTrue(m.get_policy('p', 'p') == [p_rule1, p_rule2]) self.assertTrue(m.get_policy('g', 'g') == [g_rule]) + def test_update_policy(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + old_rule = ['admin', 'domain1', 'data1', 'read'] + new_rule = ['admin', 'domain1', 'data2', 'read'] + + m.add_policy('p', 'p', old_rule) + self.assertTrue(m.has_policy('p', 'p', old_rule)) + + m.update_policy('p', 'p', old_rule, new_rule) + self.assertFalse(m.has_policy('p', 'p', old_rule)) + self.assertTrue(m.has_policy('p', 'p', new_rule)) + + def test_update_policies(self): + m = Model() + m.load_model(get_examples("basic_model.conf")) + + old_rules = [['admin', 'domain1', 'data1', 'read'], + ['admin', 'domain1', 'data2', 'read'], + ['admin', 'domain1', 'data3', 'read']] + new_rules = [['admin', 'domain1', 'data4', 'read'], + ['admin', 'domain1', 'data5', 'read'], + ['admin', 'domain1', 'data6', 'read']] + + m.add_policies('p', 'p', old_rules) + + for old_rule in old_rules: + self.assertTrue(m.has_policy('p', 'p', old_rule)) + + m.update_policies('p', 'p', old_rules, new_rules) + + for old_rule in old_rules: + self.assertFalse(m.has_policy('p', 'p', old_rule)) + for new_rule in new_rules: + self.assertTrue(m.has_policy('p', 'p', new_rule)) + def test_remove_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) From 2de5c46a77afb100f6af13c74c3c91fd251acfb2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 19 Feb 2021 13:43:56 +0000 Subject: [PATCH 108/349] chore(release): 0.17.0 [skip ci] # [0.17.0](https://github.com/casbin/pycasbin/compare/v0.16.2...v0.17.0) (2021-02-19) ### Features * add update_policy() ([7f7d26f](https://github.com/casbin/pycasbin/commit/7f7d26fe3f6cddcf54a2e42cef83565eb21e202c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6ea681..335e94b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.17.0](https://github.com/casbin/pycasbin/compare/v0.16.2...v0.17.0) (2021-02-19) + + +### Features + +* add update_policy() ([7f7d26f](https://github.com/casbin/pycasbin/commit/7f7d26fe3f6cddcf54a2e42cef83565eb21e202c)) + ## [0.16.2](https://github.com/casbin/pycasbin/compare/v0.16.1...v0.16.2) (2021-02-03) diff --git a/setup.cfg b/setup.cfg index 135da325..7feecc91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.16.2 +version = 0.17.0 From 88b281174d051dbd1a8d6e60aaf31e3073b5699a Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 23 Feb 2021 19:44:59 +0800 Subject: [PATCH 109/349] feat: add AddNamedDomainMatchingFunc and AddNamedMatchingFunc to enforcer (#122) * feat: add AddNamedDomainMatchingFunc and AddNamedMatchingFunc to enforcer Signed-off-by: Zxilly * refactor: remove log output Signed-off-by: Zxilly * refactor: rename rm to rmMap Signed-off-by: Zxilly * style: format Signed-off-by: Zxilly * refactor: rename to python-style variable Signed-off-by: Zxilly * refactor: remove unnecessary statements Signed-off-by: Zxilly --- casbin/__init__.py | 1 + casbin/core_enforcer.py | 48 +++++++++++++++---- casbin/enforcer.py | 13 ++--- casbin/model/policy.py | 5 +- .../rbac/default_role_manager/role_manager.py | 14 +++++- casbin/synced_enforcer.py | 16 ++++++- casbin/util/__init__.py | 2 +- tests/test_enforcer.py | 10 ++-- 8 files changed, 82 insertions(+), 27 deletions(-) diff --git a/casbin/__init__.py b/casbin/__init__.py index 67694841..76c80e7a 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -1,2 +1,3 @@ from .enforcer import * from .synced_enforcer import SyncedEnforcer +from . import util diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 8367f579..cf0bbb82 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -1,9 +1,10 @@ -from casbin.persist.adapters import FileAdapter +import logging + +from casbin.effect import DefaultEffector, Effector from casbin.model import Model, FunctionMap +from casbin.persist.adapters import FileAdapter from casbin.rbac import default_role_manager from casbin.util import generate_g_function, SimpleEval, util -from casbin.effect import DefaultEffector, Effector -import logging class CoreEnforcer: @@ -16,7 +17,7 @@ class CoreEnforcer: adapter = None watcher = None - rm = None + rm_map = None enabled = False auto_save = False @@ -63,7 +64,7 @@ def init_with_model_and_adapter(self, m, adapter=None): self.load_policy() def _initialize(self): - self.rm = default_role_manager.RoleManager(10) + self.rm_map = dict() self.eft = DefaultEffector() self.watcher = None @@ -71,6 +72,8 @@ def _initialize(self): self.auto_save = True self.auto_build_role_links = True + self.init_rm_map() + @staticmethod def new_model(path="", text=""): """creates a model.""" @@ -122,12 +125,11 @@ def set_watcher(self, watcher): def get_role_manager(self): """gets the current role manager.""" - return self.rm + return self.rm_map['g'] def set_role_manager(self, rm): """sets the current role manager.""" - - self.rm = rm + self.rm_map['g'] = rm def set_effector(self, eft): """sets the current effector.""" @@ -139,12 +141,18 @@ def clear_policy(self): self.model.clear_policy() + def init_rm_map(self): + if 'g' in self.model.model.keys(): + for ptype in self.model.model['g']: + self.rm_map[ptype] = default_role_manager.RoleManager(10) + def load_policy(self): """reloads the policy from file/database.""" self.model.clear_policy() self.adapter.load_policy(self.model) + self.init_rm_map() self.model.print_policy() if self.auto_build_role_links: self.build_role_links() @@ -157,6 +165,7 @@ def load_filtered_policy(self, filter): raise ValueError("filtered policies are not supported by this adapter") self.adapter.load_filtered_policy(self.model, filter) + self.init_rm_map() self.model.print_policy() if self.auto_build_role_links: self.build_role_links() @@ -203,8 +212,25 @@ def enable_auto_build_role_links(self, auto_build_role_links): def build_role_links(self): """manually rebuild the role inheritance relations.""" - self.rm.clear() - self.model.build_role_links(self.rm) + for rm in self.rm_map.values(): + rm.clear() + self.model.build_role_links(self.rm_map) + + def add_named_matching_func(self, ptype, fn): + """add_named_matching_func add MatchingFunc by ptype RoleManager""" + try: + self.rm_map[ptype].add_matching_func(fn) + return True + except: + return False + + def add_named_domain_matching_func(self, ptype, fn): + """add_named_domain_matching_func add MatchingFunc by ptype to RoleManager""" + try: + self.rm_map[ptype].add_domain_matching_func(fn) + return True + except: + return False def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", @@ -320,6 +346,8 @@ def enforce(self, *rvals): return result + + @staticmethod def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") diff --git a/casbin/enforcer.py b/casbin/enforcer.py index ee8d0e7e..46021927 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -138,12 +138,13 @@ def get_implicit_roles_for_user(self, name, *domain): name = q[0] q = q[1:] - roles = self.rm.get_roles(name, *domain) - for r in roles: - if r not in roleSet: - res.append(r) - q.append(r) - roleSet[r] = True + for rm in self.rm_map.values(): + roles = rm.get_roles(name, *domain) + for r in roles: + if r not in roleSet: + res.append(r) + q.append(r) + roleSet[r] = True return res diff --git a/casbin/model/policy.py b/casbin/model/policy.py index d0323cfa..e9b41039 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -8,13 +8,14 @@ def __init__(self): self.logger = logging.getLogger() self.model = {} - def build_role_links(self, rm): + def build_role_links(self, rm_map): """initializes the roles in RBAC.""" if "g" not in self.model.keys(): return - for ast in self.model["g"].values(): + for ptype, ast in self.model["g"].items(): + rm = rm_map[ptype] ast.build_role_links(rm) def print_policy(self): diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index bfb8b1e4..bf33fbfe 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -1,6 +1,7 @@ -from casbin.rbac import RoleManager import logging +from casbin.rbac import RoleManager + class RoleManager(RoleManager): """provides a default implementation for the RoleManager interface""" @@ -8,15 +9,24 @@ class RoleManager(RoleManager): all_roles = dict() max_hierarchy_level = 0 + def __init__(self, max_hierarchy_level): self.logger = logging.getLogger() self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level self.matching_func = None + self.domain_matching_func = None + self.has_pattern = None + self.has_domain_pattern = None - def add_matching_func(self, fn): + def add_matching_func(self, fn=None): + self.has_pattern = True self.matching_func = fn + def add_domain_matching_func(self, fn=None): + self.has_domain_pattern = True + self.domain_matching_func = fn + def has_role(self, name): if self.matching_func is None: return name in self.all_roles.keys() diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index bce7720b..8f43ebf4 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -1,8 +1,10 @@ -from casbin.enforcer import Enforcer -from casbin.util.rwlock import RWLockWrite import threading import time +from casbin.enforcer import Enforcer +from casbin.util.rwlock import RWLockWrite + + class AtomicBool(): def __init__(self, value): @@ -497,6 +499,16 @@ def enable_enforce(self, enabled=True): with self._wl: return self._e.enable_enforce(enabled) + def add_named_matching_func(self, ptype, fn): + """add_named_matching_func add MatchingFunc by ptype RoleManager""" + with self._wl: + self._e.add_named_matching_func(ptype, fn) + + def add_named_domain_matching_func(self, ptype, fn): + """add_named_domain_matching_func add MatchingFunc by ptype to RoleManager""" + with self._wl: + self._e.add_named_domain_matching_func(ptype, fn) + def is_filtered(self): """returns true if the loaded policy has been filtered.""" with self._rl: diff --git a/casbin/util/__init__.py b/casbin/util/__init__.py index 16ff7acd..a40b64ef 100644 --- a/casbin/util/__init__.py +++ b/casbin/util/__init__.py @@ -1,3 +1,3 @@ from .builtin_operators import * -from .util import * from .expression import * +from .util import * \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index a10d36b8..89e0256c 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -1,7 +1,9 @@ -import casbin import os -from unittest import TestCase import time +from unittest import TestCase + +import casbin + def get_examples(path): examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/" @@ -176,7 +178,7 @@ def test_enforce_rbac_with_pattern(self): get_examples("rbac_with_pattern_policy.csv")) #set matching function to key_match2 - e.get_role_manager().add_matching_func(casbin.util.key_match2) + e.add_named_matching_func('g2', casbin.util.key_match2) self.assertTrue(e.enforce("alice", "/book/1", "GET")) self.assertTrue(e.enforce("alice", "/book/2", "GET")) @@ -188,7 +190,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen/2", "GET")) #replace key_match2 with key_match3 - e.get_role_manager().add_matching_func(casbin.util.key_match3) + e.add_named_matching_func('g2', casbin.util.key_match3) self.assertTrue(e.enforce("alice", "/book2/1", "GET")) self.assertTrue(e.enforce("alice", "/book2/2", "GET")) self.assertTrue(e.enforce("alice", "/pen2/1", "GET")) From a112c7f95756e55369e630d373ea76307309564f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 23 Feb 2021 11:46:10 +0000 Subject: [PATCH 110/349] chore(release): 0.18.0 [skip ci] # [0.18.0](https://github.com/casbin/pycasbin/compare/v0.17.0...v0.18.0) (2021-02-23) ### Features * add AddNamedDomainMatchingFunc and AddNamedMatchingFunc to enforcer ([#122](https://github.com/casbin/pycasbin/issues/122)) ([e01f393](https://github.com/casbin/pycasbin/commit/e01f393c8c41a954b201ecf9de2ad2350f87b651)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 335e94b9..5cbcc11f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.18.0](https://github.com/casbin/pycasbin/compare/v0.17.0...v0.18.0) (2021-02-23) + + +### Features + +* add AddNamedDomainMatchingFunc and AddNamedMatchingFunc to enforcer ([#122](https://github.com/casbin/pycasbin/issues/122)) ([e01f393](https://github.com/casbin/pycasbin/commit/e01f393c8c41a954b201ecf9de2ad2350f87b651)) + # [0.17.0](https://github.com/casbin/pycasbin/compare/v0.16.2...v0.17.0) (2021-02-19) diff --git a/setup.cfg b/setup.cfg index 7feecc91..7cf4999e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.17.0 +version = 0.18.0 From 44df9a7d4a19308a4c595b29c7b996415a858a96 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Thu, 25 Feb 2021 20:30:56 +0800 Subject: [PATCH 111/349] perf: reposition build_role_links() Signed-off-by: Zxilly --- casbin/core_enforcer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index cf0bbb82..c4f7e4bc 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -169,8 +169,8 @@ def load_filtered_policy(self, filter): self.model.print_policy() if self.auto_build_role_links: self.build_role_links() - - def load_increment_filtered_policy(self,filter): + + def load_increment_filtered_policy(self, filter): """LoadIncrementalFilteredPolicy append a filtered policy from file/database.""" if not hasattr(self.adapter, "is_filtered"): raise ValueError("filtered policies are not supported by this adapter") @@ -214,7 +214,8 @@ def build_role_links(self): for rm in self.rm_map.values(): rm.clear() - self.model.build_role_links(self.rm_map) + + self.model.build_role_links(self.rm_map) def add_named_matching_func(self, ptype, fn): """add_named_matching_func add MatchingFunc by ptype RoleManager""" @@ -261,7 +262,7 @@ def enforce(self, *rvals): exp_string = self.model.model["m"]["m"].value has_eval = util.has_eval(exp_string) - if not has_eval: + if not has_eval: expression = self._get_expression(exp_string, functions) policy_effects = set() @@ -346,12 +347,10 @@ def enforce(self, *rvals): return result - - @staticmethod def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") expr = expr.replace("||", "or") - expr = expr.replace("!","not") + expr = expr.replace("!", "not") return SimpleEval(expr, functions) From 1704a07a04b8485c2d7a413866e778dbf58bad9e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 25 Feb 2021 13:04:25 +0000 Subject: [PATCH 112/349] chore(release): 0.18.1 [skip ci] ## [0.18.1](https://github.com/casbin/pycasbin/compare/v0.18.0...v0.18.1) (2021-02-25) ### Performance Improvements * reposition build_role_links() ([396ef7a](https://github.com/casbin/pycasbin/commit/396ef7ae5a650a102751dd602aed4ddd2775efba)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cbcc11f..9077b458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.18.1](https://github.com/casbin/pycasbin/compare/v0.18.0...v0.18.1) (2021-02-25) + + +### Performance Improvements + +* reposition build_role_links() ([396ef7a](https://github.com/casbin/pycasbin/commit/396ef7ae5a650a102751dd602aed4ddd2775efba)) + # [0.18.0](https://github.com/casbin/pycasbin/compare/v0.17.0...v0.18.0) (2021-02-23) diff --git a/setup.cfg b/setup.cfg index 7cf4999e..87d5f0ab 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.18.0 +version = 0.18.1 From ef4da2acd16d8e787f2bbc455aa6a4004c873835 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 2 Mar 2021 15:20:41 +0800 Subject: [PATCH 113/349] fix: typo Signed-off-by: Zxilly --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4670d7b4..80eeed75 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,8 +28,8 @@ jobs: - name: Install setuptools run: python -m pip install --upgrade setuptools wheel twine - - name: Rlease + - name: Release env: GH_TOKEN: ${{ secrets.GH_TOKEN }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: npx semantic-release \ No newline at end of file + run: npx semantic-release From 2a4ad0ff9350592f762251131f7d7fa3f8beef8d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 2 Mar 2021 07:44:52 +0000 Subject: [PATCH 114/349] chore(release): 0.18.2 [skip ci] ## [0.18.2](https://github.com/casbin/pycasbin/compare/v0.18.1...v0.18.2) (2021-03-02) ### Bug Fixes * typo ([32a572f](https://github.com/casbin/pycasbin/commit/32a572fcc4b114d93ffd59508a85e90297fd9744)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9077b458..243f9684 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.18.2](https://github.com/casbin/pycasbin/compare/v0.18.1...v0.18.2) (2021-03-02) + + +### Bug Fixes + +* typo ([32a572f](https://github.com/casbin/pycasbin/commit/32a572fcc4b114d93ffd59508a85e90297fd9744)) + ## [0.18.1](https://github.com/casbin/pycasbin/compare/v0.18.0...v0.18.1) (2021-02-25) diff --git a/setup.cfg b/setup.cfg index 87d5f0ab..2794349a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.18.1 +version = 0.18.2 From 5c2358381b608810b0b98f1bb11820a23a946096 Mon Sep 17 00:00:00 2001 From: Romain Arnal Date: Fri, 12 Mar 2021 14:25:43 +0100 Subject: [PATCH 115/349] perf: refacto & improve performance Signed-off-by: Romain Arnal --- .gitignore | 5 +- casbin/enforcer.py | 49 ++++++------------- casbin/management_enforcer.py | 42 +++------------- casbin/model/assertion.py | 5 +- casbin/model/policy.py | 44 +++++------------ .../rbac/default_role_manager/role_manager.py | 12 ++--- casbin/util/util.py | 12 +---- test_filter.py | 6 +-- 8 files changed, 49 insertions(+), 126 deletions(-) diff --git a/.gitignore b/.gitignore index 188deb51..47a276bb 100644 --- a/.gitignore +++ b/.gitignore @@ -103,4 +103,7 @@ venv.bak/ # mypy .mypy_cache/ _trial_temp -.idea \ No newline at end of file +.idea + +# vscode +.vscode diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 46021927..554262e7 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -26,14 +26,7 @@ def get_users_for_role(self, name): def has_role_for_user(self, name, role): """ determines whether a user has a role. """ roles = self.get_roles_for_user(name) - - hasRole = False - for r in roles: - if r == role: - hasRole = True - break - - return hasRole + return any(r == role for r in roles) def add_role_for_user(self, user, role): """ @@ -116,7 +109,7 @@ def has_permission_for_user(self, user, *permission): """ return self.has_policy(join_slice(user, *permission)) - def get_implicit_roles_for_user(self, name, *domain): + def get_implicit_roles_for_user(self, name, domain=None): """ gets implicit roles that a user has. Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. @@ -127,28 +120,22 @@ def get_implicit_roles_for_user(self, name, *domain): get_roles_for_user("alice") can only get: ["role:admin"]. But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. """ - res = list() - roleSet = dict() - roleSet[name] = True - - q = list() - q.append(name) + res = [] + queue = [name] - while len(q) > 0: - name = q[0] - q = q[1:] + while queue: + name = queue.pop(0) for rm in self.rm_map.values(): - roles = rm.get_roles(name, *domain) + roles = rm.get_roles(name, domain) for r in roles: - if r not in roleSet: + if r not in res: res.append(r) - q.append(r) - roleSet[r] = True + queue.append(r) return res - def get_implicit_permissions_for_user(self, user, *domain): + def get_implicit_permissions_for_user(self, user, domain=None): """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. @@ -160,23 +147,17 @@ def get_implicit_permissions_for_user(self, user, *domain): get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. """ - roles = self.get_implicit_roles_for_user(user, *domain) + roles = self.get_implicit_roles_for_user(user, domain) roles.insert(0, user) - withDomain = False - if len(domain) == 1: - withDomain = True - elif len(domain) > 1: - return None - res = [] - permissions = [list()] for role in roles: - if withDomain: - permissions = self.get_permissions_for_user_in_domain(role, domain[0]) + if domain: + permissions = self.get_permissions_for_user_in_domain(role, domain) else: permissions = self.get_permissions_for_user(role) + res.extend(permissions) return res @@ -227,4 +208,4 @@ def delete_roles_for_user_in_domain(self, user, role, domain): def get_permissions_for_user_in_domain(self, user, domain): """gets permissions for a user or role inside domain.""" - return self.get_filtered_policy(0, user, domain) \ No newline at end of file + return self.get_filtered_policy(0, user, domain) diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 02ebd7cd..fcb4f2e7 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -79,12 +79,7 @@ def has_named_policy(self, ptype, *params): str_slice = params[0] return self.model.has_policy('p', ptype, str_slice) - policy = [] - - for param in params: - policy.append(param) - - return self.model.has_policy('p', ptype, policy) + return self.model.has_policy('p', ptype, list(params)) def add_policy(self, *params): """adds an authorization rule to the current policy. @@ -113,12 +108,7 @@ def add_named_policy(self, ptype, *params): str_slice = params[0] rule_added = self._add_policy('p', ptype, str_slice) else: - policy = [] - - for param in params: - policy.append(param) - - rule_added = self._add_policy('p', ptype, policy) + rule_added = self._add_policy('p', ptype, list(params)) return rule_added @@ -164,12 +154,7 @@ def remove_named_policy(self, ptype, *params): str_slice = params[0] rule_removed = self._remove_policy('p', ptype, str_slice) else: - policy = [] - - for param in params: - policy.append(param) - - rule_removed = self._remove_policy('p', ptype, policy) + rule_removed = self._remove_policy('p', ptype, list(params)) return rule_removed @@ -193,12 +178,7 @@ def has_named_grouping_policy(self, ptype, *params): str_slice = params[0] return self.model.has_policy('g', ptype, str_slice) - policy = [] - - for param in params: - policy.append(param) - - return self.model.has_policy('g', ptype, policy) + return self.model.has_policy('g', ptype, list(params)) def add_grouping_policy(self, *params): """adds a role inheritance rule to the current policy. @@ -227,12 +207,7 @@ def add_named_grouping_policy(self, ptype, *params): str_slice = params[0] rule_added = self._add_policy('g', ptype, str_slice) else: - policy = [] - - for param in params: - policy.append(param) - - rule_added = self._add_policy('g', ptype, policy) + rule_added = self._add_policy('g', ptype, list(params)) if self.auto_build_role_links: self.build_role_links() @@ -268,12 +243,7 @@ def remove_named_grouping_policy(self, ptype, *params): str_slice = params[0] rule_removed = self._remove_policy('g', ptype, str_slice) else: - policy = [] - - for param in params: - policy.append(param) - - rule_removed = self._remove_policy('g', ptype, policy) + rule_removed = self._remove_policy('g', ptype, list(params)) if self.auto_build_role_links: self.build_role_links() diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 7ad0cce5..476657a0 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -13,11 +13,10 @@ def __init__(self): def build_role_links(self, rm): self.rm = rm count = self.value.count("_") + if count < 2: + raise RuntimeError('the number of "_" in role definition should be at least 2') for rule in self.policy: - if count < 2: - raise RuntimeError('the number of "_" in role definition should be at least 2') - if len(rule) < count: raise RuntimeError("grouping policy elements do not meet role definition") diff --git a/casbin/model/policy.py b/casbin/model/policy.py index e9b41039..ed17a59b 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,8 +1,5 @@ import logging -from casbin import util - - class Policy: def __init__(self): self.logger = logging.getLogger() @@ -36,7 +33,7 @@ def clear_policy(self): if sec not in self.model.keys(): continue - for key, ast in self.model[sec].items(): + for key in self.model[sec].keys(): self.model[sec][key].policy = [] def get_policy(self, sec, ptype): @@ -46,19 +43,10 @@ def get_policy(self, sec, ptype): def get_filtered_policy(self, sec, ptype, field_index, *field_values): """gets rules based on field filters from a policy.""" - res = [] - - for rule in self.model[sec][ptype].policy: - matched = True - for i, field_value in enumerate(field_values): - if field_value != '' and rule[field_index + i] != field_value: - matched = False - break - - if matched: - res.append(rule) - - return res + return [ + rule for rule in self.model[sec][ptype].policy + if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)) + ] def has_policy(self, sec, ptype, rule): """determines whether a model has the specified policy rule.""" @@ -80,14 +68,14 @@ def add_policy(self, sec, ptype, rule): def add_policies(self,sec,ptype,rules): """adds policy rules to the model.""" - + for rule in rules: if self.has_policy(sec,ptype,rule): return False for rule in rules: self.model[sec][ptype].policy.append(rule) - + return True def update_policy(self, sec, ptype, old_rule, new_rule): @@ -109,7 +97,6 @@ def update_policies(self, sec, ptype, old_rules, new_rules): def remove_policy(self, sec, ptype, rule): """removes a policy rule from the model.""" - if not self.has_policy(sec, ptype, rule): return False @@ -126,7 +113,7 @@ def remove_policies(self, sec, ptype, rules): self.model[sec][ptype].policy.remove(rule) if rule in self.model[sec][ptype].policy: return False - + return True def remove_filtered_policy(self, sec, ptype, field_index, *field_values): @@ -140,13 +127,7 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): return res for rule in self.model[sec][ptype].policy: - matched = True - for i, field_value in enumerate(field_values): - if field_value != '' and rule[field_index + i] != field_value: - matched = False - break - - if matched: + if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)): res = True else: tmp.append(rule) @@ -157,7 +138,6 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): def get_values_for_field_in_policy(self, sec, ptype, field_index): """gets all values for a field for all rules in a policy, duplicated values are removed.""" - values = [] if sec not in self.model.keys(): return values @@ -165,6 +145,8 @@ def get_values_for_field_in_policy(self, sec, ptype, field_index): return values for rule in self.model[sec][ptype].policy: - values.append(rule[field_index]) + value = rule[field_index] + if value not in values: + values.append(value) - return util.array_remove_duplicates(values) + return values diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index bf33fbfe..a07c6130 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -103,23 +103,21 @@ def has_link(self, name1, name2, *domain): return True return False - def get_roles(self, name, *domain): + def get_roles(self, name, domain=None): """ gets the roles that a subject inherits. domain is a prefix to the roles. """ - if len(domain) == 1: - name = domain[0] + "::" + name - elif len(domain) > 1: - return RuntimeError("error: domain should be 1 parameter") + if domain: + name = domain + "::" + name if not self.has_role(name): return [] roles = self.create_role(name).get_roles() - if len(domain) == 1: + if domain: for key, value in enumerate(roles): - roles[key] = value[len(domain[0]) + 2:] + roles[key] = value[len(domain) + 2:] return roles diff --git a/casbin/util/util.py b/casbin/util/util.py index 34a53e4a..5c89c943 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -48,17 +48,7 @@ def join_slice(a, *b): def set_subtract(a, b): ''' returns the elements in `a` that aren't in `b`. ''' - mb = dict() - - for x in b: - mb[x] = True - - diff = list() - for x in a: - if x not in mb: - diff.append(x) - - return diff + return [i for i in a if i not in b] def has_eval(s): '''determine whether matcher contains function eval''' diff --git a/test_filter.py b/test_filter.py index 22d91104..1a632b40 100644 --- a/test_filter.py +++ b/test_filter.py @@ -34,7 +34,7 @@ def test_load_filtered_policy(self): self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) self.assertFalse(e.has_policy(['admin', 'domain2', 'data2','read'])) - + with self.assertRaises(RuntimeError): e.save_policy() @@ -121,5 +121,5 @@ def test_filtered_adapter_invalid_filepath(self): adapter = casbin.persist.adapters.FilteredAdapter("examples/does_not_exist_policy.csv") e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) - with self.assertRaises(FileNotFoundError): - e.load_filtered_policy(None) \ No newline at end of file + with self.assertRaises(RuntimeError): + e.load_filtered_policy(None) From 6693ba66686de0dcd3d62f0679d1e81b4ff2e62a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Mar 2021 16:20:30 +0000 Subject: [PATCH 116/349] chore(release): 0.18.3 [skip ci] ## [0.18.3](https://github.com/casbin/pycasbin/compare/v0.18.2...v0.18.3) (2021-03-12) ### Performance Improvements * refacto & improve performance ([ff7f288](https://github.com/casbin/pycasbin/commit/ff7f288f487f5eab6b29898eb81f68257e6eb508)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 243f9684..c439ce1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.18.3](https://github.com/casbin/pycasbin/compare/v0.18.2...v0.18.3) (2021-03-12) + + +### Performance Improvements + +* refacto & improve performance ([ff7f288](https://github.com/casbin/pycasbin/commit/ff7f288f487f5eab6b29898eb81f68257e6eb508)) + ## [0.18.2](https://github.com/casbin/pycasbin/compare/v0.18.1...v0.18.2) (2021-03-02) diff --git a/setup.cfg b/setup.cfg index 2794349a..13dc7726 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.18.2 +version = 0.18.3 From 2a2fb042b989a955646aca6d03de8a37d9e3204c Mon Sep 17 00:00:00 2001 From: divyagar Date: Tue, 16 Mar 2021 21:57:03 +0530 Subject: [PATCH 117/349] perf: Added code to convert config value to different types Signed-off-by: divyagar --- casbin/config/config.py | 23 +++++++++++++++++++++++ tests/config/test_config.py | 8 ++++++++ 2 files changed, 31 insertions(+) diff --git a/casbin/config/config.py b/casbin/config/config.py index 149a2dde..ac91a793 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -98,6 +98,29 @@ def _write(self, section, line_num, b): del b[:] + def get_bool(self, key): + """lookups up the value using the provided key and converts the value to a bool.""" + return self.get(key).capitalize() == "True" + + def get_int(self, key): + """lookups up the value using the provided key and converts the value to a int""" + return int(self.get(key)) + + def get_float(self, key): + """lookups up the value using the provided key and converts the value to a float""" + return float(self.get(key)) + + def get_string(self, key): + """lookups up the value using the provided key and converts the value to a string""" + return self.get(key) + + def get_strings(self, key): + """lookups up the value using the provided key and converts the value to an array of string""" + value = self.get(key) + if value == "": + return None + return value.split(",") + def set(self, key, value): if len(key) == 0: raise RuntimeError("key is empty") diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 81e526d5..319cb572 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -38,3 +38,11 @@ def test_new_config(self): self.assertEqual(config.get("multi3::name"), "r.sub==p.sub && r.obj==p.obj") self.assertEqual(config.get("multi4::name"), "") self.assertEqual(config.get("multi5::name"), "r.sub==p.sub && r.obj==p.obj") + + self.assertEqual(config.get_bool("multi5::name"), False) + self.assertEqual(config.get_string("multi5::name"), "r.sub==p.sub && r.obj==p.obj") + self.assertEqual(config.get_strings("multi5::name"), ['r.sub==p.sub && r.obj==p.obj']) + with self.assertRaises(ValueError): + config.get_int("multi5::name") + with self.assertRaises(ValueError): + config.get_float("multi5::name") From 5fe3bb6190d0fcf9823c6c20db74ece8693305e9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 17 Mar 2021 02:02:26 +0000 Subject: [PATCH 118/349] chore(release): 0.18.4 [skip ci] ## [0.18.4](https://github.com/casbin/pycasbin/compare/v0.18.3...v0.18.4) (2021-03-17) ### Performance Improvements * Added code to convert config value to different types ([c87fdfa](https://github.com/casbin/pycasbin/commit/c87fdfa37f68e9c32e198e044862a9a2f1cb36b7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c439ce1d..99c89353 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.18.4](https://github.com/casbin/pycasbin/compare/v0.18.3...v0.18.4) (2021-03-17) + + +### Performance Improvements + +* Added code to convert config value to different types ([c87fdfa](https://github.com/casbin/pycasbin/commit/c87fdfa37f68e9c32e198e044862a9a2f1cb36b7)) + ## [0.18.3](https://github.com/casbin/pycasbin/compare/v0.18.2...v0.18.3) (2021-03-12) diff --git a/setup.cfg b/setup.cfg index 13dc7726..2a79fea6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.18.3 +version = 0.18.4 From 11823397b7b7857a918782e77829fd4337e82f1d Mon Sep 17 00:00:00 2001 From: divyagar Date: Wed, 17 Mar 2021 13:56:59 +0530 Subject: [PATCH 119/349] feat: Added dispatcher class Signed-off-by: divyagar --- casbin/persist/dispatcher.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 casbin/persist/dispatcher.py diff --git a/casbin/persist/dispatcher.py b/casbin/persist/dispatcher.py new file mode 100644 index 00000000..b499cde1 --- /dev/null +++ b/casbin/persist/dispatcher.py @@ -0,0 +1,21 @@ +class Dispatcher: + """Dispatcher is the interface for pycasbin dispatcher""" + def add_policies(self, sec, ptype, rules): + """add_policies adds policies rule to all instance.""" + pass + + def remove_policies(self, sec, ptype, rules): + """remove_policies removes policies rule from all instance.""" + pass + + def remove_filtered_policy(self, sec, ptype, field_index, field_values): + """remove_filtered_policy removes policy rules that match the filter from all instance.""" + pass + + def clear_policy(self): + """clear_policy clears all current policy in all instances.""" + pass + + def update_policy(self, sec, ptype, old_rule, new_rule): + """update_policy updates policy rule from all instance.""" + pass From 0f6e2ece436eb71d699d7814c62bb109a9e6b0b0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 18 Mar 2021 02:56:17 +0000 Subject: [PATCH 120/349] chore(release): 0.19.0 [skip ci] # [0.19.0](https://github.com/casbin/pycasbin/compare/v0.18.4...v0.19.0) (2021-03-18) ### Features * Added dispatcher class ([5c4a992](https://github.com/casbin/pycasbin/commit/5c4a992971a492874cfd32dc4eb94a3d61dbfcd7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 99c89353..55568806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.19.0](https://github.com/casbin/pycasbin/compare/v0.18.4...v0.19.0) (2021-03-18) + + +### Features + +* Added dispatcher class ([5c4a992](https://github.com/casbin/pycasbin/commit/5c4a992971a492874cfd32dc4eb94a3d61dbfcd7)) + ## [0.18.4](https://github.com/casbin/pycasbin/compare/v0.18.3...v0.18.4) (2021-03-17) diff --git a/setup.cfg b/setup.cfg index 2a79fea6..dbbb3825 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.18.4 +version = 0.19.0 From 7b50544c1ae87feffdfe2f3dc82d57a1b8c61425 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Thu, 18 Mar 2021 18:45:17 +0800 Subject: [PATCH 121/349] fix: relocate unittest and fix file path Signed-off-by: Zxilly --- casbin/__init__.py | 3 ++ casbin/persist/__init__.py | 3 +- examples/rbac_with_domains_policy.csv | 1 - test_filter.py => tests/test_filter.py | 68 +++++++++++++------------- 4 files changed, 39 insertions(+), 36 deletions(-) rename test_filter.py => tests/test_filter.py (66%) diff --git a/casbin/__init__.py b/casbin/__init__.py index 76c80e7a..df423716 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -1,3 +1,6 @@ from .enforcer import * from .synced_enforcer import SyncedEnforcer from . import util +from .persist import * +from .effect import * +from .model import * \ No newline at end of file diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index d681a76f..0c22ec89 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -1,3 +1,4 @@ from .adapter import * from .adapter_filtered import * -from .batch_adapter import * \ No newline at end of file +from .batch_adapter import * +from .adapters import * \ No newline at end of file diff --git a/examples/rbac_with_domains_policy.csv b/examples/rbac_with_domains_policy.csv index 8810a7cc..8558d171 100644 --- a/examples/rbac_with_domains_policy.csv +++ b/examples/rbac_with_domains_policy.csv @@ -2,6 +2,5 @@ p, 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, domain2 \ No newline at end of file diff --git a/test_filter.py b/tests/test_filter.py similarity index 66% rename from test_filter.py rename to tests/test_filter.py index 1a632b40..314f8352 100644 --- a/test_filter.py +++ b/tests/test_filter.py @@ -1,29 +1,32 @@ import casbin -import os from unittest import TestCase -import unittest +from tests.test_enforcer import get_examples + + class Filter: - #P,G are strings + # P,G are strings P = [] G = [] + + class TestFilteredAdapter(TestCase): def test_init_filtered_adapter(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) - self.assertFalse(e.has_policy(['admin', 'domain1', 'data1','read'])) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + self.assertFalse(e.has_policy(['admin', 'domain1', 'data1', 'read'])) def test_load_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] filter.G = ["", "", "domain1"] try: e.load_policy() except: - raise RuntimeError("nexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) + raise RuntimeError("unexpected error in LoadFilteredPolicy") + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) try: e.load_filtered_policy(filter) except: @@ -32,8 +35,8 @@ def test_load_filtered_policy(self): if not e.is_filtered: raise RuntimeError("adapter did not set the filtered flag correctly") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) - self.assertFalse(e.has_policy(['admin', 'domain2', 'data2','read'])) + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertFalse(e.has_policy(['admin', 'domain2', 'data2', 'read'])) with self.assertRaises(RuntimeError): e.save_policy() @@ -42,17 +45,17 @@ def test_load_filtered_policy(self): e.get_adapter().save_policy(e.get_model()) def test_append_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] filter.G = ["", "", "domain1"] try: e.load_policy() except: - raise RuntimeError("nexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) + raise RuntimeError("unexpected error in LoadFilteredPolicy") + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) try: e.load_filtered_policy(filter) except: @@ -61,8 +64,8 @@ def test_append_filtered_policy(self): if not e.is_filtered: raise RuntimeError("adapter did not set the filtered flag correctly") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) - self.assertFalse(e.has_policy(['admin', 'domain2', 'data2','read'])) + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertFalse(e.has_policy(['admin', 'domain2', 'data2', 'read'])) filter.P = ["", "domain2"] filter.G = ["", "", "domain2"] @@ -71,22 +74,20 @@ def test_append_filtered_policy(self): except: raise RuntimeError("unexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1','read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2','read'])) - + self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) def test_filtered_policy_invalid_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) - filter = ["","domain1"] + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = ["", "domain1"] with self.assertRaises(RuntimeError): e.load_filtered_policy(filter) - def test_filtered_policy_empty_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/rbac_with_domains_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) try: e.load_filtered_policy(None) @@ -101,9 +102,8 @@ def test_filtered_policy_empty_filter(self): except: raise RuntimeError("unexpected error in SavePolicy") - def test_unsupported_filtered_policy(self): - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", "examples/rbac_with_domains_policy.csv") + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) filter = Filter() filter.P = ["", "domain1"] filter.G = ["", "", "domain1"] @@ -112,14 +112,14 @@ def test_unsupported_filtered_policy(self): def test_filtered_adapter_empty_filepath(self): adapter = casbin.persist.adapters.FilteredAdapter("") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf",adapter) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): e.load_filtered_policy(None) def test_filtered_adapter_invalid_filepath(self): - adapter = casbin.persist.adapters.FilteredAdapter("examples/does_not_exist_policy.csv") - e = casbin.Enforcer("examples/rbac_with_domains_model.conf", adapter) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("does_not_exist_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): e.load_filtered_policy(None) From 22bbf91959bcdbffed30485a39598d7b0ee516fc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 18 Mar 2021 15:47:54 +0000 Subject: [PATCH 122/349] chore(release): 0.19.1 [skip ci] ## [0.19.1](https://github.com/casbin/pycasbin/compare/v0.19.0...v0.19.1) (2021-03-18) ### Bug Fixes * relocate unittest and fix file path ([5ed07b2](https://github.com/casbin/pycasbin/commit/5ed07b2f2aef9137a35048d3921d9986bae3254c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55568806..c0b2f2a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.19.1](https://github.com/casbin/pycasbin/compare/v0.19.0...v0.19.1) (2021-03-18) + + +### Bug Fixes + +* relocate unittest and fix file path ([5ed07b2](https://github.com/casbin/pycasbin/commit/5ed07b2f2aef9137a35048d3921d9986bae3254c)) + # [0.19.0](https://github.com/casbin/pycasbin/compare/v0.18.4...v0.19.0) (2021-03-18) diff --git a/setup.cfg b/setup.cfg index dbbb3825..641ec0b4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.19.0 +version = 0.19.1 From c51cf54de0325363d8e51b32e5ff786ea3a21c7c Mon Sep 17 00:00:00 2001 From: Jon Lee Date: Thu, 25 Mar 2021 16:29:52 +0800 Subject: [PATCH 123/349] chore: switch to GitHub Actions --- .github/workflows/build.yml | 88 +++++++++++++++++++++++++++ .github/workflows/release.yml | 35 ----------- .travis.yml | 25 -------- README.md | 2 +- examples/rbac_with_domains_policy.csv | 10 +-- 5 files changed, 94 insertions(+), 66 deletions(-) create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/release.yml delete mode 100755 .travis.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..732a1523 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,88 @@ +name: build +on: + push: + branches: + - master + pull_request: + branches: [ master ] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + os: [ubuntu-18.04, macOS-latest, windows-latest] + include: + # pypy3 on Mac OS currently fails trying to compile + # brotlipy. Moving pypy3 to only test linux. + - python-version: pypy3 + os: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install coveralls + + - name: Run tests + run: coverage run -m unittest discover -s tests -t tests + + - name: Upload coverage data to coveralls.io + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: ${{ matrix.os }} - ${{ matrix.python-version }} + COVERALLS_PARALLEL: true + + coveralls: + name: Indicate completion to coveralls.io + needs: test + runs-on: ubuntu-latest + container: python:3-slim + steps: + - name: Finished + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + release: + name: Release + runs-on: ubuntu-latest + needs: [ test, coveralls ] + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v1 + + - name: Setup + run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi + + - name: Set up python + uses: actions/setup-python@v2 + with: + python-version: 3.8 + + - name: Install setuptools + run: python -m pip install --upgrade setuptools wheel twine + + - name: Release + env: + GH_TOKEN: ${{ secrets.GH_TOKEN }} + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + run: npx semantic-release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 80eeed75..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: release -on: - push: - branches: - - master -jobs: - release: - name: Release - runs-on: ubuntu-18.04 - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Setup Node.js - uses: actions/setup-node@v1 - - - name: Setup - run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi - - - name: Set up python - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - - name: Install setuptools - run: python -m pip install --upgrade setuptools wheel twine - - - name: Release - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: npx semantic-release diff --git a/.travis.yml b/.travis.yml deleted file mode 100755 index ea48290e..00000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: python -python: - - "3.4" - - "3.5" - - "3.6" - - "3.7" - - "3.8" - - "pypy3" -install: - - pip install -r requirements.txt - - pip install coveralls -script: - - coverage run -m unittest discover -s tests -t tests -after_success: - - coveralls -deploy: - provider: pypi - user: techlee - password: - secure: qoBLYrYCxU3qe9SP3tCdgqVEmqEQAdCFfC/h8ENeY3dD0cyBV/yaitjY92bQxLgialouDUojiyOpvEl4apl4XQSvVhywtIqBaY5w8h1SuRUNMOkCP8LiWQt5NbsVI6Liad9IRviknsiIkW3LTqOrS+zRWmhZ6Yz/wvc+QMFY4JX51EWLPwtFTSbuiG0VtJgN9s7LgX8Q6GvNW0Alp3mfJnVTqk/HCtJuHkQwd1EK5Zr2ePO+KwlLaTpvRrAAcQVEWuHysieriTNgz9GTXvVEoGAuP/meq8DIRGDQmqnMz4vjj7oxg+gaVioUfnhN35ZrTF0wggJaCeytkSjbbDCspIc47/zaK7041ZQO9r0A1eeErkSi1dUo3WH4yO3HWfWxg5HOLN+40CYPStlmC77/jG0vvYBdmdcD8h+cRmY0W426G8xf9aqnYNzI7VP8tVceO9ySVHgeKCdVPcGRht8nHQdsNHFxUijViThtzyhf+RKF1UXbIxlZUuWirwR2+rfGSY6oYdXIb0MUBXxLqwVVIPBrQJOuaMEZG4tAVxaa3K/YiRIN5rvTZp8Ru5YrPsAyGhINg3t2JrPoyU236JIlNKliQHPVvOUftGryD/kvhpDIl+kOKFkNoMCQnolbGMGEx+ZL7g4IBpR+iNkvJNEyV1KProPdGJR5ajUuGEAmdkM= - distributions: "sdist bdist_wheel" - skip_existing: true - on: - tags: true - python: '3.6' \ No newline at end of file diff --git a/README.md b/README.md index 3ed0ffc1..8461970c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ PyCasbin ==== -[![Build Status](https://www.travis-ci.com/casbin/pycasbin.svg?branch=master)](https://www.travis-ci.com/casbin/pycasbin) +[![GitHub Action](https://github.com/casbin/pycasbin/workflows/build/badge.svg?branch=master)](https://github.com/casbin/pycasbin/actions) [![Coverage Status](https://coveralls.io/repos/github/casbin/pycasbin/badge.svg)](https://coveralls.io/github/casbin/pycasbin) [![Version](https://img.shields.io/pypi/v/casbin.svg)](https://pypi.org/project/casbin/) [![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) diff --git a/examples/rbac_with_domains_policy.csv b/examples/rbac_with_domains_policy.csv index 8558d171..9db696d4 100644 --- a/examples/rbac_with_domains_policy.csv +++ b/examples/rbac_with_domains_policy.csv @@ -1,6 +1,6 @@ -p, admin, domain1, data1, read -p, admin, domain1, data1, write -p, admin, domain2, data2, read -p, admin, domain2, data2, write -g, alice, admin, domain1 +p, 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, domain2 \ No newline at end of file From bbdfc2bad69e40cf15519f432e9862691da6fc69 Mon Sep 17 00:00:00 2001 From: divyagar Date: Thu, 1 Apr 2021 07:19:40 +0530 Subject: [PATCH 124/349] fix: Added checks in init_with_model_and_adapter in CoreEnforcer Signed-off-by: divyagar --- casbin/core_enforcer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index c4f7e4bc..c679d484 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -2,6 +2,7 @@ from casbin.effect import DefaultEffector, Effector from casbin.model import Model, FunctionMap +from casbin.persist import Adapter from casbin.persist.adapters import FileAdapter from casbin.rbac import default_role_manager from casbin.util import generate_g_function, SimpleEval, util @@ -51,6 +52,10 @@ def init_with_adapter(self, model_path, adapter=None): def init_with_model_and_adapter(self, m, adapter=None): """initializes an enforcer with a model and a database adapter.""" + + if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter): + raise RuntimeError("Invalid parameters for enforcer.") + self.adapter = adapter self.model = m From 2096b492eb171f96b9f62a55625226edb7412dd4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 1 Apr 2021 02:18:36 +0000 Subject: [PATCH 125/349] chore(release): 0.19.2 [skip ci] ## [0.19.2](https://github.com/casbin/pycasbin/compare/v0.19.1...v0.19.2) (2021-04-01) ### Bug Fixes * Added checks in init_with_model_and_adapter in CoreEnforcer ([1c55727](https://github.com/casbin/pycasbin/commit/1c55727661616edc62865f4ffd15e3f262ddf1d5)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b2f2a6..e984d46e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [0.19.2](https://github.com/casbin/pycasbin/compare/v0.19.1...v0.19.2) (2021-04-01) + + +### Bug Fixes + +* Added checks in init_with_model_and_adapter in CoreEnforcer ([1c55727](https://github.com/casbin/pycasbin/commit/1c55727661616edc62865f4ffd15e3f262ddf1d5)) + ## [0.19.1](https://github.com/casbin/pycasbin/compare/v0.19.0...v0.19.1) (2021-03-18) diff --git a/setup.cfg b/setup.cfg index 641ec0b4..5fd04003 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.19.1 +version = 0.19.2 From c0ace8b1b4c9c04005aa66b4251b184cb7485fb2 Mon Sep 17 00:00:00 2001 From: divyagar Date: Thu, 18 Mar 2021 19:03:30 +0530 Subject: [PATCH 126/349] feat: added distributed enforcer file along with respective unit tests Signed-off-by: divyagar --- casbin/__init__.py | 1 + casbin/distributed_enforcer.py | 134 ++++++++++++++++++ casbin/internal_enforcer.py | 4 + casbin/model/assertion.py | 23 ++- casbin/model/model.py | 1 - casbin/model/policy.py | 32 +++++ casbin/model/policy_op.py | 5 + casbin/persist/adapters/update_adapter.py | 9 ++ .../rbac/default_role_manager/role_manager.py | 10 +- casbin/synced_enforcer.py | 2 +- tests/test_distributed_api.py | 91 ++++++++++++ 11 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 casbin/distributed_enforcer.py create mode 100644 casbin/model/policy_op.py create mode 100644 casbin/persist/adapters/update_adapter.py create mode 100644 tests/test_distributed_api.py diff --git a/casbin/__init__.py b/casbin/__init__.py index df423716..9991b3ab 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -1,5 +1,6 @@ from .enforcer import * from .synced_enforcer import SyncedEnforcer +from .distributed_enforcer import DistributedEnforcer from . import util from .persist import * from .effect import * diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py new file mode 100644 index 00000000..06cfcf0d --- /dev/null +++ b/casbin/distributed_enforcer.py @@ -0,0 +1,134 @@ +from casbin import SyncedEnforcer +import logging + +from casbin.persist import batch_adapter +from casbin.model.policy_op import PolicyOp +from casbin.persist.adapters import update_adapter + + +class DistributedEnforcer(SyncedEnforcer): + """DistributedEnforcer wraps SyncedEnforcer for dispatcher.""" + + def __init__(self, model=None, adapter=None): + self.logger = logging.getLogger() + SyncedEnforcer.__init__(self, model, adapter) + + def add_policy_self(self, should_persist, sec, ptype, rules): + """ + AddPolicySelf provides a method for dispatcher to add authorization rules to the current policy. + The function returns the rules affected and error. + """ + no_exists_policy = [] + for rule in rules: + if self.get_model().has_policy(sec, ptype, rule): + no_exists_policy.append(rule) + + if should_persist: + try: + if isinstance(self.adapter, batch_adapter): + self.adapter.add_policies(sec, ptype, rules) + except Exception as e: + self.logger.log("An error occurred: " + e) + + self.get_model().add_policies(sec, ptype, no_exists_policy) + + if sec == "g": + try: + self.build_incremental_role_links(PolicyOp.Policy_add, ptype, no_exists_policy) + except Exception as e: + self.logger.log("An exception occurred: " + e) + return no_exists_policy + + return rules + + def remove_policy_self(self, should_persist, sec, ptype, rules): + """ + remove_policy_self provides a method for dispatcher to remove policies from current policy. + The function returns the rules affected and error. + """ + if(should_persist): + try: + if(isinstance(self.adapter, batch_adapter)): + self.adapter.remove_policy(sec, ptype, rules) + except Exception as e: + self.logger.log("An exception occurred: " + e) + + self.get_model().remove_policies(sec, ptype, rules) + + if sec == "g": + try: + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, rules) + except Exception as e: + self.logger.log("An exception occurred: " + e) + return rules + + return rules + + def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, *field_values): + """ + remove_filtered_policy_self provides a method for dispatcher to remove an authorization + rule from the current policy,field filters can be specified. + The function returns the rules affected and error. + """ + if should_persist: + try: + self.adapter.remove_filtered_policy(sec, ptype, field_index, field_values) + except Exception as e: + self.logger.log("An exception occurred: " + e) + + effects = self.get_model().remove_filtered_policy_returns_effects(sec, ptype, field_index, field_values) + + if sec == "g": + try: + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, effects) + except Exception as e: + self.logger.log("An exception occurred: " + e) + return effects + + return effects + + def clear_policy_self(self, should_persist): + """ + clear_policy_self provides a method for dispatcher to clear all rules from the current policy. + """ + if should_persist: + try: + self.adapter.save_policy(None) + except Exception as e: + self.logger.log("An exception occurred: " + e) + + self.get_model().clear_policy() + + def update_policy_self(self, should_persist, sec, ptype, old_rule, new_rule): + """ + update_policy_self provides a method for dispatcher to update an authorization rule from the current policy. + """ + if should_persist: + try: + if isinstance(self.adapter, update_adapter): + self.adapter.update_policy(sec, ptype, old_rule, new_rule) + except Exception as e: + self.logger.log("An exception occurred: " + e) + return False + + rule_updated = self.get_model().update_policy(sec, ptype, old_rule, new_rule) + + if not rule_updated: + return False + + rules = [] + if sec == "g": + try: + rules.append(old_rule) + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, rules) + except Exception as e: + return False + + try: + rules.append(new_rule) + self.build_incremental_role_links(PolicyOp.Policy_add, ptype, rules) + except Exception as e: + return False + + + return True \ No newline at end of file diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 871a2fdd..d006dc5a 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -1,4 +1,5 @@ from casbin.core_enforcer import CoreEnforcer +from casbin.model.policy_op import PolicyOp class InternalEnforcer(CoreEnforcer): """ @@ -38,6 +39,9 @@ def _add_policies(self,sec,ptype,rules): return rules_added + def build_incremental_role_links(self, op, ptype, rules): + self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) + def _update_policy(self, sec, ptype, old_rule, new_rule): """updates a rule from the current policy.""" rule_updated = self.model.update_policy(sec, ptype, old_rule, new_rule) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 476657a0..a5a803ea 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -1,5 +1,5 @@ import logging - +from casbin.model.policy_op import PolicyOp class Assertion: def __init__(self): @@ -24,3 +24,24 @@ def build_role_links(self, rm): self.logger.info("Role links for: {}".format(self.key)) self.rm.print_roles() + + def build_incremental_role_links(self, rm, op, rules): + self.rm = rm + count = 0 + for i in range(len(self.value)): + if self.value[i] == "_": + count += 1 + + for rule in rules: + if count < 2: + raise TypeError("the number of \"_\" in role definition should be at least 2") + if len(rule) < count: + raise TypeError("grouping policy elements do not meet role definition") + if(len(rule) > count): + rule = rule[0, count] + if op == PolicyOp.Policy_add: + rm.add_link(rule[0], rule[1], rule[2: len(rule)]) + elif op == PolicyOp.Policy_remove: + rm.delete_link(rule[0], rule[1], rule[2: len(rule)]) + else: + raise TypeError("Invalid operation: " + str(op)) \ No newline at end of file diff --git a/casbin/model/model.py b/casbin/model/model.py index f3e197d6..cd998842 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -2,7 +2,6 @@ from casbin import util, config from .policy import Policy - class Model(Policy): section_name_map = { diff --git a/casbin/model/policy.py b/casbin/model/policy.py index ed17a59b..98df3a70 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -15,6 +15,10 @@ def build_role_links(self, rm_map): rm = rm_map[ptype] ast.build_role_links(rm) + def build_incremental_role_links(self, rm, op, sec, ptype, rules): + if sec == "g": + self.model.get(sec).get(ptype).build_incremental_role_links(rm, op, rules) + def print_policy(self): """Log using info""" @@ -116,6 +120,34 @@ def remove_policies(self, sec, ptype, rules): return True + def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, field_values): + """ + remove_filtered_policy_returns_effects removes policy rules based on field filters from the model. + """ + tmp = [] + effects = [] + first_index = -1 + for rule in self.model[sec][ptype].policy: + matched = True + for i in range(len(field_values)): + field_value = field_values[i] + if(field_value != "" and rule[field_index + i] != field_value): + matched = False + break + + if matched: + if first_index == -1: + first_index = self.model[sec][ptype].policy.index(rule) + effects.append(rule) + else: + tmp.append(rule) + + if first_index != -1: + self.model[sec][ptype].policy = tmp + + return effects + + def remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes policy rules based on field filters from the model.""" tmp = [] diff --git a/casbin/model/policy_op.py b/casbin/model/policy_op.py new file mode 100644 index 00000000..de4b146e --- /dev/null +++ b/casbin/model/policy_op.py @@ -0,0 +1,5 @@ +import enum + +class PolicyOp(enum.Enum): + Policy_add = 1 + Policy_remove = 2 \ No newline at end of file diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py new file mode 100644 index 00000000..11d4f3fe --- /dev/null +++ b/casbin/persist/adapters/update_adapter.py @@ -0,0 +1,9 @@ +class UpdateAdapter: + """ UpdateAdapter is the interface for Casbin adapters with add update policy function. """ + + def update_policy(self, sec, ptype, old_rule, new_policy): + """ + update_policy updates a policy rule from storage. + This is part of the Auto-Save feature. + """ + pass \ No newline at end of file diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index a07c6130..f58defe3 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -47,8 +47,9 @@ def clear(self): def add_link(self, name1, name2, *domain): if len(domain) == 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 + if len(domain[0]) > 1: + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 elif len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") @@ -69,8 +70,9 @@ def add_link(self, name1, name2, *domain): def delete_link(self, name1, name2, *domain): if len(domain) == 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 + if len(domain[0]) > 1: + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 elif len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 8f43ebf4..4f3bbda6 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -21,7 +21,7 @@ def value(self, value): with self._lock: self._value = value -class SyncedEnforcer(): +class SyncedEnforcer(Enforcer): """SyncedEnforcer wraps Enforcer and provides synchronized access. It's also a drop-in replacement for Enforcer""" diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py new file mode 100644 index 00000000..ed7ea744 --- /dev/null +++ b/tests/test_distributed_api.py @@ -0,0 +1,91 @@ +import casbin +from tests.test_enforcer import get_examples, TestCaseBase + + +class TestDistributedApi(TestCaseBase): + + def get_enforcer(self, model=None, adapter=None): + return casbin.DistributedEnforcer( + model, + adapter, + ) + + def test(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv") + ) + + e.add_policy_self(False, "p", "p", [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"] + ]) + e.add_policy_self(False, "g", "g", [["alice", "data2_admin"]]) + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("data2_admin", "data2", "read")) + self.assertTrue(e.enforce("data2_admin", "data2", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + + e.update_policy_self(False, "p", "p", ["alice", "data1", "read"],["alice", "data1", "write"]) + e.update_policy_self(False, "g", "g", ["alice", "data2_admin"], ["tom", "alice"]) + + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("data2_admin", "data2", "read")) + self.assertTrue(e.enforce("data2_admin", "data2", "write")) + self.assertFalse(e.enforce("tom", "data1", "read")) + self.assertTrue(e.enforce("tom", "data1", "write")) + + e.remove_policy_self(False, "p", "p", [ + ["alice", "data1", "write"] + ]) + e.remove_policy_self(False, "g", "g", [ + ["alice", "data2_admin"] + ]) + + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("data2_admin", "data2", "read")) + self.assertTrue(e.enforce("data2_admin", "data2", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + + e.remove_filtered_policy_self(False, "p", "p", 0, "bob", "data2", "write") + e.remove_filtered_policy_self(False, "g", "g", 0, "tom", "data2_admin") + + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("data2_admin", "data2", "read")) + self.assertTrue(e.enforce("data2_admin", "data2", "write")) + self.assertFalse(e.enforce("tom", "data1", "read")) + self.assertFalse(e.enforce("tom", "data1", "write")) + + e.clear_policy_self(False) + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("data2_admin", "data2", "read")) + self.assertFalse(e.enforce("data2_admin", "data2", "write")) + + +class TestDistributedApiSynced(TestDistributedApi): + + def get_enforcer(self, model=None, adapter=None): + return casbin.DistributedEnforcer( + model, + adapter, + ) \ No newline at end of file From bf195cca8413b2c0fde0787dd03a9e51390fa0e9 Mon Sep 17 00:00:00 2001 From: divyagar Date: Fri, 2 Apr 2021 07:38:51 +0530 Subject: [PATCH 127/349] refactor: made necessary changes in DistributedEnforcer and Policy classes Signed-off-by: divyagar --- casbin/distributed_enforcer.py | 18 ++++++++---------- casbin/internal_enforcer.py | 3 --- casbin/model/policy.py | 34 ++++++++++++++++++++-------------- casbin/synced_enforcer.py | 7 +++++-- tests/test_distributed_api.py | 9 --------- 5 files changed, 33 insertions(+), 38 deletions(-) diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 06cfcf0d..afb67f9a 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -18,9 +18,10 @@ def add_policy_self(self, should_persist, sec, ptype, rules): AddPolicySelf provides a method for dispatcher to add authorization rules to the current policy. The function returns the rules affected and error. """ + no_exists_policy = [] for rule in rules: - if self.get_model().has_policy(sec, ptype, rule): + if not self.get_model().has_policy(sec, ptype, rule): no_exists_policy.append(rule) if should_persist: @@ -39,7 +40,7 @@ def add_policy_self(self, should_persist, sec, ptype, rules): self.logger.log("An exception occurred: " + e) return no_exists_policy - return rules + return no_exists_policy def remove_policy_self(self, should_persist, sec, ptype, rules): """ @@ -53,16 +54,16 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): except Exception as e: self.logger.log("An exception occurred: " + e) - self.get_model().remove_policies(sec, ptype, rules) + effected = self.get_model().remove_policies_with_effected(sec, ptype, rules) if sec == "g": try: self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, rules) except Exception as e: self.logger.log("An exception occurred: " + e) - return rules + return effected - return rules + return effected def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, *field_values): """ @@ -116,17 +117,14 @@ def update_policy_self(self, should_persist, sec, ptype, old_rule, new_rule): if not rule_updated: return False - rules = [] if sec == "g": try: - rules.append(old_rule) - self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, rules) + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, [old_rule]) except Exception as e: return False try: - rules.append(new_rule) - self.build_incremental_role_links(PolicyOp.Policy_add, ptype, rules) + self.build_incremental_role_links(PolicyOp.Policy_add, ptype, [new_rule]) except Exception as e: return False diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index d006dc5a..60138c10 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -39,9 +39,6 @@ def _add_policies(self,sec,ptype,rules): return rules_added - def build_incremental_role_links(self, op, ptype, rules): - self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) - def _update_policy(self, sec, ptype, old_rule, new_rule): """updates a rule from the current policy.""" rule_updated = self.model.update_policy(sec, ptype, old_rule, new_rule) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 98df3a70..d56301f1 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -120,30 +120,36 @@ def remove_policies(self, sec, ptype, rules): return True - def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, field_values): + def remove_policies_with_effected(self, sec, ptype, rules): + effected = [] + for rule in rules: + if self.has_policy(sec, ptype, rule): + effected.append(rule) + self.remove_policy(sec, ptype, rule) + + return effected + + def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field_values): """ remove_filtered_policy_returns_effects removes policy rules based on field filters from the model. """ tmp = [] effects = [] - first_index = -1 + + if(len(field_values) == 0): + return [] + if sec not in self.model.keys(): + return [] + if ptype not in self.model[sec]: + return [] + for rule in self.model[sec][ptype].policy: - matched = True - for i in range(len(field_values)): - field_value = field_values[i] - if(field_value != "" and rule[field_index + i] != field_value): - matched = False - break - - if matched: - if first_index == -1: - first_index = self.model[sec][ptype].policy.index(rule) + if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values[0])): effects.append(rule) else: tmp.append(rule) - if first_index != -1: - self.model[sec][ptype].policy = tmp + self.model[sec][ptype].policy = tmp return effects diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 4f3bbda6..4313af91 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -21,7 +21,7 @@ def value(self, value): with self._lock: self._value = value -class SyncedEnforcer(Enforcer): +class SyncedEnforcer(): """SyncedEnforcer wraps Enforcer and provides synchronized access. It's also a drop-in replacement for Enforcer""" @@ -566,4 +566,7 @@ def remove_grouping_policies(self,rules): def remove_named_grouping_policies(self,ptype,rules): """ removes role inheritance rules from the current named policy.""" with self._wl: - return self._e.remove_named_grouping_policies(ptype,rules) \ No newline at end of file + return self._e.remove_named_grouping_policies(ptype,rules) + + def build_incremental_role_links(self, op, ptype, rules): + self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) \ No newline at end of file diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py index ed7ea744..36e2dbeb 100644 --- a/tests/test_distributed_api.py +++ b/tests/test_distributed_api.py @@ -80,12 +80,3 @@ def test(self): self.assertFalse(e.enforce("bob", "data2", "write")) self.assertFalse(e.enforce("data2_admin", "data2", "read")) self.assertFalse(e.enforce("data2_admin", "data2", "write")) - - -class TestDistributedApiSynced(TestDistributedApi): - - def get_enforcer(self, model=None, adapter=None): - return casbin.DistributedEnforcer( - model, - adapter, - ) \ No newline at end of file From b4bea41374fc38aece9b0227e3ebec6b0ef0d6ab Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 6 Apr 2021 10:54:58 +0000 Subject: [PATCH 128/349] chore(release): 0.20.0 [skip ci] # [0.20.0](https://github.com/casbin/pycasbin/compare/v0.19.2...v0.20.0) (2021-04-06) ### Features * added distributed enforcer file along with respective unit tests ([f167ebf](https://github.com/casbin/pycasbin/commit/f167ebf40f5d170745a3f48692d2185e205f3449)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e984d46e..e9e0fed1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [0.20.0](https://github.com/casbin/pycasbin/compare/v0.19.2...v0.20.0) (2021-04-06) + + +### Features + +* added distributed enforcer file along with respective unit tests ([f167ebf](https://github.com/casbin/pycasbin/commit/f167ebf40f5d170745a3f48692d2185e205f3449)) + ## [0.19.2](https://github.com/casbin/pycasbin/compare/v0.19.1...v0.19.2) (2021-04-01) diff --git a/setup.cfg b/setup.cfg index 5fd04003..df041459 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.19.2 +version = 0.20.0 From e46dbd8bcc04af4aae994493942934aab2ff2871 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Tue, 6 Apr 2021 19:29:26 +0800 Subject: [PATCH 129/349] feat: add EnforceEx (#134) * feat: add EnforceEx Signed-off-by: Zxilly * fix: wrong implement Signed-off-by: Zxilly * feat: new effector interface BREAKING CHANGE: Custom effectors will need a rewrite Signed-off-by: Andreas Bichinger Co-authored-by: Andreas Bichinger --- casbin/core_enforcer.py | 29 ++++++++++---- casbin/effect/__init__.py | 24 +++++++++++- casbin/effect/default_effector.py | 39 ------------------- casbin/effect/default_effectors.py | 61 ++++++++++++++++++++++++++++++ casbin/effect/effector.py | 11 +++++- casbin/synced_enforcer.py | 8 ++++ tests/test_enforcer.py | 51 +++++++++++++++---------- 7 files changed, 154 insertions(+), 69 deletions(-) delete mode 100644 casbin/effect/default_effector.py create mode 100644 casbin/effect/default_effectors.py diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index c679d484..2570a07e 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -1,6 +1,6 @@ import logging -from casbin.effect import DefaultEffector, Effector +from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap from casbin.persist import Adapter from casbin.persist.adapters import FileAdapter @@ -70,7 +70,7 @@ def init_with_model_and_adapter(self, m, adapter=None): def _initialize(self): self.rm_map = dict() - self.eft = DefaultEffector() + self.eft = get_effector(self.model.model["e"]["e"].value) self.watcher = None self.enabled = True @@ -242,6 +242,15 @@ def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). """ + result, _ = self.enforceEx(*rvals) + return result + + def enforceEx(self, *rvals): + """decides whether a "subject" can access a "object" with the operation "action", + input parameters are usually: (sub, obj, act). + return judge result with reason + """ + explain_index = -1 if not self.enabled: return False @@ -271,12 +280,12 @@ def enforce(self, *rvals): expression = self._get_expression(exp_string, functions) policy_effects = set() - matcher_results = set() r_parameters = dict(zip(r_tokens, rvals)) policy_len = len(self.model.model["p"]["p"].policy) + explain_index = -1 if not 0 == policy_len: for i, pvals in enumerate(self.model.model["p"]["p"].policy): if len(p_tokens) != len(pvals): @@ -301,8 +310,6 @@ def enforce(self, *rvals): if 0 == result: policy_effects.add(Effector.INDETERMINATE) continue - else: - matcher_results.add(result) else: raise RuntimeError("matcher result should be bool, int or float") @@ -317,7 +324,8 @@ def enforce(self, *rvals): else: policy_effects.add(Effector.ALLOW) - if "priority(p_eft) || deny" == self.model.model["e"]["e"].value: + if self.eft.intermediate_effect(policy_effects) != Effector.INDETERMINATE: + explain_index = i break else: @@ -336,7 +344,8 @@ def enforce(self, *rvals): else: policy_effects.add(Effector.INDETERMINATE) - result = self.eft.merge_effects(self.model.model["e"]["e"].value, policy_effects, matcher_results) + final_effect = self.eft.final_effect(policy_effects) + result = effect_to_bool(final_effect) # Log request. @@ -350,7 +359,11 @@ def enforce(self, *rvals): # leaving this in error for now, if it's very noise this can be changed to info or debug self.logger.error(req_str) - return result + explain_rule = [] + if explain_index != -1 and explain_index < policy_len: + explain_rule = self.model.model["p"]["p"].policy[explain_index] + + return result, explain_rule @staticmethod def _get_expression(expr, functions=None): diff --git a/casbin/effect/__init__.py b/casbin/effect/__init__.py index 5adfc358..51634371 100644 --- a/casbin/effect/__init__.py +++ b/casbin/effect/__init__.py @@ -1,2 +1,24 @@ -from .default_effector import DefaultEffector +from .default_effectors import AllowOverrideEffector, DenyOverrideEffector, AllowAndDenyEffector, PriorityEffector from .effector import Effector + +def get_effector(expr): + ''' creates an effector based on the current policy effect expression ''' + + if expr == "some(where (p_eft == allow))": + return AllowOverrideEffector() + elif expr == "!some(where (p_eft == deny))": + return DenyOverrideEffector() + elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": + return AllowAndDenyEffector() + elif expr == "priority(p_eft) || deny": + return PriorityEffector() + else: + raise RuntimeError("unsupported effect") + +def effect_to_bool(effect): + """ """ + if effect == Effector.ALLOW: + return True + if effect == Effector.DENY: + return False + raise RuntimeError("effect can't be converted to boolean") \ No newline at end of file diff --git a/casbin/effect/default_effector.py b/casbin/effect/default_effector.py deleted file mode 100644 index 47ee9765..00000000 --- a/casbin/effect/default_effector.py +++ /dev/null @@ -1,39 +0,0 @@ -from .effector import Effector - - -class DefaultEffector(Effector): - """default effector for Casbin.""" - - def merge_effects(self, expr, effects, results): - """merges all matching results collected by the enforcer into a single decision.""" - - effects = set(effects) - result = False - if expr == "some(where (p_eft == allow))": - if self.ALLOW in effects: - result = True - - elif expr == "!some(where (p_eft == deny))": - result = True - - if self.DENY in effects: - result = False - - elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": - if self.DENY in effects: - result = False - elif self.ALLOW in effects: - result = True - - elif expr == "priority(p_eft) || deny": - for eft in effects: - if eft != self.INDETERMINATE: - if eft == self.ALLOW: - result = True - else: - result = False - break - else: - raise RuntimeError("unsupported effect") - - return result diff --git a/casbin/effect/default_effectors.py b/casbin/effect/default_effectors.py new file mode 100644 index 00000000..77d65ebb --- /dev/null +++ b/casbin/effect/default_effectors.py @@ -0,0 +1,61 @@ +from .effector import Effector + +class AllowOverrideEffector(Effector): + + def intermediate_effect(self, effects): + """ returns a intermediate effect based on the matched effects of the enforcer """ + if Effector.ALLOW in effects: + return Effector.ALLOW + return Effector.INDETERMINATE + + def final_effect(self, effects): + """ returns the final effect based on the matched effects of the enforcer """ + if Effector.ALLOW in effects: + return Effector.ALLOW + return Effector.DENY + +class DenyOverrideEffector(Effector): + + def intermediate_effect(self, effects): + """ returns a intermediate effect based on the matched effects of the enforcer """ + if Effector.DENY in effects: + return Effector.DENY + return Effector.INDETERMINATE + + def final_effect(self, effects): + """ returns the final effect based on the matched effects of the enforcer """ + if Effector.DENY in effects: + return Effector.DENY + return Effector.ALLOW + +class AllowAndDenyEffector(Effector): + + def intermediate_effect(self, effects): + """ returns a intermediate effect based on the matched effects of the enforcer """ + if Effector.DENY in effects: + return Effector.DENY + return Effector.INDETERMINATE + + def final_effect(self, effects): + """ returns the final effect based on the matched effects of the enforcer """ + if Effector.DENY in effects or Effector.ALLOW not in effects: + return Effector.DENY + return Effector.ALLOW + +class PriorityEffector(Effector): + + def intermediate_effect(self, effects): + """ returns a intermediate effect based on the matched effects of the enforcer """ + if Effector.ALLOW in effects: + return Effector.ALLOW + if Effector.DENY in effects: + return Effector.DENY + return Effector.INDETERMINATE + + def final_effect(self, effects): + """ returns the final effect based on the matched effects of the enforcer """ + if Effector.ALLOW in effects: + return Effector.ALLOW + if Effector.DENY in effects: + return Effector.DENY + return Effector.DENY diff --git a/casbin/effect/effector.py b/casbin/effect/effector.py index c9501acd..13b08885 100644 --- a/casbin/effect/effector.py +++ b/casbin/effect/effector.py @@ -7,6 +7,13 @@ class Effector: DENY = 2 - def merge_effects(self, expr, effects, results): - """merges all matching results collected by the enforcer into a single decision.""" + def intermediate_effect(self, effects): + """ returns a intermediate effect based on the matched effects of the enforcer """ pass + + def final_effect(self, effects): + """ returns the final effect based on the matched effects of the enforcer """ + pass + + + diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 4313af91..62f25ab2 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -132,6 +132,14 @@ def enforce(self, *rvals): with self._rl: return self._e.enforce(*rvals) + def enforceEx(self, *rvals): + """decides whether a "subject" can access a "object" with the operation "action", + input parameters are usually: (sub, obj, act). + return judge result with reason + """ + with self._rl: + return self._e.enforceEx(*rvals) + def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" with self._rl: diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 89e0256c..aa5ff988 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -9,12 +9,14 @@ def get_examples(path): examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/" return os.path.abspath(examples_path + path) + class TestSub(): def __init__(self, name, age): self.name = name self.age = age + class TestCaseBase(TestCase): def get_enforcer(self, model=None, adapter=None): return casbin.Enforcer( @@ -22,6 +24,7 @@ def get_enforcer(self, model=None, adapter=None): adapter, ) + class TestConfig(TestCaseBase): def test_enforcer_basic(self): @@ -35,6 +38,16 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertFalse(e.enforce('bob', 'data1', 'write')) + def test_enforceEx_basic(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + self.assertTupleEqual(e.enforceEx('alice', 'data1', 'read'), (True, ['alice', 'data1', 'read'])) + self.assertTupleEqual(e.enforceEx('alice', 'data2', 'read'), (False, [])) + self.assertTupleEqual(e.enforceEx('bob', 'data2', 'write'), (True, ['bob', 'data2', 'write'])) + self.assertTupleEqual(e.enforceEx('bob', 'data1', 'write'), (False, [])) + def test_model_set_load(self): e = self.get_enforcer( get_examples("basic_model.conf"), @@ -47,7 +60,6 @@ def test_model_set_load(self): e.load_model() self.assertTrue(e.model) - def test_enforcer_basic_without_spaces(self): e = self.get_enforcer( get_examples("basic_model_without_spaces.conf"), @@ -69,7 +81,7 @@ def test_enforce_basic_with_root(self): def test_enforce_basic_without_resources(self): e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) + get_examples("basic_without_resources_policy.csv")) self.assertTrue(e.enforce('alice', 'read')) self.assertFalse(e.enforce('alice', 'write')) self.assertTrue(e.enforce('bob', 'write')) @@ -77,7 +89,7 @@ def test_enforce_basic_without_resources(self): def test_enforce_basic_without_users(self): e = self.get_enforcer(get_examples("basic_without_users_model.conf"), - get_examples("basic_without_users_policy.csv")) + get_examples("basic_without_users_policy.csv")) self.assertTrue(e.enforce('data1', 'read')) self.assertFalse(e.enforce('data1', 'write')) self.assertTrue(e.enforce('data2', 'write')) @@ -85,13 +97,13 @@ def test_enforce_basic_without_users(self): def test_enforce_ip_match(self): e = self.get_enforcer(get_examples("ipmatch_model.conf"), - get_examples("ipmatch_policy.csv")) + get_examples("ipmatch_policy.csv")) self.assertTrue(e.enforce('192.168.2.1', 'data1', 'read')) self.assertFalse(e.enforce('192.168.3.1', 'data1', 'read')) def test_enforce_key_match(self): e = self.get_enforcer(get_examples("keymatch_model.conf"), - get_examples("keymatch_policy.csv")) + get_examples("keymatch_policy.csv")) self.assertTrue(e.enforce('alice', '/alice_data/test', 'GET')) self.assertFalse(e.enforce('alice', '/bob_data/test', 'GET')) self.assertTrue(e.enforce('cathy', '/cathy_data', 'GET')) @@ -100,7 +112,7 @@ def test_enforce_key_match(self): def test_enforce_key_match2(self): e = self.get_enforcer(get_examples("keymatch2_model.conf"), - get_examples("keymatch2_policy.csv")) + get_examples("keymatch2_policy.csv")) self.assertTrue(e.enforce('alice', '/alice_data/resource', 'GET')) self.assertTrue(e.enforce('alice', '/alice_data2/123/using/456', 'GET')) @@ -145,7 +157,8 @@ def test_enforce_rbac_with_deny(self): self.assertFalse(e.enforce('alice', 'data2', 'write')) def test_enforce_rbac_with_domains(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) + e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv")) self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'read')) self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'write')) self.assertFalse(e.enforce('alice', 'domain1', 'data2', 'read')) @@ -162,7 +175,7 @@ def test_enforce_rbac_with_not_deny(self): def test_enforce_rbac_with_resource_roles(self): e = self.get_enforcer(get_examples("rbac_with_resource_roles_model.conf"), - get_examples("rbac_with_resource_roles_policy.csv")) + get_examples("rbac_with_resource_roles_policy.csv")) self.assertTrue(e.enforce('alice', 'data1', 'read')) self.assertTrue(e.enforce('alice', 'data1', 'write')) self.assertFalse(e.enforce('alice', 'data2', 'read')) @@ -175,9 +188,9 @@ def test_enforce_rbac_with_resource_roles(self): def test_enforce_rbac_with_pattern(self): e = self.get_enforcer(get_examples("rbac_with_pattern_model.conf"), - get_examples("rbac_with_pattern_policy.csv")) + get_examples("rbac_with_pattern_policy.csv")) - #set matching function to key_match2 + # set matching function to key_match2 e.add_named_matching_func('g2', casbin.util.key_match2) self.assertTrue(e.enforce("alice", "/book/1", "GET")) @@ -189,7 +202,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen/1", "GET")) self.assertTrue(e.enforce("bob", "/pen/2", "GET")) - #replace key_match2 with key_match3 + # replace key_match2 with key_match3 e.add_named_matching_func('g2', casbin.util.key_match3) self.assertTrue(e.enforce("alice", "/book2/1", "GET")) self.assertTrue(e.enforce("alice", "/book2/2", "GET")) @@ -208,12 +221,12 @@ def test_enforce_abac_log_enabled(self): def test_abac_with_sub_rule(self): e = self.get_enforcer(get_examples("abac_rule_model.conf"), - get_examples("abac_rule_policy.csv")) + get_examples("abac_rule_policy.csv")) sub1 = TestSub("alice", 16) sub2 = TestSub("bob", 20) sub3 = TestSub("alice", 65) - + self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) self.assertFalse(e.enforce(sub1, "/data1", "write")) @@ -231,13 +244,13 @@ def test_abac_with_sub_rule(self): def test_abac_with_multiple_sub_rules(self): e = self.get_enforcer(get_examples("abac_multiple_rules_model.conf"), - get_examples("abac_multiple_rules_policy.csv")) + get_examples("abac_multiple_rules_policy.csv")) sub1 = TestSub("alice", 16) sub2 = TestSub("alice", 20) sub3 = TestSub("bob", 65) sub4 = TestSub("bob", 35) - + self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) self.assertFalse(e.enforce(sub1, "/data1", "write")) @@ -258,6 +271,7 @@ def test_abac_with_multiple_sub_rules(self): self.assertFalse(e.enforce(sub4, "/data1", "write")) self.assertTrue(e.enforce(sub4, "/data2", "write")) + class TestConfigSynced(TestConfig): def get_enforcer(self, model=None, adapter=None): @@ -272,10 +286,9 @@ def test_auto_loading_policy(self): get_examples("basic_policy.csv"), ) - e.start_auto_load_policy(5/1000) + e.start_auto_load_policy(5 / 1000) self.assertTrue(e.is_auto_loading_running()) e.stop_auto_load_policy() - #thread needs a moment to exit - time.sleep(10/1000) + # thread needs a moment to exit + time.sleep(10 / 1000) self.assertFalse(e.is_auto_loading_running()) - From 8bf307b057c44a8b430a30a72cf4cbbe028e6156 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 6 Apr 2021 11:33:35 +0000 Subject: [PATCH 130/349] chore(release): 1.0.0 [skip ci] # [1.0.0](https://github.com/casbin/pycasbin/compare/v0.20.0...v1.0.0) (2021-04-06) ### Features * add EnforceEx ([#134](https://github.com/casbin/pycasbin/issues/134)) ([c577e1d](https://github.com/casbin/pycasbin/commit/c577e1da65e3ddf4cf360d851f9f9a5ef6c94650)) ### BREAKING CHANGES * Custom effectors will need a rewrite Signed-off-by: Andreas Bichinger Co-authored-by: Andreas Bichinger --- CHANGELOG.md | 16 ++++++++++++++++ setup.cfg | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9e0fed1..f3cec2c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Semantic Versioning Changelog +# [1.0.0](https://github.com/casbin/pycasbin/compare/v0.20.0...v1.0.0) (2021-04-06) + + +### Features + +* add EnforceEx ([#134](https://github.com/casbin/pycasbin/issues/134)) ([c577e1d](https://github.com/casbin/pycasbin/commit/c577e1da65e3ddf4cf360d851f9f9a5ef6c94650)) + + +### BREAKING CHANGES + +* Custom effectors will need a rewrite + +Signed-off-by: Andreas Bichinger + +Co-authored-by: Andreas Bichinger + # [0.20.0](https://github.com/casbin/pycasbin/compare/v0.19.2...v0.20.0) (2021-04-06) diff --git a/setup.cfg b/setup.cfg index df041459..ba40987d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 0.20.0 +version = 1.0.0 From d31a0e5a68670401da84ba5d11f499338d7691ff Mon Sep 17 00:00:00 2001 From: Jon Lee Date: Sat, 10 Apr 2021 00:58:18 +0800 Subject: [PATCH 131/349] fix: wrong domain length judge and bug in build_incremental_role_links() (#139) Signed-off-by: Jon Lee --- casbin/model/assertion.py | 23 +++++++++---------- .../rbac/default_role_manager/role_manager.py | 16 ++++++------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index a5a803ea..353ce67d 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -1,6 +1,7 @@ import logging from casbin.model.policy_op import PolicyOp + class Assertion: def __init__(self): self.logger = logging.getLogger() @@ -19,6 +20,8 @@ def build_role_links(self, rm): for rule in self.policy: if len(rule) < count: raise RuntimeError("grouping policy elements do not meet role definition") + if len(rule) > count: + rule = rule[:count] self.rm.add_link(*rule[:count]) @@ -27,21 +30,17 @@ def build_role_links(self, rm): def build_incremental_role_links(self, rm, op, rules): self.rm = rm - count = 0 - for i in range(len(self.value)): - if self.value[i] == "_": - count += 1 - + count = self.value.count("_") + if count < 2: + raise RuntimeError('the number of "_" in role definition should be at least 2') for rule in rules: - if count < 2: - raise TypeError("the number of \"_\" in role definition should be at least 2") if len(rule) < count: raise TypeError("grouping policy elements do not meet role definition") - if(len(rule) > count): - rule = rule[0, count] + if len(rule) > count: + rule = rule[:count] if op == PolicyOp.Policy_add: - rm.add_link(rule[0], rule[1], rule[2: len(rule)]) + rm.add_link(rule[0], rule[1], *rule[2:]) elif op == PolicyOp.Policy_remove: - rm.delete_link(rule[0], rule[1], rule[2: len(rule)]) + rm.delete_link(rule[0], rule[1], *rule[2:]) else: - raise TypeError("Invalid operation: " + str(op)) \ No newline at end of file + raise TypeError("Invalid operation: " + str(op)) diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index f58defe3..103c2aa2 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -9,7 +9,6 @@ class RoleManager(RoleManager): all_roles = dict() max_hierarchy_level = 0 - def __init__(self, max_hierarchy_level): self.logger = logging.getLogger() self.all_roles = dict() @@ -47,9 +46,8 @@ def clear(self): def add_link(self, name1, name2, *domain): if len(domain) == 1: - if len(domain[0]) > 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 elif len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") @@ -70,9 +68,8 @@ def add_link(self, name1, name2, *domain): def delete_link(self, name1, name2, *domain): if len(domain) == 1: - if len(domain[0]) > 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 + name1 = domain[0] + "::" + name1 + name2 = domain[0] + "::" + name2 elif len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") @@ -101,9 +98,10 @@ def has_link(self, name1, name2, *domain): return role1.has_role(name2, self.max_hierarchy_level) else: for key, role in self.all_roles.items(): - if self.matching_func(name1, key) and role.has_role(name2, self.max_hierarchy_level, self.matching_func): + if self.matching_func(name1, key) and role.has_role(name2, self.max_hierarchy_level, + self.matching_func): return True - return False + return False def get_roles(self, name, domain=None): """ From 3a9e0fc58a855d07d68d16184855f320a358552f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 9 Apr 2021 17:10:15 +0000 Subject: [PATCH 132/349] chore(release): 1.0.1 [skip ci] ## [1.0.1](https://github.com/casbin/pycasbin/compare/v1.0.0...v1.0.1) (2021-04-09) ### Bug Fixes * wrong domain length judge and bug in build_incremental_role_links() ([#139](https://github.com/casbin/pycasbin/issues/139)) ([51b8833](https://github.com/casbin/pycasbin/commit/51b883301e0f2c104ef31658e2074ac6ca586451)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3cec2c9..ca372fec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.1](https://github.com/casbin/pycasbin/compare/v1.0.0...v1.0.1) (2021-04-09) + + +### Bug Fixes + +* wrong domain length judge and bug in build_incremental_role_links() ([#139](https://github.com/casbin/pycasbin/issues/139)) ([51b8833](https://github.com/casbin/pycasbin/commit/51b883301e0f2c104ef31658e2074ac6ca586451)) + # [1.0.0](https://github.com/casbin/pycasbin/compare/v0.20.0...v1.0.0) (2021-04-06) diff --git a/setup.cfg b/setup.cfg index ba40987d..361a94cb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.0 +version = 1.0.1 From 8ce32b04dc6ac702eceac69d032e6af13483e7cc Mon Sep 17 00:00:00 2001 From: Zxilly Date: Mon, 12 Apr 2021 18:35:57 +0800 Subject: [PATCH 133/349] fix: rename enforceEx() to enforce_ex() (#143) Signed-off-by: Zxilly --- casbin/core_enforcer.py | 4 ++-- casbin/synced_enforcer.py | 4 ++-- tests/test_enforcer.py | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 2570a07e..dccf10c0 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -242,10 +242,10 @@ def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). """ - result, _ = self.enforceEx(*rvals) + result, _ = self.enforce_ex(*rvals) return result - def enforceEx(self, *rvals): + def enforce_ex(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). return judge result with reason diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 62f25ab2..61130128 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -132,13 +132,13 @@ def enforce(self, *rvals): with self._rl: return self._e.enforce(*rvals) - def enforceEx(self, *rvals): + def enforce_ex(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). return judge result with reason """ with self._rl: - return self._e.enforceEx(*rvals) + return self._e.enforce_ex(*rvals) def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index aa5ff988..0d130f41 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -38,15 +38,15 @@ def test_enforcer_basic(self): self.assertTrue(e.enforce('bob', 'data2', 'write')) self.assertFalse(e.enforce('bob', 'data1', 'write')) - def test_enforceEx_basic(self): + def test_enforce_ex_basic(self): e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), ) - self.assertTupleEqual(e.enforceEx('alice', 'data1', 'read'), (True, ['alice', 'data1', 'read'])) - self.assertTupleEqual(e.enforceEx('alice', 'data2', 'read'), (False, [])) - self.assertTupleEqual(e.enforceEx('bob', 'data2', 'write'), (True, ['bob', 'data2', 'write'])) - self.assertTupleEqual(e.enforceEx('bob', 'data1', 'write'), (False, [])) + self.assertTupleEqual(e.enforce_ex('alice', 'data1', 'read'), (True, ['alice', 'data1', 'read'])) + self.assertTupleEqual(e.enforce_ex('alice', 'data2', 'read'), (False, [])) + self.assertTupleEqual(e.enforce_ex('bob', 'data2', 'write'), (True, ['bob', 'data2', 'write'])) + self.assertTupleEqual(e.enforce_ex('bob', 'data1', 'write'), (False, [])) def test_model_set_load(self): e = self.get_enforcer( From 44a145fc3b12c252728b36c81e6d2c0598489a62 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 12 Apr 2021 11:02:19 +0000 Subject: [PATCH 134/349] chore(release): 1.0.2 [skip ci] ## [1.0.2](https://github.com/casbin/pycasbin/compare/v1.0.1...v1.0.2) (2021-04-12) ### Bug Fixes * rename enforceEx() to enforce_ex() ([#143](https://github.com/casbin/pycasbin/issues/143)) ([e52351a](https://github.com/casbin/pycasbin/commit/e52351a41c91d2546dddbc4f448c59ff70e9de1a)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca372fec..5ee19dbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.2](https://github.com/casbin/pycasbin/compare/v1.0.1...v1.0.2) (2021-04-12) + + +### Bug Fixes + +* rename enforceEx() to enforce_ex() ([#143](https://github.com/casbin/pycasbin/issues/143)) ([e52351a](https://github.com/casbin/pycasbin/commit/e52351a41c91d2546dddbc4f448c59ff70e9de1a)) + ## [1.0.1](https://github.com/casbin/pycasbin/compare/v1.0.0...v1.0.1) (2021-04-09) diff --git a/setup.cfg b/setup.cfg index 361a94cb..5da876cf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.1 +version = 1.0.2 From b0cfd3cebc6fe4a9d7de32d7628edcc80d126465 Mon Sep 17 00:00:00 2001 From: lzxin Date: Thu, 22 Apr 2021 17:46:00 +0800 Subject: [PATCH 135/349] fix: r.user.name -> r_user_name instead of r_user.name in ABAC model Signed-off-by: lzxin --- casbin/util/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/util/util.py b/casbin/util/util.py index 5c89c943..baa0d565 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -6,8 +6,8 @@ def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" - s = s.replace("r.", "r_") - s = s.replace("p.", "p_") + s = re.sub(r'\br\.', 'r_', s) + s = re.sub(r'\bp\.', 'p_', s) return s From e3935f3510891b3431efe90fde27a3b7d4475345 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 23 Apr 2021 03:42:19 +0000 Subject: [PATCH 136/349] chore(release): 1.0.3 [skip ci] ## [1.0.3](https://github.com/casbin/pycasbin/compare/v1.0.2...v1.0.3) (2021-04-23) ### Bug Fixes * r.user.name -> r_user_name instead of r_user.name in ABAC model ([192529e](https://github.com/casbin/pycasbin/commit/192529e11a8ddc848f3f2ee22c448c8afaf41f1b)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ee19dbd..a7f7933d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.3](https://github.com/casbin/pycasbin/compare/v1.0.2...v1.0.3) (2021-04-23) + + +### Bug Fixes + +* r.user.name -> r_user_name instead of r_user.name in ABAC model ([192529e](https://github.com/casbin/pycasbin/commit/192529e11a8ddc848f3f2ee22c448c8afaf41f1b)) + ## [1.0.2](https://github.com/casbin/pycasbin/compare/v1.0.1...v1.0.2) (2021-04-12) diff --git a/setup.cfg b/setup.cfg index 5da876cf..7501c853 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.2 +version = 1.0.3 From effdceb8262c37cf486c87bcad45158561de0130 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Fri, 23 Apr 2021 22:58:23 +0800 Subject: [PATCH 137/349] perf: remove unused variable Signed-off-by: Zxilly --- casbin/core_enforcer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index dccf10c0..7b80d19a 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -250,7 +250,6 @@ def enforce_ex(self, *rvals): input parameters are usually: (sub, obj, act). return judge result with reason """ - explain_index = -1 if not self.enabled: return False From ca211a5febdbb9b7763e98b6a85086fbe776f9d8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 24 Apr 2021 14:18:17 +0000 Subject: [PATCH 138/349] chore(release): 1.0.4 [skip ci] ## [1.0.4](https://github.com/casbin/pycasbin/compare/v1.0.3...v1.0.4) (2021-04-24) ### Performance Improvements * remove unused variable ([e642898](https://github.com/casbin/pycasbin/commit/e642898c5a0b9a8ccefb2104321985b82f420aa4)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7f7933d..95066eb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.4](https://github.com/casbin/pycasbin/compare/v1.0.3...v1.0.4) (2021-04-24) + + +### Performance Improvements + +* remove unused variable ([e642898](https://github.com/casbin/pycasbin/commit/e642898c5a0b9a8ccefb2104321985b82f420aa4)) + ## [1.0.3](https://github.com/casbin/pycasbin/compare/v1.0.2...v1.0.3) (2021-04-23) diff --git a/setup.cfg b/setup.cfg index 7501c853..aba364b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.3 +version = 1.0.4 From d2a492e2725a6a50dab8eb1c3a95da3f5f9aa79a Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 12 May 2021 00:56:28 +0800 Subject: [PATCH 139/349] ci: remove python 3.5 Signed-off-by: Zxilly --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 732a1523..d2e8b8d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.5, 3.6, 3.7, 3.8, 3.9] + python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-18.04, macOS-latest, windows-latest] include: # pypy3 on Mac OS currently fails trying to compile From 52cd70e5c821e35377b2b1846051a463f6e4f9bb Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 12 May 2021 19:07:02 +0800 Subject: [PATCH 140/349] fix: replace fnmatch with wcmatch for glob match, keep same behavior with Go Casbin (#149) * fix: keep same behavior with go-casbin Signed-off-by: Zxilly * build: add wcmatch as dependency Signed-off-by: Zxilly * ci: refresh check Signed-off-by: Zxilly --- casbin/util/builtin_operators.py | 7 ++++--- requirements.txt | 1 + tests/util/test_builtin_operators.py | 22 +++++++++++----------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 509eba42..a6eaa0cb 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -1,6 +1,7 @@ -import fnmatch -import re import ipaddress +import re + +from wcmatch import pathlib KEY_MATCH2_PATTERN = re.compile(r'(.*?):[^\/]+(.*?)') KEY_MATCH3_PATTERN = re.compile(r'(.*?){[^\/]+}(.*?)') @@ -86,7 +87,7 @@ def regex_match_func(*args): def glob_match(string, pattern): """determines whether string matches the pattern in glob expression.""" - return fnmatch.fnmatch(string, pattern) + return pathlib.Path(string).globmatch(pattern) def glob_match_func(*args): diff --git a/requirements.txt b/requirements.txt index 52aeb858..37a1c82d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ simpleeval>=0.9.10 +wcmatch >= 8.1.2 \ No newline at end of file diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 1bd3e2d6..ae0c4621 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -90,30 +90,30 @@ def test_glob_match(self): self.assertTrue(util.glob_match_func("/foo", "/foo*")) self.assertFalse(util.glob_match_func("/foo", "/foo/*")) self.assertFalse(util.glob_match_func("/foo/bar", "/foo")) - self.assertTrue(util.glob_match_func("/foo/bar", "/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/foo/bar", "/foo*")) self.assertTrue(util.glob_match_func("/foo/bar", "/foo/*")) self.assertFalse(util.glob_match_func("/foobar", "/foo")) self.assertTrue(util.glob_match_func("/foobar", "/foo*")) self.assertFalse(util.glob_match_func("/foobar", "/foo/*")) - self.assertTrue(util.glob_match_func("/prefix/foo", "*/foo")) # differ from Casbin Go - self.assertTrue(util.glob_match_func("/prefix/foo", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo")) + self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo/*")) self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo")) - self.assertTrue(util.glob_match_func("/prefix/foo/bar", "*/foo*")) # differ from Casbin Go - self.assertTrue(util.glob_match_func("/prefix/foo/bar", "*/foo/*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo*")) + self.assertFalse(util.glob_match_func("/prefix/foo/bar", "*/foo/*")) self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo")) - self.assertTrue(util.glob_match_func("/prefix/foobar", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo/*")) - self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "*/foo")) # differ from Casbin Go - self.assertTrue(util.glob_match_func("/prefix/subprefix/foo", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo")) + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo/*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo")) - self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo*")) # differ from Casbin Go - self.assertTrue(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo/*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo*")) + self.assertFalse(util.glob_match_func("/prefix/subprefix/foo/bar", "*/foo/*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo")) - self.assertTrue(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*")) # differ from Casbin Go + self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/*")) def test_ip_match(self): From 1606721997c18ca63e0f54e28b56190b9b6caf5c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 12 May 2021 11:10:07 +0000 Subject: [PATCH 141/349] chore(release): 1.0.5 [skip ci] ## [1.0.5](https://github.com/casbin/pycasbin/compare/v1.0.4...v1.0.5) (2021-05-12) ### Bug Fixes * replace fnmatch with wcmatch for glob match, keep same behavior with Go Casbin ([#149](https://github.com/casbin/pycasbin/issues/149)) ([906f40c](https://github.com/casbin/pycasbin/commit/906f40c6c6b0d9e31104ca3ea92e4a8498c30619)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95066eb3..6ac5562b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.5](https://github.com/casbin/pycasbin/compare/v1.0.4...v1.0.5) (2021-05-12) + + +### Bug Fixes + +* replace fnmatch with wcmatch for glob match, keep same behavior with Go Casbin ([#149](https://github.com/casbin/pycasbin/issues/149)) ([906f40c](https://github.com/casbin/pycasbin/commit/906f40c6c6b0d9e31104ca3ea92e4a8498c30619)) + ## [1.0.4](https://github.com/casbin/pycasbin/compare/v1.0.3...v1.0.4) (2021-04-24) diff --git a/setup.cfg b/setup.cfg index aba364b9..0017b5ef 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.4 +version = 1.0.5 From a3fc29ae126fa57b6923c5f67effe6417d8a8e1f Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Wed, 12 May 2021 23:32:48 +0800 Subject: [PATCH 142/349] fix: optimized install_requires parsing process --- setup.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 95776195..f922db61 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,19 @@ import setuptools +from os import path desc_file = "README.md" +here = path.abspath(path.dirname(__file__)) + with open(desc_file, "r") as fh: long_description = fh.read() +# get the dependencies and installs +with open(path.join(here, "requirements.txt"), encoding="utf-8") as f: + all_reqs = f.read().split("\n") + +install_requires = [x.strip() for x in all_reqs if "git+" not in x] + setuptools.setup( name="casbin", author="TechLee", @@ -15,7 +24,7 @@ url="https://github.com/casbin/pycasbin", keywords=["casbin", "acl", "rbac", "abac", "auth", "authz", "authorization", "access control", "permission"], packages=setuptools.find_packages(exclude=("tests",)), - install_requires=['simpleeval>=0.9.10'], + install_requires=install_requires, python_requires=">=3.3", license="Apache 2.0", classifiers=[ From 204414a17a40ee48a3a4314b9e2f211e091822d0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 13 May 2021 01:43:11 +0000 Subject: [PATCH 143/349] chore(release): 1.0.6 [skip ci] ## [1.0.6](https://github.com/casbin/pycasbin/compare/v1.0.5...v1.0.6) (2021-05-13) ### Bug Fixes * optimized install_requires parsing process ([34d3a4f](https://github.com/casbin/pycasbin/commit/34d3a4f5cfb120e0b074c557e831c74fb0e3101f)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ac5562b..e7dac786 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.0.6](https://github.com/casbin/pycasbin/compare/v1.0.5...v1.0.6) (2021-05-13) + + +### Bug Fixes + +* optimized install_requires parsing process ([34d3a4f](https://github.com/casbin/pycasbin/commit/34d3a4f5cfb120e0b074c557e831c74fb0e3101f)) + ## [1.0.5](https://github.com/casbin/pycasbin/compare/v1.0.4...v1.0.5) (2021-05-12) diff --git a/setup.cfg b/setup.cfg index 0017b5ef..3107eb54 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.5 +version = 1.0.6 From 11e3952af42517c7fe2851abed2b4cf150329a9f Mon Sep 17 00:00:00 2001 From: Ted Elliott Date: Sun, 16 May 2021 02:48:00 -0400 Subject: [PATCH 144/349] feat: Use named loggers (#153) Signed-off-by: tl24 --- casbin/core_enforcer.py | 3 ++- casbin/distributed_enforcer.py | 2 +- casbin/model/assertion.py | 2 +- casbin/model/policy.py | 2 +- casbin/rbac/default_role_manager/role_manager.py | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 7b80d19a..d92b3e29 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -25,7 +25,7 @@ class CoreEnforcer: auto_build_role_links = False def __init__(self, model=None, adapter=None): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) if isinstance(model, str): if isinstance(adapter, str): self.init_with_file(model, adapter) @@ -59,6 +59,7 @@ def init_with_model_and_adapter(self, m, adapter=None): self.adapter = adapter self.model = m + m.logger = self.logger self.model.print_model() self.fm = FunctionMap.load_function_map() diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index afb67f9a..0e749ff5 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -10,7 +10,7 @@ class DistributedEnforcer(SyncedEnforcer): """DistributedEnforcer wraps SyncedEnforcer for dispatcher.""" def __init__(self, model=None, adapter=None): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) SyncedEnforcer.__init__(self, model, adapter) def add_policy_self(self, should_persist, sec, ptype, rules): diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 353ce67d..4283c1d4 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -4,7 +4,7 @@ class Assertion: def __init__(self): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) self.key = "" self.value = "" self.tokens = [] diff --git a/casbin/model/policy.py b/casbin/model/policy.py index d56301f1..aca055a4 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -2,7 +2,7 @@ class Policy: def __init__(self): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) self.model = {} def build_role_links(self, rm_map): diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 103c2aa2..16bf0a5f 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -10,7 +10,7 @@ class RoleManager(RoleManager): max_hierarchy_level = 0 def __init__(self, max_hierarchy_level): - self.logger = logging.getLogger() + self.logger = logging.getLogger(__name__) self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level self.matching_func = None From 02346ad808978048dc73ac299f0b09a9320b2021 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 23 May 2021 21:40:19 +0800 Subject: [PATCH 145/349] fix: enforce_ex now works fine when it was disabled Signed-off-by: Zxilly --- casbin/core_enforcer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index d92b3e29..e58aa4b6 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -59,7 +59,7 @@ def init_with_model_and_adapter(self, m, adapter=None): self.adapter = adapter self.model = m - m.logger = self.logger + m.logger = self.logger self.model.print_model() self.fm = FunctionMap.load_function_map() @@ -253,7 +253,7 @@ def enforce_ex(self, *rvals): """ if not self.enabled: - return False + return [False, []] functions = self.fm.get_functions() From a8063aad0acc6ee3f8516f2c77a6c40de48f7a36 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 23 May 2021 14:09:32 +0000 Subject: [PATCH 146/349] chore(release): 1.1.0 [skip ci] # [1.1.0](https://github.com/casbin/pycasbin/compare/v1.0.6...v1.1.0) (2021-05-23) ### Bug Fixes * enforce_ex now works fine when it was disabled ([15f58c9](https://github.com/casbin/pycasbin/commit/15f58c9af82b10e1a6dd4ce584ea39f1235031b9)) ### Features * Use named loggers ([#153](https://github.com/casbin/pycasbin/issues/153)) ([40e7f00](https://github.com/casbin/pycasbin/commit/40e7f001c77034854e0916dba13bc87f30c2ce7d)) --- CHANGELOG.md | 12 ++++++++++++ setup.cfg | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dac786..78424833 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Semantic Versioning Changelog +# [1.1.0](https://github.com/casbin/pycasbin/compare/v1.0.6...v1.1.0) (2021-05-23) + + +### Bug Fixes + +* enforce_ex now works fine when it was disabled ([15f58c9](https://github.com/casbin/pycasbin/commit/15f58c9af82b10e1a6dd4ce584ea39f1235031b9)) + + +### Features + +* Use named loggers ([#153](https://github.com/casbin/pycasbin/issues/153)) ([40e7f00](https://github.com/casbin/pycasbin/commit/40e7f001c77034854e0916dba13bc87f30c2ce7d)) + ## [1.0.6](https://github.com/casbin/pycasbin/compare/v1.0.5...v1.0.6) (2021-05-13) diff --git a/setup.cfg b/setup.cfg index 3107eb54..613a9b48 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.0.6 +version = 1.1.0 From a1024aff726958d5278543ee3a11ea716e8a827e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20M=E1=BA=A1nh=20L=C6=B0=C6=A1ng?= Date: Mon, 24 May 2021 11:23:32 +0700 Subject: [PATCH 147/349] fix: function 'keyMatch3' not defined MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lương Quang Mạnh --- casbin/model/function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/casbin/model/function.py b/casbin/model/function.py index b116a441..84ec863d 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -12,6 +12,7 @@ def load_function_map(): fm = FunctionMap() fm.add_function("keyMatch", util.key_match_func) fm.add_function("keyMatch2", util.key_match2_func) + fm.add_function("keyMatch3", util.key_match3_func) fm.add_function("regexMatch", util.regex_match_func) fm.add_function("ipMatch", util.ip_match_func) fm.add_function("globMatch", util.glob_match_func) From 7d5f788ff4be9cc56055000a9f016d44a226aeee Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 24 May 2021 08:27:40 +0000 Subject: [PATCH 148/349] chore(release): 1.1.1 [skip ci] ## [1.1.1](https://github.com/casbin/pycasbin/compare/v1.1.0...v1.1.1) (2021-05-24) ### Bug Fixes * function 'keyMatch3' not defined ([e97bb6a](https://github.com/casbin/pycasbin/commit/e97bb6aadc58faca33d6c606d9bae31adfbd2259)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78424833..2288f3ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.1.1](https://github.com/casbin/pycasbin/compare/v1.1.0...v1.1.1) (2021-05-24) + + +### Bug Fixes + +* function 'keyMatch3' not defined ([e97bb6a](https://github.com/casbin/pycasbin/commit/e97bb6aadc58faca33d6c606d9bae31adfbd2259)) + # [1.1.0](https://github.com/casbin/pycasbin/compare/v1.0.6...v1.1.0) (2021-05-23) diff --git a/setup.cfg b/setup.cfg index 613a9b48..7dbb9bb8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.1.0 +version = 1.1.1 From ce3717099a675b2e4e5a7e328ae959d89a856c9d Mon Sep 17 00:00:00 2001 From: sheny1xuan <43725202+sheny1xuan@users.noreply.github.com> Date: Fri, 4 Jun 2021 23:48:56 +0800 Subject: [PATCH 149/349] refactor: refactor ManagementEnforcer.update_policy and update_policies (#160) Signed-off-by: Shen Yixuan <1479765922@qq.com> --- casbin/model/policy.py | 51 ++++++++++++++++++++++++--- examples/priority_model_explicit.conf | 14 ++++++++ examples/priority_policy_explicit.csv | 12 +++++++ tests/model/test_policy.py | 35 ++++++++++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) create mode 100644 examples/priority_model_explicit.conf create mode 100644 examples/priority_policy_explicit.csv diff --git a/casbin/model/policy.py b/casbin/model/policy.py index aca055a4..2f1931c3 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -85,19 +85,60 @@ def add_policies(self,sec,ptype,rules): def update_policy(self, sec, ptype, old_rule, new_rule): """update a policy rule from the model.""" - if not self.has_policy(sec, ptype, old_rule): + if sec not in self.model.keys(): + return False + if ptype not in self.model[sec]: + return False + + ast = self.model[sec][ptype] + + if old_rule in ast.policy: + rule_index = ast.policy.index(old_rule) + else: return False - return self.remove_policy(sec, ptype, old_rule) and self.add_policy(sec, ptype, new_rule) + if "p_priority" in ast.tokens: + priority_index = ast.tokens.index("p_priority") + if old_rule[priority_index] == new_rule[priority_index]: + ast.policy[rule_index] = new_rule + else: + raise Exception("New rule should have the same priority with old rule.") + else: + ast.policy[rule_index] = new_rule + + return True def update_policies(self, sec, ptype, old_rules, new_rules): """update policy rules from the model.""" - for rule in old_rules: - if not self.has_policy(sec, ptype, rule): + if sec not in self.model.keys(): + return False + if ptype not in self.model[sec]: + return False + if len(old_rules) != len(new_rules): + return False + + ast = self.model[sec][ptype] + old_rules_index = [] + + for old_rule in old_rules: + if old_rule in ast.policy: + old_rules_index.append(ast.policy.index(old_rule)) + else: return False - return self.remove_policies(sec, ptype, old_rules) and self.add_policies(sec, ptype, new_rules) + if "p_priority" in ast.tokens: + priority_index = ast.tokens.index("p_priority") + for idx, old_rule, new_rule in zip(old_rules_index, old_rules, new_rules): + if old_rule[priority_index] == new_rule[priority_index]: + ast.policy[idx] = new_rule + else: + raise Exception("New rule should have the same priority with old rule.") + else: + for idx, old_rule, new_rule in zip(old_rules_index ,old_rules, new_rules): + ast.policy[idx] = new_rule + + return True def remove_policy(self, sec, ptype, rule): """removes a policy rule from the model.""" diff --git a/examples/priority_model_explicit.conf b/examples/priority_model_explicit.conf new file mode 100644 index 00000000..5df75b27 --- /dev/null +++ b/examples/priority_model_explicit.conf @@ -0,0 +1,14 @@ +[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.act \ No newline at end of file diff --git a/examples/priority_policy_explicit.csv b/examples/priority_policy_explicit.csv new file mode 100644 index 00000000..0fec82c5 --- /dev/null +++ b/examples/priority_policy_explicit.csv @@ -0,0 +1,12 @@ +p, 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_group diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index 65bd6507..4a8d08f1 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -68,6 +68,19 @@ def test_update_policy(self): self.assertFalse(m.has_policy('p', 'p', old_rule)) self.assertTrue(m.has_policy('p', 'p', new_rule)) + m = Model() + m.load_model(get_examples("priority_model_explicit.conf")) + + old_rule = ['1', 'admin', 'data1', 'read', 'allow'] + new_rule = ['1', 'admin', 'data2', 'read', 'allow'] + + m.add_policy('p', 'p', old_rule) + self.assertTrue(m.has_policy('p', 'p', old_rule)) + + m.update_policy('p', 'p', old_rule, new_rule) + self.assertFalse(m.has_policy('p', 'p', old_rule)) + self.assertTrue(m.has_policy('p', 'p', new_rule)) + def test_update_policies(self): m = Model() m.load_model(get_examples("basic_model.conf")) @@ -91,6 +104,28 @@ def test_update_policies(self): for new_rule in new_rules: self.assertTrue(m.has_policy('p', 'p', new_rule)) + m = Model() + m.load_model(get_examples("priority_model_explicit.conf")) + + old_rules = [['1', 'admin', 'data1', 'read', 'allow'], + ['1', 'admin', 'data2', 'read', 'allow'], + ['1', 'admin', 'data3', 'read', 'allow']] + new_rules = [['1', 'admin', 'data4', 'read', 'allow'], + ['1', 'admin', 'data5', 'read', 'allow'], + ['1', 'admin', 'data6', 'read', 'allow']] + + m.add_policies('p', 'p', old_rules) + + for old_rule in old_rules: + self.assertTrue(m.has_policy('p', 'p', old_rule)) + + m.update_policies('p', 'p', old_rules, new_rules) + + for old_rule in old_rules: + self.assertFalse(m.has_policy('p', 'p', old_rule)) + for new_rule in new_rules: + self.assertTrue(m.has_policy('p', 'p', new_rule)) + def test_remove_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) From a2912bcfad44fc7bf58975daaa0cd8b0dbd3ed40 Mon Sep 17 00:00:00 2001 From: jetz Date: Mon, 7 Jun 2021 14:57:29 +0800 Subject: [PATCH 150/349] fix: start auto loading policy for SyncedEnforcer Signed-off-by: jetz --- casbin/synced_enforcer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 61130128..5f408766 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -49,6 +49,7 @@ def start_auto_load_policy(self, interval): return self._auto_loading.value = True self._auto_loading_thread = threading.Thread(target=self._auto_load_policy, args=[interval], daemon=True) + self._auto_loading_thread.start() def stop_auto_load_policy(self): """stops the thread started by start_auto_load_policy""" @@ -577,4 +578,4 @@ def remove_named_grouping_policies(self,ptype,rules): return self._e.remove_named_grouping_policies(ptype,rules) def build_incremental_role_links(self, op, ptype, rules): - self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) \ No newline at end of file + self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) From 5161cff835fbf0f546c4397e687b072caa7160fe Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 7 Jun 2021 15:07:31 +0000 Subject: [PATCH 151/349] chore(release): 1.1.2 [skip ci] ## [1.1.2](https://github.com/casbin/pycasbin/compare/v1.1.1...v1.1.2) (2021-06-07) ### Bug Fixes * start auto loading policy for SyncedEnforcer ([4724f7a](https://github.com/casbin/pycasbin/commit/4724f7a2c137ba5357634aee1eebc1b1c28959f9)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2288f3ea..efba723c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.1.2](https://github.com/casbin/pycasbin/compare/v1.1.1...v1.1.2) (2021-06-07) + + +### Bug Fixes + +* start auto loading policy for SyncedEnforcer ([4724f7a](https://github.com/casbin/pycasbin/commit/4724f7a2c137ba5357634aee1eebc1b1c28959f9)) + ## [1.1.1](https://github.com/casbin/pycasbin/compare/v1.1.0...v1.1.1) (2021-05-24) diff --git a/setup.cfg b/setup.cfg index 7dbb9bb8..f0295041 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.1.1 +version = 1.1.2 From aa5eb71957da9ce5f6350d7d46748dbb55383b48 Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Tue, 15 Jun 2021 00:44:43 +0800 Subject: [PATCH 152/349] ci: code reformatted and linter tests added (#166) * fix: code reformat under PEP-8 using latest black Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> * fix: requirements_dev.txt added (for black) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> * ci: Github Actions build.yml file lint test added - added Run Linters check in build.yml file - removed checks for pypy3 Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- .github/workflows/build.yml | 29 +- casbin/__init__.py | 2 +- casbin/config/__init__.py | 2 +- casbin/config/config.py | 40 +- casbin/core_enforcer.py | 30 +- casbin/distributed_enforcer.py | 37 +- casbin/effect/__init__.py | 25 +- casbin/effect/default_effectors.py | 24 +- casbin/effect/effector.py | 7 +- casbin/enforcer.py | 13 +- casbin/internal_enforcer.py | 28 +- casbin/management_enforcer.py | 143 ++++--- casbin/model/assertion.py | 12 +- casbin/model/model.py | 13 +- casbin/model/policy.py | 38 +- casbin/model/policy_op.py | 3 +- casbin/persist/__init__.py | 2 +- casbin/persist/adapter_filtered.py | 6 +- casbin/persist/adapters/__init__.py | 2 +- casbin/persist/adapters/adapter_filtered.py | 69 +-- casbin/persist/adapters/file_adapter.py | 10 +- casbin/persist/adapters/update_adapter.py | 4 +- casbin/persist/batch_adapter.py | 8 +- casbin/persist/dispatcher.py | 1 + casbin/rbac/default_role_manager/__init__.py | 2 +- .../rbac/default_role_manager/role_manager.py | 9 +- casbin/synced_enforcer.py | 86 ++-- casbin/util/__init__.py | 2 +- casbin/util/builtin_operators.py | 11 +- casbin/util/expression.py | 16 +- casbin/util/rwlock.py | 11 +- casbin/util/util.py | 24 +- requirements_dev.txt | 2 + setup.py | 14 +- tests/config/test_config.py | 8 +- tests/model/test_policy.py | 142 ++++--- tests/rbac/test_role_manager.py | 15 +- tests/test_distributed_api.py | 37 +- tests/test_enforcer.py | 246 ++++++----- tests/test_filter.py | 51 ++- tests/test_management_api.py | 243 ++++++----- tests/test_model_benchmark.py | 10 +- tests/test_rbac_api.py | 398 +++++++++++------- tests/util/test_builtin_operators.py | 41 +- tests/util/test_rwlock.py | 35 +- tests/util/test_util.py | 56 ++- 46 files changed, 1185 insertions(+), 822 deletions(-) create mode 100644 requirements_dev.txt diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d2e8b8d0..6baf0c8e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,10 +1,9 @@ name: build on: push: - branches: - - master + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: test: @@ -14,11 +13,10 @@ jobs: matrix: python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-18.04, macOS-latest, windows-latest] - include: - # pypy3 on Mac OS currently fails trying to compile - # brotlipy. Moving pypy3 to only test linux. - - python-version: pypy3 - os: ubuntu-latest +# pypy3 currently fails trying to find the header #include "codecs.h". Removed pypy3 for now. +# include: +# - python-version: pypy3 +# os: ubuntu-latest steps: - name: Checkout @@ -32,6 +30,7 @@ jobs: - name: Install dependencies run: | pip install -r requirements.txt + pip install -r requirements_dev.txt pip install coveralls - name: Run tests @@ -44,6 +43,20 @@ jobs: COVERALLS_FLAG_NAME: ${{ matrix.os }} - ${{ matrix.python-version }} COVERALLS_PARALLEL: true + lint: + name: Run Linters + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Super-Linter + uses: github/super-linter@v4.2.2 + env: + VALIDATE_PYTHON_BLACK: true + DEFAULT_BRANCH: master + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + coveralls: name: Indicate completion to coveralls.io needs: test diff --git a/casbin/__init__.py b/casbin/__init__.py index 9991b3ab..86f8a949 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -4,4 +4,4 @@ from . import util from .persist import * from .effect import * -from .model import * \ No newline at end of file +from .model import * diff --git a/casbin/config/__init__.py b/casbin/config/__init__.py index 3558f420..cca5d9bd 100644 --- a/casbin/config/__init__.py +++ b/casbin/config/__init__.py @@ -1 +1 @@ -from .config import Config \ No newline at end of file +from .config import Config diff --git a/casbin/config/config.py b/casbin/config/config.py index ac91a793..9dba5b3e 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -5,13 +5,13 @@ class Config: """represents an implementation of the ConfigInterface""" # DEFAULT_SECTION specifies the name of a section if no name provided - DEFAULT_SECTION = 'default' + DEFAULT_SECTION = "default" # DEFAULT_COMMENT defines what character(s) indicate a comment `#` - DEFAULT_COMMENT = '#' + DEFAULT_COMMENT = "#" # DEFAULT_COMMENT_SEM defines what alternate character(s) indicate a comment `;` - DEFAULT_COMMENT_SEM = ';' + DEFAULT_COMMENT_SEM = ";" # DEFAULT_MULTI_LINE_SEPARATOR defines what character indicates a multi-line content - DEFAULT_MULTI_LINE_SEPARATOR = '\\' + DEFAULT_MULTI_LINE_SEPARATOR = "\\" _data = dict() @@ -32,7 +32,7 @@ def new_config_from_text(text): return c def add_config(self, section, option, value): - if section == '': + if section == "": section = self.DEFAULT_SECTION if section not in self._data.keys(): @@ -41,11 +41,11 @@ def add_config(self, section, option, value): self._data[section][option] = value def _parse(self, fname): - with open(fname, 'r', encoding='utf-8') as f: + with open(fname, "r", encoding="utf-8") as f: self._parse_buffer(f) def _parse_buffer(self, f): - section = '' + section = "" line_num = 0 buf = [] can_write = False @@ -63,19 +63,23 @@ def _parse_buffer(self, f): break line = line.strip() - if '' == line or self.DEFAULT_COMMENT == line[0:1] or self.DEFAULT_COMMENT_SEM == line[0:1]: + if ( + "" == line + or self.DEFAULT_COMMENT == line[0:1] + or self.DEFAULT_COMMENT_SEM == line[0:1] + ): can_write = True continue - elif '[' == line[0:1] and ']' == line[-1]: + elif "[" == line[0:1] and "]" == line[-1]: if len(buf) > 0: self._write(section, line_num, buf) can_write = False section = line[1:-1] else: - p = '' + p = "" if self.DEFAULT_MULTI_LINE_SEPARATOR == line[-1]: p = line[0:-1].strip() - p = p + ' ' + p = p + " " else: p = line can_write = True @@ -86,10 +90,14 @@ def _write(self, section, line_num, b): buf = "".join(b) if len(buf) <= 0: return - option_val = buf.split('=', 1) + option_val = buf.split("=", 1) if len(option_val) != 2: - raise RuntimeError('parse the content error : line {} , {} = ?'.format(line_num, option_val[0])) + raise RuntimeError( + "parse the content error : line {} , {} = ?".format( + line_num, option_val[0] + ) + ) option = option_val[0].strip() value = option_val[1].strip() @@ -125,7 +133,7 @@ def set(self, key, value): if len(key) == 0: raise RuntimeError("key is empty") - keys = key.lower().split('::') + keys = key.lower().split("::") if len(keys) >= 2: section = keys[0] option = keys[1] @@ -137,7 +145,7 @@ def set(self, key, value): def get(self, key): """section.key or key""" - keys = key.lower().split('::') + keys = key.lower().split("::") if len(keys) >= 2: section = keys[0] option = keys[1] @@ -148,4 +156,4 @@ def get(self, key): if section in self._data.keys(): if option in self._data[section].keys(): return self._data[section][option] - return '' + return "" diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index e58aa4b6..fbdc8c28 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -53,7 +53,11 @@ def init_with_adapter(self, model_path, adapter=None): def init_with_model_and_adapter(self, m, adapter=None): """initializes an enforcer with a model and a database adapter.""" - if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter): + if ( + not isinstance(m, Model) + or adapter is not None + and not isinstance(adapter, Adapter) + ): raise RuntimeError("Invalid parameters for enforcer.") self.adapter = adapter @@ -131,11 +135,11 @@ def set_watcher(self, watcher): def get_role_manager(self): """gets the current role manager.""" - return self.rm_map['g'] + return self.rm_map["g"] def set_role_manager(self, rm): """sets the current role manager.""" - self.rm_map['g'] = rm + self.rm_map["g"] = rm def set_effector(self, eft): """sets the current effector.""" @@ -143,13 +147,13 @@ def set_effector(self, eft): self.eft = eft def clear_policy(self): - """ clears all policy.""" + """clears all policy.""" self.model.clear_policy() def init_rm_map(self): - if 'g' in self.model.model.keys(): - for ptype in self.model.model['g']: + if "g" in self.model.model.keys(): + for ptype in self.model.model["g"]: self.rm_map[ptype] = default_role_manager.RoleManager(10) def load_policy(self): @@ -296,7 +300,10 @@ def enforce_ex(self, *rvals): if util.has_eval(exp_string): rule_names = util.get_eval_value(exp_string) - rules = [util.escape_assertion(p_parameters[rule_name]) for rule_name in rule_names] + rules = [ + util.escape_assertion(p_parameters[rule_name]) + for rule_name in rule_names + ] exp_with_rule = util.replace_eval(exp_string, rules) expression = self._get_expression(exp_with_rule, functions) @@ -324,13 +331,18 @@ def enforce_ex(self, *rvals): else: policy_effects.add(Effector.ALLOW) - if self.eft.intermediate_effect(policy_effects) != Effector.INDETERMINATE: + if ( + self.eft.intermediate_effect(policy_effects) + != Effector.INDETERMINATE + ): explain_index = i break else: if has_eval: - raise RuntimeError("please make sure rule exists in policy when using eval() in matcher") + raise RuntimeError( + "please make sure rule exists in policy when using eval() in matcher" + ) parameters = r_parameters.copy() diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 0e749ff5..4f6a9547 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -18,7 +18,7 @@ def add_policy_self(self, should_persist, sec, ptype, rules): AddPolicySelf provides a method for dispatcher to add authorization rules to the current policy. The function returns the rules affected and error. """ - + no_exists_policy = [] for rule in rules: if not self.get_model().has_policy(sec, ptype, rule): @@ -35,7 +35,9 @@ def add_policy_self(self, should_persist, sec, ptype, rules): if sec == "g": try: - self.build_incremental_role_links(PolicyOp.Policy_add, ptype, no_exists_policy) + self.build_incremental_role_links( + PolicyOp.Policy_add, ptype, no_exists_policy + ) except Exception as e: self.logger.log("An exception occurred: " + e) return no_exists_policy @@ -47,9 +49,9 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): remove_policy_self provides a method for dispatcher to remove policies from current policy. The function returns the rules affected and error. """ - if(should_persist): + if should_persist: try: - if(isinstance(self.adapter, batch_adapter)): + if isinstance(self.adapter, batch_adapter): self.adapter.remove_policy(sec, ptype, rules) except Exception as e: self.logger.log("An exception occurred: " + e) @@ -65,7 +67,9 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): return effected - def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, *field_values): + def remove_filtered_policy_self( + self, should_persist, sec, ptype, field_index, *field_values + ): """ remove_filtered_policy_self provides a method for dispatcher to remove an authorization rule from the current policy,field filters can be specified. @@ -73,15 +77,21 @@ def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, * """ if should_persist: try: - self.adapter.remove_filtered_policy(sec, ptype, field_index, field_values) + self.adapter.remove_filtered_policy( + sec, ptype, field_index, field_values + ) except Exception as e: self.logger.log("An exception occurred: " + e) - effects = self.get_model().remove_filtered_policy_returns_effects(sec, ptype, field_index, field_values) + effects = self.get_model().remove_filtered_policy_returns_effects( + sec, ptype, field_index, field_values + ) if sec == "g": try: - self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, effects) + self.build_incremental_role_links( + PolicyOp.Policy_remove, ptype, effects + ) except Exception as e: self.logger.log("An exception occurred: " + e) return effects @@ -119,14 +129,17 @@ def update_policy_self(self, should_persist, sec, ptype, old_rule, new_rule): if sec == "g": try: - self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, [old_rule]) + self.build_incremental_role_links( + PolicyOp.Policy_remove, ptype, [old_rule] + ) except Exception as e: return False try: - self.build_incremental_role_links(PolicyOp.Policy_add, ptype, [new_rule]) + self.build_incremental_role_links( + PolicyOp.Policy_add, ptype, [new_rule] + ) except Exception as e: return False - - return True \ No newline at end of file + return True diff --git a/casbin/effect/__init__.py b/casbin/effect/__init__.py index 51634371..aab74db3 100644 --- a/casbin/effect/__init__.py +++ b/casbin/effect/__init__.py @@ -1,11 +1,17 @@ -from .default_effectors import AllowOverrideEffector, DenyOverrideEffector, AllowAndDenyEffector, PriorityEffector +from .default_effectors import ( + AllowOverrideEffector, + DenyOverrideEffector, + AllowAndDenyEffector, + PriorityEffector, +) from .effector import Effector + def get_effector(expr): - ''' creates an effector based on the current policy effect expression ''' + """creates an effector based on the current policy effect expression""" if expr == "some(where (p_eft == allow))": - return AllowOverrideEffector() + return AllowOverrideEffector() elif expr == "!some(where (p_eft == deny))": return DenyOverrideEffector() elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": @@ -15,10 +21,11 @@ def get_effector(expr): else: raise RuntimeError("unsupported effect") + def effect_to_bool(effect): - """ """ - if effect == Effector.ALLOW: - return True - if effect == Effector.DENY: - return False - raise RuntimeError("effect can't be converted to boolean") \ No newline at end of file + """ """ + if effect == Effector.ALLOW: + return True + if effect == Effector.DENY: + return False + raise RuntimeError("effect can't be converted to boolean") diff --git a/casbin/effect/default_effectors.py b/casbin/effect/default_effectors.py index 77d65ebb..9e00ee41 100644 --- a/casbin/effect/default_effectors.py +++ b/casbin/effect/default_effectors.py @@ -1,51 +1,51 @@ from .effector import Effector -class AllowOverrideEffector(Effector): +class AllowOverrideEffector(Effector): def intermediate_effect(self, effects): - """ returns a intermediate effect based on the matched effects of the enforcer """ + """returns a intermediate effect based on the matched effects of the enforcer""" if Effector.ALLOW in effects: return Effector.ALLOW return Effector.INDETERMINATE def final_effect(self, effects): - """ returns the final effect based on the matched effects of the enforcer """ + """returns the final effect based on the matched effects of the enforcer""" if Effector.ALLOW in effects: return Effector.ALLOW return Effector.DENY -class DenyOverrideEffector(Effector): +class DenyOverrideEffector(Effector): def intermediate_effect(self, effects): - """ returns a intermediate effect based on the matched effects of the enforcer """ + """returns a intermediate effect based on the matched effects of the enforcer""" if Effector.DENY in effects: return Effector.DENY return Effector.INDETERMINATE def final_effect(self, effects): - """ returns the final effect based on the matched effects of the enforcer """ + """returns the final effect based on the matched effects of the enforcer""" if Effector.DENY in effects: return Effector.DENY return Effector.ALLOW -class AllowAndDenyEffector(Effector): +class AllowAndDenyEffector(Effector): def intermediate_effect(self, effects): - """ returns a intermediate effect based on the matched effects of the enforcer """ + """returns a intermediate effect based on the matched effects of the enforcer""" if Effector.DENY in effects: return Effector.DENY return Effector.INDETERMINATE def final_effect(self, effects): - """ returns the final effect based on the matched effects of the enforcer """ + """returns the final effect based on the matched effects of the enforcer""" if Effector.DENY in effects or Effector.ALLOW not in effects: return Effector.DENY return Effector.ALLOW -class PriorityEffector(Effector): +class PriorityEffector(Effector): def intermediate_effect(self, effects): - """ returns a intermediate effect based on the matched effects of the enforcer """ + """returns a intermediate effect based on the matched effects of the enforcer""" if Effector.ALLOW in effects: return Effector.ALLOW if Effector.DENY in effects: @@ -53,7 +53,7 @@ def intermediate_effect(self, effects): return Effector.INDETERMINATE def final_effect(self, effects): - """ returns the final effect based on the matched effects of the enforcer """ + """returns the final effect based on the matched effects of the enforcer""" if Effector.ALLOW in effects: return Effector.ALLOW if Effector.DENY in effects: diff --git a/casbin/effect/effector.py b/casbin/effect/effector.py index 13b08885..45106db4 100644 --- a/casbin/effect/effector.py +++ b/casbin/effect/effector.py @@ -8,12 +8,9 @@ class Effector: DENY = 2 def intermediate_effect(self, effects): - """ returns a intermediate effect based on the matched effects of the enforcer """ + """returns a intermediate effect based on the matched effects of the enforcer""" pass def final_effect(self, effects): - """ returns the final effect based on the matched effects of the enforcer """ + """returns the final effect based on the matched effects of the enforcer""" pass - - - diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 554262e7..8f5d71c8 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,9 +1,10 @@ from casbin.management_enforcer import ManagementEnforcer from casbin.util import join_slice, set_subtract + class Enforcer(ManagementEnforcer): """ - Enforcer = ManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API + Enforcer = ManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API """ """creates an enforcer via file or DB. @@ -16,15 +17,15 @@ class Enforcer(ManagementEnforcer): """ def get_roles_for_user(self, name): - """ gets the roles that a user has. """ + """gets the roles that a user has.""" return self.model.model["g"]["g"].rm.get_roles(name) def get_users_for_role(self, name): - """ gets the users that has a role. """ + """gets the users that has a role.""" return self.model.model["g"]["g"].rm.get_users(name) def has_role_for_user(self, name, role): - """ determines whether a user has a role. """ + """determines whether a user has a role.""" roles = self.get_roles_for_user(name) return any(r == role for r in roles) @@ -190,11 +191,11 @@ def get_implicit_users_for_permission(self, *permission): def get_roles_for_user_in_domain(self, name, domain): """gets the roles that a user has inside a domain.""" - return self.model.model['g']['g'].rm.get_roles(name, domain) + return self.model.model["g"]["g"].rm.get_roles(name, domain) def get_users_for_role_in_domain(self, name, domain): """gets the users that has a role inside a domain.""" - return self.model.model['g']['g'].rm.get_users(name, domain) + return self.model.model["g"]["g"].rm.get_users(name, domain) def add_role_for_user_in_domain(self, user, role, domain): """adds a role for a user inside a domain.""" diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 60138c10..cac136f3 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -1,9 +1,10 @@ from casbin.core_enforcer import CoreEnforcer from casbin.model.policy_op import PolicyOp + class InternalEnforcer(CoreEnforcer): """ - InternalEnforcer = CoreEnforcer + Internal API. + InternalEnforcer = CoreEnforcer + Internal API. """ def _add_policy(self, sec, ptype, rule): @@ -20,17 +21,17 @@ def _add_policy(self, sec, ptype, rule): self.watcher.update() return rule_added - - def _add_policies(self,sec,ptype,rules): + + def _add_policies(self, sec, ptype, rules): """adds rules to the current policy.""" rules_added = self.model.add_policies(sec, ptype, rules) if not rules_added: return rules_added if self.adapter and self.auto_save: - if hasattr(self.adapter,'add_policies') is False: + if hasattr(self.adapter, "add_policies") is False: return False - + if self.adapter.add_policies(sec, ptype, rules) is False: return False @@ -72,7 +73,7 @@ def _update_policies(self, sec, ptype, old_rules, new_rules): self.watcher.update() return rules_updated - + def _remove_policy(self, sec, ptype, rule): """removes a rule from the current policy.""" rule_removed = self.model.remove_policy(sec, ptype, rule) @@ -95,7 +96,7 @@ def _remove_policies(self, sec, ptype, rules): return rules_removed if self.adapter and self.auto_save: - if hasattr(self.adapter,'remove_policies') is False: + if hasattr(self.adapter, "remove_policies") is False: return False if self.adapter.remove_policies(sec, ptype, rules) is False: @@ -108,15 +109,22 @@ def _remove_policies(self, sec, ptype, rules): def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes rules based on field filters from the current policy.""" - rule_removed = self.model.remove_filtered_policy(sec, ptype, field_index, *field_values) + rule_removed = self.model.remove_filtered_policy( + sec, ptype, field_index, *field_values + ) if not rule_removed: return rule_removed if self.adapter and self.auto_save: - if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: + if ( + self.adapter.remove_filtered_policy( + sec, ptype, field_index, *field_values + ) + is False + ): return False if self.watcher: self.watcher.update() - return rule_removed \ No newline at end of file + return rule_removed diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index fcb4f2e7..2ef80337 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -1,61 +1,62 @@ from casbin.internal_enforcer import InternalEnforcer + class ManagementEnforcer(InternalEnforcer): """ - ManagementEnforcer = InternalEnforcer + Management API. + ManagementEnforcer = InternalEnforcer + Management API. """ def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" - return self.get_all_named_subjects('p') + return self.get_all_named_subjects("p") def get_all_named_subjects(self, ptype): """gets the list of subjects that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy('p', ptype, 0) + return self.model.get_values_for_field_in_policy("p", ptype, 0) def get_all_objects(self): """gets the list of objects that show up in the current policy.""" - return self.get_all_named_objects('p') + return self.get_all_named_objects("p") def get_all_named_objects(self, ptype): """gets the list of objects that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy('p', ptype, 1) + return self.model.get_values_for_field_in_policy("p", ptype, 1) def get_all_actions(self): """gets the list of actions that show up in the current policy.""" - return self.get_all_named_actions('p') + return self.get_all_named_actions("p") def get_all_named_actions(self, ptype): """gets the list of actions that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy('p', ptype, 2) + return self.model.get_values_for_field_in_policy("p", ptype, 2) def get_all_roles(self): """gets the list of roles that show up in the current named policy.""" - return self.get_all_named_roles('g') + return self.get_all_named_roles("g") def get_all_named_roles(self, ptype): """gets all the authorization rules in the policy.""" - return self.model.get_values_for_field_in_policy('g', ptype, 1) + return self.model.get_values_for_field_in_policy("g", ptype, 1) def get_policy(self): """gets all the authorization rules in the policy.""" - return self.get_named_policy('p') + return self.get_named_policy("p") def get_filtered_policy(self, field_index, *field_values): """gets all the authorization rules in the policy, field filters can be specified.""" - return self.get_filtered_named_policy('p', field_index, *field_values) + return self.get_filtered_named_policy("p", field_index, *field_values) def get_named_policy(self, ptype): """gets all the authorization rules in the named policy.""" - return self.model.get_policy('p', ptype) + return self.model.get_policy("p", ptype) def get_filtered_named_policy(self, ptype, field_index, *field_values): """gets all the authorization rules in the named policy, field filters can be specified.""" - return self.model.get_filtered_policy('p', ptype, field_index, *field_values) + return self.model.get_filtered_policy("p", ptype, field_index, *field_values) def get_grouping_policy(self): """gets all the role inheritance rules in the policy.""" - return self.get_named_grouping_policy('g') + return self.get_named_grouping_policy("g") def get_filtered_grouping_policy(self, field_index, *field_values): """gets all the role inheritance rules in the policy, field filters can be specified.""" @@ -63,23 +64,23 @@ def get_filtered_grouping_policy(self, field_index, *field_values): def get_named_grouping_policy(self, ptype): """gets all the role inheritance rules in the policy.""" - return self.model.get_policy('g', ptype) + return self.model.get_policy("g", ptype) def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """gets all the role inheritance rules in the policy, field filters can be specified.""" - return self.model.get_filtered_policy('g', ptype, field_index, *field_values) + return self.model.get_filtered_policy("g", ptype, field_index, *field_values) def has_policy(self, *params): """determines whether an authorization rule exists.""" - return self.has_named_policy('p', *params) + return self.has_named_policy("p", *params) def has_named_policy(self, ptype, *params): """determines whether a named authorization rule exists.""" if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - return self.model.has_policy('p', ptype, str_slice) + return self.model.has_policy("p", ptype, str_slice) - return self.model.has_policy('p', ptype, list(params)) + return self.model.has_policy("p", ptype, list(params)) def add_policy(self, *params): """adds an authorization rule to the current policy. @@ -87,15 +88,15 @@ def add_policy(self, *params): 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. """ - return self.add_named_policy('p', *params) - - def add_policies(self,rules): + return self.add_named_policy("p", *params) + + def add_policies(self, rules): """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. """ - return self.add_named_policies('p',rules) + return self.add_named_policies("p", rules) def add_named_policy(self, ptype, *params): """adds an authorization rule to the current named policy. @@ -106,79 +107,79 @@ def add_named_policy(self, ptype, *params): if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - rule_added = self._add_policy('p', ptype, str_slice) + rule_added = self._add_policy("p", ptype, str_slice) else: - rule_added = self._add_policy('p', ptype, list(params)) + rule_added = self._add_policy("p", ptype, list(params)) return rule_added - def add_named_policies(self,ptype,rules): + def add_named_policies(self, ptype, rules): """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.""" - return self._add_policies('p',ptype,rules) + return self._add_policies("p", ptype, rules) def update_policy(self, old_rule, new_rule): """updates an authorization rule from the current policy.""" - return self.update_named_policy('p', old_rule, new_rule) + return self.update_named_policy("p", old_rule, new_rule) def update_policies(self, old_rules, new_rules): """updates authorization rules from the current policy.""" - return self.update_named_policies('p', old_rules, new_rules) + return self.update_named_policies("p", old_rules, new_rules) def update_named_policy(self, ptype, old_rule, new_rule): """updates an authorization rule from the current named policy.""" - return self._update_policy('p', ptype, old_rule, new_rule) + return self._update_policy("p", ptype, old_rule, new_rule) def update_named_policies(self, ptype, old_rules, new_rules): """updates authorization rules from the current named policy.""" - return self._update_policies('p', ptype, old_rules, new_rules) + return self._update_policies("p", ptype, old_rules, new_rules) def remove_policy(self, *params): """removes an authorization rule from the current policy.""" - return self.remove_named_policy('p', *params) + return self.remove_named_policy("p", *params) - def remove_policies(self,rules): + def remove_policies(self, rules): """removes authorization rules from the current policy.""" - return self.remove_named_policies('p',rules) + return self.remove_named_policies("p", rules) def remove_filtered_policy(self, field_index, *field_values): """removes an authorization rule from the current policy, field filters can be specified.""" - return self.remove_filtered_named_policy('p', field_index, *field_values) + return self.remove_filtered_named_policy("p", field_index, *field_values) def remove_named_policy(self, ptype, *params): """removes an authorization rule from the current named policy.""" if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - rule_removed = self._remove_policy('p', ptype, str_slice) + rule_removed = self._remove_policy("p", ptype, str_slice) else: - rule_removed = self._remove_policy('p', ptype, list(params)) + rule_removed = self._remove_policy("p", ptype, list(params)) return rule_removed - def remove_named_policies(self,ptype,rules): + def remove_named_policies(self, ptype, rules): """removes authorization rules from the current named policy.""" - return self._remove_policies('p',ptype,rules) + return self._remove_policies("p", ptype, rules) def remove_filtered_named_policy(self, ptype, field_index, *field_values): """removes an authorization rule from the current named policy, field filters can be specified.""" - return self._remove_filtered_policy('p', ptype, field_index, *field_values) + return self._remove_filtered_policy("p", ptype, field_index, *field_values) def has_grouping_policy(self, *params): """determines whether a role inheritance rule exists.""" - return self.has_named_grouping_policy('g', *params) + return self.has_named_grouping_policy("g", *params) def has_named_grouping_policy(self, ptype, *params): """determines whether a named role inheritance rule exists.""" if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - return self.model.has_policy('g', ptype, str_slice) + return self.model.has_policy("g", ptype, str_slice) - return self.model.has_policy('g', ptype, list(params)) + return self.model.has_policy("g", ptype, list(params)) def add_grouping_policy(self, *params): """adds a role inheritance rule to the current policy. @@ -186,15 +187,15 @@ def add_grouping_policy(self, *params): 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. """ - return self.add_named_grouping_policy('g', *params) + return self.add_named_grouping_policy("g", *params) - def add_grouping_policies(self,rules): + def add_grouping_policies(self, rules): """adds role inheritance rulea to the current policy. - + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule. """ - return self.add_named_grouping_policies('g',rules) + return self.add_named_grouping_policies("g", rules) def add_named_grouping_policy(self, ptype, *params): """adds a named role inheritance rule to the current policy. @@ -205,62 +206,66 @@ def add_named_grouping_policy(self, ptype, *params): if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - rule_added = self._add_policy('g', ptype, str_slice) + rule_added = self._add_policy("g", ptype, str_slice) else: - rule_added = self._add_policy('g', ptype, list(params)) + rule_added = self._add_policy("g", ptype, list(params)) if self.auto_build_role_links: self.build_role_links() return rule_added - def add_named_grouping_policies(self,ptype,rules): - """"adds named role inheritance rules to the current policy. - + def add_named_grouping_policies(self, ptype, rules): + """ "adds named role inheritance rules to the current policy. + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" - rules_added = self._add_policies('g',ptype,rules) + rules_added = self._add_policies("g", ptype, rules) if self.auto_build_role_links: self.build_role_links() - + return rules_added def remove_grouping_policy(self, *params): """removes a role inheritance rule from the current policy.""" - return self.remove_named_grouping_policy('g', *params) + return self.remove_named_grouping_policy("g", *params) - def remove_grouping_policies(self,rules): + def remove_grouping_policies(self, rules): """removes role inheritance rulea from the current policy.""" - return self.remove_named_grouping_policies('g',rules) + return self.remove_named_grouping_policies("g", rules) def remove_filtered_grouping_policy(self, field_index, *field_values): """removes a role inheritance rule from the current policy, field filters can be specified.""" - return self.remove_filtered_named_grouping_policy('g', field_index, *field_values) + return self.remove_filtered_named_grouping_policy( + "g", field_index, *field_values + ) def remove_named_grouping_policy(self, ptype, *params): """removes a role inheritance rule from the current named policy.""" if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] - rule_removed = self._remove_policy('g', ptype, str_slice) + rule_removed = self._remove_policy("g", ptype, str_slice) else: - rule_removed = self._remove_policy('g', ptype, list(params)) + rule_removed = self._remove_policy("g", ptype, list(params)) if self.auto_build_role_links: self.build_role_links() return rule_removed - - def remove_named_grouping_policies(self,ptype,rules): - """ removes role inheritance rules from the current named policy.""" - rules_removed = self._remove_policies('g',ptype,rules) + + def remove_named_grouping_policies(self, ptype, rules): + """removes role inheritance rules from the current named policy.""" + rules_removed = self._remove_policies("g", ptype, rules) if self.auto_build_role_links: self.build_role_links() - + return rules_removed def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" - rule_removed = self._remove_filtered_policy('g', ptype, field_index, *field_values) + rule_removed = self._remove_filtered_policy( + "g", ptype, field_index, *field_values + ) if self.auto_build_role_links: self.build_role_links() @@ -268,4 +273,4 @@ def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_value def add_function(self, name, func): """adds a customized function.""" - self.fm.add_function(name, func) \ No newline at end of file + self.fm.add_function(name, func) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 4283c1d4..b4c77778 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -15,11 +15,15 @@ def build_role_links(self, rm): self.rm = rm count = self.value.count("_") if count < 2: - raise RuntimeError('the number of "_" in role definition should be at least 2') + raise RuntimeError( + 'the number of "_" in role definition should be at least 2' + ) for rule in self.policy: if len(rule) < count: - raise RuntimeError("grouping policy elements do not meet role definition") + raise RuntimeError( + "grouping policy elements do not meet role definition" + ) if len(rule) > count: rule = rule[:count] @@ -32,7 +36,9 @@ def build_incremental_role_links(self, rm, op, rules): self.rm = rm count = self.value.count("_") if count < 2: - raise RuntimeError('the number of "_" in role definition should be at least 2') + raise RuntimeError( + 'the number of "_" in role definition should be at least 2' + ) for rule in rules: if len(rule) < count: raise TypeError("grouping policy elements do not meet role definition") diff --git a/casbin/model/model.py b/casbin/model/model.py index cd998842..8047f686 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -2,14 +2,15 @@ from casbin import util, config from .policy import Policy + class Model(Policy): section_name_map = { - 'r': 'request_definition', - 'p': 'policy_definition', - 'g': 'role_definition', - 'e': 'policy_effect', - 'm': 'matchers', + "r": "request_definition", + "p": "policy_definition", + "g": "role_definition", + "e": "policy_effect", + "m": "matchers", } def _load_assertion(self, cfg, sec, key): @@ -27,7 +28,7 @@ def add_def(self, sec, key, value): if "r" == sec or "p" == sec: ast.tokens = ast.value.split(",") - for i,token in enumerate(ast.tokens): + for i, token in enumerate(ast.tokens): ast.tokens[i] = key + "_" + token.strip() else: ast.value = util.remove_comments(util.escape_assertion(ast.value)) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 2f1931c3..d447dd4f 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,5 +1,6 @@ import logging + class Policy: def __init__(self): self.logger = logging.getLogger(__name__) @@ -48,8 +49,12 @@ def get_policy(self, sec, ptype): def get_filtered_policy(self, sec, ptype, field_index, *field_values): """gets rules based on field filters from a policy.""" return [ - rule for rule in self.model[sec][ptype].policy - if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)) + rule + for rule in self.model[sec][ptype].policy + if all( + value == "" or rule[field_index + i] == value + for i, value in enumerate(field_values) + ) ] def has_policy(self, sec, ptype, rule): @@ -70,11 +75,11 @@ def add_policy(self, sec, ptype, rule): return False - def add_policies(self,sec,ptype,rules): + def add_policies(self, sec, ptype, rules): """adds policy rules to the model.""" for rule in rules: - if self.has_policy(sec,ptype,rule): + if self.has_policy(sec, ptype, rule): return False for rule in rules: @@ -133,9 +138,11 @@ def update_policies(self, sec, ptype, old_rules, new_rules): if old_rule[priority_index] == new_rule[priority_index]: ast.policy[idx] = new_rule else: - raise Exception("New rule should have the same priority with old rule.") + raise Exception( + "New rule should have the same priority with old rule." + ) else: - for idx, old_rule, new_rule in zip(old_rules_index ,old_rules, new_rules): + for idx, old_rule, new_rule in zip(old_rules_index, old_rules, new_rules): ast.policy[idx] = new_rule return True @@ -153,7 +160,7 @@ def remove_policies(self, sec, ptype, rules): """RemovePolicies removes policy rules from the model.""" for rule in rules: - if not self.has_policy(sec,ptype,rule): + if not self.has_policy(sec, ptype, rule): return False self.model[sec][ptype].policy.remove(rule) if rule in self.model[sec][ptype].policy: @@ -170,14 +177,16 @@ def remove_policies_with_effected(self, sec, ptype, rules): return effected - def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field_values): + def remove_filtered_policy_returns_effects( + self, sec, ptype, field_index, *field_values + ): """ remove_filtered_policy_returns_effects removes policy rules based on field filters from the model. """ tmp = [] effects = [] - if(len(field_values) == 0): + if len(field_values) == 0: return [] if sec not in self.model.keys(): return [] @@ -185,7 +194,10 @@ def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field return [] for rule in self.model[sec][ptype].policy: - if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values[0])): + if all( + value == "" or rule[field_index + i] == value + for i, value in enumerate(field_values[0]) + ): effects.append(rule) else: tmp.append(rule) @@ -194,7 +206,6 @@ def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field return effects - def remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes policy rules based on field filters from the model.""" tmp = [] @@ -206,7 +217,10 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): return res for rule in self.model[sec][ptype].policy: - if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)): + if all( + value == "" or rule[field_index + i] == value + for i, value in enumerate(field_values) + ): res = True else: tmp.append(rule) diff --git a/casbin/model/policy_op.py b/casbin/model/policy_op.py index de4b146e..7ef171da 100644 --- a/casbin/model/policy_op.py +++ b/casbin/model/policy_op.py @@ -1,5 +1,6 @@ import enum + class PolicyOp(enum.Enum): Policy_add = 1 - Policy_remove = 2 \ No newline at end of file + Policy_remove = 2 diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index 0c22ec89..0c04a1c4 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -1,4 +1,4 @@ from .adapter import * from .adapter_filtered import * from .batch_adapter import * -from .adapters import * \ No newline at end of file +from .adapters import * diff --git a/casbin/persist/adapter_filtered.py b/casbin/persist/adapter_filtered.py index fbb4fefc..5cd1038c 100644 --- a/casbin/persist/adapter_filtered.py +++ b/casbin/persist/adapter_filtered.py @@ -1,13 +1,15 @@ from .adapter import Adapter """ FilteredAdapter is the interface for Casbin adapters supporting filtered policies.""" + + class FilteredAdapter(Adapter): def is_filtered(self): """IsFiltered returns true if the loaded policy has been filtered Marks if the loaded policy is filtered or not """ pass - + def load_filtered_policy(self, model, filter): """Loads policy rules that match the filter from the storage.""" - pass \ No newline at end of file + pass diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index f1b9da13..414ab547 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -1,2 +1,2 @@ from .file_adapter import FileAdapter -from .adapter_filtered import FilteredAdapter \ No newline at end of file +from .adapter_filtered import FilteredAdapter diff --git a/casbin/persist/adapters/adapter_filtered.py b/casbin/persist/adapters/adapter_filtered.py index 08e500f5..b37a0da0 100644 --- a/casbin/persist/adapters/adapter_filtered.py +++ b/casbin/persist/adapters/adapter_filtered.py @@ -3,87 +3,92 @@ from .file_adapter import FileAdapter import os + class Filter: - #P,G are string [] + # P,G are string [] P = [] G = [] -class FilteredAdapter (FileAdapter,persist.FilteredAdapter): + +class FilteredAdapter(FileAdapter, persist.FilteredAdapter): filtered = False _file_path = "" filter = Filter() - #new_filtered_adapte is the constructor for FilteredAdapter. - def __init__(self,file_path): + # new_filtered_adapte is the constructor for FilteredAdapter. + def __init__(self, file_path): self.filtered = True self._file_path = file_path - - def load_policy(self,model): + + def load_policy(self, model): if not os.path.isfile(self._file_path): raise RuntimeError("invalid file path, file path cannot be empty") - self.filtered=False + self.filtered = False self._load_policy_file(model) - #load_filtered_policy loads only policy rules that match the filter. - def load_filtered_policy(self,model,filter): + # load_filtered_policy loads only policy rules that match the filter. + def load_filtered_policy(self, model, filter): if filter == None: return self.load_policy(model) - + if not os.path.isfile(self._file_path): raise RuntimeError("invalid file path, file path cannot be empty") try: - filter_value = [filter.__dict__['P']]+[filter.__dict__['G']] + filter_value = [filter.__dict__["P"]] + [filter.__dict__["G"]] except: raise RuntimeError("invalid filter type") - self.load_filtered_policy_file(model,filter_value,persist.load_policy_line) + self.load_filtered_policy_file(model, filter_value, persist.load_policy_line) self.filtered = True - def load_filtered_policy_file(self,model,filter,hanlder): + def load_filtered_policy_file(self, model, filter, hanlder): with open(self._file_path, "rb") as file: while True: line = file.readline() line = line.decode().strip() - if line == '\n': + if line == "\n": continue - if not line : + if not line: break - if filter_line(line,filter): + if filter_line(line, filter): continue - - hanlder(line,model) - #is_filtered returns true if the loaded policy has been filtered. + hanlder(line, model) + + # is_filtered returns true if the loaded policy has been filtered. def is_filtered(self): return self.filtered - def save_policy(self,model): + + def save_policy(self, model): if self.filtered: raise RuntimeError("cannot save a filtered policy") self._save_policy_file(model) -def filter_line(line,filter): + +def filter_line(line, filter): if filter == None: return False - - p = line.split(',') + + p = line.split(",") if len(p) == 0: return True filter_slice = [] - if p[0].strip()== 'p': + if p[0].strip() == "p": filter_slice = filter[0] - elif p[0].strip() == 'g': + elif p[0].strip() == "g": filter_slice = filter[1] - return filter_words(p,filter_slice) + return filter_words(p, filter_slice) + -def filter_words(line,filter): - if len(line) < len(filter)+1: +def filter_words(line, filter): + if len(line) < len(filter) + 1: return True - skip_line=False - for i,v in enumerate(filter): - if(len(v) >0 and ( v.strip() != line[i+1].strip() ) ): + skip_line = False + for i, v in enumerate(filter): + if len(v) > 0 and (v.strip() != line[i + 1].strip()): skip_line = True break - return skip_line \ No newline at end of file + return skip_line diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index 7f2e86f6..8dc8d667 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -1,10 +1,12 @@ from casbin import persist import os + class FileAdapter(persist.Adapter): """the file adapter for Casbin. It can load policy from file or save policy to file. """ + _file_path = "" def __init__(self, file_path): @@ -51,12 +53,12 @@ def _save_policy_file(self, model): def add_policy(self, sec, ptype, rule): pass - - def add_policies(self,sec,ptype,rules): + + def add_policies(self, sec, ptype, rules): pass def remove_policy(self, sec, ptype, rule): pass - def remove_policies(self,sec,ptype,rules): - pass \ No newline at end of file + def remove_policies(self, sec, ptype, rules): + pass diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index 11d4f3fe..a6bea8d9 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -1,9 +1,9 @@ class UpdateAdapter: - """ UpdateAdapter is the interface for Casbin adapters with add update policy function. """ + """UpdateAdapter is the interface for Casbin adapters with add update policy function.""" def update_policy(self, sec, ptype, old_rule, new_policy): """ update_policy updates a policy rule from storage. This is part of the Auto-Save feature. """ - pass \ No newline at end of file + pass diff --git a/casbin/persist/batch_adapter.py b/casbin/persist/batch_adapter.py index 6dbf88da..8ccec0f3 100644 --- a/casbin/persist/batch_adapter.py +++ b/casbin/persist/batch_adapter.py @@ -1,11 +1,13 @@ from .adapter import Adapter """BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.""" + + class BatchAdapter(Adapter): - def add_policies(self,sec,ptype,rules): + def add_policies(self, sec, ptype, rules): """AddPolicies adds policy rules to the storage.""" pass - def remove_policies(self,sec,ptype,rules): + def remove_policies(self, sec, ptype, rules): """RemovePolicies removes policy rules from the storage.""" - pass \ No newline at end of file + pass diff --git a/casbin/persist/dispatcher.py b/casbin/persist/dispatcher.py index b499cde1..6ea589c1 100644 --- a/casbin/persist/dispatcher.py +++ b/casbin/persist/dispatcher.py @@ -1,5 +1,6 @@ class Dispatcher: """Dispatcher is the interface for pycasbin dispatcher""" + def add_policies(self, sec, ptype, rules): """add_policies adds policies rule to all instance.""" pass diff --git a/casbin/rbac/default_role_manager/__init__.py b/casbin/rbac/default_role_manager/__init__.py index 4ff0284e..cd3c1521 100644 --- a/casbin/rbac/default_role_manager/__init__.py +++ b/casbin/rbac/default_role_manager/__init__.py @@ -1 +1 @@ -from .role_manager import RoleManager \ No newline at end of file +from .role_manager import RoleManager diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 16bf0a5f..926b5222 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -98,8 +98,9 @@ def has_link(self, name1, name2, *domain): return role1.has_role(name2, self.max_hierarchy_level) else: for key, role in self.all_roles.items(): - if self.matching_func(name1, key) and role.has_role(name2, self.max_hierarchy_level, - self.matching_func): + if self.matching_func(name1, key) and role.has_role( + name2, self.max_hierarchy_level, self.matching_func + ): return True return False @@ -117,7 +118,7 @@ def get_roles(self, name, domain=None): roles = self.create_role(name).get_roles() if domain: for key, value in enumerate(roles): - roles[key] = value[len(domain) + 2:] + roles[key] = value[len(domain) + 2 :] return roles @@ -138,7 +139,7 @@ def get_users(self, name, *domain): for role in self.all_roles.values(): if role.has_direct_role(name): if len(domain) == 1: - names.append(role.name[len(domain[0]) + 2:]) + names.append(role.name[len(domain[0]) + 2 :]) else: names.append(role.name) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 5f408766..339f44b1 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -5,8 +5,7 @@ from casbin.util.rwlock import RWLockWrite -class AtomicBool(): - +class AtomicBool: def __init__(self, value): self._lock = threading.Lock() self._value = value @@ -15,15 +14,16 @@ def __init__(self, value): def value(self): with self._lock: return self._value - + @value.setter def value(self, value): with self._lock: self._value = value -class SyncedEnforcer(): - """SyncedEnforcer wraps Enforcer and provides synchronized access. +class SyncedEnforcer: + + """SyncedEnforcer wraps Enforcer and provides synchronized access. It's also a drop-in replacement for Enforcer""" def __init__(self, model=None, adapter=None): @@ -39,18 +39,20 @@ def is_auto_loading_running(self): return self._auto_loading.value def _auto_load_policy(self, interval): - while self.is_auto_loading_running(): - time.sleep(interval) - self.load_policy() + while self.is_auto_loading_running(): + time.sleep(interval) + self.load_policy() def start_auto_load_policy(self, interval): """starts a thread that will call load_policy every interval seconds""" if self.is_auto_loading_running(): return self._auto_loading.value = True - self._auto_loading_thread = threading.Thread(target=self._auto_load_policy, args=[interval], daemon=True) + self._auto_loading_thread = threading.Thread( + target=self._auto_load_policy, args=[interval], daemon=True + ) self._auto_loading_thread.start() - + def stop_auto_load_policy(self): """stops the thread started by start_auto_load_policy""" if self.is_auto_loading_running(): @@ -103,7 +105,7 @@ def set_effector(self, eft): self._e.set_effector(eft) def clear_policy(self): - """ clears all policy.""" + """clears all policy.""" with self._wl: return self._e.clear_policy() @@ -113,7 +115,7 @@ def load_policy(self): return self._e.load_policy() def load_filtered_policy(self, filter): - """"reloads a filtered policy from file/database.""" + """ "reloads a filtered policy from file/database.""" with self._wl: return self._e.load_filtered_policy(filter) @@ -145,7 +147,7 @@ def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" with self._rl: return self._e.get_all_subjects() - + def get_all_named_subjects(self, ptype): """gets the list of subjects that show up in the current named policy.""" with self._rl: @@ -219,7 +221,9 @@ def get_named_grouping_policy(self, ptype): def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """gets all the role inheritance rules in the policy, field filters can be specified.""" with self._rl: - return self._e.get_filtered_named_grouping_policy(ptype, field_index, *field_values) + return self._e.get_filtered_named_grouping_policy( + ptype, field_index, *field_values + ) def has_policy(self, *params): """determines whether an authorization rule exists.""" @@ -265,7 +269,9 @@ def remove_named_policy(self, ptype, *params): def remove_filtered_named_policy(self, ptype, field_index, *field_values): """removes an authorization rule from the current named policy, field filters can be specified.""" with self._wl: - return self._e.remove_filtered_named_policy(ptype, field_index, *field_values) + return self._e.remove_filtered_named_policy( + ptype, field_index, *field_values + ) def has_grouping_policy(self, *params): """determines whether a role inheritance rule exists.""" @@ -311,7 +317,9 @@ def remove_named_grouping_policy(self, ptype, *params): def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" with self._wl: - return self._e.remove_filtered_named_grouping_policy(ptype, field_index, *field_values) + return self._e.remove_filtered_named_grouping_policy( + ptype, field_index, *field_values + ) def add_function(self, name, func): """adds a customized function.""" @@ -321,17 +329,17 @@ def add_function(self, name, func): # enforcer.py def get_roles_for_user(self, name): - """ gets the roles that a user has. """ + """gets the roles that a user has.""" with self._rl: return self._e.get_roles_for_user(name) def get_users_for_role(self, name): - """ gets the users that has a role. """ + """gets the users that has a role.""" with self._rl: return self._e.get_users_for_role(name) def has_role_for_user(self, name, role): - """ determines whether a user has a role. """ + """determines whether a user has a role.""" with self._rl: return self._e.has_role_for_user(name, role) @@ -523,59 +531,61 @@ def is_filtered(self): with self._rl: self._e.is_filtered() - def add_policies(self,rules): + def add_policies(self, rules): """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. """ with self._wl: return self._e.add_policies(rules) - def add_named_policies(self,ptype,rules): + def add_named_policies(self, ptype, rules): """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.""" with self._wl: - return self._e.add_named_policies(ptype,rules) + return self._e.add_named_policies(ptype, rules) - def remove_policies(self,rules): + def remove_policies(self, rules): """removes authorization rules from the current policy.""" with self._wl: return self._e.remove_policies(rules) - def remove_named_policies(self,ptype,rules): + def remove_named_policies(self, ptype, rules): """removes authorization rules from the current named policy.""" with self._wl: - return self._e.remove_named_policies(ptype,rules) + return self._e.remove_named_policies(ptype, rules) - def add_grouping_policies(self,rules): + def add_grouping_policies(self, rules): """adds role inheritance rulea to the current policy. - + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule. """ with self._wl: return self._e.add_grouping_policies(rules) - def add_named_grouping_policies(self,ptype,rules): - """"adds named role inheritance rules to the current policy. - + def add_named_grouping_policies(self, ptype, rules): + """ "adds named role inheritance rules to the current policy. + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" with self._wl: - return self._e.add_named_grouping_policies(ptype,rules) + return self._e.add_named_grouping_policies(ptype, rules) - def remove_grouping_policies(self,rules): + def remove_grouping_policies(self, rules): """removes role inheritance rulea from the current policy.""" with self._wl: return self._e.addremove_grouping_policies_policies(rules) - def remove_named_grouping_policies(self,ptype,rules): - """ removes role inheritance rules from the current named policy.""" + def remove_named_grouping_policies(self, ptype, rules): + """removes role inheritance rules from the current named policy.""" with self._wl: - return self._e.remove_named_grouping_policies(ptype,rules) + return self._e.remove_named_grouping_policies(ptype, rules) def build_incremental_role_links(self, op, ptype, rules): - self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) + self.get_model().build_incremental_role_links( + self.get_role_manager(), op, "g", ptype, rules + ) diff --git a/casbin/util/__init__.py b/casbin/util/__init__.py index a40b64ef..92b6317c 100644 --- a/casbin/util/__init__.py +++ b/casbin/util/__init__.py @@ -1,3 +1,3 @@ from .builtin_operators import * from .expression import * -from .util import * \ No newline at end of file +from .util import * diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index a6eaa0cb..80f52139 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -3,8 +3,8 @@ from wcmatch import pathlib -KEY_MATCH2_PATTERN = re.compile(r'(.*?):[^\/]+(.*?)') -KEY_MATCH3_PATTERN = re.compile(r'(.*?){[^\/]+}(.*?)') +KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") +KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+}(.*?)") def key_match(key1, key2): @@ -22,8 +22,7 @@ def key_match(key1, key2): def key_match_func(*args): - """The wrapper for key_match. - """ + """The wrapper for key_match.""" name1 = args[0] name2 = args[1] @@ -36,7 +35,7 @@ def key_match2(key1, key2): """ key2 = key2.replace("/*", "/.*") - key2 = KEY_MATCH2_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + key2 = KEY_MATCH2_PATTERN.sub(r"\g<1>[^\/]+\g<2>", key2, 0) return regex_match(key1, "^" + key2 + "$") @@ -54,7 +53,7 @@ def key_match3(key1, key2): """ key2 = key2.replace("/*", "/.*") - key2 = KEY_MATCH3_PATTERN.sub(r'\g<1>[^\/]+\g<2>', key2, 0) + key2 = KEY_MATCH3_PATTERN.sub(r"\g<1>[^\/]+\g<2>", key2, 0) return regex_match(key1, "^" + key2 + "$") diff --git a/casbin/util/expression.py b/casbin/util/expression.py index cd9a20db..ea129df8 100644 --- a/casbin/util/expression.py +++ b/casbin/util/expression.py @@ -3,25 +3,25 @@ class SimpleEval(SimpleEval): - """ Rewrite SimpleEval. - >>> s = SimpleEval("20 + 30 - ( 10 * 5)") - >>> s.eval() - 0 - """ + """Rewrite SimpleEval. + >>> s = SimpleEval("20 + 30 - ( 10 * 5)") + >>> s.eval() + 0 + """ ast_parsed_value = None def __init__(self, expr, functions=None): """Create the evaluator instance. Set up valid operators (+,-, etc) - functions (add, random, get_val, whatever) and names. """ + functions (add, random, get_val, whatever) and names.""" super(SimpleEval, self).__init__(functions=functions) if expr != "": self.expr = expr self.expr_parsed_value = ast.parse(expr.strip()).body[0].value def eval(self, names=None): - """ evaluate an expresssion, using the operators, functions and - names previously set up. """ + """evaluate an expresssion, using the operators, functions and + names previously set up.""" if names: self.names = names diff --git a/casbin/util/rwlock.py b/casbin/util/rwlock.py index 2f54b905..105312e1 100644 --- a/casbin/util/rwlock.py +++ b/casbin/util/rwlock.py @@ -2,8 +2,9 @@ # This implementation was adapted from https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock -class RWLockWrite(): - ''' write preferring readers-wirter lock ''' + +class RWLockWrite: + """write preferring readers-wirter lock""" def __init__(self): self._lock = RLock() @@ -17,7 +18,7 @@ def aquire_read(self): while self._waiting_writers > 0 or self._writer_active: self._cond.wait() self._active_readers += 1 - + def release_read(self): with self._lock: self._active_readers -= 1 @@ -43,8 +44,8 @@ def gen_rlock(self): def gen_wlock(self): return WriteRWLock(self) -class ReadRWLock(): +class ReadRWLock: def __init__(self, rwlock): self.rwlock = rwlock @@ -55,8 +56,8 @@ def __exit__(self, exc_type, exc_value, traceback): self.rwlock.release_read() return False -class WriteRWLock(): +class WriteRWLock: def __init__(self, rwlock): self.rwlock = rwlock diff --git a/casbin/util/util.py b/casbin/util/util.py index baa0d565..93531509 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -1,13 +1,14 @@ from collections import OrderedDict import re -eval_reg = re.compile(r'\beval\((?P[^)]*)\)') +eval_reg = re.compile(r"\beval\((?P[^)]*)\)") + def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" - s = re.sub(r'\br\.', 'r_', s) - s = re.sub(r'\bp\.', 'p_', s) + s = re.sub(r"\br\.", "r_", s) + s = re.sub(r"\bp\.", "p_", s) return s @@ -38,35 +39,40 @@ def params_to_string(*s): return ", ".join(s) + def join_slice(a, *b): - ''' joins a string and a slice into a new slice.''' + """joins a string and a slice into a new slice.""" res = [a] res.extend(b) return res + def set_subtract(a, b): - ''' returns the elements in `a` that aren't in `b`. ''' + """returns the elements in `a` that aren't in `b`.""" return [i for i in a if i not in b] + def has_eval(s): - '''determine whether matcher contains function eval''' + """determine whether matcher contains function eval""" return eval_reg.search(s) + def replace_eval(expr, rules): - ''' replace all occurences of function eval with rules ''' + """replace all occurences of function eval with rules""" pos = 0 match = eval_reg.search(expr, pos) while match: rule = "(" + rules.pop(0) + ")" - expr = expr[:match.start()] + rule + expr[match.end():] + expr = expr[: match.start()] + rule + expr[match.end() :] pos = match.start() + len(rule) match = eval_reg.search(expr, pos) return expr + def get_eval_value(s): - '''returns the parameters of function eval''' + """returns the parameters of function eval""" sub_match = eval_reg.findall(s) return sub_match.copy() diff --git a/requirements_dev.txt b/requirements_dev.txt new file mode 100644 index 00000000..4efdb2ca --- /dev/null +++ b/requirements_dev.txt @@ -0,0 +1,2 @@ +-r requirements.txt +black==21.6b0 \ No newline at end of file diff --git a/setup.py b/setup.py index f922db61..a882e339 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,17 @@ long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/casbin/pycasbin", - keywords=["casbin", "acl", "rbac", "abac", "auth", "authz", "authorization", "access control", "permission"], + keywords=[ + "casbin", + "acl", + "rbac", + "abac", + "auth", + "authz", + "authorization", + "access control", + "permission", + ], packages=setuptools.find_packages(exclude=("tests",)), install_requires=install_requires, python_requires=">=3.3", @@ -38,4 +48,4 @@ "Operating System :: OS Independent", ], data_files=[desc_file], -) \ No newline at end of file +) diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 319cb572..c7413f93 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -40,8 +40,12 @@ def test_new_config(self): self.assertEqual(config.get("multi5::name"), "r.sub==p.sub && r.obj==p.obj") self.assertEqual(config.get_bool("multi5::name"), False) - self.assertEqual(config.get_string("multi5::name"), "r.sub==p.sub && r.obj==p.obj") - self.assertEqual(config.get_strings("multi5::name"), ['r.sub==p.sub && r.obj==p.obj']) + self.assertEqual( + config.get_string("multi5::name"), "r.sub==p.sub && r.obj==p.obj" + ) + self.assertEqual( + config.get_strings("multi5::name"), ["r.sub==p.sub && r.obj==p.obj"] + ) with self.assertRaises(ValueError): config.get_int("multi5::name") with self.assertRaises(ValueError): diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index 4a8d08f1..757c9e53 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -9,144 +9,152 @@ def test_get_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) - rule = ['admin', 'domain1', 'data1', 'read'] + rule = ["admin", "domain1", "data1", "read"] - m.add_policy('p', 'p', rule) + m.add_policy("p", "p", rule) - self.assertTrue(m.get_policy('p', 'p') == [rule]) + self.assertTrue(m.get_policy("p", "p") == [rule]) def test_has_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) - rule = ['admin', 'domain1', 'data1', 'read'] - m.add_policy('p', 'p', rule) + rule = ["admin", "domain1", "data1", "read"] + m.add_policy("p", "p", rule) - self.assertTrue(m.has_policy('p', 'p', rule)) + self.assertTrue(m.has_policy("p", "p", rule)) def test_add_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) - rule = ['admin', 'domain1', 'data1', 'read'] + rule = ["admin", "domain1", "data1", "read"] - self.assertFalse(m.has_policy('p', 'p', rule)) + self.assertFalse(m.has_policy("p", "p", rule)) - m.add_policy('p', 'p', rule) - self.assertTrue(m.has_policy('p', 'p', rule)) + m.add_policy("p", "p", rule) + self.assertTrue(m.has_policy("p", "p", rule)) def test_add_role_policy(self): m = Model() m.load_model(get_examples("rbac_model.conf")) - p_rule1 = ['alice', 'data1', 'read'] - m.add_policy('p', 'p', p_rule1) - self.assertTrue(m.has_policy('p', 'p', p_rule1)) + p_rule1 = ["alice", "data1", "read"] + m.add_policy("p", "p", p_rule1) + self.assertTrue(m.has_policy("p", "p", p_rule1)) - p_rule2 = ['data2_admin', 'data2', 'read'] - m.add_policy('p', 'p', p_rule2) - self.assertTrue(m.has_policy('p', 'p', p_rule2)) + p_rule2 = ["data2_admin", "data2", "read"] + m.add_policy("p", "p", p_rule2) + self.assertTrue(m.has_policy("p", "p", p_rule2)) - g_rule = ['alice', 'data2_admin'] - m.add_policy('g', 'g', g_rule) - self.assertTrue(m.has_policy('g', 'g', g_rule)) + g_rule = ["alice", "data2_admin"] + m.add_policy("g", "g", g_rule) + self.assertTrue(m.has_policy("g", "g", g_rule)) - self.assertTrue(m.get_policy('p', 'p') == [p_rule1, p_rule2]) - self.assertTrue(m.get_policy('g', 'g') == [g_rule]) + self.assertTrue(m.get_policy("p", "p") == [p_rule1, p_rule2]) + self.assertTrue(m.get_policy("g", "g") == [g_rule]) def test_update_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) - old_rule = ['admin', 'domain1', 'data1', 'read'] - new_rule = ['admin', 'domain1', 'data2', 'read'] + old_rule = ["admin", "domain1", "data1", "read"] + new_rule = ["admin", "domain1", "data2", "read"] - m.add_policy('p', 'p', old_rule) - self.assertTrue(m.has_policy('p', 'p', old_rule)) + m.add_policy("p", "p", old_rule) + self.assertTrue(m.has_policy("p", "p", old_rule)) - m.update_policy('p', 'p', old_rule, new_rule) - self.assertFalse(m.has_policy('p', 'p', old_rule)) - self.assertTrue(m.has_policy('p', 'p', new_rule)) + m.update_policy("p", "p", old_rule, new_rule) + self.assertFalse(m.has_policy("p", "p", old_rule)) + self.assertTrue(m.has_policy("p", "p", new_rule)) m = Model() m.load_model(get_examples("priority_model_explicit.conf")) - old_rule = ['1', 'admin', 'data1', 'read', 'allow'] - new_rule = ['1', 'admin', 'data2', 'read', 'allow'] + old_rule = ["1", "admin", "data1", "read", "allow"] + new_rule = ["1", "admin", "data2", "read", "allow"] - m.add_policy('p', 'p', old_rule) - self.assertTrue(m.has_policy('p', 'p', old_rule)) + m.add_policy("p", "p", old_rule) + self.assertTrue(m.has_policy("p", "p", old_rule)) - m.update_policy('p', 'p', old_rule, new_rule) - self.assertFalse(m.has_policy('p', 'p', old_rule)) - self.assertTrue(m.has_policy('p', 'p', new_rule)) + m.update_policy("p", "p", old_rule, new_rule) + self.assertFalse(m.has_policy("p", "p", old_rule)) + self.assertTrue(m.has_policy("p", "p", new_rule)) def test_update_policies(self): m = Model() m.load_model(get_examples("basic_model.conf")) - old_rules = [['admin', 'domain1', 'data1', 'read'], - ['admin', 'domain1', 'data2', 'read'], - ['admin', 'domain1', 'data3', 'read']] - new_rules = [['admin', 'domain1', 'data4', 'read'], - ['admin', 'domain1', 'data5', 'read'], - ['admin', 'domain1', 'data6', 'read']] + old_rules = [ + ["admin", "domain1", "data1", "read"], + ["admin", "domain1", "data2", "read"], + ["admin", "domain1", "data3", "read"], + ] + new_rules = [ + ["admin", "domain1", "data4", "read"], + ["admin", "domain1", "data5", "read"], + ["admin", "domain1", "data6", "read"], + ] - m.add_policies('p', 'p', old_rules) + m.add_policies("p", "p", old_rules) for old_rule in old_rules: - self.assertTrue(m.has_policy('p', 'p', old_rule)) + self.assertTrue(m.has_policy("p", "p", old_rule)) - m.update_policies('p', 'p', old_rules, new_rules) + m.update_policies("p", "p", old_rules, new_rules) for old_rule in old_rules: - self.assertFalse(m.has_policy('p', 'p', old_rule)) + self.assertFalse(m.has_policy("p", "p", old_rule)) for new_rule in new_rules: - self.assertTrue(m.has_policy('p', 'p', new_rule)) + self.assertTrue(m.has_policy("p", "p", new_rule)) m = Model() m.load_model(get_examples("priority_model_explicit.conf")) - old_rules = [['1', 'admin', 'data1', 'read', 'allow'], - ['1', 'admin', 'data2', 'read', 'allow'], - ['1', 'admin', 'data3', 'read', 'allow']] - new_rules = [['1', 'admin', 'data4', 'read', 'allow'], - ['1', 'admin', 'data5', 'read', 'allow'], - ['1', 'admin', 'data6', 'read', 'allow']] + old_rules = [ + ["1", "admin", "data1", "read", "allow"], + ["1", "admin", "data2", "read", "allow"], + ["1", "admin", "data3", "read", "allow"], + ] + new_rules = [ + ["1", "admin", "data4", "read", "allow"], + ["1", "admin", "data5", "read", "allow"], + ["1", "admin", "data6", "read", "allow"], + ] - m.add_policies('p', 'p', old_rules) + m.add_policies("p", "p", old_rules) for old_rule in old_rules: - self.assertTrue(m.has_policy('p', 'p', old_rule)) + self.assertTrue(m.has_policy("p", "p", old_rule)) - m.update_policies('p', 'p', old_rules, new_rules) + m.update_policies("p", "p", old_rules, new_rules) for old_rule in old_rules: - self.assertFalse(m.has_policy('p', 'p', old_rule)) + self.assertFalse(m.has_policy("p", "p", old_rule)) for new_rule in new_rules: - self.assertTrue(m.has_policy('p', 'p', new_rule)) + self.assertTrue(m.has_policy("p", "p", new_rule)) def test_remove_policy(self): m = Model() m.load_model(get_examples("basic_model.conf")) - rule = ['admin', 'domain1', 'data1', 'read'] - m.add_policy('p', 'p', rule) - self.assertTrue(m.has_policy('p', 'p', rule)) + rule = ["admin", "domain1", "data1", "read"] + m.add_policy("p", "p", rule) + self.assertTrue(m.has_policy("p", "p", rule)) - m.remove_policy('p', 'p', rule) - self.assertFalse(m.has_policy('p', 'p', rule)) - self.assertFalse(m.remove_policy('p', 'p', rule)) + m.remove_policy("p", "p", rule) + self.assertFalse(m.has_policy("p", "p", rule)) + self.assertFalse(m.remove_policy("p", "p", rule)) def test_remove_filtered_policy(self): m = Model() m.load_model(get_examples("rbac_with_domains_model.conf")) - rule = ['admin', 'domain1', 'data1', 'read'] - m.add_policy('p', 'p', rule) + rule = ["admin", "domain1", "data1", "read"] + m.add_policy("p", "p", rule) - res = m.remove_filtered_policy('p', 'p', 1, 'domain1', 'data1') + res = m.remove_filtered_policy("p", "p", 1, "domain1", "data1") self.assertTrue(res) - res = m.remove_filtered_policy('p', 'p', 1, 'domain1', 'data1') + res = m.remove_filtered_policy("p", "p", 1, "domain1", "data1") self.assertFalse(res) diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py index c1bfe0b9..4a4cab06 100644 --- a/tests/rbac/test_role_manager.py +++ b/tests/rbac/test_role_manager.py @@ -4,11 +4,12 @@ import time from concurrent.futures import ThreadPoolExecutor + def get_role_manager(): return default_role_manager.RoleManager(max_hierarchy_level=10) -class TestDefaultRoleManager(TestCase): +class TestDefaultRoleManager(TestCase): def test_role(self): rm = get_role_manager() rm.add_link("u1", "g1") @@ -197,29 +198,28 @@ def test_matching_func_order(self): self.assertTrue(rm.has_link("u1", "root")) rm.clear() - + rm.add_link("u1", "g1") rm.add_link(r"g\d+", "root") self.assertTrue(rm.has_link("u1", "root")) rm.clear() - + rm.add_link("u1", r"g\d+") rm.add_link("g1", "root") self.assertTrue(rm.has_link("u1", "root")) rm.clear() - + rm.add_link("g1", "root") rm.add_link("u1", r"g\d+") self.assertTrue(rm.has_link("u1", "root")) def test_concurrent_has_link_with_matching_func(self): - def matching_func(*args): time.sleep(0.01) return regex_match_func(*args) - + rm = get_role_manager() rm.add_matching_func(matching_func) rm.add_link(r"u\d+", "users") @@ -228,7 +228,6 @@ def test_has_link(role): return rm.has_link(role, "users") executor = ThreadPoolExecutor(10) - futures = [executor.submit(test_has_link, "u"+str(i)) for i in range(10)] + futures = [executor.submit(test_has_link, "u" + str(i)) for i in range(10)] for future in futures: self.assertTrue(future.result()) - \ No newline at end of file diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py index 36e2dbeb..41a67ac8 100644 --- a/tests/test_distributed_api.py +++ b/tests/test_distributed_api.py @@ -3,7 +3,6 @@ class TestDistributedApi(TestCaseBase): - def get_enforcer(self, model=None, adapter=None): return casbin.DistributedEnforcer( model, @@ -12,16 +11,20 @@ def get_enforcer(self, model=None, adapter=None): def test(self): e = self.get_enforcer( - get_examples("rbac_model.conf"), - get_examples("rbac_policy.csv") + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") ) - e.add_policy_self(False, "p", "p", [ - ["alice", "data1", "read"], - ["bob", "data2", "write"], - ["data2_admin", "data2", "read"], - ["data2_admin", "data2", "write"] - ]) + e.add_policy_self( + False, + "p", + "p", + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) e.add_policy_self(False, "g", "g", [["alice", "data2_admin"]]) self.assertTrue(e.enforce("alice", "data1", "read")) @@ -33,8 +36,12 @@ def test(self): self.assertTrue(e.enforce("alice", "data2", "read")) self.assertTrue(e.enforce("alice", "data2", "write")) - e.update_policy_self(False, "p", "p", ["alice", "data1", "read"],["alice", "data1", "write"]) - e.update_policy_self(False, "g", "g", ["alice", "data2_admin"], ["tom", "alice"]) + e.update_policy_self( + False, "p", "p", ["alice", "data1", "read"], ["alice", "data1", "write"] + ) + e.update_policy_self( + False, "g", "g", ["alice", "data2_admin"], ["tom", "alice"] + ) self.assertFalse(e.enforce("alice", "data1", "read")) self.assertTrue(e.enforce("alice", "data1", "write")) @@ -45,12 +52,8 @@ def test(self): self.assertFalse(e.enforce("tom", "data1", "read")) self.assertTrue(e.enforce("tom", "data1", "write")) - e.remove_policy_self(False, "p", "p", [ - ["alice", "data1", "write"] - ]) - e.remove_policy_self(False, "g", "g", [ - ["alice", "data2_admin"] - ]) + e.remove_policy_self(False, "p", "p", [["alice", "data1", "write"]]) + e.remove_policy_self(False, "g", "g", [["alice", "data2_admin"]]) self.assertFalse(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("alice", "data1", "write")) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 0d130f41..534da6a8 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -10,8 +10,7 @@ def get_examples(path): return os.path.abspath(examples_path + path) -class TestSub(): - +class TestSub: def __init__(self, name, age): self.name = name self.age = age @@ -26,27 +25,30 @@ def get_enforcer(self, model=None, adapter=None): class TestConfig(TestCaseBase): - def test_enforcer_basic(self): e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), ) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) - self.assertFalse(e.enforce('bob', 'data1', 'write')) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "write")) def test_enforce_ex_basic(self): e = self.get_enforcer( get_examples("basic_model.conf"), get_examples("basic_policy.csv"), ) - self.assertTupleEqual(e.enforce_ex('alice', 'data1', 'read'), (True, ['alice', 'data1', 'read'])) - self.assertTupleEqual(e.enforce_ex('alice', 'data2', 'read'), (False, [])) - self.assertTupleEqual(e.enforce_ex('bob', 'data2', 'write'), (True, ['bob', 'data2', 'write'])) - self.assertTupleEqual(e.enforce_ex('bob', 'data1', 'write'), (False, [])) + self.assertTupleEqual( + e.enforce_ex("alice", "data1", "read"), (True, ["alice", "data1", "read"]) + ) + self.assertTupleEqual(e.enforce_ex("alice", "data2", "read"), (False, [])) + self.assertTupleEqual( + e.enforce_ex("bob", "data2", "write"), (True, ["bob", "data2", "write"]) + ) + self.assertTupleEqual(e.enforce_ex("bob", "data1", "write"), (False, [])) def test_model_set_load(self): e = self.get_enforcer( @@ -76,122 +78,154 @@ def test_enforcer_basic_without_spaces(self): self.assertTrue(e.enforce("bob", "data2", "write")) def test_enforce_basic_with_root(self): - e = self.get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) - self.assertTrue(e.enforce('root', 'any', 'any')) + e = self.get_enforcer( + get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv") + ) + self.assertTrue(e.enforce("root", "any", "any")) def test_enforce_basic_without_resources(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - self.assertTrue(e.enforce('alice', 'read')) - self.assertFalse(e.enforce('alice', 'write')) - self.assertTrue(e.enforce('bob', 'write')) - self.assertFalse(e.enforce('bob', 'read')) + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + self.assertTrue(e.enforce("alice", "read")) + self.assertFalse(e.enforce("alice", "write")) + self.assertTrue(e.enforce("bob", "write")) + self.assertFalse(e.enforce("bob", "read")) def test_enforce_basic_without_users(self): - e = self.get_enforcer(get_examples("basic_without_users_model.conf"), - get_examples("basic_without_users_policy.csv")) - self.assertTrue(e.enforce('data1', 'read')) - self.assertFalse(e.enforce('data1', 'write')) - self.assertTrue(e.enforce('data2', 'write')) - self.assertFalse(e.enforce('data2', 'read')) + e = self.get_enforcer( + get_examples("basic_without_users_model.conf"), + get_examples("basic_without_users_policy.csv"), + ) + self.assertTrue(e.enforce("data1", "read")) + self.assertFalse(e.enforce("data1", "write")) + self.assertTrue(e.enforce("data2", "write")) + self.assertFalse(e.enforce("data2", "read")) def test_enforce_ip_match(self): - e = self.get_enforcer(get_examples("ipmatch_model.conf"), - get_examples("ipmatch_policy.csv")) - self.assertTrue(e.enforce('192.168.2.1', 'data1', 'read')) - self.assertFalse(e.enforce('192.168.3.1', 'data1', 'read')) + e = self.get_enforcer( + get_examples("ipmatch_model.conf"), get_examples("ipmatch_policy.csv") + ) + self.assertTrue(e.enforce("192.168.2.1", "data1", "read")) + self.assertFalse(e.enforce("192.168.3.1", "data1", "read")) def test_enforce_key_match(self): - e = self.get_enforcer(get_examples("keymatch_model.conf"), - get_examples("keymatch_policy.csv")) - self.assertTrue(e.enforce('alice', '/alice_data/test', 'GET')) - self.assertFalse(e.enforce('alice', '/bob_data/test', 'GET')) - self.assertTrue(e.enforce('cathy', '/cathy_data', 'GET')) - self.assertTrue(e.enforce('cathy', '/cathy_data', 'POST')) - self.assertFalse(e.enforce('cathy', '/cathy_data/12', 'POST')) + e = self.get_enforcer( + get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv") + ) + self.assertTrue(e.enforce("alice", "/alice_data/test", "GET")) + self.assertFalse(e.enforce("alice", "/bob_data/test", "GET")) + self.assertTrue(e.enforce("cathy", "/cathy_data", "GET")) + self.assertTrue(e.enforce("cathy", "/cathy_data", "POST")) + self.assertFalse(e.enforce("cathy", "/cathy_data/12", "POST")) def test_enforce_key_match2(self): - e = self.get_enforcer(get_examples("keymatch2_model.conf"), - get_examples("keymatch2_policy.csv")) - self.assertTrue(e.enforce('alice', '/alice_data/resource', 'GET')) - self.assertTrue(e.enforce('alice', '/alice_data2/123/using/456', 'GET')) + e = self.get_enforcer( + get_examples("keymatch2_model.conf"), get_examples("keymatch2_policy.csv") + ) + self.assertTrue(e.enforce("alice", "/alice_data/resource", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data2/123/using/456", "GET")) def test_enforce_priority(self): - e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('alice', 'data1', 'write')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'write')) + e = self.get_enforcer( + get_examples("priority_model.conf"), get_examples("priority_policy.csv") + ) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) - self.assertFalse(e.enforce('bob', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data1', 'write')) - self.assertTrue(e.enforce('bob', 'data2', 'read')) - self.assertFalse(e.enforce('bob', 'data2', 'write')) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertTrue(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) def test_enforce_priority_indeterminate(self): - e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_indeterminate_policy.csv")) - self.assertFalse(e.enforce('alice', 'data1', 'read')) + e = self.get_enforcer( + get_examples("priority_model.conf"), + get_examples("priority_indeterminate_policy.csv"), + ) + self.assertFalse(e.enforce("alice", "data1", "read")) def test_enforce_rbac(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data1', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) - self.assertTrue(e.enforce('alice', 'data2', 'read')) - self.assertTrue(e.enforce('alice', 'data2', 'write')) - self.assertFalse(e.enforce('bogus', 'data2', 'write')) # test non-existant subject + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + self.assertFalse( + e.enforce("bogus", "data2", "write") + ) # test non-existant subject def test_enforce_rbac__empty_policy(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) - self.assertFalse(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data2', 'write')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("empty_policy.csv") + ) + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) def test_enforce_rbac_with_deny(self): - e = self.get_enforcer(get_examples("rbac_with_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) - self.assertTrue(e.enforce('alice', 'data2', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_with_deny_model.conf"), + get_examples("rbac_with_deny_policy.csv"), + ) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) def test_enforce_rbac_with_domains(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'read')) - self.assertTrue(e.enforce('alice', 'domain1', 'data1', 'write')) - self.assertFalse(e.enforce('alice', 'domain1', 'data2', 'read')) - self.assertFalse(e.enforce('alice', 'domain1', 'data2', 'write')) - - self.assertFalse(e.enforce('bob', 'domain2', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'domain2', 'data1', 'write')) - self.assertTrue(e.enforce('bob', 'domain2', 'data2', 'read')) - self.assertTrue(e.enforce('bob', 'domain2', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "write")) + + self.assertFalse(e.enforce("bob", "domain2", "data1", "read")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "write")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) def test_enforce_rbac_with_not_deny(self): - e = self.get_enforcer(get_examples("rbac_with_not_deny_model.conf"), get_examples("rbac_with_deny_policy.csv")) - self.assertFalse(e.enforce('alice', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_with_not_deny_model.conf"), + get_examples("rbac_with_deny_policy.csv"), + ) + self.assertFalse(e.enforce("alice", "data2", "write")) def test_enforce_rbac_with_resource_roles(self): - e = self.get_enforcer(get_examples("rbac_with_resource_roles_model.conf"), - get_examples("rbac_with_resource_roles_policy.csv")) - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertTrue(e.enforce('alice', 'data1', 'write')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertTrue(e.enforce('alice', 'data2', 'write')) - - self.assertFalse(e.enforce('bob', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data1', 'write')) - self.assertFalse(e.enforce('bob', 'data2', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_with_resource_roles_model.conf"), + get_examples("rbac_with_resource_roles_policy.csv"), + ) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) def test_enforce_rbac_with_pattern(self): - e = self.get_enforcer(get_examples("rbac_with_pattern_model.conf"), - get_examples("rbac_with_pattern_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_with_pattern_model.conf"), + get_examples("rbac_with_pattern_policy.csv"), + ) # set matching function to key_match2 - e.add_named_matching_func('g2', casbin.util.key_match2) + e.add_named_matching_func("g2", casbin.util.key_match2) self.assertTrue(e.enforce("alice", "/book/1", "GET")) self.assertTrue(e.enforce("alice", "/book/2", "GET")) @@ -203,7 +237,7 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen/2", "GET")) # replace key_match2 with key_match3 - e.add_named_matching_func('g2', casbin.util.key_match3) + e.add_named_matching_func("g2", casbin.util.key_match3) self.assertTrue(e.enforce("alice", "/book2/1", "GET")) self.assertTrue(e.enforce("alice", "/book2/2", "GET")) self.assertTrue(e.enforce("alice", "/pen2/1", "GET")) @@ -215,13 +249,14 @@ def test_enforce_rbac_with_pattern(self): def test_enforce_abac_log_enabled(self): e = self.get_enforcer(get_examples("abac_model.conf")) - sub = 'alice' - obj = {'Owner': 'alice', 'id': 'data1'} - self.assertTrue(e.enforce(sub, obj, 'write')) + sub = "alice" + obj = {"Owner": "alice", "id": "data1"} + self.assertTrue(e.enforce(sub, obj, "write")) def test_abac_with_sub_rule(self): - e = self.get_enforcer(get_examples("abac_rule_model.conf"), - get_examples("abac_rule_policy.csv")) + e = self.get_enforcer( + get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv") + ) sub1 = TestSub("alice", 16) sub2 = TestSub("bob", 20) @@ -243,8 +278,10 @@ def test_abac_with_sub_rule(self): self.assertFalse(e.enforce(sub3, "/data2", "write")) def test_abac_with_multiple_sub_rules(self): - e = self.get_enforcer(get_examples("abac_multiple_rules_model.conf"), - get_examples("abac_multiple_rules_policy.csv")) + e = self.get_enforcer( + get_examples("abac_multiple_rules_model.conf"), + get_examples("abac_multiple_rules_policy.csv"), + ) sub1 = TestSub("alice", 16) sub2 = TestSub("alice", 20) @@ -273,7 +310,6 @@ def test_abac_with_multiple_sub_rules(self): class TestConfigSynced(TestConfig): - def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, diff --git a/tests/test_filter.py b/tests/test_filter.py index 314f8352..72dfce54 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -11,12 +11,16 @@ class Filter: class TestFilteredAdapter(TestCase): def test_init_filtered_adapter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("rbac_with_domains_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) - self.assertFalse(e.has_policy(['admin', 'domain1', 'data1', 'read'])) + self.assertFalse(e.has_policy(["admin", "domain1", "data1", "read"])) def test_load_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("rbac_with_domains_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -25,8 +29,8 @@ def test_load_filtered_policy(self): e.load_policy() except: raise RuntimeError("unexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) try: e.load_filtered_policy(filter) except: @@ -35,8 +39,8 @@ def test_load_filtered_policy(self): if not e.is_filtered: raise RuntimeError("adapter did not set the filtered flag correctly") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) - self.assertFalse(e.has_policy(['admin', 'domain2', 'data2', 'read'])) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertFalse(e.has_policy(["admin", "domain2", "data2", "read"])) with self.assertRaises(RuntimeError): e.save_policy() @@ -45,7 +49,9 @@ def test_load_filtered_policy(self): e.get_adapter().save_policy(e.get_model()) def test_append_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("rbac_with_domains_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -54,8 +60,8 @@ def test_append_filtered_policy(self): e.load_policy() except: raise RuntimeError("unexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) try: e.load_filtered_policy(filter) except: @@ -64,8 +70,8 @@ def test_append_filtered_policy(self): if not e.is_filtered: raise RuntimeError("adapter did not set the filtered flag correctly") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) - self.assertFalse(e.has_policy(['admin', 'domain2', 'data2', 'read'])) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertFalse(e.has_policy(["admin", "domain2", "data2", "read"])) filter.P = ["", "domain2"] filter.G = ["", "", "domain2"] @@ -74,11 +80,13 @@ def test_append_filtered_policy(self): except: raise RuntimeError("unexpected error in LoadFilteredPolicy") - self.assertTrue(e.has_policy(['admin', 'domain1', 'data1', 'read'])) - self.assertTrue(e.has_policy(['admin', 'domain2', 'data2', 'read'])) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) def test_filtered_policy_invalid_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("rbac_with_domains_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = ["", "domain1"] @@ -86,7 +94,9 @@ def test_filtered_policy_invalid_filter(self): e.load_filtered_policy(filter) def test_filtered_policy_empty_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("rbac_with_domains_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) try: @@ -103,7 +113,10 @@ def test_filtered_policy_empty_filter(self): raise RuntimeError("unexpected error in SavePolicy") def test_unsupported_filtered_policy(self): - e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) filter = Filter() filter.P = ["", "domain1"] filter.G = ["", "", "domain1"] @@ -118,7 +131,9 @@ def test_filtered_adapter_empty_filepath(self): e.load_filtered_policy(None) def test_filtered_adapter_invalid_filepath(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("does_not_exist_policy.csv")) + adapter = casbin.persist.adapters.FilteredAdapter( + get_examples("does_not_exist_policy.csv") + ) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 6330bd22..503d91d0 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -1,8 +1,8 @@ import casbin from tests.test_enforcer import get_examples, TestCaseBase -class TestManagementApi(TestCaseBase): +class TestManagementApi(TestCaseBase): def get_enforcer(self, model=None, adapter=None): return casbin.Enforcer( model, @@ -16,56 +16,88 @@ def test_get_list(self): # True, ) - self.assertEqual(e.get_all_subjects(), ['alice', 'bob', 'data2_admin']) - self.assertEqual(e.get_all_objects(), ['data1', 'data2']) - self.assertEqual(e.get_all_actions(), ['read', 'write']) - self.assertEqual(e.get_all_roles(), ['data2_admin']) + self.assertEqual(e.get_all_subjects(), ["alice", "bob", "data2_admin"]) + self.assertEqual(e.get_all_objects(), ["data1", "data2"]) + self.assertEqual(e.get_all_actions(), ["read", "write"]) + self.assertEqual(e.get_all_roles(), ["data2_admin"]) def test_get_policy_api(self): e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv"), ) - self.assertEqual(e.get_policy(), [ - ['alice', 'data1', 'read'], - ['bob', 'data2', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write'], - ]) - - self.assertEqual(e.get_filtered_policy(0, 'alice'), [['alice', 'data1', 'read']]) - self.assertEqual(e.get_filtered_policy(0, 'bob'), [['bob', 'data2', 'write']]) - self.assertEqual(e.get_filtered_policy(0, 'data2_admin'), - [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]) - self.assertEqual(e.get_filtered_policy(1, 'data1'), [['alice', 'data1', 'read']]) - self.assertEqual(e.get_filtered_policy(1, 'data2'), - [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write']]) - self.assertEqual(e.get_filtered_policy(2, 'read'), - [['alice', 'data1', 'read'], ['data2_admin', 'data2', 'read']]) - self.assertEqual(e.get_filtered_policy(2, 'write'), - [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]) - self.assertEqual(e.get_filtered_policy(0, 'data2_admin', 'data2'), - [['data2_admin', 'data2', 'read'], ['data2_admin', 'data2', 'write']]) + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + + self.assertEqual( + e.get_filtered_policy(0, "alice"), [["alice", "data1", "read"]] + ) + self.assertEqual(e.get_filtered_policy(0, "bob"), [["bob", "data2", "write"]]) + self.assertEqual( + e.get_filtered_policy(0, "data2_admin"), + [["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"]], + ) + self.assertEqual( + e.get_filtered_policy(1, "data1"), [["alice", "data1", "read"]] + ) + self.assertEqual( + e.get_filtered_policy(1, "data2"), + [ + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + self.assertEqual( + e.get_filtered_policy(2, "read"), + [["alice", "data1", "read"], ["data2_admin", "data2", "read"]], + ) + self.assertEqual( + e.get_filtered_policy(2, "write"), + [["bob", "data2", "write"], ["data2_admin", "data2", "write"]], + ) + self.assertEqual( + e.get_filtered_policy(0, "data2_admin", "data2"), + [["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"]], + ) # Note: "" (empty string) in fieldValues means matching all values. - self.assertEqual(e.get_filtered_policy(0, 'data2_admin', '', 'read'), [['data2_admin', 'data2', 'read']]) - self.assertEqual(e.get_filtered_policy(1, 'data2', 'write'), - [['bob', 'data2', 'write'], ['data2_admin', 'data2', 'write']]) - - self.assertTrue(e.has_policy(['alice', 'data1', 'read'])) - self.assertTrue(e.has_policy(['bob', 'data2', 'write'])) - self.assertFalse(e.has_policy(['alice', 'data2', 'read'])) - self.assertFalse(e.has_policy(['bob', 'data3', 'write'])) - self.assertEqual(e.get_grouping_policy(), [['alice', 'data2_admin']]) - self.assertEqual(e.get_filtered_grouping_policy(0, 'alice'), [['alice', 'data2_admin']]) - self.assertEqual(e.get_filtered_grouping_policy(0, 'bob'), []) - self.assertEqual(e.get_filtered_grouping_policy(1, 'data1_admin'), []) - self.assertEqual(e.get_filtered_grouping_policy(1, 'data2_admin'), [['alice', 'data2_admin']]) + self.assertEqual( + e.get_filtered_policy(0, "data2_admin", "", "read"), + [["data2_admin", "data2", "read"]], + ) + self.assertEqual( + e.get_filtered_policy(1, "data2", "write"), + [["bob", "data2", "write"], ["data2_admin", "data2", "write"]], + ) + + self.assertTrue(e.has_policy(["alice", "data1", "read"])) + self.assertTrue(e.has_policy(["bob", "data2", "write"])) + self.assertFalse(e.has_policy(["alice", "data2", "read"])) + self.assertFalse(e.has_policy(["bob", "data3", "write"])) + self.assertEqual(e.get_grouping_policy(), [["alice", "data2_admin"]]) + self.assertEqual( + e.get_filtered_grouping_policy(0, "alice"), [["alice", "data2_admin"]] + ) + self.assertEqual(e.get_filtered_grouping_policy(0, "bob"), []) + self.assertEqual(e.get_filtered_grouping_policy(1, "data1_admin"), []) + self.assertEqual( + e.get_filtered_grouping_policy(1, "data2_admin"), [["alice", "data2_admin"]] + ) # Note: "" (empty string) in fieldValues means matching all values. - self.assertEqual(e.get_filtered_grouping_policy(0, '', 'data2_admin'), [['alice', 'data2_admin']]) - self.assertTrue(e.has_grouping_policy(['alice', 'data2_admin'])) - self.assertFalse(e.has_grouping_policy(['bob', 'data2_admin'])) + self.assertEqual( + e.get_filtered_grouping_policy(0, "", "data2_admin"), + [["alice", "data2_admin"]], + ) + self.assertTrue(e.has_grouping_policy(["alice", "data2_admin"])) + self.assertFalse(e.has_grouping_policy(["bob", "data2_admin"])) def test_modify_policy_api(self): e = self.get_enforcer( @@ -73,76 +105,87 @@ def test_modify_policy_api(self): get_examples("rbac_policy.csv"), # True, ) - self.assertEqual(e.get_policy(), [ - ['alice', 'data1', 'read'], - ['bob', 'data2', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write'], - ]) - - e.add_policy('eve', 'data3', 'read') - e.add_named_policy('p', ['eve', 'data3', 'write']) - self.assertEqual(e.get_policy(), [ - ['alice', 'data1', 'read'], - ['bob', 'data2', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write'], - ['eve', 'data3', 'read'], - ['eve', 'data3', 'write'], - ]) - + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + + e.add_policy("eve", "data3", "read") + e.add_named_policy("p", ["eve", "data3", "write"]) + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ], + ) + rules = [ - ['jack', 'data4', 'read'], - ['katy', 'data4', 'write'], - ['leyo', 'data4', 'read'], - ['ham', 'data4', 'write'] + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], ] named_policies = [ - ['jack', 'data4', 'write'], - ['katy', 'data4', 'read'], - ['leyo', 'data4', 'write'], - ['ham', 'data4', 'read'] + ["jack", "data4", "write"], + ["katy", "data4", "read"], + ["leyo", "data4", "write"], + ["ham", "data4", "read"], ] e.add_policies(rules) - e.add_named_policies('p',named_policies) - - self.assertEqual(e.get_policy(), [ - ['alice', 'data1', 'read'], - ['bob', 'data2', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write'], - ['eve', 'data3', 'read'], - ['eve', 'data3', 'write'], - ["jack", "data4", "read"], - ["katy", "data4", "write"], - ["leyo", "data4", "read"], - ["ham", "data4", "write"], - ['jack', 'data4', 'write'], - ['katy', 'data4', 'read'], - ['leyo', 'data4', 'write'], - ['ham', 'data4', 'read'] - ] + e.add_named_policies("p", named_policies) + + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ["jack", "data4", "write"], + ["katy", "data4", "read"], + ["leyo", "data4", "write"], + ["ham", "data4", "read"], + ], ) e.remove_policies(rules) - e.remove_named_policies('p',named_policies) - - e.add_named_policy('p', 'testing') - self.assertEqual(e.get_policy(), [ - ['alice', 'data1', 'read'], - ['bob', 'data2', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write'], - ['eve', 'data3', 'read'], - ['eve', 'data3', 'write'], - ['testing'] - ]) + e.remove_named_policies("p", named_policies) + + e.add_named_policy("p", "testing") + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ["testing"], + ], + ) -class TestManagementApiSynced(TestManagementApi): +class TestManagementApiSynced(TestManagementApi): def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - ) \ No newline at end of file + ) diff --git a/tests/test_model_benchmark.py b/tests/test_model_benchmark.py index 895bcfed..8e03a4c2 100644 --- a/tests/test_model_benchmark.py +++ b/tests/test_model_benchmark.py @@ -21,7 +21,9 @@ def print_time_diff(start, end, time): class TestModelBenchmark(TestCaseBase): def test_benchmark_basic_model(self): - e = self.get_enforcer(get_examples("basic_model.conf"), get_examples("basic_policy.csv")) + e = self.get_enforcer( + get_examples("basic_model.conf"), get_examples("basic_policy.csv") + ) time = 10000 start = datetime.datetime.now() @@ -31,7 +33,9 @@ def test_benchmark_basic_model(self): print_time_diff(start, end, time) def test_benchmark_rbac_model(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) time = 10000 start = datetime.datetime.now() @@ -40,8 +44,8 @@ def test_benchmark_rbac_model(self): end = datetime.datetime.now() print_time_diff(start, end, time) -class TestModelBenchmarkSynced(TestModelBenchmark): +class TestModelBenchmarkSynced(TestModelBenchmark): def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index d1d4889b..137b3bf6 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -4,220 +4,302 @@ class TestRbacApi(TestCaseBase): def test_get_roles_for_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) - self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) - self.assertEqual(e.get_roles_for_user('bob'), []) - self.assertEqual(e.get_roles_for_user('data2_admin'), []) - self.assertEqual(e.get_roles_for_user('non_exist'), []) + self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) + self.assertEqual(e.get_roles_for_user("bob"), []) + self.assertEqual(e.get_roles_for_user("data2_admin"), []) + self.assertEqual(e.get_roles_for_user("non_exist"), []) def test_get_users_for_role(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) - self.assertEqual(e.get_users_for_role('data2_admin'), ['alice']) + self.assertEqual(e.get_users_for_role("data2_admin"), ["alice"]) def test_has_role_for_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) - self.assertTrue(e.has_role_for_user('alice', 'data2_admin')) - self.assertFalse(e.has_role_for_user('alice', 'data1_admin')) + self.assertTrue(e.has_role_for_user("alice", "data2_admin")) + self.assertFalse(e.has_role_for_user("alice", "data1_admin")) def test_add_role_for_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - e.add_role_for_user('alice', 'data1_admin') - self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + e.add_role_for_user("alice", "data1_admin") + self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"]) def test_delete_role_for_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - e.add_role_for_user('alice', 'data1_admin') - self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin', 'data1_admin']) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + e.add_role_for_user("alice", "data1_admin") + self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"]) - e.delete_role_for_user('alice', 'data1_admin') - self.assertEqual(e.get_roles_for_user('alice'), ['data2_admin']) + e.delete_role_for_user("alice", "data1_admin") + self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) def test_delete_roles_for_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - e.delete_roles_for_user('alice') - self.assertEqual(e.get_roles_for_user('alice'), []) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + e.delete_roles_for_user("alice") + self.assertEqual(e.get_roles_for_user("alice"), []) def test_delete_user(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - e.delete_user('alice') - self.assertEqual(e.get_roles_for_user('alice'), []) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + e.delete_user("alice") + self.assertEqual(e.get_roles_for_user("alice"), []) def test_delete_role(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) - e.delete_role('data2_admin') - self.assertTrue(e.enforce('alice', 'data1', 'read')) - self.assertFalse(e.enforce('alice', 'data1', 'write')) - self.assertFalse(e.enforce('alice', 'data2', 'read')) - self.assertFalse(e.enforce('alice', 'data2', 'write')) - self.assertFalse(e.enforce('bob', 'data1', 'read')) - self.assertFalse(e.enforce('bob', 'data1', 'write')) - self.assertFalse(e.enforce('bob', 'data2', 'read')) - self.assertTrue(e.enforce('bob', 'data2', 'write')) + e = self.get_enforcer( + get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") + ) + e.delete_role("data2_admin") + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) def test_delete_permission(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - e.delete_permission('read') - self.assertFalse(e.enforce('alice', 'read')) - self.assertFalse(e.enforce('alice', 'write')) - self.assertFalse(e.enforce('bob', 'read')) - self.assertTrue(e.enforce('bob', 'write')) + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + e.delete_permission("read") + self.assertFalse(e.enforce("alice", "read")) + self.assertFalse(e.enforce("alice", "write")) + self.assertFalse(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) def test_add_permission_for_user(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - e.delete_permission('read') - e.add_permission_for_user('bob', 'read') - self.assertTrue(e.enforce('bob', 'read')) - self.assertTrue(e.enforce('bob', 'write')) + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + e.delete_permission("read") + e.add_permission_for_user("bob", "read") + self.assertTrue(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) def test_delete_permission_for_user(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - e.add_permission_for_user('bob', 'read') + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + e.add_permission_for_user("bob", "read") - self.assertTrue(e.enforce('bob', 'read')) - e.delete_permission_for_user('bob', 'read') - self.assertFalse(e.enforce('bob', 'read')) - self.assertTrue(e.enforce('bob', 'write')) + self.assertTrue(e.enforce("bob", "read")) + e.delete_permission_for_user("bob", "read") + self.assertFalse(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) def test_delete_permissions_for_user(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - e.delete_permissions_for_user('bob') + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + e.delete_permissions_for_user("bob") - self.assertTrue(e.enforce('alice', 'read')) - self.assertFalse(e.enforce('bob', 'read')) - self.assertFalse(e.enforce('bob', 'write')) + self.assertTrue(e.enforce("alice", "read")) + self.assertFalse(e.enforce("bob", "read")) + self.assertFalse(e.enforce("bob", "write")) def test_get_permissions_for_user(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - self.assertEqual(e.get_permissions_for_user('alice'), [['alice', 'read']]) + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + self.assertEqual(e.get_permissions_for_user("alice"), [["alice", "read"]]) def test_has_permission_for_user(self): - e = self.get_enforcer(get_examples("basic_without_resources_model.conf"), - get_examples("basic_without_resources_policy.csv")) - self.assertTrue(e.has_permission_for_user('alice', *['read'])) - self.assertFalse(e.has_permission_for_user('alice', *['write'])) - self.assertFalse(e.has_permission_for_user('bob', *['read'])) - self.assertTrue(e.has_permission_for_user('bob', *['write'])) + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + self.assertTrue(e.has_permission_for_user("alice", *["read"])) + self.assertFalse(e.has_permission_for_user("alice", *["write"])) + self.assertFalse(e.has_permission_for_user("bob", *["read"])) + self.assertTrue(e.has_permission_for_user("bob", *["write"])) def test_enforce_implicit_roles_api(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), - get_examples("rbac_with_hierarchy_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) - self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) - self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) + self.assertTrue( + e.get_permissions_for_user("alice") == [["alice", "data1", "read"]] + ) + self.assertTrue( + e.get_permissions_for_user("bob") == [["bob", "data2", "write"]] + ) - self.assertTrue(e.get_implicit_roles_for_user('alice') == ['admin', 'data1_admin', 'data2_admin']) - self.assertTrue(e.get_implicit_roles_for_user('bob') == []) + self.assertTrue( + e.get_implicit_roles_for_user("alice") + == ["admin", "data1_admin", "data2_admin"] + ) + self.assertTrue(e.get_implicit_roles_for_user("bob") == []) def test_enforce_implicit_roles_with_domain(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_hierarchy_with_domains_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv"), + ) - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) self.assertTrue( - e.get_implicit_roles_for_user('alice', 'domain1') == ["role:global_admin", "role:reader", "role:writer"]) + e.get_roles_for_user_in_domain("alice", "domain1") == ["role:global_admin"] + ) + self.assertTrue( + e.get_implicit_roles_for_user("alice", "domain1") + == ["role:global_admin", "role:reader", "role:writer"] + ) def test_enforce_implicit_permissions_api(self): - e = self.get_enforcer(get_examples("rbac_model.conf"), - get_examples("rbac_with_hierarchy_policy.csv")) - self.assertTrue(e.get_permissions_for_user('alice') == [["alice", "data1", "read"]]) - self.assertTrue(e.get_permissions_for_user('bob') == [["bob", "data2", "write"]]) - self.assertTrue(e.get_implicit_permissions_for_user('alice') == [ - ['alice', 'data1', 'read'], - ['data1_admin', 'data1', 'read'], - ['data1_admin', 'data1', 'write'], - ['data2_admin', 'data2', 'read'], - ['data2_admin', 'data2', 'write']]) - self.assertTrue(e.get_implicit_permissions_for_user('bob') == [["bob", "data2", "write"]]) + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + self.assertTrue( + e.get_permissions_for_user("alice") == [["alice", "data1", "read"]] + ) + self.assertTrue( + e.get_permissions_for_user("bob") == [["bob", "data2", "write"]] + ) + self.assertTrue( + e.get_implicit_permissions_for_user("alice") + == [ + ["alice", "data1", "read"], + ["data1_admin", "data1", "read"], + ["data1_admin", "data1", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ] + ) + self.assertTrue( + e.get_implicit_permissions_for_user("bob") == [["bob", "data2", "write"]] + ) def test_enforce_implicit_permissions_api_with_domain(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_hierarchy_with_domains_policy.csv")) - - self.assertTrue(e.get_roles_for_user_in_domain('alice', 'domain1') == ['role:global_admin']) - self.assertTrue(e.get_implicit_roles_for_user('alice', 'domain1') == - ['role:global_admin', 'role:reader', 'role:writer']) - self.assertTrue(e.get_implicit_permissions_for_user('alice', 'domain1') == [ - ['alice', 'domain1', 'data2', 'read'], - ["role:reader", "domain1", "data1", "read"], - ["role:writer", "domain1", "data1", "write"]]) - self.assertTrue(e.get_implicit_permissions_for_user('bob', 'domain1') == []) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv"), + ) + + self.assertTrue( + e.get_roles_for_user_in_domain("alice", "domain1") == ["role:global_admin"] + ) + self.assertTrue( + e.get_implicit_roles_for_user("alice", "domain1") + == ["role:global_admin", "role:reader", "role:writer"] + ) + self.assertTrue( + e.get_implicit_permissions_for_user("alice", "domain1") + == [ + ["alice", "domain1", "data2", "read"], + ["role:reader", "domain1", "data1", "read"], + ["role:writer", "domain1", "data1", "write"], + ] + ) + self.assertTrue(e.get_implicit_permissions_for_user("bob", "domain1") == []) def test_enforce_get_users_in_domain(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['alice']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain1') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain1') == []) - self.assertTrue(e.get_users_for_role_in_domain('admin', 'domain2') == ['bob']) - self.assertTrue(e.get_users_for_role_in_domain('non_exist', 'domain2') == []) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertTrue(e.get_users_for_role_in_domain("admin", "domain1") == ["alice"]) + self.assertTrue(e.get_users_for_role_in_domain("non_exist", "domain1") == []) + self.assertTrue(e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) + self.assertTrue(e.get_users_for_role_in_domain("non_exist", "domain2") == []) + e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + e.add_role_for_user_in_domain("bob", "admin", "domain1") + self.assertTrue(e.get_users_for_role_in_domain("admin", "domain1") == ["bob"]) + self.assertTrue(e.get_users_for_role_in_domain("non_exist", "domain1") == []) + self.assertTrue(e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) + self.assertTrue(e.get_users_for_role_in_domain("non_exist", "domain2") == []) def test_enforce_user_api_with_domain(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain1'), ['alice']) - self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain1'), []) - self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain2'), ['bob']) - self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain2'), []) - - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - - self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain1'), ['bob']) - self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain1'), []) - self.assertEqual(e.get_users_for_role_in_domain('admin', 'domain2'), ['bob']) - self.assertEqual(e.get_users_for_role_in_domain('non_exist', 'domain2'), []) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertEqual(e.get_users_for_role_in_domain("admin", "domain1"), ["alice"]) + self.assertEqual(e.get_users_for_role_in_domain("non_exist", "domain1"), []) + self.assertEqual(e.get_users_for_role_in_domain("admin", "domain2"), ["bob"]) + self.assertEqual(e.get_users_for_role_in_domain("non_exist", "domain2"), []) + + e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + e.add_role_for_user_in_domain("bob", "admin", "domain1") + + self.assertEqual(e.get_users_for_role_in_domain("admin", "domain1"), ["bob"]) + self.assertEqual(e.get_users_for_role_in_domain("non_exist", "domain1"), []) + self.assertEqual(e.get_users_for_role_in_domain("admin", "domain2"), ["bob"]) + self.assertEqual(e.get_users_for_role_in_domain("non_exist", "domain2"), []) def test_enforce_get_roles_with_domain(self): - e = self.get_enforcer(get_examples("rbac_with_domains_model.conf"), - get_examples("rbac_with_domains_policy.csv")) - self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain1'), ['admin']) - self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain1'), []) - self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain1'), []) - self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain1'), []) - - self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain2'), []) - self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin']) - self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), []) - self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) - - e.delete_roles_for_user_in_domain('alice', 'admin', 'domain1') - e.add_role_for_user_in_domain('bob', 'admin', 'domain1') - - self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain1'), []) - self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain1'), ['admin']) - self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain1'), []) - self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain1'), []) - - self.assertEqual(e.get_roles_for_user_in_domain('alice', 'domain2'), []) - self.assertEqual(e.get_roles_for_user_in_domain('bob', 'domain2'), ['admin']) - self.assertEqual(e.get_roles_for_user_in_domain('admin', 'domain2'), []) - self.assertEqual(e.get_roles_for_user_in_domain('non_exist', 'domain2'), []) + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain1"), ["admin"]) + self.assertEqual(e.get_roles_for_user_in_domain("bob", "domain1"), []) + self.assertEqual(e.get_roles_for_user_in_domain("admin", "domain1"), []) + self.assertEqual(e.get_roles_for_user_in_domain("non_exist", "domain1"), []) + + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain2"), []) + self.assertEqual(e.get_roles_for_user_in_domain("bob", "domain2"), ["admin"]) + self.assertEqual(e.get_roles_for_user_in_domain("admin", "domain2"), []) + self.assertEqual(e.get_roles_for_user_in_domain("non_exist", "domain2"), []) + + e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + e.add_role_for_user_in_domain("bob", "admin", "domain1") + + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain1"), []) + self.assertEqual(e.get_roles_for_user_in_domain("bob", "domain1"), ["admin"]) + self.assertEqual(e.get_roles_for_user_in_domain("admin", "domain1"), []) + self.assertEqual(e.get_roles_for_user_in_domain("non_exist", "domain1"), []) + + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain2"), []) + self.assertEqual(e.get_roles_for_user_in_domain("bob", "domain2"), ["admin"]) + self.assertEqual(e.get_roles_for_user_in_domain("admin", "domain2"), []) + self.assertEqual(e.get_roles_for_user_in_domain("non_exist", "domain2"), []) def test_implicit_user_api(self): - e = self.get_enforcer(get_examples("rbac_model.conf"),get_examples("rbac_with_hierarchy_policy.csv")) + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + + self.assertEqual( + ["alice"], e.get_implicit_users_for_permission("data1", "read") + ) + self.assertEqual( + ["alice"], e.get_implicit_users_for_permission("data1", "write") + ) + self.assertEqual( + ["alice"], e.get_implicit_users_for_permission("data2", "read") + ) + self.assertEqual( + ["alice", "bob"], e.get_implicit_users_for_permission("data2", "write") + ) - self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "read")) - self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "write")) - self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read")) - self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) class TestRbacApiSynced(TestRbacApi): - def get_enforcer(self, model=None, adapter=None): return casbin.SyncedEnforcer( model, adapter, - ) \ No newline at end of file + ) diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index ae0c4621..aa9bb132 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -3,7 +3,6 @@ class TestBuiltinOperators(TestCase): - def test_key_match(self): self.assertFalse(util.key_match_func("/foo", "/")) self.assertTrue(util.key_match_func("/foo", "/foo")) @@ -23,23 +22,31 @@ def test_key_match2(self): self.assertTrue(util.key_match2_func("/foo", "/foo")) self.assertTrue(util.key_match2_func("/foo", "/foo*")) self.assertFalse(util.key_match2_func("/foo", "/foo/*")) - self.assertFalse(util.key_match2_func("/foo/bar", "/foo")) # different with KeyMatch. + self.assertFalse( + util.key_match2_func("/foo/bar", "/foo") + ) # different with KeyMatch. self.assertFalse(util.key_match2_func("/foo/bar", "/foo*")) self.assertTrue(util.key_match2_func("/foo/bar", "/foo/*")) - self.assertFalse(util.key_match2_func("/foobar", "/foo")) # different with KeyMatch. + self.assertFalse( + util.key_match2_func("/foobar", "/foo") + ) # different with KeyMatch. self.assertFalse(util.key_match2_func("/foobar", "/foo*")) self.assertFalse(util.key_match2_func("/foobar", "/foo/*")) self.assertFalse(util.key_match2_func("/", "/:resource")) self.assertTrue(util.key_match2_func("/resource1", "/:resource")) self.assertFalse(util.key_match2_func("/myid", "/:id/using/:resId")) - self.assertTrue(util.key_match2_func("/myid/using/myresid", "/:id/using/:resId")) + self.assertTrue( + util.key_match2_func("/myid/using/myresid", "/:id/using/:resId") + ) self.assertFalse(util.key_match2_func("/proxy/myid", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/res", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/res/res2", "/proxy/:id/*")) - self.assertTrue(util.key_match2_func("/proxy/myid/res/res2/res3", "/proxy/:id/*")) + self.assertTrue( + util.key_match2_func("/proxy/myid/res/res2/res3", "/proxy/:id/*") + ) self.assertFalse(util.key_match2_func("/proxy/", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/alice", "/:id")) @@ -63,16 +70,22 @@ def test_key_match3(self): self.assertFalse(util.key_match3_func("/", "/{resource}")) self.assertTrue(util.key_match3_func("/resource1", "/{resource}")) self.assertFalse(util.key_match3_func("/myid", "/{id}/using/{resId}")) - self.assertTrue(util.key_match3_func("/myid/using/myresid", "/{id}/using/{resId}")) + self.assertTrue( + util.key_match3_func("/myid/using/myresid", "/{id}/using/{resId}") + ) self.assertFalse(util.key_match3_func("/proxy/myid", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/res", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/res/res2", "/proxy/{id}/*")) - self.assertTrue(util.key_match3_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*")) + self.assertTrue( + util.key_match3_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*") + ) self.assertFalse(util.key_match3_func("/proxy/", "/proxy/{id}/*")) - self.assertFalse(util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}")) + self.assertFalse( + util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}") + ) def test_regex_match(self): self.assertTrue(util.regex_match_func("/topic/create", "/topic/create")) @@ -81,9 +94,15 @@ def test_regex_match(self): self.assertFalse(util.regex_match_func("/topic/edit", "/topic/edit/[0-9]+")) self.assertTrue(util.regex_match_func("/topic/edit/123", "/topic/edit/[0-9]+")) self.assertFalse(util.regex_match_func("/topic/edit/abc", "/topic/edit/[0-9]+")) - self.assertFalse(util.regex_match_func("/foo/delete/123", "/topic/delete/[0-9]+")) - self.assertTrue(util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+")) - self.assertFalse(util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+")) + self.assertFalse( + util.regex_match_func("/foo/delete/123", "/topic/delete/[0-9]+") + ) + self.assertTrue( + util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+") + ) + self.assertFalse( + util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+") + ) def test_glob_match(self): self.assertTrue(util.glob_match_func("/foo", "/foo")) diff --git a/tests/util/test_rwlock.py b/tests/util/test_rwlock.py index aa91940e..38c7c791 100644 --- a/tests/util/test_rwlock.py +++ b/tests/util/test_rwlock.py @@ -4,8 +4,8 @@ import time import queue -class TestRWLock(TestCase): +class TestRWLock(TestCase): def gen_locks(self): rw_lock = RWLockWrite() rl = rw_lock.gen_rlock() @@ -14,8 +14,8 @@ def gen_locks(self): def test_multiple_readers(self): [rl, _] = self.gen_locks() - - delay = 5/1000 #5ms + + delay = 5 / 1000 # 5ms num_readers = 10 start = time.time() @@ -27,13 +27,13 @@ def read(): futures = [executor.submit(read) for i in range(num_readers)] [future.result() for future in futures] exec_time = time.time() - start - - self.assertLess(exec_time, delay*num_readers) + + self.assertLess(exec_time, delay * num_readers) def test_single_writer(self): [_, wl] = self.gen_locks() - delay = 5/1000 #5ms + delay = 5 / 1000 # 5ms num_writers = 10 start = time.time() @@ -45,41 +45,36 @@ def write(): futures = [executor.submit(write) for i in range(num_writers)] [future.result() for future in futures] exec_time = time.time() - start - - self.assertGreaterEqual(exec_time, delay*num_writers) + + self.assertGreaterEqual(exec_time, delay * num_writers) def test_writer_preference(self): [rl, wl] = self.gen_locks() q = queue.Queue() - delay = 5/1000 #5ms + delay = 5 / 1000 # 5ms start = time.time() def read(): with rl: time.sleep(delay) - q.put('r') + q.put("r") def write(): with wl: time.sleep(delay) - q.put('w') + q.put("w") executor = ThreadPoolExecutor(10) futures = [executor.submit(read) for i in range(3)] - time.sleep(1/1000) + time.sleep(1 / 1000) futures += [executor.submit(write) for i in range(3)] - time.sleep(1/1000) + time.sleep(1 / 1000) futures += [executor.submit(read) for i in range(3)] [future.result() for future in futures] - sequence = '' + sequence = "" while not q.empty(): sequence += q.get() - self.assertEqual(sequence, 'rrrwwwrrr') - - - - - + self.assertEqual(sequence, "rrrwwwrrr") diff --git a/tests/util/test_util.py b/tests/util/test_util.py index 3216e821..0b4be0e4 100644 --- a/tests/util/test_util.py +++ b/tests/util/test_util.py @@ -3,27 +3,42 @@ class TestUtil(TestCase): - def test_remove_comments(self): - self.assertEqual(util.remove_comments("r.act == p.act # comments"), "r.act == p.act") - self.assertEqual(util.remove_comments("r.act == p.act#comments"), "r.act == p.act") + self.assertEqual( + util.remove_comments("r.act == p.act # comments"), "r.act == p.act" + ) + self.assertEqual( + util.remove_comments("r.act == p.act#comments"), "r.act == p.act" + ) self.assertEqual(util.remove_comments("r.act == p.act###"), "r.act == p.act") self.assertEqual(util.remove_comments("### comments"), "") self.assertEqual(util.remove_comments("r.act == p.act"), "r.act == p.act") def test_escape_assertion(self): - self.assertEqual(util.escape_assertion("m = r.sub == p.sub && r.obj == p.obj && r.act == p.act"), - "m = r_sub == p_sub && r_obj == p_obj && r_act == p_act") + self.assertEqual( + util.escape_assertion( + "m = r.sub == p.sub && r.obj == p.obj && r.act == p.act" + ), + "m = r_sub == p_sub && r_obj == p_obj && r_act == p_act", + ) def test_array_remove_duplicates(self): - res = util.array_remove_duplicates(["data", "data1", "data2", "data1", "data2", "data3"]) - self.assertEqual(res, ['data', 'data1', 'data2', 'data3']) + res = util.array_remove_duplicates( + ["data", "data1", "data2", "data1", "data2", "data3"] + ) + self.assertEqual(res, ["data", "data1", "data2", "data3"]) def test_array_to_string(self): - self.assertEqual(util.array_to_string(['data', 'data1', 'data2', 'data3']), "data, data1, data2, data3") + self.assertEqual( + util.array_to_string(["data", "data1", "data2", "data3"]), + "data, data1, data2, data3", + ) def test_params_to_string(self): - self.assertEqual(util.params_to_string('data', 'data1', 'data2', 'data3'), "data, data1, data2, data3") + self.assertEqual( + util.params_to_string("data", "data1", "data2", "data3"), + "data, data1, data2, data3", + ) def test_has_eval(self): self.assertTrue(util.has_eval("eval() && a && b && c")) @@ -35,12 +50,25 @@ def test_has_eval(self): self.assertTrue(util.has_eval("eval(a) && eval(b) && a && b && c")) def test_replace_eval(self): - self.assertEqual(util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c") - self.assertEqual(util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)") + self.assertEqual( + util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c" + ) + self.assertEqual( + util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)" + ) def test_get_eval_value(self): self.assertEqual(util.get_eval_value("eval(a) && a && b && c"), ["a"]) self.assertEqual(util.get_eval_value("a && eval(a) && b && c"), ["a"]) - self.assertEqual(util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"]) - self.assertEqual(util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"]) - self.assertEqual(util.get_eval_value("eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)"), ["p.sub_rule", "p.domain_rule"]) + self.assertEqual( + util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"] + ) + self.assertEqual( + util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"] + ) + self.assertEqual( + util.get_eval_value( + "eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)" + ), + ["p.sub_rule", "p.domain_rule"], + ) From d497f95452cfd11148bed10bdc34b379c90fc36f Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Wed, 16 Jun 2021 23:00:31 +0800 Subject: [PATCH 153/349] fix: get_implicit_users_for_permission() (#168) ci: upgrade node.js version test: addded more comprehensive tests for get_implicit_users_for_permission() Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- .github/workflows/build.yml | 6 ++++-- casbin/enforcer.py | 14 ++++++++------ tests/test_rbac_api.py | 9 +++++++++ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6baf0c8e..92b1350d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -81,7 +81,9 @@ jobs: fetch-depth: 0 - name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 + with: + node-version: '16' - name: Setup run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi @@ -98,4 +100,4 @@ jobs: env: GH_TOKEN: ${{ secrets.GH_TOKEN }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - run: npx semantic-release + run: npx semantic-release diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 8f5d71c8..69a372af 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,5 +1,5 @@ from casbin.management_enforcer import ManagementEnforcer -from casbin.util import join_slice, set_subtract +from casbin.util import join_slice, array_remove_duplicates, set_subtract class Enforcer(ManagementEnforcer): @@ -174,13 +174,15 @@ def get_implicit_users_for_permission(self, *permission): get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. Note: only users will be returned, roles (2nd arg in "g") will be excluded. """ - subjects = self.get_all_subjects() - roles = self.get_all_roles() - - users = set_subtract(subjects, roles) + p_subjects = self.get_all_subjects() + g_inherit = self.model.get_values_for_field_in_policy("g", "g", 1) + g_subjects = self.model.get_values_for_field_in_policy("g", "g", 0) + subjects = array_remove_duplicates(g_subjects + p_subjects) res = list() - for user in users: + subjects = set_subtract(subjects, g_inherit) + + for user in subjects: req = join_slice(user, *permission) allowed = self.enforce(*req) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 137b3bf6..9f058d70 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -296,6 +296,15 @@ def test_implicit_user_api(self): ["alice", "bob"], e.get_implicit_users_for_permission("data2", "write") ) + e.clear_policy() + e.add_policy("admin", "data1", "read") + e.add_policy("bob", "data1", "read") + e.add_grouping_policy("alice", "admin") + + self.assertEqual( + ["alice", "bob"], e.get_implicit_users_for_permission("data1", "read") + ) + class TestRbacApiSynced(TestRbacApi): def get_enforcer(self, model=None, adapter=None): From 86cdf2f4e15367164f9e4db2d623d44a118b75d4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 16 Jun 2021 15:03:04 +0000 Subject: [PATCH 154/349] chore(release): 1.1.3 [skip ci] ## [1.1.3](https://github.com/casbin/pycasbin/compare/v1.1.2...v1.1.3) (2021-06-16) ### Bug Fixes * get_implicit_users_for_permission() ([#168](https://github.com/casbin/pycasbin/issues/168)) ([92e1110](https://github.com/casbin/pycasbin/commit/92e1110e18b73cf993935bbf9db3bfb79be59e67)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efba723c..c7375cc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.1.3](https://github.com/casbin/pycasbin/compare/v1.1.2...v1.1.3) (2021-06-16) + + +### Bug Fixes + +* get_implicit_users_for_permission() ([#168](https://github.com/casbin/pycasbin/issues/168)) ([92e1110](https://github.com/casbin/pycasbin/commit/92e1110e18b73cf993935bbf9db3bfb79be59e67)) + ## [1.1.2](https://github.com/casbin/pycasbin/compare/v1.1.1...v1.1.2) (2021-06-07) diff --git a/setup.cfg b/setup.cfg index f0295041..af0dd5b6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.1.2 +version = 1.1.3 From d389d57d1bd670fa6dd41eedcb3e9e1bd4c9ab16 Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Thu, 17 Jun 2021 14:27:21 +0800 Subject: [PATCH 155/349] ci: use "github-actions" to send release notice Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- .github/workflows/build.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 92b1350d..be120595 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,10 +13,6 @@ jobs: matrix: python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-18.04, macOS-latest, windows-latest] -# pypy3 currently fails trying to find the header #include "codecs.h". Removed pypy3 for now. -# include: -# - python-version: pypy3 -# os: ubuntu-latest steps: - name: Checkout @@ -98,6 +94,6 @@ jobs: - name: Release env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} run: npx semantic-release From eeed04bf3fb7894590a543214cfeb3a26993c056 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Tue, 22 Jun 2021 00:15:33 +0800 Subject: [PATCH 156/349] docs: Add asynccasbin to README. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8461970c..f7b7d4b6 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ PyCasbin [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) +**News**: Async version PyCasbin can be found at: https://pypi.org/project/asynccasbin/ + **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ Casbin is a powerful and efficient open-source access control library for Python projects. It provides support for enforcing authorization based on various [access control models](https://en.wikipedia.org/wiki/Computer_security_model). From e917aafcd5879958df15fae22c996b3ed56dbc25 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 27 Jun 2021 22:15:17 +0800 Subject: [PATCH 157/349] feat: add support for add_domain_matching_function (#165) * feat: add support for add_domain_matching_function Signed-off-by: Zxilly * style: remove unused code Signed-off-by: Zxilly * perf: fix enforcer API Signed-off-by: Zxilly * test: fix readlock tests Signed-off-by: Zxilly * fix: add error handler for potential keymatch error Signed-off-by: Zxilly * style: reformat by black Signed-off-by: Zxilly --- casbin/core_enforcer.py | 6 +- casbin/enforcer.py | 2 +- .../rbac/default_role_manager/role_manager.py | 310 ++++++++++++------ casbin/util/builtin_operators.py | 3 + examples/rbac_with_all_pattern_model.conf | 14 + examples/rbac_with_all_pattern_policy.csv | 4 + examples/rbac_with_domain_pattern_model.conf | 14 + examples/rbac_with_domain_pattern_policy.csv | 7 + requirements.txt | 2 +- tests/test_rbac_api.py | 27 +- tests/util/test_rwlock.py | 2 +- 11 files changed, 273 insertions(+), 118 deletions(-) create mode 100644 examples/rbac_with_all_pattern_model.conf create mode 100644 examples/rbac_with_all_pattern_policy.csv create mode 100644 examples/rbac_with_domain_pattern_model.conf create mode 100644 examples/rbac_with_domain_pattern_policy.csv diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index fbdc8c28..5d0ef772 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -237,11 +237,11 @@ def add_named_matching_func(self, ptype, fn): def add_named_domain_matching_func(self, ptype, fn): """add_named_domain_matching_func add MatchingFunc by ptype to RoleManager""" - try: + if ptype in self.rm_map.keys(): self.rm_map[ptype].add_domain_matching_func(fn) return True - except: - return False + + return False def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 69a372af..42b9c195 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -136,7 +136,7 @@ def get_implicit_roles_for_user(self, name, domain=None): return res - def get_implicit_permissions_for_user(self, user, domain=None): + def get_implicit_permissions_for_user(self, user, domain=""): """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 926b5222..955b0117 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -9,14 +9,14 @@ class RoleManager(RoleManager): all_roles = dict() max_hierarchy_level = 0 - def __init__(self, max_hierarchy_level): + def __init__(self, max_hierarchy_level=10): self.logger = logging.getLogger(__name__) self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level self.matching_func = None self.domain_matching_func = None - self.has_pattern = None - self.has_domain_pattern = None + self.has_pattern = False + self.has_domain_pattern = False def add_matching_func(self, fn=None): self.has_pattern = True @@ -26,99 +26,152 @@ def add_domain_matching_func(self, fn=None): self.has_domain_pattern = True self.domain_matching_func = fn - def has_role(self, name): - if self.matching_func is None: - return name in self.all_roles.keys() + def has_role(self, role): + + if not self.has_pattern and not self.has_domain_pattern: + return role in self.all_roles.values() + + for known_role in list(self.all_roles.values()): + if self.has_pattern: + if not self.matching_func(role.name, known_role.name): + continue + else: + if not role.name == known_role.name: + continue + + if self.has_domain_pattern: + if not self.domain_matching_func(role.domain, known_role.domain): + continue + else: + if not role.domain == known_role.domain: + continue + return True + + def create_role(self, name, domain=""): + role = Role(name, domain) + if domain: + key = domain + "::" + name else: - for key in self.all_roles.keys(): - if self.matching_func(name, key): - return True - return False + key = name - def create_role(self, name): - if name not in self.all_roles.keys(): - self.all_roles[name] = Role(name) + if key not in self.all_roles.keys(): + self.all_roles[key] = role - return self.all_roles[name] + return self.all_roles[key] def clear(self): self.all_roles.clear() def add_link(self, name1, name2, *domain): - if len(domain) == 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 - elif len(domain) > 1: + if len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") + elif len(domain) == 1: + domain = domain[0] + else: + domain = "" - role1 = self.create_role(name1) - role2 = self.create_role(name2) + role1 = self.create_role(name1, domain) + role2 = self.create_role(name2, domain) role1.add_role(role2) - if self.matching_func is not None: - for key, role in self.all_roles.items(): - if self.matching_func(key, name1) and name1 != key: - self.all_roles[key].add_role(role1) - if self.matching_func(key, name2) and name2 != key: - self.all_roles[name2].add_role(role) - if self.matching_func(name1, key) and name1 != key: - self.all_roles[key].add_role(role1) - if self.matching_func(name2, key) and name2 != key: - self.all_roles[name2].add_role(role) + if self.has_pattern: + for role in self.all_roles.values(): + if self.has_domain_pattern: + if not self.domain_matching_func(domain, role.domain): + continue + else: + if domain != role.domain: + continue + + def duplicate_judge(): + return role1.name != role.name and role2.name != role.name + + if ( + match_error_handler(self.matching_func, role.name, role1.name) + or match_error_handler(self.matching_func, role1.name, role.name) + and duplicate_judge() + ): + self.all_roles[role.get_key()].add_role(role1) + + if ( + match_error_handler(self.matching_func, role.name, role2.name) + or match_error_handler(self.matching_func, role2.name, role.name) + and duplicate_judge() + ): + self.all_roles[role2.get_key()].add_role(role) def delete_link(self, name1, name2, *domain): - if len(domain) == 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 - elif len(domain) > 1: - raise RuntimeError("error: domain should be 1 parameter") + role1, role2 = two_role_domain_wrapper(self, name1, name2, domain) - if not self.has_role(name1) or not self.has_role(name2): + if not self.has_role(role1) or not self.has_role(role2): raise RuntimeError("error: name1 or name2 does not exist") - role1 = self.create_role(name1) - role2 = self.create_role(name2) role1.delete_role(role2) def has_link(self, name1, name2, *domain): - if len(domain) == 1: - name1 = domain[0] + "::" + name1 - name2 = domain[0] + "::" + name2 - elif len(domain) > 1: + if len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") + elif len(domain) == 1: + domain = domain[0] + else: + domain = "" + + role1, role2 = two_role_domain_wrapper(self, name1, name2, domain) - if name1 == name2: + if role1 == role2: return True - if not self.has_role(name1) or not self.has_role(name2): + if not self.has_role(role1) or not self.has_role(role2): return False - if self.matching_func is None: - role1 = self.create_role(name1) - return role1.has_role(name2, self.max_hierarchy_level) - else: - for key, role in self.all_roles.items(): - if self.matching_func(name1, key) and role.has_role( - name2, self.max_hierarchy_level, self.matching_func + if not self.has_pattern and not self.has_domain_pattern: + return role1.has_role(role2, self.max_hierarchy_level, None, None) + + # Here is has_pattern logic. + for role in self.all_roles.values(): + if self.has_domain_pattern: + if not self.domain_matching_func(domain, role.domain): + continue + else: + if role.domain != domain: + continue + + def role_judge(): + if role.has_role( + role2, + self.max_hierarchy_level, + self.matching_func, + self.domain_matching_func, ): return True - return False + return False + + if self.has_pattern: + if self.matching_func(role1.name, role.name): + return role_judge() + else: + if role1.name == role.name: + return role_judge() + return False - def get_roles(self, name, domain=None): + def get_roles(self, name, *domain): """ gets the roles that a subject inherits. domain is a prefix to the roles. """ - if domain: - name = domain + "::" + name + if len(domain) > 1: + raise RuntimeError("error: domain should be 1 parameter") + elif len(domain) == 1: + domain = domain[0] + else: + domain = "" + + role = role_domain_wrapper(self, name, domain) - if not self.has_role(name): + if not self.has_role(role): return [] - roles = self.create_role(name).get_roles() - if domain: - for key, value in enumerate(roles): - roles[key] = value[len(domain) + 2 :] + roles = self.create_role(name, domain).get_roles() return roles @@ -127,23 +180,17 @@ def get_users(self, name, *domain): gets the users that inherits a subject. domain is an unreferenced parameter here, may be used in other implementations. """ - if len(domain) == 1: - name = domain[0] + "::" + name - elif len(domain) > 1: - return RuntimeError("error: domain should be 1 parameter") + target_role = role_domain_wrapper(self, name, domain) - if not self.has_role(name): + if not self.has_role(target_role): return [] - names = [] + roles = [] for role in self.all_roles.values(): - if role.has_direct_role(name): - if len(domain) == 1: - names.append(role.name[len(domain[0]) + 2 :]) - else: - names.append(role.name) + if role.has_direct_role(target_role): + roles.append(role.name) - return names + return roles def print_roles(self): line = [] @@ -157,48 +204,74 @@ def print_roles(self): class Role: """represents the data structure for a role in RBAC.""" - name = "" - - roles = [] - - def __init__(self, name): + def __init__(self, name: str, domain: str = ""): self.name = name self.roles = [] - - def add_role(self, role): - for rr in self.roles: - if rr.name == role.name: - return - + self.domain = domain + + def __eq__(self, other: "Role"): + return ( + type(other) == type(self) + and self.name == other.name + and self.domain == other.domain + ) + + def __hash__(self): + return hash(self.name + "::" + self.domain) + + def get_key(self): + if self.domain: + return self.domain + "::" + self.name + return self.name + + def add_role(self, role: "Role"): + if role in self.roles: + return self.roles.append(role) - def delete_role(self, role): - for rr in self.roles: - if rr.name == role.name: - self.roles.remove(rr) - return + def delete_role(self, role: "Role"): + if role in self.roles: + self.roles.remove(role) + + def has_role( + self, + role: "Role", + hierarchy_level: int, + matching_func=None, + domain_matching_func=None, + ): - def has_role(self, name, hierarchy_level, matching_func=None): - if self.has_direct_role(name, matching_func): + if self.has_direct_role(role, matching_func, domain_matching_func): return True if hierarchy_level <= 0: return False - for role in self.roles: - if role.has_role(name, hierarchy_level - 1, matching_func): + for knownRole in self.roles: + if knownRole.has_role( + role, hierarchy_level - 1, matching_func, domain_matching_func + ): return True return False - def has_direct_role(self, name, matching_func=None): - if matching_func is None: - for role in self.roles: - if role.name == name: - return True - else: - for role in self.roles: - if matching_func(name, role.name): - return True + def has_direct_role( + self, role: "Role", matching_func=None, domain_matching_func=None + ): + for known_role in self.roles: + if matching_func: + if not matching_func(role.name, known_role.name): + continue + else: + if not role.name == known_role.name: + continue + + if domain_matching_func: + if not domain_matching_func(role.domain, known_role.domain): + continue + else: + if not role.domain == known_role.domain: + continue + return True return False def to_string(self): @@ -213,8 +286,37 @@ def to_string(self): return self.name + " < (" + names + ")" def get_roles(self): - names = [] + roles = [] for role in self.roles: - names.append(role.name) + roles.append(role.name) + + return roles + + +def role_domain_wrapper(obj, name, domain): + if type(domain) != str: + if not domain or len(domain) == 0: + domain = "" + elif len(domain) == 1: + domain = domain[0] + elif len(domain) > 1: + raise RuntimeError("error: domain should be 1 parameter") - return names + role = Role(name, domain) + + if not obj.has_role(role): + return role + return obj.create_role(name, domain) + + +def two_role_domain_wrapper(obj, name1, name2, domain): + return role_domain_wrapper(obj, name1, domain), role_domain_wrapper( + obj, name2, domain + ) + + +def match_error_handler(fn, key1, key2): + try: + return fn(key1, key2) + except: + return False diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 80f52139..e93b88f9 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -37,6 +37,9 @@ def key_match2(key1, key2): key2 = key2.replace("/*", "/.*") key2 = KEY_MATCH2_PATTERN.sub(r"\g<1>[^\/]+\g<2>", key2, 0) + if key2 == "*": + key2 = "(.*)" + return regex_match(key1, "^" + key2 + "$") diff --git a/examples/rbac_with_all_pattern_model.conf b/examples/rbac_with_all_pattern_model.conf new file mode 100644 index 00000000..045bfa57 --- /dev/null +++ b/examples/rbac_with_all_pattern_model.conf @@ -0,0 +1,14 @@ +[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 \ No newline at end of file diff --git a/examples/rbac_with_all_pattern_policy.csv b/examples/rbac_with_all_pattern_policy.csv new file mode 100644 index 00000000..8097be8a --- /dev/null +++ b/examples/rbac_with_all_pattern_policy.csv @@ -0,0 +1,4 @@ +p, alice, domain1, book_group, read +p, alice, domain2, book_group, write + +g, /book/:id, book_group, * \ No newline at end of file diff --git a/examples/rbac_with_domain_pattern_model.conf b/examples/rbac_with_domain_pattern_model.conf new file mode 100644 index 00000000..774e4418 --- /dev/null +++ b/examples/rbac_with_domain_pattern_model.conf @@ -0,0 +1,14 @@ +[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 \ No newline at end of file diff --git a/examples/rbac_with_domain_pattern_policy.csv b/examples/rbac_with_domain_pattern_policy.csv new file mode 100644 index 00000000..c8abd35e --- /dev/null +++ b/examples/rbac_with_domain_pattern_policy.csv @@ -0,0 +1,7 @@ +p, 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 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 37a1c82d..d6ba9787 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -simpleeval>=0.9.10 +simpleeval >= 0.9.10 wcmatch >= 8.1.2 \ No newline at end of file diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 9f058d70..1f353aa1 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -172,6 +172,7 @@ def test_enforce_implicit_permissions_api(self): get_examples("rbac_model.conf"), get_examples("rbac_with_hierarchy_policy.csv"), ) + self.assertTrue( e.get_permissions_for_user("alice") == [["alice", "data1", "read"]] ) @@ -220,6 +221,7 @@ def test_enforce_get_users_in_domain(self): get_examples("rbac_with_domains_model.conf"), get_examples("rbac_with_domains_policy.csv"), ) + print(e.get_users_for_role_in_domain("admin", "domain1")) self.assertTrue(e.get_users_for_role_in_domain("admin", "domain1") == ["alice"]) self.assertTrue(e.get_users_for_role_in_domain("non_exist", "domain1") == []) self.assertTrue(e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) @@ -296,14 +298,23 @@ def test_implicit_user_api(self): ["alice", "bob"], e.get_implicit_users_for_permission("data2", "write") ) - e.clear_policy() - e.add_policy("admin", "data1", "read") - e.add_policy("bob", "data1", "read") - e.add_grouping_policy("alice", "admin") - - self.assertEqual( - ["alice", "bob"], e.get_implicit_users_for_permission("data1", "read") - ) + def test_domain_match_model(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_pattern_model.conf"), + get_examples("rbac_with_domain_pattern_policy.csv"), + ) + e.get_role_manager().add_domain_matching_func(casbin.util.key_match2_func) + + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "write")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "read")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "write")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "read")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "write")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) class TestRbacApiSynced(TestRbacApi): diff --git a/tests/util/test_rwlock.py b/tests/util/test_rwlock.py index 38c7c791..f7309f8f 100644 --- a/tests/util/test_rwlock.py +++ b/tests/util/test_rwlock.py @@ -16,7 +16,7 @@ def test_multiple_readers(self): [rl, _] = self.gen_locks() delay = 5 / 1000 # 5ms - num_readers = 10 + num_readers = 1000 start = time.time() def read(): From 3f470d6ab0e59f8f56aa146a3e6cf29c5aa5a08f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 27 Jun 2021 14:18:04 +0000 Subject: [PATCH 158/349] chore(release): 1.2.0 [skip ci] # [1.2.0](https://github.com/casbin/pycasbin/compare/v1.1.3...v1.2.0) (2021-06-27) ### Features * add support for add_domain_matching_function ([#165](https://github.com/casbin/pycasbin/issues/165)) ([19b9503](https://github.com/casbin/pycasbin/commit/19b9503ced6e75ec9a818e2fb1803319ea997166)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7375cc3..dfff34dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.2.0](https://github.com/casbin/pycasbin/compare/v1.1.3...v1.2.0) (2021-06-27) + + +### Features + +* add support for add_domain_matching_function ([#165](https://github.com/casbin/pycasbin/issues/165)) ([19b9503](https://github.com/casbin/pycasbin/commit/19b9503ced6e75ec9a818e2fb1803319ea997166)) + ## [1.1.3](https://github.com/casbin/pycasbin/compare/v1.1.2...v1.1.3) (2021-06-16) diff --git a/setup.cfg b/setup.cfg index af0dd5b6..fbe8bed6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.1.3 +version = 1.2.0 From b569ed8af71c9c2683515583c3085999ca313cfd Mon Sep 17 00:00:00 2001 From: Zxilly Date: Wed, 30 Jun 2021 10:20:05 +0800 Subject: [PATCH 159/349] fix: matching_func works with multiply match (#172) * fix: matching_func works with multipie match Closes https://github.com/casbin/pycasbin/issues/171 Signed-off-by: Zxilly * chore: typo Closes https://github.com/casbin/pycasbin/issues/171 Signed-off-by: Zxilly --- casbin/rbac/default_role_manager/role_manager.py | 8 ++++++-- examples/rbac_with_multiply_matched_pattern.conf | 16 ++++++++++++++++ examples/rbac_with_multiply_matched_pattern.csv | 16 ++++++++++++++++ tests/test_enforcer.py | 10 ++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 examples/rbac_with_multiply_matched_pattern.conf create mode 100644 examples/rbac_with_multiply_matched_pattern.csv diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 955b0117..059409da 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -148,10 +148,14 @@ def role_judge(): if self.has_pattern: if self.matching_func(role1.name, role.name): - return role_judge() + if role_judge(): + return True + continue else: if role1.name == role.name: - return role_judge() + if role_judge(): + return True + continue return False def get_roles(self, name, *domain): diff --git a/examples/rbac_with_multiply_matched_pattern.conf b/examples/rbac_with_multiply_matched_pattern.conf new file mode 100644 index 00000000..908abc79 --- /dev/null +++ b/examples/rbac_with_multiply_matched_pattern.conf @@ -0,0 +1,16 @@ +# https://github.com/casbin/pycasbin/issues/171 +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, role + +[role_definition] +g = _, _ +g2 = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g2(r.act, p.role) && (g(r.sub, p.sub) || p.sub=='*') && keyMatch(r.obj, p.obj) \ No newline at end of file diff --git a/examples/rbac_with_multiply_matched_pattern.csv b/examples/rbac_with_multiply_matched_pattern.csv new file mode 100644 index 00000000..ab2aa3fb --- /dev/null +++ b/examples/rbac_with_multiply_matched_pattern.csv @@ -0,0 +1,16 @@ +# https://github.com/casbin/pycasbin/issues/171 +p, root, *, owner + +g, root@localhost, root + +g2, *.read, viewer + +g2, *.read, editor +g2, *.update, editor + +g2, *.read, admin +g2, *.update, admin +g2, *.create, admin +g2, *.delete, admin + +g2, *.*, owner \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 534da6a8..b63ef51f 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -247,6 +247,16 @@ def test_enforce_rbac_with_pattern(self): self.assertTrue(e.enforce("bob", "/pen2/1", "GET")) self.assertTrue(e.enforce("bob", "/pen2/2", "GET")) + def test_rbac_with_multipy_matched_pattern(self): + e = self.get_enforcer( + get_examples("rbac_with_multiply_matched_pattern.conf"), + get_examples("rbac_with_multiply_matched_pattern.csv"), + ) + + e.add_named_matching_func("g2", casbin.util.glob_match) + + self.assertTrue(e.enforce("root@localhost", "/", "org.create")) + def test_enforce_abac_log_enabled(self): e = self.get_enforcer(get_examples("abac_model.conf")) sub = "alice" From 7b2f634717a7dcbc8fcdb9629760d23e8aa32599 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Jun 2021 02:22:13 +0000 Subject: [PATCH 160/349] chore(release): 1.2.1 [skip ci] ## [1.2.1](https://github.com/casbin/pycasbin/compare/v1.2.0...v1.2.1) (2021-06-30) ### Bug Fixes * matching_func works with multiply match ([#172](https://github.com/casbin/pycasbin/issues/172)) ([bbe45ad](https://github.com/casbin/pycasbin/commit/bbe45ad8a526477d0c0ca3c3915896e85cbfb2f4)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dfff34dc..d1a2b098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.2.1](https://github.com/casbin/pycasbin/compare/v1.2.0...v1.2.1) (2021-06-30) + + +### Bug Fixes + +* matching_func works with multiply match ([#172](https://github.com/casbin/pycasbin/issues/172)) ([bbe45ad](https://github.com/casbin/pycasbin/commit/bbe45ad8a526477d0c0ca3c3915896e85cbfb2f4)) + # [1.2.0](https://github.com/casbin/pycasbin/compare/v1.1.3...v1.2.0) (2021-06-27) diff --git a/setup.cfg b/setup.cfg index fbe8bed6..c79c8b4d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.2.0 +version = 1.2.1 From 02f144c4caf806071f3f42d6a415baabbc1b82d2 Mon Sep 17 00:00:00 2001 From: Zxilly Date: Sun, 4 Jul 2021 23:41:28 +0800 Subject: [PATCH 161/349] feat: add API for role manager besides "g" (#173) Signed-off-by: Zxilly --- casbin/core_enforcer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 5d0ef772..93a8f426 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -137,13 +137,20 @@ def get_role_manager(self): """gets the current role manager.""" return self.rm_map["g"] + def get_named_role_manager(self, ptype): + if ptype in self.rm_map.keys(): + return self.rm_map.get(ptype) + raise ValueError("ptype not found") + def set_role_manager(self, rm): """sets the current role manager.""" self.rm_map["g"] = rm + def set_named_role_manager(self, ptype, rm): + self.rm_map[ptype] = rm + def set_effector(self, eft): """sets the current effector.""" - self.eft = eft def clear_policy(self): From 71efb8fc015c9a7e2a98104c77c736d97d10206a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 4 Jul 2021 15:43:42 +0000 Subject: [PATCH 162/349] chore(release): 1.3.0 [skip ci] # [1.3.0](https://github.com/casbin/pycasbin/compare/v1.2.1...v1.3.0) (2021-07-04) ### Features * add API for role manager besides "g" ([#173](https://github.com/casbin/pycasbin/issues/173)) ([23d39a5](https://github.com/casbin/pycasbin/commit/23d39a56da06968ab76659a30b430054c45db14b)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a2b098..0f1e6843 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.3.0](https://github.com/casbin/pycasbin/compare/v1.2.1...v1.3.0) (2021-07-04) + + +### Features + +* add API for role manager besides "g" ([#173](https://github.com/casbin/pycasbin/issues/173)) ([23d39a5](https://github.com/casbin/pycasbin/commit/23d39a56da06968ab76659a30b430054c45db14b)) + ## [1.2.1](https://github.com/casbin/pycasbin/compare/v1.2.0...v1.2.1) (2021-06-30) diff --git a/setup.cfg b/setup.cfg index c79c8b4d..86b5d989 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.2.1 +version = 1.3.0 From b5e943c152812600d6d73fec583f583356069b9c Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Fri, 9 Jul 2021 23:56:31 +0800 Subject: [PATCH 163/349] refactor: modify model structure (#174) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/core_enforcer.py | 28 ++++++------- casbin/model/model.py | 8 ++-- casbin/model/policy.py | 87 ++++++++++++++++++++++++----------------- tests/test_enforcer.py | 2 +- 4 files changed, 70 insertions(+), 55 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 93a8f426..60a104fc 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -75,7 +75,7 @@ def init_with_model_and_adapter(self, m, adapter=None): def _initialize(self): self.rm_map = dict() - self.eft = get_effector(self.model.model["e"]["e"].value) + self.eft = get_effector(self.model["e"]["e"].value) self.watcher = None self.enabled = True @@ -159,8 +159,8 @@ def clear_policy(self): self.model.clear_policy() def init_rm_map(self): - if "g" in self.model.model.keys(): - for ptype in self.model.model["g"]: + if "g" in self.model.keys(): + for ptype in self.model["g"]: self.rm_map[ptype] = default_role_manager.RoleManager(10) def load_policy(self): @@ -268,24 +268,24 @@ def enforce_ex(self, *rvals): functions = self.fm.get_functions() - if "g" in self.model.model.keys(): - for key, ast in self.model.model["g"].items(): + if "g" in self.model.keys(): + for key, ast in self.model["g"].items(): rm = ast.rm functions[key] = generate_g_function(rm) - if "m" not in self.model.model.keys(): + if "m" not in self.model.keys(): raise RuntimeError("model is undefined") - if "m" not in self.model.model["m"].keys(): + if "m" not in self.model["m"].keys(): raise RuntimeError("model is undefined") - r_tokens = self.model.model["r"]["r"].tokens - p_tokens = self.model.model["p"]["p"].tokens + r_tokens = self.model["r"]["r"].tokens + p_tokens = self.model["p"]["p"].tokens if len(r_tokens) != len(rvals): raise RuntimeError("invalid request size") - exp_string = self.model.model["m"]["m"].value + exp_string = self.model["m"]["m"].value has_eval = util.has_eval(exp_string) if not has_eval: expression = self._get_expression(exp_string, functions) @@ -294,11 +294,11 @@ def enforce_ex(self, *rvals): r_parameters = dict(zip(r_tokens, rvals)) - policy_len = len(self.model.model["p"]["p"].policy) + policy_len = len(self.model["p"]["p"].policy) explain_index = -1 if not 0 == policy_len: - for i, pvals in enumerate(self.model.model["p"]["p"].policy): + for i, pvals in enumerate(self.model["p"]["p"].policy): if len(p_tokens) != len(pvals): raise RuntimeError("invalid policy size") @@ -353,7 +353,7 @@ def enforce_ex(self, *rvals): parameters = r_parameters.copy() - for token in self.model.model["p"]["p"].tokens: + for token in self.model["p"]["p"].tokens: parameters[token] = "" result = expression.eval(parameters) @@ -380,7 +380,7 @@ def enforce_ex(self, *rvals): explain_rule = [] if explain_index != -1 and explain_index < policy_len: - explain_rule = self.model.model["p"]["p"].policy[explain_index] + explain_rule = self.model["p"]["p"].policy[explain_index] return result, explain_rule diff --git a/casbin/model/model.py b/casbin/model/model.py index 8047f686..63919312 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -33,10 +33,10 @@ def add_def(self, sec, key, value): else: ast.value = util.remove_comments(util.escape_assertion(ast.value)) - if sec not in self.model.keys(): - self.model[sec] = {} + if sec not in self.keys(): + self[sec] = {} - self.model[sec][key] = ast + self[sec][key] = ast return True @@ -76,6 +76,6 @@ def load_model_from_text(self, text): def print_model(self): self.logger.info("Model:") - for k, v in self.model.items(): + for k, v in self.items(): for i, j in v.items(): self.logger.info("%s.%s: %s", k, i, j.value) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index d447dd4f..a0a087b3 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -6,51 +6,66 @@ def __init__(self): self.logger = logging.getLogger(__name__) self.model = {} + def __getitem__(self, item): + return self.model.get(item) + + def __setitem__(self, key, value): + self.model[key] = value + + def keys(self): + return self.model.keys() + + def values(self): + return self.model.values() + + def items(self): + return self.model.items() + def build_role_links(self, rm_map): """initializes the roles in RBAC.""" - if "g" not in self.model.keys(): + if "g" not in self.keys(): return - for ptype, ast in self.model["g"].items(): + for ptype, ast in self["g"].items(): rm = rm_map[ptype] ast.build_role_links(rm) def build_incremental_role_links(self, rm, op, sec, ptype, rules): if sec == "g": - self.model.get(sec).get(ptype).build_incremental_role_links(rm, op, rules) + self[sec].get(ptype).build_incremental_role_links(rm, op, rules) def print_policy(self): """Log using info""" self.logger.info("Policy:") for sec in ["p", "g"]: - if sec not in self.model.keys(): + if sec not in self.keys(): continue - for key, ast in self.model[sec].items(): + for key, ast in self[sec].items(): self.logger.info("{} : {} : {}".format(key, ast.value, ast.policy)) def clear_policy(self): """clears all current policy.""" for sec in ["p", "g"]: - if sec not in self.model.keys(): + if sec not in self.keys(): continue - for key in self.model[sec].keys(): - self.model[sec][key].policy = [] + for key in self[sec].keys(): + self[sec][key].policy = [] def get_policy(self, sec, ptype): """gets all rules in a policy.""" - return self.model[sec][ptype].policy + return self[sec][ptype].policy def get_filtered_policy(self, sec, ptype, field_index, *field_values): """gets rules based on field filters from a policy.""" return [ rule - for rule in self.model[sec][ptype].policy + for rule in self[sec][ptype].policy if all( value == "" or rule[field_index + i] == value for i, value in enumerate(field_values) @@ -59,18 +74,18 @@ def get_filtered_policy(self, sec, ptype, field_index, *field_values): def has_policy(self, sec, ptype, rule): """determines whether a model has the specified policy rule.""" - if sec not in self.model.keys(): + if sec not in self.keys(): return False - if ptype not in self.model[sec]: + if ptype not in self[sec]: return False - return rule in self.model[sec][ptype].policy + return rule in self[sec][ptype].policy def add_policy(self, sec, ptype, rule): """adds a policy rule to the model.""" if not self.has_policy(sec, ptype, rule): - self.model[sec][ptype].policy.append(rule) + self[sec][ptype].policy.append(rule) return True return False @@ -83,19 +98,19 @@ def add_policies(self, sec, ptype, rules): return False for rule in rules: - self.model[sec][ptype].policy.append(rule) + self[sec][ptype].policy.append(rule) return True def update_policy(self, sec, ptype, old_rule, new_rule): """update a policy rule from the model.""" - if sec not in self.model.keys(): + if sec not in self.keys(): return False - if ptype not in self.model[sec]: + if ptype not in self[sec]: return False - ast = self.model[sec][ptype] + ast = self[sec][ptype] if old_rule in ast.policy: rule_index = ast.policy.index(old_rule) @@ -116,14 +131,14 @@ def update_policy(self, sec, ptype, old_rule, new_rule): def update_policies(self, sec, ptype, old_rules, new_rules): """update policy rules from the model.""" - if sec not in self.model.keys(): + if sec not in self.keys(): return False - if ptype not in self.model[sec]: + if ptype not in self[sec]: return False if len(old_rules) != len(new_rules): return False - ast = self.model[sec][ptype] + ast = self[sec][ptype] old_rules_index = [] for old_rule in old_rules: @@ -152,9 +167,9 @@ def remove_policy(self, sec, ptype, rule): if not self.has_policy(sec, ptype, rule): return False - self.model[sec][ptype].policy.remove(rule) + self[sec][ptype].policy.remove(rule) - return rule not in self.model[sec][ptype].policy + return rule not in self[sec][ptype].policy def remove_policies(self, sec, ptype, rules): """RemovePolicies removes policy rules from the model.""" @@ -162,8 +177,8 @@ def remove_policies(self, sec, ptype, rules): for rule in rules: if not self.has_policy(sec, ptype, rule): return False - self.model[sec][ptype].policy.remove(rule) - if rule in self.model[sec][ptype].policy: + self[sec][ptype].policy.remove(rule) + if rule in self[sec][ptype].policy: return False return True @@ -188,12 +203,12 @@ def remove_filtered_policy_returns_effects( if len(field_values) == 0: return [] - if sec not in self.model.keys(): + if sec not in self.keys(): return [] - if ptype not in self.model[sec]: + if ptype not in self[sec]: return [] - for rule in self.model[sec][ptype].policy: + for rule in self[sec][ptype].policy: if all( value == "" or rule[field_index + i] == value for i, value in enumerate(field_values[0]) @@ -202,7 +217,7 @@ def remove_filtered_policy_returns_effects( else: tmp.append(rule) - self.model[sec][ptype].policy = tmp + self[sec][ptype].policy = tmp return effects @@ -211,12 +226,12 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): tmp = [] res = False - if sec not in self.model.keys(): + if sec not in self.keys(): return res - if ptype not in self.model[sec]: + if ptype not in self[sec]: return res - for rule in self.model[sec][ptype].policy: + for rule in self[sec][ptype].policy: if all( value == "" or rule[field_index + i] == value for i, value in enumerate(field_values) @@ -225,19 +240,19 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): else: tmp.append(rule) - self.model[sec][ptype].policy = tmp + self[sec][ptype].policy = tmp return res def get_values_for_field_in_policy(self, sec, ptype, field_index): """gets all values for a field for all rules in a policy, duplicated values are removed.""" values = [] - if sec not in self.model.keys(): + if sec not in self.keys(): return values - if ptype not in self.model[sec]: + if ptype not in self[sec]: return values - for rule in self.model[sec][ptype].policy: + for rule in self[sec][ptype].policy: value = rule[field_index] if value not in values: values.append(value) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index b63ef51f..37cd4147 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -60,7 +60,7 @@ def test_model_set_load(self): self.assertTrue(e.model is None) # creating new model e.load_model() - self.assertTrue(e.model) + self.assertTrue(e.model is not None) def test_enforcer_basic_without_spaces(self): e = self.get_enforcer( From 6f930c2da7ae76bbebe8e52e81951ad232f10de4 Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Mon, 19 Jul 2021 16:38:34 +0800 Subject: [PATCH 164/349] feat: finish up UpdateAdapter (#177) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/persist/adapters/__init__.py | 1 + casbin/persist/adapters/update_adapter.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index 414ab547..804ae9ca 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -1,2 +1,3 @@ from .file_adapter import FileAdapter from .adapter_filtered import FilteredAdapter +from .update_adapter import UpdateAdapter diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index a6bea8d9..b9356ec6 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -7,3 +7,9 @@ def update_policy(self, sec, ptype, old_rule, new_policy): This is part of the Auto-Save feature. """ pass + + def update_policies(self, sec, ptype, old_rules, new_rules): + """ + UpdatePolicies updates some policy rules to storage, like db, redis. + """ + pass From e27fc6a307897f1f60454dc0aeb2cf61b73ea8be Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 19 Jul 2021 08:40:43 +0000 Subject: [PATCH 165/349] chore(release): 1.4.0 [skip ci] # [1.4.0](https://github.com/casbin/pycasbin/compare/v1.3.0...v1.4.0) (2021-07-19) ### Features * finish up UpdateAdapter ([#177](https://github.com/casbin/pycasbin/issues/177)) ([d69a2df](https://github.com/casbin/pycasbin/commit/d69a2dfa7711aebcf7cec880fa724f5d3b3d3944)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1e6843..e9a80ce6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.4.0](https://github.com/casbin/pycasbin/compare/v1.3.0...v1.4.0) (2021-07-19) + + +### Features + +* finish up UpdateAdapter ([#177](https://github.com/casbin/pycasbin/issues/177)) ([d69a2df](https://github.com/casbin/pycasbin/commit/d69a2dfa7711aebcf7cec880fa724f5d3b3d3944)) + # [1.3.0](https://github.com/casbin/pycasbin/compare/v1.2.1...v1.3.0) (2021-07-04) diff --git a/setup.cfg b/setup.cfg index 86b5d989..278babc8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.3.0 +version = 1.4.0 From a9d6588db92e2cb96626e3eca28a90bc6da4c04e Mon Sep 17 00:00:00 2001 From: ffyuanda Date: Tue, 17 Aug 2021 21:23:37 +0800 Subject: [PATCH 166/349] feat: multiple request, policy, effect, matcher type support (#181) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/core_enforcer.py | 54 +++++++++++++++---- casbin/synced_enforcer.py | 3 ++ casbin/util/util.py | 16 ++++-- .../multiple_policy_definitions_model.conf | 19 +++++++ .../multiple_policy_definitions_policy.csv | 5 ++ tests/test_enforcer.py | 21 +++++++- 6 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 examples/multiple_policy_definitions_model.conf create mode 100644 examples/multiple_policy_definitions_policy.csv diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 60a104fc..924dded1 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -8,6 +8,18 @@ from casbin.util import generate_g_function, SimpleEval, util +class EnforceContext: + """ + EnforceContext is used as the first element of the parameter "rvals" in method "enforce" + """ + + def __init__(self, rtype: str, ptype: str, etype: str, mtype: str): + self.rtype: str = rtype + self.ptype: str = ptype + self.etype: str = etype + self.mtype: str = mtype + + class CoreEnforcer: """CoreEnforcer defines the core functionality of an enforcer.""" @@ -250,6 +262,15 @@ def add_named_domain_matching_func(self, ptype, fn): return False + def new_enforce_context(self, suffix: str) -> EnforceContext: + + return EnforceContext( + rtype="r" + suffix, + ptype="p" + suffix, + etype="e" + suffix, + mtype="m" + suffix, + ) + def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). @@ -263,6 +284,11 @@ def enforce_ex(self, *rvals): return judge result with reason """ + rtype = "r" + ptype = "p" + etype = "e" + mtype = "m" + if not self.enabled: return [False, []] @@ -273,19 +299,28 @@ def enforce_ex(self, *rvals): rm = ast.rm functions[key] = generate_g_function(rm) + if len(rvals) != 0: + if isinstance(rvals[0], EnforceContext): + enforce_context = rvals[0] + rtype = enforce_context.rtype + ptype = enforce_context.ptype + etype = enforce_context.etype + mtype = enforce_context.mtype + rvals = rvals[1:] + if "m" not in self.model.keys(): raise RuntimeError("model is undefined") if "m" not in self.model["m"].keys(): raise RuntimeError("model is undefined") - r_tokens = self.model["r"]["r"].tokens - p_tokens = self.model["p"]["p"].tokens + r_tokens = self.model["r"][rtype].tokens + p_tokens = self.model["p"][ptype].tokens if len(r_tokens) != len(rvals): raise RuntimeError("invalid request size") - exp_string = self.model["m"]["m"].value + exp_string = self.model["m"][mtype].value has_eval = util.has_eval(exp_string) if not has_eval: expression = self._get_expression(exp_string, functions) @@ -294,11 +329,11 @@ def enforce_ex(self, *rvals): r_parameters = dict(zip(r_tokens, rvals)) - policy_len = len(self.model["p"]["p"].policy) + policy_len = len(self.model["p"][ptype].policy) explain_index = -1 if not 0 == policy_len: - for i, pvals in enumerate(self.model["p"]["p"].policy): + for i, pvals in enumerate(self.model["p"][ptype].policy): if len(p_tokens) != len(pvals): raise RuntimeError("invalid policy size") @@ -327,8 +362,9 @@ def enforce_ex(self, *rvals): else: raise RuntimeError("matcher result should be bool, int or float") - if "p_eft" in parameters.keys(): - eft = parameters["p_eft"] + p_eft_key = ptype + "_eft" + if p_eft_key in parameters.keys(): + eft = parameters[p_eft_key] if "allow" == eft: policy_effects.add(Effector.ALLOW) elif "deny" == eft: @@ -353,7 +389,7 @@ def enforce_ex(self, *rvals): parameters = r_parameters.copy() - for token in self.model["p"]["p"].tokens: + for token in self.model["p"][ptype].tokens: parameters[token] = "" result = expression.eval(parameters) @@ -380,7 +416,7 @@ def enforce_ex(self, *rvals): explain_rule = [] if explain_index != -1 and explain_index < policy_len: - explain_rule = self.model["p"]["p"].policy[explain_index] + explain_rule = self.model["p"][ptype].policy[explain_index] return result, explain_rule diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 339f44b1..3947806f 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -589,3 +589,6 @@ def build_incremental_role_links(self, op, ptype, rules): self.get_model().build_incremental_role_links( self.get_role_manager(), op, "g", ptype, rules ) + + def new_enforce_context(self, suffix: str) -> "EnforceContext": + return self._e.new_enforce_context(suffix) diff --git a/casbin/util/util.py b/casbin/util/util.py index 93531509..3003af67 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -6,9 +6,19 @@ def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" - - s = re.sub(r"\br\.", "r_", s) - s = re.sub(r"\bp\.", "p_", s) + eval_p = re.search(r"\bp(\d?)\.", s) + if eval_p is not None: + p_suffix = eval_p.group(1) + p_before = re.compile(f"\\bp{p_suffix}\\.") + p_after = f"p{p_suffix}_" + s = re.sub(p_before, p_after, s) + + eval_r = re.search(r"\br(\d?)\.", s) + if eval_r is not None: + r_suffix = eval_r.group(1) + r_before = re.compile(f"\\br{r_suffix}\\.") + r_after = f"r{r_suffix}_" + s = re.sub(r_before, r_after, s) return s diff --git a/examples/multiple_policy_definitions_model.conf b/examples/multiple_policy_definitions_model.conf new file mode 100644 index 00000000..22a3be6e --- /dev/null +++ b/examples/multiple_policy_definitions_model.conf @@ -0,0 +1,19 @@ +[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 diff --git a/examples/multiple_policy_definitions_policy.csv b/examples/multiple_policy_definitions_policy.csv new file mode 100644 index 00000000..ff74eac7 --- /dev/null +++ b/examples/multiple_policy_definitions_policy.csv @@ -0,0 +1,5 @@ +p, 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_admin diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 37cd4147..d817b58a 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -148,6 +148,25 @@ def test_enforce_priority_indeterminate(self): ) self.assertFalse(e.enforce("alice", "data1", "read")) + def test_multiple_policy_definitions(self): + + e = self.get_enforcer( + get_examples("multiple_policy_definitions_model.conf"), + get_examples("multiple_policy_definitions_policy.csv"), + ) + + enforce_context = e.new_enforce_context("2") + enforce_context.etype = "e" + + sub1 = TestSub("alice", 70) + sub2 = TestSub("bob", 30) + + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce(enforce_context, sub1, "/data1", "read")) + self.assertTrue(e.enforce(enforce_context, sub2, "/data1", "read")) + self.assertFalse(e.enforce(enforce_context, sub2, "/data1", "write")) + self.assertFalse(e.enforce(enforce_context, sub1, "/data2", "read")) + def test_enforce_rbac(self): e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") @@ -161,7 +180,7 @@ def test_enforce_rbac(self): e.enforce("bogus", "data2", "write") ) # test non-existant subject - def test_enforce_rbac__empty_policy(self): + def test_enforce_rbac_empty_policy(self): e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("empty_policy.csv") ) From c368a18094662ad0bbd13c2c3aa9a636fcfbc1cd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 17 Aug 2021 13:25:50 +0000 Subject: [PATCH 167/349] chore(release): 1.5.0 [skip ci] # [1.5.0](https://github.com/casbin/pycasbin/compare/v1.4.0...v1.5.0) (2021-08-17) ### Features * multiple request, policy, effect, matcher type support ([#181](https://github.com/casbin/pycasbin/issues/181)) ([468503b](https://github.com/casbin/pycasbin/commit/468503b1c573ac835e55eb4d0d6d23b638030c63)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a80ce6..e4b18935 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.5.0](https://github.com/casbin/pycasbin/compare/v1.4.0...v1.5.0) (2021-08-17) + + +### Features + +* multiple request, policy, effect, matcher type support ([#181](https://github.com/casbin/pycasbin/issues/181)) ([468503b](https://github.com/casbin/pycasbin/commit/468503b1c573ac835e55eb4d0d6d23b638030c63)) + # [1.4.0](https://github.com/casbin/pycasbin/compare/v1.3.0...v1.4.0) (2021-07-19) diff --git a/setup.cfg b/setup.cfg index 278babc8..696ba158 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.4.0 +version = 1.5.0 From bef52085f5b0b11731d6caed655819edbbeeeff8 Mon Sep 17 00:00:00 2001 From: ffyuanda Date: Fri, 27 Aug 2021 19:39:58 +0800 Subject: [PATCH 168/349] feat: key_match4 builtin operator added (#183) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/util/builtin_operators.py | 50 ++++++++++++++++++++++++++++ tests/util/test_builtin_operators.py | 49 +++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index e93b88f9..cf478864 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -5,6 +5,7 @@ KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+}(.*?)") +KEY_MATCH4_PATTERN = re.compile(r"{([^/]+)}") def key_match(key1, key2): @@ -68,6 +69,55 @@ def key_match3_func(*args): return key_match3(name1, name2) +def key_match4(key1: str, key2: str) -> bool: + """ + key_match4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. + Besides what key_match3 does, key_match4 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 key_match3 will match both. + """ + key2 = key2.replace("/*", "/.*") + + tokens: [str] = [] + + def repl(matchobj): + tokens.append(matchobj.group(1)) + return "([^/]+)" + + key2 = KEY_MATCH4_PATTERN.sub(repl, key2) + + regexp = re.compile("^" + key2 + "$") + matches = regexp.match(key1) + + if matches is None: + return False + if len(tokens) != len(matches.groups()): + raise Exception("KeyMatch4: number of tokens is not equal to number of values") + + tokens_matches = dict() + + for i in range(len(tokens)): + token, match = tokens[i], matches.groups()[i] + + if token not in tokens_matches.keys(): + tokens_matches[token] = match + else: + if tokens_matches[token] != match: + return False + return True + + +def key_match4_func(*args) -> bool: + """ + key_match4_func is the wrapper for key_match4. + """ + name1 = args[0] + name2 = args[1] + + return key_match4(name1, name2) + + def regex_match(key1, key2): """determines whether key1 matches the pattern of key2 in regular expression.""" diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index aa9bb132..11c3f512 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -87,6 +87,55 @@ def test_key_match3(self): util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}") ) + def test_key_match4(self): + self.assertTrue( + util.key_match4_func("/parent/123/child/123", "/parent/{id}/child/{id}") + ) + self.assertFalse( + util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{id}") + ) + + self.assertTrue( + util.key_match4_func( + "/parent/123/child/123", "/parent/{id}/child/{another_id}" + ) + ) + self.assertTrue( + util.key_match4_func( + "/parent/123/child/456", "/parent/{id}/child/{another_id}" + ) + ) + + self.assertTrue( + util.key_match4_func( + "/parent/123/child/456", "/parent/{id}/child/{another_id}" + ) + ) + self.assertFalse( + util.key_match4_func( + "/parent/123/child/123/book/456", "/parent/{id}/child/{id}/book/{id}" + ) + ) + self.assertFalse( + util.key_match4_func( + "/parent/123/child/456/book/123", "/parent/{id}/child/{id}/book/{id}" + ) + ) + self.assertFalse( + util.key_match4_func( + "/parent/123/child/456/book/", "/parent/{id}/child/{id}/book/{id}" + ) + ) + self.assertFalse( + util.key_match4_func( + "/parent/123/child/456", "/parent/{id}/child/{id}/book/{id}" + ) + ) + + self.assertFalse( + util.key_match4_func("/parent/123/child/123", "/parent/{i/d}/child/{i/d}") + ) + def test_regex_match(self): self.assertTrue(util.regex_match_func("/topic/create", "/topic/create")) self.assertTrue(util.regex_match_func("/topic/create/123", "/topic/create")) From 559b4a5e58c93ac12120e7e991bb07d8a6d7c526 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 27 Aug 2021 11:42:23 +0000 Subject: [PATCH 169/349] chore(release): 1.6.0 [skip ci] # [1.6.0](https://github.com/casbin/pycasbin/compare/v1.5.0...v1.6.0) (2021-08-27) ### Features * key_match4 builtin operator added ([#183](https://github.com/casbin/pycasbin/issues/183)) ([ef127b3](https://github.com/casbin/pycasbin/commit/ef127b352313fb17b3010d5a9bb5029e732c1546)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b18935..913b3f21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.6.0](https://github.com/casbin/pycasbin/compare/v1.5.0...v1.6.0) (2021-08-27) + + +### Features + +* key_match4 builtin operator added ([#183](https://github.com/casbin/pycasbin/issues/183)) ([ef127b3](https://github.com/casbin/pycasbin/commit/ef127b352313fb17b3010d5a9bb5029e732c1546)) + # [1.5.0](https://github.com/casbin/pycasbin/compare/v1.4.0...v1.5.0) (2021-08-17) diff --git a/setup.cfg b/setup.cfg index 696ba158..4bdcc483 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.5.0 +version = 1.6.0 From 5d8051815853f145800cc3b8d14ee667dbd49b8f Mon Sep 17 00:00:00 2001 From: ffyuanda <46557895+ffyuanda@users.noreply.github.com> Date: Sun, 29 Aug 2021 12:26:38 +0800 Subject: [PATCH 170/349] feat: support in operator on list/set and other compound types Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/util/expression.py | 4 ++-- .../rbac_model_matcher_using_in_op_bracket.conf | 14 ++++++++++++++ examples/rbac_policy.csv | 2 +- tests/test_enforcer.py | 11 +++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 examples/rbac_model_matcher_using_in_op_bracket.conf diff --git a/casbin/util/expression.py b/casbin/util/expression.py index ea129df8..6f519c63 100644 --- a/casbin/util/expression.py +++ b/casbin/util/expression.py @@ -1,8 +1,8 @@ -from simpleeval import SimpleEval +from simpleeval import EvalWithCompoundTypes import ast -class SimpleEval(SimpleEval): +class SimpleEval(EvalWithCompoundTypes): """Rewrite SimpleEval. >>> s = SimpleEval("20 + 30 - ( 10 * 5)") >>> s.eval() diff --git a/examples/rbac_model_matcher_using_in_op_bracket.conf b/examples/rbac_model_matcher_using_in_op_bracket.conf new file mode 100644 index 00000000..954d6c58 --- /dev/null +++ b/examples/rbac_model_matcher_using_in_op_bracket.conf @@ -0,0 +1,14 @@ +[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'] diff --git a/examples/rbac_policy.csv b/examples/rbac_policy.csv index 9bbfa7cf..41502726 100644 --- a/examples/rbac_policy.csv +++ b/examples/rbac_policy.csv @@ -3,4 +3,4 @@ p, bob, data2, write p, data2_admin, data2, read p, data2_admin, data2, write -g, alice, data2_admin \ No newline at end of file +g, alice, data2_admin diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index d817b58a..86b954dc 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -337,6 +337,17 @@ def test_abac_with_multiple_sub_rules(self): self.assertFalse(e.enforce(sub4, "/data1", "write")) self.assertTrue(e.enforce(sub4, "/data2", "write")) + def test_matcher_using_in_operator_bracket(self): + e = self.get_enforcer( + get_examples("rbac_model_matcher_using_in_op_bracket.conf"), + get_examples("rbac_policy.csv"), + ) + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data3", "scribble")) + self.assertFalse(e.enforce("alice", "data4", "scribble")) + class TestConfigSynced(TestConfig): def get_enforcer(self, model=None, adapter=None): From b4a4834e734ee35438770ef161872836e500e534 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 29 Aug 2021 09:01:03 +0000 Subject: [PATCH 171/349] chore(release): 1.7.0 [skip ci] # [1.7.0](https://github.com/casbin/pycasbin/compare/v1.6.0...v1.7.0) (2021-08-29) ### Features * support in operator on list/set and other compound types ([3f6ee8f](https://github.com/casbin/pycasbin/commit/3f6ee8f0069ce0a3e0554b5143403a28c6afdfc1)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 913b3f21..6a2d1047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.7.0](https://github.com/casbin/pycasbin/compare/v1.6.0...v1.7.0) (2021-08-29) + + +### Features + +* support in operator on list/set and other compound types ([3f6ee8f](https://github.com/casbin/pycasbin/commit/3f6ee8f0069ce0a3e0554b5143403a28c6afdfc1)) + # [1.6.0](https://github.com/casbin/pycasbin/compare/v1.5.0...v1.6.0) (2021-08-27) diff --git a/setup.cfg b/setup.cfg index 4bdcc483..b4b77175 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.6.0 +version = 1.7.0 From 4b9f20c82934af9e64de7c35c8ab90ee72de040e Mon Sep 17 00:00:00 2001 From: ffyuanda Date: Tue, 31 Aug 2021 22:57:24 +0800 Subject: [PATCH 172/349] feat: PyCasbin Watcher interfaces added (#186) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/persist/watcher.py | 39 +++++++++++++++++++ casbin/persist/watcher_ex.py | 66 ++++++++++++++++++++++++++++++++ casbin/persist/watcher_update.py | 35 +++++++++++++++++ 3 files changed, 140 insertions(+) create mode 100644 casbin/persist/watcher.py create mode 100644 casbin/persist/watcher_ex.py create mode 100644 casbin/persist/watcher_update.py diff --git a/casbin/persist/watcher.py b/casbin/persist/watcher.py new file mode 100644 index 00000000..d78f140b --- /dev/null +++ b/casbin/persist/watcher.py @@ -0,0 +1,39 @@ +# Copyright 2021 The Casbin Authors. All Rights Reserved. +# +# 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. + + +class Watcher: + """Watcher is the interface for PyCasbin watchers.""" + + def set_update_callback(self, func: callable): + """ + set_update_callback sets the callback function that the watcher will call + when the policy in DB has been changed by other instances. + A classic callback is Enforcer.LoadPolicy(). + """ + pass + + def update(self): + """ + update calls the update callback of other instances to synchronize their policy. + It is usually called after changing the policy in DB, like Enforcer.SavePolicy(), + Enforcer.AddPolicy(), Enforcer.RemovePolicy(), etc. + """ + pass + + def close(self): + """ + close stops and releases the watcher, the callback function will not be called any more. + """ + pass diff --git a/casbin/persist/watcher_ex.py b/casbin/persist/watcher_ex.py new file mode 100644 index 00000000..ea063354 --- /dev/null +++ b/casbin/persist/watcher_ex.py @@ -0,0 +1,66 @@ +# Copyright 2021 The Casbin Authors. All Rights Reserved. +# +# 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. + +from .watcher import Watcher +from casbin.model import Model + + +class WatcherEx(Watcher): + """ + WatcherEx is the strengthened version of PyCasbin Watcher. + """ + + def update_for_add_policy(self, sec: str, ptype: str, *params: str): + """ + update_for_add_policy calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.AddPolicy() + """ + pass + + def update_for_remove_policy(self, sec: str, ptype: str, *params: str): + """ + update_for_remove_policy calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.RemovePolicy() + """ + pass + + def update_for_remove_filtered_policy( + self, sec: str, ptype: str, field_index: int, *field_values: str + ): + """ + update_for_remove_filtered_policy calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.RemoveFilteredNamedGroupingPolicy() + """ + pass + + def update_for_save_policy(self, model: Model): + """ + update_for_save_policy calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.RemoveFilteredNamedGroupingPolicy() + """ + pass + + def update_for_add_policies(self, sec: str, ptype: str, *rules: [str]): + """ + update_for_add_policies calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.AddPolicies() + """ + pass + + def update_for_remove_policies(self, sec: str, ptype: str, *rules: [str]): + """ + update_for_remove_policies calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.RemovePolicies() + """ + pass diff --git a/casbin/persist/watcher_update.py b/casbin/persist/watcher_update.py new file mode 100644 index 00000000..7e472251 --- /dev/null +++ b/casbin/persist/watcher_update.py @@ -0,0 +1,35 @@ +# Copyright 2021 The Casbin Authors. All Rights Reserved. +# +# 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. + +from .watcher import Watcher + + +class WatcherUpdatable(Watcher): + """ + WatcherUpdatable is the strengthened version of PyCasbin Watcher. + """ + + def update_for_update_policy(self, old_rule: [str], new_rule: [str]): + """ + update_for_update_policy calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.UpdatePolicy() + """ + pass + + def update_for_update_policies(self, old_rules: [str], new_rules: [str]): + """ + update_for_update_policies calls the update callback of other instances to synchronize their policy. + It is called after Enforcer.UpdatePolicies() + """ + pass From 9a49f7d9fa9235668a9e4e0d0fae1908d34acf2d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 31 Aug 2021 15:01:25 +0000 Subject: [PATCH 173/349] chore(release): 1.8.0 [skip ci] # [1.8.0](https://github.com/casbin/pycasbin/compare/v1.7.0...v1.8.0) (2021-08-31) ### Features * PyCasbin Watcher interfaces added ([#186](https://github.com/casbin/pycasbin/issues/186)) ([d98429f](https://github.com/casbin/pycasbin/commit/d98429f02ac130fc5b7c4ad1448d967998c3d891)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a2d1047..1ebb362e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.8.0](https://github.com/casbin/pycasbin/compare/v1.7.0...v1.8.0) (2021-08-31) + + +### Features + +* PyCasbin Watcher interfaces added ([#186](https://github.com/casbin/pycasbin/issues/186)) ([d98429f](https://github.com/casbin/pycasbin/commit/d98429f02ac130fc5b7c4ad1448d967998c3d891)) + # [1.7.0](https://github.com/casbin/pycasbin/compare/v1.6.0...v1.7.0) (2021-08-29) diff --git a/setup.cfg b/setup.cfg index b4b77175..2313a5a8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.7.0 +version = 1.8.0 From bf4816488e530352a4f340fef4e42b6a73028c6b Mon Sep 17 00:00:00 2001 From: ffyuanda Date: Thu, 16 Sep 2021 09:03:59 +0800 Subject: [PATCH 174/349] fix: fm instance property (#189) Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> --- casbin/model/function.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/casbin/model/function.py b/casbin/model/function.py index 84ec863d..2153b0a3 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -2,7 +2,10 @@ class FunctionMap: - fm = dict() + fm = None + + def __init__(self): + self.fm = dict() def add_function(self, name, func): self.fm[name] = func From 4715dca70a0fe6a086dd976547b492bb3cc23517 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 16 Sep 2021 01:06:17 +0000 Subject: [PATCH 175/349] chore(release): 1.8.1 [skip ci] ## [1.8.1](https://github.com/casbin/pycasbin/compare/v1.8.0...v1.8.1) (2021-09-16) ### Bug Fixes * fm instance property ([#189](https://github.com/casbin/pycasbin/issues/189)) ([e06078a](https://github.com/casbin/pycasbin/commit/e06078ada44ba9b3d794367bc8f52c1547dd2249)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ebb362e..d71ab25a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.8.1](https://github.com/casbin/pycasbin/compare/v1.8.0...v1.8.1) (2021-09-16) + + +### Bug Fixes + +* fm instance property ([#189](https://github.com/casbin/pycasbin/issues/189)) ([e06078a](https://github.com/casbin/pycasbin/commit/e06078ada44ba9b3d794367bc8f52c1547dd2249)) + # [1.8.0](https://github.com/casbin/pycasbin/compare/v1.7.0...v1.8.0) (2021-08-31) diff --git a/setup.cfg b/setup.cfg index 2313a5a8..9e82d4b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.8.0 +version = 1.8.1 From 578faee08019c8bd630df834644a9fd80e281903 Mon Sep 17 00:00:00 2001 From: ffyuanda Date: Sat, 25 Sep 2021 22:27:52 +0800 Subject: [PATCH 176/349] feat: add explicit priority support (#190) * feat: added explicit priority support Signed-off-by: ffyuanda <46557895+ffyuanda@users.noreply.github.com> * feat: updated load_policy to match casbin Signed-off-by: ffyuanda --- casbin/core_enforcer.py | 39 ++++++++++++++++++++++----- casbin/effect/__init__.py | 2 +- casbin/model/assertion.py | 2 ++ casbin/model/model.py | 19 +++++++++++++ casbin/model/policy.py | 35 +++++++++++++++++++++--- examples/priority_model_explicit.conf | 2 +- tests/test_enforcer.py | 26 ++++++++++++++++++ 7 files changed, 112 insertions(+), 13 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 924dded1..d269165a 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -1,4 +1,4 @@ -import logging +import logging, copy from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap @@ -177,14 +177,36 @@ def init_rm_map(self): def load_policy(self): """reloads the policy from file/database.""" + need_to_rebuild = False + new_model = copy.copy(self.model) + new_model.clear_policy() - self.model.clear_policy() - self.adapter.load_policy(self.model) + try: - self.init_rm_map() - self.model.print_policy() - if self.auto_build_role_links: - self.build_role_links() + self.adapter.load_policy(new_model) + + new_model.sort_policies_by_priority() + + self.init_rm_map() + self.model.print_policy() + + if self.auto_build_role_links: + + need_to_rebuild = True + for rm in self.rm_map.values(): + rm.clear() + + new_model.build_role_links(self.rm_map) + self.build_role_links() + + self.model = new_model + + except Exception as e: + + if self.auto_build_role_links and need_to_rebuild: + self.build_role_links() + + raise e def load_filtered_policy(self, filter): """reloads a filtered policy from file/database.""" @@ -194,6 +216,9 @@ def load_filtered_policy(self, filter): raise ValueError("filtered policies are not supported by this adapter") self.adapter.load_filtered_policy(self.model, filter) + + self.model.sort_policies_by_priority() + self.init_rm_map() self.model.print_policy() if self.auto_build_role_links: diff --git a/casbin/effect/__init__.py b/casbin/effect/__init__.py index aab74db3..88bc31da 100644 --- a/casbin/effect/__init__.py +++ b/casbin/effect/__init__.py @@ -16,7 +16,7 @@ def get_effector(expr): return DenyOverrideEffector() elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": return AllowAndDenyEffector() - elif expr == "priority(p_eft) || deny": + elif expr == "priority(p_eft) || deny" or expr == "subjectPriority(p_eft) || deny": return PriorityEffector() else: raise RuntimeError("unsupported effect") diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index b4c77778..c2f52c68 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -10,6 +10,8 @@ def __init__(self): self.tokens = [] self.policy = [] self.rm = None + self.priority_index: int = -1 + self.policy_map: dict = {} def build_role_links(self, rm): self.rm = rm diff --git a/casbin/model/model.py b/casbin/model/model.py index 63919312..af083a79 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -79,3 +79,22 @@ def print_model(self): for k, v in self.items(): for i, j in v.items(): self.logger.info("%s.%s: %s", k, i, j.value) + + def sort_policies_by_priority(self): + for ptype, assertion in self["p"].items(): + for index, token in enumerate(assertion.tokens): + if token == f"{ptype}_priority": + assertion.priority_index = index + break + + if assertion.priority_index == -1: + continue + + assertion.policy = sorted( + assertion.policy, key=lambda x: x[assertion.priority_index] + ) + + for i, policy in enumerate(assertion.policy): + assertion.policy_map[",".join(policy)] = i + + return None diff --git a/casbin/model/policy.py b/casbin/model/policy.py index a0a087b3..e60b1c1f 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,5 +1,7 @@ import logging +DEFAULT_SEP = "," + class Policy: def __init__(self): @@ -83,12 +85,37 @@ def has_policy(self, sec, ptype, rule): def add_policy(self, sec, ptype, rule): """adds a policy rule to the model.""" - + assertion = self[sec][ptype] if not self.has_policy(sec, ptype, rule): - self[sec][ptype].policy.append(rule) - return True + assertion.policy.append(rule) + else: + return False + + if sec == "p" and assertion.priority_index >= 0: + try: + idx_insert = int(rule[assertion.priority_index]) - return False + i = len(assertion.policy) - 1 + for i in range(i, 0, -1): + + try: + idx = int(assertion.policy[i - 1][assertion.priority_index]) + except Exception as e: + print(e) + + if idx > idx_insert: + assertion.policy[i] = assertion.policy[i - 1] + else: + break + + assertion.policy[i] = rule + assertion.policy_map[DEFAULT_SEP.join(rule)] = i + + except Exception as e: + print(e) + + assertion.policy_map[DEFAULT_SEP.join(rule)] = len(assertion.policy) - 1 + return True def add_policies(self, sec, ptype, rules): """adds policy rules to the model.""" diff --git a/examples/priority_model_explicit.conf b/examples/priority_model_explicit.conf index 5df75b27..9c528475 100644 --- a/examples/priority_model_explicit.conf +++ b/examples/priority_model_explicit.conf @@ -11,4 +11,4 @@ g = _, _ e = priority(p.eft) || deny [matchers] -m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act \ No newline at end of file +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 86b954dc..d3d24f54 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -141,6 +141,32 @@ def test_enforce_priority(self): self.assertTrue(e.enforce("bob", "data2", "read")) self.assertFalse(e.enforce("bob", "data2", "write")) + def test_enforce_priority_explicit(self): + e = self.get_enforcer( + get_examples("priority_model_explicit.conf"), + get_examples("priority_policy_explicit.csv"), + ) + + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "read")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "write")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "read")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "write")) + + e.add_policy("1", "bob", "data2", "write", "deny") + + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "read")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "write")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "read")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "write")) + def test_enforce_priority_indeterminate(self): e = self.get_enforcer( get_examples("priority_model.conf"), From 1013ac9aae622bfe21157ecf54445d61e5987ef9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 25 Sep 2021 14:30:00 +0000 Subject: [PATCH 177/349] chore(release): 1.9.0 [skip ci] # [1.9.0](https://github.com/casbin/pycasbin/compare/v1.8.1...v1.9.0) (2021-09-25) ### Features * add explicit priority support ([#190](https://github.com/casbin/pycasbin/issues/190)) ([9445086](https://github.com/casbin/pycasbin/commit/94450866a6e3103df8a42fc76893f91e0cb555a4)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d71ab25a..7b3eb3bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.9.0](https://github.com/casbin/pycasbin/compare/v1.8.1...v1.9.0) (2021-09-25) + + +### Features + +* add explicit priority support ([#190](https://github.com/casbin/pycasbin/issues/190)) ([9445086](https://github.com/casbin/pycasbin/commit/94450866a6e3103df8a42fc76893f91e0cb555a4)) + ## [1.8.1](https://github.com/casbin/pycasbin/compare/v1.8.0...v1.8.1) (2021-09-16) diff --git a/setup.cfg b/setup.cfg index 9e82d4b8..8759370b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.8.1 +version = 1.9.0 From 049bb5887c91f1a3f2acde2dd5bcb583d6db0680 Mon Sep 17 00:00:00 2001 From: "Swagat S. Bhuyan" <41104465+SwagatSBhuyan@users.noreply.github.com> Date: Tue, 28 Sep 2021 16:57:09 +0530 Subject: [PATCH 178/349] fix: added utf8 encoding (#194) Signed-off-by: Swagat S. Bhuyan --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index a882e339..86fb8896 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ here = path.abspath(path.dirname(__file__)) -with open(desc_file, "r") as fh: +with open(desc_file, "r", encoding="utf-8") as fh: long_description = fh.read() # get the dependencies and installs From b23cca3d567494750b673224b805bad8d39aea8f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 28 Sep 2021 11:29:28 +0000 Subject: [PATCH 179/349] chore(release): 1.9.1 [skip ci] ## [1.9.1](https://github.com/casbin/pycasbin/compare/v1.9.0...v1.9.1) (2021-09-28) ### Bug Fixes * added utf8 encoding ([#194](https://github.com/casbin/pycasbin/issues/194)) ([6b42210](https://github.com/casbin/pycasbin/commit/6b42210031ce7383823f17a2b8134a58f5f5a71d)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b3eb3bd..ccc9c771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.1](https://github.com/casbin/pycasbin/compare/v1.9.0...v1.9.1) (2021-09-28) + + +### Bug Fixes + +* added utf8 encoding ([#194](https://github.com/casbin/pycasbin/issues/194)) ([6b42210](https://github.com/casbin/pycasbin/commit/6b42210031ce7383823f17a2b8134a58f5f5a71d)) + # [1.9.0](https://github.com/casbin/pycasbin/compare/v1.8.1...v1.9.0) (2021-09-25) diff --git a/setup.cfg b/setup.cfg index 8759370b..58c6ec79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.0 +version = 1.9.1 From bf51136ceaef1ccbcbf7b45d0ad4062a1b05679b Mon Sep 17 00:00:00 2001 From: Jon Lee Date: Wed, 29 Sep 2021 17:40:58 +0800 Subject: [PATCH 180/349] fix: 'keyMatch4' not defined(#198) (#199) Signed-off-by: Jon Lee --- casbin/model/function.py | 1 + 1 file changed, 1 insertion(+) diff --git a/casbin/model/function.py b/casbin/model/function.py index 2153b0a3..aedc9ef7 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -16,6 +16,7 @@ def load_function_map(): fm.add_function("keyMatch", util.key_match_func) fm.add_function("keyMatch2", util.key_match2_func) fm.add_function("keyMatch3", util.key_match3_func) + fm.add_function("keyMatch4", util.key_match4_func) fm.add_function("regexMatch", util.regex_match_func) fm.add_function("ipMatch", util.ip_match_func) fm.add_function("globMatch", util.glob_match_func) From f52ab2392d52624f43b0aabdf9f8127084efb377 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 29 Sep 2021 09:43:16 +0000 Subject: [PATCH 181/349] chore(release): 1.9.2 [skip ci] ## [1.9.2](https://github.com/casbin/pycasbin/compare/v1.9.1...v1.9.2) (2021-09-29) ### Bug Fixes * 'keyMatch4' not defined([#198](https://github.com/casbin/pycasbin/issues/198)) ([#199](https://github.com/casbin/pycasbin/issues/199)) ([b888965](https://github.com/casbin/pycasbin/commit/b888965c080df08c6415f45e606a24ba45698b90)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccc9c771..e41febcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.2](https://github.com/casbin/pycasbin/compare/v1.9.1...v1.9.2) (2021-09-29) + + +### Bug Fixes + +* 'keyMatch4' not defined([#198](https://github.com/casbin/pycasbin/issues/198)) ([#199](https://github.com/casbin/pycasbin/issues/199)) ([b888965](https://github.com/casbin/pycasbin/commit/b888965c080df08c6415f45e606a24ba45698b90)) + ## [1.9.1](https://github.com/casbin/pycasbin/compare/v1.9.0...v1.9.1) (2021-09-28) diff --git a/setup.cfg b/setup.cfg index 58c6ec79..0d76ba8c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.1 +version = 1.9.2 From 4cde5cbba5e3d79d451deb56ed4b7511693293c4 Mon Sep 17 00:00:00 2001 From: elfisworking Date: Sun, 24 Oct 2021 10:28:57 +0800 Subject: [PATCH 182/349] fix: update simpleeval version to 0.9.11 Signed-off-by: elfisworking --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d6ba9787..4ff6dd17 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -simpleeval >= 0.9.10 +simpleeval >= 0.9.11 wcmatch >= 8.1.2 \ No newline at end of file From 8532d6a21fb2dfbbd6f902e7c62f6cae8315704a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 24 Oct 2021 02:53:30 +0000 Subject: [PATCH 183/349] chore(release): 1.9.3 [skip ci] ## [1.9.3](https://github.com/casbin/pycasbin/compare/v1.9.2...v1.9.3) (2021-10-24) ### Bug Fixes * update simpleeval version to 0.9.11 ([79fdf21](https://github.com/casbin/pycasbin/commit/79fdf212639263c44be4c27b545017e3b50863b0)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e41febcd..c69632cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.3](https://github.com/casbin/pycasbin/compare/v1.9.2...v1.9.3) (2021-10-24) + + +### Bug Fixes + +* update simpleeval version to 0.9.11 ([79fdf21](https://github.com/casbin/pycasbin/commit/79fdf212639263c44be4c27b545017e3b50863b0)) + ## [1.9.2](https://github.com/casbin/pycasbin/compare/v1.9.1...v1.9.2) (2021-09-29) diff --git a/setup.cfg b/setup.cfg index 0d76ba8c..2b1e5eea 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.2 +version = 1.9.3 From 232d410864dc93e45c66a1b889209f4b86e73d1c Mon Sep 17 00:00:00 2001 From: elfisworking Date: Fri, 29 Oct 2021 11:31:46 +0800 Subject: [PATCH 184/349] fix: load_policy_line function load error Signed-off-by: elfisworking --- casbin/persist/adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index b290be2c..567bd770 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -7,7 +7,7 @@ def load_policy_line(line, model): if line[:1] == "#": return - tokens = line.split(", ") + tokens = [token.strip() for token in line.split(",")] key = tokens[0] sec = key[0] From 8915fbe517bebb4f95caf65125f3cf297c8db4b7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 29 Oct 2021 15:37:23 +0000 Subject: [PATCH 185/349] chore(release): 1.9.4 [skip ci] ## [1.9.4](https://github.com/casbin/pycasbin/compare/v1.9.3...v1.9.4) (2021-10-29) ### Bug Fixes * load_policy_line function load error ([bca7eed](https://github.com/casbin/pycasbin/commit/bca7eed0cf61ecab381ed462921b776ec6aa7e3c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69632cc..b0ce5e3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.4](https://github.com/casbin/pycasbin/compare/v1.9.3...v1.9.4) (2021-10-29) + + +### Bug Fixes + +* load_policy_line function load error ([bca7eed](https://github.com/casbin/pycasbin/commit/bca7eed0cf61ecab381ed462921b776ec6aa7e3c)) + ## [1.9.3](https://github.com/casbin/pycasbin/compare/v1.9.2...v1.9.3) (2021-10-24) diff --git a/setup.cfg b/setup.cfg index 2b1e5eea..f7739321 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.3 +version = 1.9.4 From a38abffcc6c6a2611b3f0882a562723f99e6eae4 Mon Sep 17 00:00:00 2001 From: Bingchang Chen <19990626.love@163.com> Date: Fri, 12 Nov 2021 19:37:07 +0800 Subject: [PATCH 186/349] fix: remove_filtered_policy_returns_effects inconsistent input with remove_filtered_policy (#211) Signed-off-by: abingcbc --- casbin/distributed_enforcer.py | 2 +- casbin/model/policy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 4f6a9547..6ae14e6e 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -84,7 +84,7 @@ def remove_filtered_policy_self( self.logger.log("An exception occurred: " + e) effects = self.get_model().remove_filtered_policy_returns_effects( - sec, ptype, field_index, field_values + sec, ptype, field_index, *field_values ) if sec == "g": diff --git a/casbin/model/policy.py b/casbin/model/policy.py index e60b1c1f..332ad146 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -238,7 +238,7 @@ def remove_filtered_policy_returns_effects( for rule in self[sec][ptype].policy: if all( value == "" or rule[field_index + i] == value - for i, value in enumerate(field_values[0]) + for i, value in enumerate(field_values) ): effects.append(rule) else: From 2912d61995aaed210c0e77ff77d9245bbf579191 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Nov 2021 11:39:18 +0000 Subject: [PATCH 187/349] chore(release): 1.9.5 [skip ci] ## [1.9.5](https://github.com/casbin/pycasbin/compare/v1.9.4...v1.9.5) (2021-11-12) ### Bug Fixes * remove_filtered_policy_returns_effects inconsistent input with remove_filtered_policy ([#211](https://github.com/casbin/pycasbin/issues/211)) ([e7e6696](https://github.com/casbin/pycasbin/commit/e7e669675c4d4225b96a3d391da04433926f9726)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0ce5e3a..e76bef20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.5](https://github.com/casbin/pycasbin/compare/v1.9.4...v1.9.5) (2021-11-12) + + +### Bug Fixes + +* remove_filtered_policy_returns_effects inconsistent input with remove_filtered_policy ([#211](https://github.com/casbin/pycasbin/issues/211)) ([e7e6696](https://github.com/casbin/pycasbin/commit/e7e669675c4d4225b96a3d391da04433926f9726)) + ## [1.9.4](https://github.com/casbin/pycasbin/compare/v1.9.3...v1.9.4) (2021-10-29) diff --git a/setup.cfg b/setup.cfg index f7739321..472e6cc1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.4 +version = 1.9.5 From fad498d39d61c7ec51fdfa2a205b2fbe9674b8a8 Mon Sep 17 00:00:00 2001 From: abingcbc <19990626.love@163.com> Date: Thu, 11 Nov 2021 20:06:45 +0800 Subject: [PATCH 188/349] fix: replace build_role_links with build_incremental_role_links Signed-off-by: abingcbc --- casbin/internal_enforcer.py | 24 ++++++++++++++++++++++++ casbin/management_enforcer.py | 29 +++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index cac136f3..87879544 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -128,3 +128,27 @@ def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): self.watcher.update() return rule_removed + + def _remove_filtered_policy_returns_effects( + self, sec, ptype, field_index, *field_values + ): + """removes rules based on field filters from the current policy.""" + rule_removed = self.model.remove_filtered_policy_returns_effects( + sec, ptype, field_index, *field_values + ) + if len(rule_removed) == 0: + return rule_removed + + if self.adapter and self.auto_save: + if ( + self.adapter.remove_filtered_policy( + sec, ptype, field_index, *field_values + ) + is False + ): + return False + + if self.watcher: + self.watcher.update() + + return rule_removed diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 2ef80337..6870cb99 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -1,4 +1,5 @@ from casbin.internal_enforcer import InternalEnforcer +from casbin.model.policy_op import PolicyOp class ManagementEnforcer(InternalEnforcer): @@ -204,14 +205,19 @@ def add_named_grouping_policy(self, ptype, *params): Otherwise the function returns true by adding the new rule. """ + rules = [] if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] rule_added = self._add_policy("g", ptype, str_slice) + rules.append(str_slice) else: rule_added = self._add_policy("g", ptype, list(params)) + rules.append(list(params)) if self.auto_build_role_links: - self.build_role_links() + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules + ) return rule_added def add_named_grouping_policies(self, ptype, rules): @@ -221,7 +227,9 @@ def add_named_grouping_policies(self, ptype, rules): Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" rules_added = self._add_policies("g", ptype, rules) if self.auto_build_role_links: - self.build_role_links() + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules + ) return rules_added @@ -242,14 +250,19 @@ def remove_filtered_grouping_policy(self, field_index, *field_values): def remove_named_grouping_policy(self, ptype, *params): """removes a role inheritance rule from the current named policy.""" + rules = [] if len(params) == 1 and isinstance(params[0], list): str_slice = params[0] rule_removed = self._remove_policy("g", ptype, str_slice) + rules.append(str_slice) else: rule_removed = self._remove_policy("g", ptype, list(params)) + rules.append(list(params)) if self.auto_build_role_links: - self.build_role_links() + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules + ) return rule_removed def remove_named_grouping_policies(self, ptype, rules): @@ -257,18 +270,22 @@ def remove_named_grouping_policies(self, ptype, rules): rules_removed = self._remove_policies("g", ptype, rules) if self.auto_build_role_links: - self.build_role_links() + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules + ) return rules_removed def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" - rule_removed = self._remove_filtered_policy( + rule_removed = self._remove_filtered_policy_returns_effects( "g", ptype, field_index, *field_values ) if self.auto_build_role_links: - self.build_role_links() + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rule_removed + ) return rule_removed def add_function(self, name, func): From 9f645bdf4fd88f59e329be4bee2b0858c606d5ae Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 12 Nov 2021 15:45:33 +0000 Subject: [PATCH 189/349] chore(release): 1.9.6 [skip ci] ## [1.9.6](https://github.com/casbin/pycasbin/compare/v1.9.5...v1.9.6) (2021-11-12) ### Bug Fixes * replace build_role_links with build_incremental_role_links ([1d60cd3](https://github.com/casbin/pycasbin/commit/1d60cd36887995b034dca7561179e13e9aae0c9f)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e76bef20..f3ba3687 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.6](https://github.com/casbin/pycasbin/compare/v1.9.5...v1.9.6) (2021-11-12) + + +### Bug Fixes + +* replace build_role_links with build_incremental_role_links ([1d60cd3](https://github.com/casbin/pycasbin/commit/1d60cd36887995b034dca7561179e13e9aae0c9f)) + ## [1.9.5](https://github.com/casbin/pycasbin/compare/v1.9.4...v1.9.5) (2021-11-12) diff --git a/setup.cfg b/setup.cfg index 472e6cc1..8bc6fd91 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.5 +version = 1.9.6 From d6f7f314129d0e9a9a8dbcb848aee3edd9dbced6 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Fri, 5 Nov 2021 10:51:29 +0100 Subject: [PATCH 190/349] fix: reimplement default role manager Signed-off-by: Andreas Bichinger --- casbin/core_enforcer.py | 6 +- casbin/enforcer.py | 2 +- casbin/rbac/default_role_manager/__init__.py | 2 +- .../rbac/default_role_manager/role_manager.py | 534 +++++++++--------- tests/rbac/test_role_manager.py | 154 +++-- tests/test_distributed_api.py | 1 - tests/test_rbac_api.py | 74 +-- 7 files changed, 434 insertions(+), 339 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index d269165a..496588b3 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -173,7 +173,11 @@ def clear_policy(self): def init_rm_map(self): if "g" in self.model.keys(): for ptype in self.model["g"]: - self.rm_map[ptype] = default_role_manager.RoleManager(10) + assertion = self.model["g"][ptype] + if assertion.value.count("_") == 2: + self.rm_map[ptype] = default_role_manager.RoleManager(10) + else: + self.rm_map[ptype] = default_role_manager.DomainManager(10) def load_policy(self): """reloads the policy from file/database.""" diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 42b9c195..ec4216e4 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -110,7 +110,7 @@ def has_permission_for_user(self, user, *permission): """ return self.has_policy(join_slice(user, *permission)) - def get_implicit_roles_for_user(self, name, domain=None): + def get_implicit_roles_for_user(self, name, domain=""): """ gets implicit roles that a user has. Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. diff --git a/casbin/rbac/default_role_manager/__init__.py b/casbin/rbac/default_role_manager/__init__.py index cd3c1521..bf5c0470 100644 --- a/casbin/rbac/default_role_manager/__init__.py +++ b/casbin/rbac/default_role_manager/__init__.py @@ -1 +1 @@ -from .role_manager import RoleManager +from .role_manager import RoleManager, DomainManager diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 059409da..ddd45751 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -1,168 +1,213 @@ import logging +from collections import namedtuple +from enum import Enum -from casbin.rbac import RoleManager +from casbin.rbac import RoleManager as RM +Link = namedtuple("Link", ["user", "role"]) -class RoleManager(RoleManager): - """provides a default implementation for the RoleManager interface""" - all_roles = dict() - max_hierarchy_level = 0 +class MatchOrder(Enum): + STR_PATTERN = 0 + PATTERN_STR = 1 + PATTERN_PATTERN = 2 + + +class Role: + def __init__(self, name): + self.name = name + self.roles = set() + self.users = set() + + def add_role(self, role): + self.roles.add(role) + role._add_user(self) + + def remove_role(self, role): + self.roles.remove(role) + role._remove_user(self) + + def _add_user(self, user): + self.users.add(user) + + def _remove_user(self, user): + self.users.remove(user) + + def copy_from(self, role): + for r in role.roles: + self.add_role(r) + for u in role.users: + u.add_role(self) + + def empty(self): + return len(self.users) + len(self.roles) == 0 + + def to_string(self): + if len(self.roles) == 0: + return "" + + names = ", ".join(self.get_roles()) + + if len(self.roles) == 1: + return self.name + " < " + names + else: + return self.name + " < (" + names + ")" + + def get_roles(self): + roles = [] + for role in self.roles: + roles.append(role.name) + + return roles + + +class RoleManager(RM): + """provides a default implementation for the RoleManager interface""" def __init__(self, max_hierarchy_level=10): self.logger = logging.getLogger(__name__) - self.all_roles = dict() self.max_hierarchy_level = max_hierarchy_level - self.matching_func = None - self.domain_matching_func = None - self.has_pattern = False - self.has_domain_pattern = False + self.matching_func = lambda name1, name2: name1 == name2 + self.all_links = list() + self.all_roles = dict() - def add_matching_func(self, fn=None): - self.has_pattern = True + def _rebuild(self): + self.all_roles = dict() + links = self.all_links + self.all_links = list() + for link in links: + self.add_link(link.user, link.role) + + def _matching_fn(self, str1, str2, match_order=MatchOrder.STR_PATTERN): + if match_order == MatchOrder.PATTERN_STR: + return match_error_handler(self.matching_func, str2, str1) + elif match_order == MatchOrder.PATTERN_PATTERN: + return match_error_handler( + self.matching_func, str1, str2 + ) or match_error_handler(self.matching_func, str2, str1) + else: # match_order == MatchOrder.STR_PATTERN + return match_error_handler(self.matching_func, str1, str2) + + def _matching_roles(self, name, match_order=MatchOrder.STR_PATTERN): + return [ + role + for role_name, role in list( + self.all_roles.items() + ) # convert view to list to avoid RuntimeError: dictionary changed size during iteration + if self._matching_fn(name, role_name, match_order) + ] + + def _get_role(self, name): + if name not in self.all_roles: + role = Role(name) + for pattern_role in self._matching_roles(name): + role.copy_from(pattern_role) + self.all_roles[name] = role + return self.all_roles[name] + + def add_matching_func(self, fn): self.matching_func = fn + self._rebuild() def add_domain_matching_func(self, fn=None): - self.has_domain_pattern = True self.domain_matching_func = fn - def has_role(self, role): - - if not self.has_pattern and not self.has_domain_pattern: - return role in self.all_roles.values() - - for known_role in list(self.all_roles.values()): - if self.has_pattern: - if not self.matching_func(role.name, known_role.name): - continue - else: - if not role.name == known_role.name: - continue - - if self.has_domain_pattern: - if not self.domain_matching_func(role.domain, known_role.domain): - continue - else: - if not role.domain == known_role.domain: - continue - return True - - def create_role(self, name, domain=""): - role = Role(name, domain) - if domain: - key = domain + "::" + name - else: - key = name - - if key not in self.all_roles.keys(): - self.all_roles[key] = role - - return self.all_roles[key] - def clear(self): - self.all_roles.clear() + self.all_roles = dict() + self.all_links = list() def add_link(self, name1, name2, *domain): - if len(domain) > 1: - raise RuntimeError("error: domain should be 1 parameter") - elif len(domain) == 1: - domain = domain[0] - else: - domain = "" + self.all_links.append(Link(name1, name2)) - role1 = self.create_role(name1, domain) - role2 = self.create_role(name2, domain) - role1.add_role(role2) - - if self.has_pattern: - for role in self.all_roles.values(): - if self.has_domain_pattern: - if not self.domain_matching_func(domain, role.domain): - continue - else: - if domain != role.domain: - continue - - def duplicate_judge(): - return role1.name != role.name and role2.name != role.name - - if ( - match_error_handler(self.matching_func, role.name, role1.name) - or match_error_handler(self.matching_func, role1.name, role.name) - and duplicate_judge() - ): - self.all_roles[role.get_key()].add_role(role1) - - if ( - match_error_handler(self.matching_func, role.name, role2.name) - or match_error_handler(self.matching_func, role2.name, role.name) - and duplicate_judge() - ): - self.all_roles[role2.get_key()].add_role(role) + user = self._get_role(name1) + role = self._get_role(name2) - def delete_link(self, name1, name2, *domain): - role1, role2 = two_role_domain_wrapper(self, name1, name2, domain) + user.add_role(role) - if not self.has_role(role1) or not self.has_role(role2): - raise RuntimeError("error: name1 or name2 does not exist") + for r in self.all_roles.values(): + if r.name != user.name and self._matching_fn( + user.name, r.name, MatchOrder.PATTERN_STR + ): + r.add_role(role) + if r.name != role.name and self._matching_fn( + role.name, r.name, MatchOrder.PATTERN_STR + ): + role.add_role(r) - role1.delete_role(role2) + def delete_link(self, name1, name2, *domain): + if Link(name1, name2) not in self.all_links: + raise RuntimeError( + f"error: link between {name1} and {name2} does not exist" + ) + self.all_links.remove(Link(name1, name2)) + + user = self._get_role(name1) + role = self._get_role(name2) + user.remove_role(role) + + for r in self.all_roles.values(): + if r.name != user.name and self._matching_fn( + user.name, r.name, MatchOrder.PATTERN_STR + ): + r.remove_role(role) + if r.name != role.name and self._matching_fn( + role.name, r.name, MatchOrder.PATTERN_STR + ): + role.remove_role(r) def has_link(self, name1, name2, *domain): - if len(domain) > 1: - raise RuntimeError("error: domain should be 1 parameter") - elif len(domain) == 1: - domain = domain[0] - else: - domain = "" - - role1, role2 = two_role_domain_wrapper(self, name1, name2, domain) + user = self._get_role(name1) + role = self._get_role(name2) - if role1 == role2: - return True + return self._has_link(name2, [user], self.max_hierarchy_level) - if not self.has_role(role1) or not self.has_role(role2): + def _has_link(self, name, roles, level): + if level <= 0 or len(roles) == 0: return False - if not self.has_pattern and not self.has_domain_pattern: - return role1.has_role(role2, self.max_hierarchy_level, None, None) + next_roles = set() + for role in roles: + if name == role.name: + return True + next_roles.update(set(role.roles)) - # Here is has_pattern logic. - for role in self.all_roles.values(): - if self.has_domain_pattern: - if not self.domain_matching_func(domain, role.domain): - continue - else: - if role.domain != domain: - continue - - def role_judge(): - if role.has_role( - role2, - self.max_hierarchy_level, - self.matching_func, - self.domain_matching_func, - ): - return True - return False - - if self.has_pattern: - if self.matching_func(role1.name, role.name): - if role_judge(): - return True - continue - else: - if role1.name == role.name: - if role_judge(): - return True - continue - return False + return self._has_link(name, list(next_roles), level - 1) def get_roles(self, name, *domain): - """ - gets the roles that a subject inherits. - domain is a prefix to the roles. - """ + user = self._get_role(name) + return [r.name for r in user.roles] + + def get_users(self, name, *domain): + role = self._get_role(name) + return [u.name for u in role.users] + + def to_string(self): + line = [] + for role in self.all_roles.values(): + text = role.to_string() + if text: + line.append(text) + return ", ".join(line) + + def print_roles(self): + self.logger.info(self.to_string()) + + +class DomainManagerBase(RM): + def __init__(self, max_hierarchy_level=10): + self.logger = logging.getLogger(__name__) + self.all_links = dict() + self.max_hierarchy_level = max_hierarchy_level + self.domain_matching_func = lambda domain1, domain2: domain1 == domain2 + self.matching_func = lambda name1, name2: name1 == name2 + + def add_matching_func(self, fn): + self.matching_func = fn + + def add_domain_matching_func(self, fn=None): + self.domain_matching_func = fn + + def _get_domain(self, *domain): if len(domain) > 1: raise RuntimeError("error: domain should be 1 parameter") elif len(domain) == 1: @@ -170,153 +215,124 @@ def get_roles(self, name, *domain): else: domain = "" - role = role_domain_wrapper(self, name, domain) - - if not self.has_role(role): - return [] - - roles = self.create_role(name, domain).get_roles() + return domain - return roles + def _get_links(self, *domain): + domain = self._get_domain(*domain) - def get_users(self, name, *domain): - """ - gets the users that inherits a subject. - domain is an unreferenced parameter here, may be used in other implementations. - """ - target_role = role_domain_wrapper(self, name, domain) + if domain not in self.all_links: + self.all_links[domain] = [] - if not self.has_role(target_role): - return [] + return self.all_links[domain] - roles = [] - for role in self.all_roles.values(): - if role.has_direct_role(target_role): - roles.append(role.name) + def _get_role_manager(self, *domain): + domain1 = self._get_domain(*domain) + domain_links = [] - return roles + for domain2, links in self.all_links.items(): + if match_error_handler(self.domain_matching_func, domain1, domain2): + domain_links = domain_links + links - def print_roles(self): - line = [] - for role in self.all_roles.values(): - text = role.to_string() - if text: - line.append(text) - self.logger.info(", ".join(line)) + rm = RoleManager(max_hierarchy_level=self.max_hierarchy_level) + rm.add_matching_func(self.matching_func) + for link in domain_links: + rm.add_link(link[0], link[1]) + return rm + def clear(self): + self.all_links = dict() -class Role: - """represents the data structure for a role in RBAC.""" - - def __init__(self, name: str, domain: str = ""): - self.name = name - self.roles = [] - self.domain = domain - - def __eq__(self, other: "Role"): - return ( - type(other) == type(self) - and self.name == other.name - and self.domain == other.domain - ) - - def __hash__(self): - return hash(self.name + "::" + self.domain) - - def get_key(self): - if self.domain: - return self.domain + "::" + self.name - return self.name - - def add_role(self, role: "Role"): - if role in self.roles: - return - self.roles.append(role) - - def delete_role(self, role: "Role"): - if role in self.roles: - self.roles.remove(role) - - def has_role( - self, - role: "Role", - hierarchy_level: int, - matching_func=None, - domain_matching_func=None, - ): - - if self.has_direct_role(role, matching_func, domain_matching_func): - return True - if hierarchy_level <= 0: - return False + def add_link(self, name1, name2, *domain): + links = self._get_links(*domain) + links.append(Link(name1, name2)) - for knownRole in self.roles: - if knownRole.has_role( - role, hierarchy_level - 1, matching_func, domain_matching_func - ): - return True + def delete_link(self, name1, name2, *domain): + links = self._get_links(*domain) + if Link(name1, name2) not in links: + raise RuntimeError( + f"error: link between {name1} and {name2} does not exist" + ) + links.remove(Link(name1, name2)) - return False + def has_link(self, name1, name2, *domain): + rm = self._get_role_manager(*domain) + return rm.has_link(name1, name2) - def has_direct_role( - self, role: "Role", matching_func=None, domain_matching_func=None - ): - for known_role in self.roles: - if matching_func: - if not matching_func(role.name, known_role.name): - continue - else: - if not role.name == known_role.name: - continue - - if domain_matching_func: - if not domain_matching_func(role.domain, known_role.domain): - continue - else: - if not role.domain == known_role.domain: - continue - return True - return False + def get_roles(self, name, *domain): + rm = self._get_role_manager(*domain) + return rm.get_roles(name) - def to_string(self): - if len(self.roles) == 0: - return "" + def get_users(self, name, *domain): + rm = self._get_role_manager(*domain) + return rm.get_users(name) - names = ", ".join(self.get_roles()) + def print_roles(self): + pass - if len(self.roles) == 1: - return self.name + " < " + names - else: - return self.name + " < (" + names + ")" - def get_roles(self): - roles = [] - for role in self.roles: - roles.append(role.name) +class DomainManager(DomainManagerBase): + def __init__(self, max_hierarchy_level=10): + super().__init__(max_hierarchy_level) + self.rm_map = dict() # type: dict[str, RoleManager] + + def _rebuild(self): + self.rm_map = dict() + + def _get_role_manager(self, *domain): + domain1 = self._get_domain(*domain) + if domain1 not in self.rm_map: + self.rm_map[domain1] = super()._get_role_manager(*domain) + + return self.rm_map[domain1] + + def _affected_role_managers(self, *domain): + domain_pattern = self._get_domain(*domain) + return [ + self.rm_map[domain_str] + for domain_str in self.rm_map.keys() + if match_error_handler( + self.domain_matching_func, domain_str, domain_pattern + ) + ] + + def add_matching_func(self, fn): + super().add_matching_func(fn) + for rm in self.rm_map.values(): + rm.add_matching_func(fn) + + def add_domain_matching_func(self, fn): + super().add_domain_matching_func(fn) + for rm in self.rm_map.values(): + rm.add_domain_matching_func(fn) + self._rebuild() - return roles + def clear(self): + super().clear() + self.rm_map = dict() + def add_link(self, name1, name2, *domain): + super().add_link(name1, name2, *domain) + for rm in self._affected_role_managers(*domain): + rm.add_link(name1, name2, *domain) -def role_domain_wrapper(obj, name, domain): - if type(domain) != str: - if not domain or len(domain) == 0: - domain = "" - elif len(domain) == 1: - domain = domain[0] - elif len(domain) > 1: - raise RuntimeError("error: domain should be 1 parameter") + def delete_link(self, name1, name2, *domain): + super().delete_link(name1, name2, *domain) + for rm in self._affected_role_managers(*domain): + rm.delete_link(name1, name2, *domain) - role = Role(name, domain) + def has_link(self, name1, name2, *domain): + return super().has_link(name1, name2, *domain) - if not obj.has_role(role): - return role - return obj.create_role(name, domain) + def get_roles(self, name, *domain): + return super().get_roles(name, *domain) + def get_users(self, name, *domain): + return super().get_users(name, *domain) -def two_role_domain_wrapper(obj, name1, name2, domain): - return role_domain_wrapper(obj, name1, domain), role_domain_wrapper( - obj, name2, domain - ) + def print_roles(self): + for domain, rm in self.rm_map.items(): + line = rm.to_string() + self.logger.info(f"{domain}: {line}") def match_error_handler(fn, key1, key2): diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py index 4a4cab06..33466975 100644 --- a/tests/rbac/test_role_manager.py +++ b/tests/rbac/test_role_manager.py @@ -3,15 +3,15 @@ from casbin.util import regex_match_func import time from concurrent.futures import ThreadPoolExecutor +import re -def get_role_manager(): - return default_role_manager.RoleManager(max_hierarchy_level=10) +class TestRoleManager(TestCase): + def get_role_manager(self): + return default_role_manager.RoleManager(max_hierarchy_level=10) - -class TestDefaultRoleManager(TestCase): def test_role(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_link("u1", "g1") rm.add_link("u2", "g1") rm.add_link("u3", "g2") @@ -26,6 +26,7 @@ def test_role(self): # / \ # u1 u2 + self.assertTrue(rm.has_link("u1", "u1")) self.assertTrue(rm.has_link("u1", "g1")) self.assertFalse(rm.has_link("u1", "g2")) self.assertTrue(rm.has_link("u1", "g3")) @@ -78,44 +79,60 @@ def test_role(self): self.assertCountEqual(rm.get_roles("g2"), []) self.assertCountEqual(rm.get_roles("g3"), []) - def test_domain_role(self): - rm = get_role_manager() - rm.add_link("u1", "g1", "domain1") - rm.add_link("u2", "g1", "domain1") - rm.add_link("u3", "admin", "domain2") - rm.add_link("u4", "admin", "domain2") - rm.add_link("u4", "admin", "domain1") - rm.add_link("g1", "admin", "domain1") + rm.clear() - # Current role inheritance tree: - # domain1:admin domain2:admin - # / \ / \ - # domain1:g1 u4 u3 - # / \ - # u1 u2 + match_fn = ( + lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False + ) - self.assertTrue(rm.has_link("u1", "g1", "domain1")) - self.assertFalse(rm.has_link("u1", "g1", "domain2")) - self.assertTrue(rm.has_link("u1", "admin", "domain1")) - self.assertFalse(rm.has_link("u1", "admin", "domain2")) + rm.add_matching_func(match_fn) - self.assertTrue(rm.has_link("u2", "g1", "domain1")) - self.assertFalse(rm.has_link("u2", "g1", "domain2")) - self.assertTrue(rm.has_link("u2", "admin", "domain1")) - self.assertFalse(rm.has_link("u2", "admin", "domain2")) + rm.add_link("u2", r"g\d+") + rm.add_link(r"u\d+", "any_user") + rm.add_link(r"g\d+", "any_group") + rm.add_link("u1", "g1") - self.assertFalse(rm.has_link("u3", "g1", "domain1")) - self.assertFalse(rm.has_link("u3", "g1", "domain2")) - self.assertFalse(rm.has_link("u3", "admin", "domain1")) - self.assertTrue(rm.has_link("u3", "admin", "domain2")) + self.assertTrue(rm.has_link("u1", "g1")) + self.assertFalse(rm.has_link("u1", "g2")) + self.assertTrue(rm.has_link("u1", "any_user")) + self.assertTrue(rm.has_link("u1", "any_group")) - self.assertFalse(rm.has_link("u4", "g1", "domain1")) - self.assertFalse(rm.has_link("u4", "g1", "domain2")) - self.assertTrue(rm.has_link("u4", "admin", "domain1")) - self.assertTrue(rm.has_link("u4", "admin", "domain2")) + self.assertTrue(rm.has_link("u2", "g1")) + self.assertTrue(rm.has_link("u2", "g2")) + self.assertTrue(rm.has_link("u2", "any_user")) + self.assertTrue(rm.has_link("u2", "any_group")) + + self.assertFalse(rm.has_link("u3", "g1")) + self.assertFalse(rm.has_link("u3", "g2")) + self.assertTrue(rm.has_link("u3", "any_user")) + self.assertFalse(rm.has_link("u3", "any_group")) + + self.assertTrue(rm.has_link("g1", "any_group")) + self.assertTrue(rm.has_link("g2", "any_group")) + + self.assertCountEqual(rm.get_roles("u1"), ["g1", "any_user"]) + self.assertCountEqual(rm.get_roles("u2"), ["g2", "g1", r"g\d+", "any_user"]) + self.assertCountEqual(rm.get_roles(r"u\d+"), ["any_user"]) + self.assertCountEqual(rm.get_roles("u3"), ["any_user"]) + self.assertCountEqual(rm.get_roles("g1"), ["any_group"]) + self.assertCountEqual(rm.get_roles("g2"), ["any_group"]) + + rm.delete_link(r"u\d+", "any_user") + rm.delete_link(r"g\d+", "any_group") + rm.delete_link("u1", "g1") + rm.add_link("u1", "g2") + + self.assertCountEqual(rm.get_roles("u1"), ["g2"]) + + rm.clear() + + rm.add_link("alice", "location_1/department_1") + rm.add_link("location_1/.*", "all_departments") + + self.assertFalse(rm.has_link("alice", "location_1/department_2")) def test_clear(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_link("u1", "g1") rm.add_link("u2", "g1") rm.add_link("u3", "g2") @@ -149,7 +166,7 @@ def test_clear(self): self.assertFalse(rm.has_link("u4", "g3")) def test_matching_func(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_matching_func(regex_match_func) rm.add_link("u1", "g1") @@ -170,7 +187,7 @@ def test_matching_func(self): self.assertTrue(rm.has_link("u3", "g3")) def test_one_to_many(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_matching_func(regex_match_func) rm.add_link("u1", r"g\d+") @@ -180,7 +197,7 @@ def test_one_to_many(self): self.assertFalse(rm.has_link("u2", "g2")) def test_many_to_one(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_matching_func(regex_match_func) rm.add_link(r"u\d+", "g1") @@ -190,7 +207,7 @@ def test_many_to_one(self): self.assertFalse(rm.has_link("u2", "g2")) def test_matching_func_order(self): - rm = get_role_manager() + rm = self.get_role_manager() rm.add_matching_func(regex_match_func) rm.add_link(r"g\d+", "root") @@ -220,7 +237,7 @@ def matching_func(*args): time.sleep(0.01) return regex_match_func(*args) - rm = get_role_manager() + rm = self.get_role_manager() rm.add_matching_func(matching_func) rm.add_link(r"u\d+", "users") @@ -231,3 +248,58 @@ def test_has_link(role): futures = [executor.submit(test_has_link, "u" + str(i)) for i in range(10)] for future in futures: self.assertTrue(future.result()) + + +class TestDomainManager(TestRoleManager): + def get_role_manager(self): + return default_role_manager.DomainManager(max_hierarchy_level=10) + + def test_domain_role(self): + rm = self.get_role_manager() + rm.add_link("u1", "g1", "domain1") + rm.add_link("u2", "g1", "domain1") + rm.add_link("u3", "admin", "domain2") + rm.add_link("u4", "admin", "domain2") + rm.add_link("u4", "admin", "domain1") + rm.add_link("g1", "admin", "domain1") + + # Current role inheritance tree: + # domain1:admin domain2:admin + # / \ / \ + # domain1:g1 u4 u3 + # / \ + # u1 u2 + + self.assertTrue(rm.has_link("u1", "g1", "domain1")) + self.assertFalse(rm.has_link("u1", "g1", "domain2")) + self.assertTrue(rm.has_link("u1", "admin", "domain1")) + self.assertFalse(rm.has_link("u1", "admin", "domain2")) + + self.assertTrue(rm.has_link("u2", "g1", "domain1")) + self.assertFalse(rm.has_link("u2", "g1", "domain2")) + self.assertTrue(rm.has_link("u2", "admin", "domain1")) + self.assertFalse(rm.has_link("u2", "admin", "domain2")) + + self.assertFalse(rm.has_link("u3", "g1", "domain1")) + self.assertFalse(rm.has_link("u3", "g1", "domain2")) + self.assertFalse(rm.has_link("u3", "admin", "domain1")) + self.assertTrue(rm.has_link("u3", "admin", "domain2")) + + self.assertFalse(rm.has_link("u4", "g1", "domain1")) + self.assertFalse(rm.has_link("u4", "g1", "domain2")) + self.assertTrue(rm.has_link("u4", "admin", "domain1")) + self.assertTrue(rm.has_link("u4", "admin", "domain2")) + + rm.clear() + match_fn = ( + lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False + ) + + rm.add_domain_matching_func(match_fn) + rm.add_link("alice", "user", ".*") + rm.add_link("user", "users", "domain1") + + self.assertTrue(rm.has_link("alice", "user", "domain1")) + self.assertTrue(rm.has_link("alice", "users", "domain1")) + self.assertTrue(rm.has_link("alice", "user", "domain2")) + self.assertFalse(rm.has_link("alice", "users", "domain2")) diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py index 41a67ac8..9704dab6 100644 --- a/tests/test_distributed_api.py +++ b/tests/test_distributed_api.py @@ -53,7 +53,6 @@ def test(self): self.assertTrue(e.enforce("tom", "data1", "write")) e.remove_policy_self(False, "p", "p", [["alice", "data1", "write"]]) - e.remove_policy_self(False, "g", "g", [["alice", "data2_admin"]]) self.assertFalse(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("alice", "data1", "write")) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 1f353aa1..0659e353 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -33,17 +33,21 @@ def test_add_role_for_user(self): get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") ) e.add_role_for_user("alice", "data1_admin") - self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"]) + self.assertCountEqual( + e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"] + ) def test_delete_role_for_user(self): e = self.get_enforcer( get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") ) e.add_role_for_user("alice", "data1_admin") - self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"]) + self.assertCountEqual( + e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"] + ) e.delete_role_for_user("alice", "data1_admin") - self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) + self.assertCountEqual(e.get_roles_for_user("alice"), ["data2_admin"]) def test_delete_roles_for_user(self): e = self.get_enforcer( @@ -140,16 +144,16 @@ def test_enforce_implicit_roles_api(self): get_examples("rbac_with_hierarchy_policy.csv"), ) - self.assertTrue( - e.get_permissions_for_user("alice") == [["alice", "data1", "read"]] + self.assertCountEqual( + e.get_permissions_for_user("alice"), [["alice", "data1", "read"]] ) - self.assertTrue( - e.get_permissions_for_user("bob") == [["bob", "data2", "write"]] + self.assertCountEqual( + e.get_permissions_for_user("bob"), [["bob", "data2", "write"]] ) - self.assertTrue( - e.get_implicit_roles_for_user("alice") - == ["admin", "data1_admin", "data2_admin"] + self.assertCountEqual( + e.get_implicit_roles_for_user("alice"), + ["admin", "data1_admin", "data2_admin"], ) self.assertTrue(e.get_implicit_roles_for_user("bob") == []) @@ -159,12 +163,12 @@ def test_enforce_implicit_roles_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertTrue( - e.get_roles_for_user_in_domain("alice", "domain1") == ["role:global_admin"] + self.assertCountEqual( + e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] ) - self.assertTrue( - e.get_implicit_roles_for_user("alice", "domain1") - == ["role:global_admin", "role:reader", "role:writer"] + self.assertCountEqual( + e.get_implicit_roles_for_user("alice", "domain1"), + ["role:global_admin", "role:reader", "role:writer"], ) def test_enforce_implicit_permissions_api(self): @@ -173,24 +177,24 @@ def test_enforce_implicit_permissions_api(self): get_examples("rbac_with_hierarchy_policy.csv"), ) - self.assertTrue( - e.get_permissions_for_user("alice") == [["alice", "data1", "read"]] + self.assertCountEqual( + e.get_permissions_for_user("alice"), [["alice", "data1", "read"]] ) - self.assertTrue( - e.get_permissions_for_user("bob") == [["bob", "data2", "write"]] + self.assertCountEqual( + e.get_permissions_for_user("bob"), [["bob", "data2", "write"]] ) - self.assertTrue( - e.get_implicit_permissions_for_user("alice") - == [ + self.assertCountEqual( + e.get_implicit_permissions_for_user("alice"), + [ ["alice", "data1", "read"], ["data1_admin", "data1", "read"], ["data1_admin", "data1", "write"], ["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"], - ] + ], ) - self.assertTrue( - e.get_implicit_permissions_for_user("bob") == [["bob", "data2", "write"]] + self.assertCountEqual( + e.get_implicit_permissions_for_user("bob"), [["bob", "data2", "write"]] ) def test_enforce_implicit_permissions_api_with_domain(self): @@ -199,22 +203,22 @@ def test_enforce_implicit_permissions_api_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertTrue( - e.get_roles_for_user_in_domain("alice", "domain1") == ["role:global_admin"] + self.assertCountEqual( + e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] ) - self.assertTrue( - e.get_implicit_roles_for_user("alice", "domain1") - == ["role:global_admin", "role:reader", "role:writer"] + self.assertCountEqual( + e.get_implicit_roles_for_user("alice", "domain1"), + ["role:global_admin", "role:reader", "role:writer"], ) - self.assertTrue( - e.get_implicit_permissions_for_user("alice", "domain1") - == [ + self.assertCountEqual( + e.get_implicit_permissions_for_user("alice", "domain1"), + [ ["alice", "domain1", "data2", "read"], ["role:reader", "domain1", "data1", "read"], ["role:writer", "domain1", "data1", "write"], - ] + ], ) - self.assertTrue(e.get_implicit_permissions_for_user("bob", "domain1") == []) + self.assertCountEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) def test_enforce_get_users_in_domain(self): e = self.get_enforcer( From dd6ff0b38b60f592a769b753bc0227d900a510b4 Mon Sep 17 00:00:00 2001 From: Andreas Bichinger Date: Sun, 7 Nov 2021 14:05:38 +0100 Subject: [PATCH 191/349] test: replace assertCountEqual(a,b) with assertEqual(sorted(a), sorted(b)) Signed-off-by: Andreas Bichinger --- tests/rbac/test_role_manager.py | 44 +++++++------- tests/test_rbac_api.py | 101 ++++++++++++++++++-------------- 2 files changed, 80 insertions(+), 65 deletions(-) diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py index 33466975..8c3fe929 100644 --- a/tests/rbac/test_role_manager.py +++ b/tests/rbac/test_role_manager.py @@ -40,13 +40,13 @@ def test_role(self): self.assertTrue(rm.has_link("u4", "g2")) self.assertTrue(rm.has_link("u4", "g3")) - self.assertCountEqual(rm.get_roles("u1"), ["g1"]) - self.assertCountEqual(rm.get_roles("u2"), ["g1"]) - self.assertCountEqual(rm.get_roles("u3"), ["g2"]) - self.assertCountEqual(rm.get_roles("u4"), ["g2", "g3"]) - self.assertCountEqual(rm.get_roles("g1"), ["g3"]) - self.assertCountEqual(rm.get_roles("g2"), []) - self.assertCountEqual(rm.get_roles("g3"), []) + self.assertEqual(rm.get_roles("u1"), ["g1"]) + self.assertEqual(rm.get_roles("u2"), ["g1"]) + self.assertEqual(rm.get_roles("u3"), ["g2"]) + self.assertEqual(sorted(rm.get_roles("u4")), sorted(["g2", "g3"])) + self.assertEqual(rm.get_roles("g1"), ["g3"]) + self.assertEqual(rm.get_roles("g2"), []) + self.assertEqual(rm.get_roles("g3"), []) rm.delete_link("g1", "g3") rm.delete_link("u4", "g2") @@ -71,13 +71,13 @@ def test_role(self): self.assertFalse(rm.has_link("u4", "g2")) self.assertTrue(rm.has_link("u4", "g3")) - self.assertCountEqual(rm.get_roles("u1"), ["g1"]) - self.assertCountEqual(rm.get_roles("u2"), ["g1"]) - self.assertCountEqual(rm.get_roles("u3"), ["g2"]) - self.assertCountEqual(rm.get_roles("u4"), ["g3"]) - self.assertCountEqual(rm.get_roles("g1"), []) - self.assertCountEqual(rm.get_roles("g2"), []) - self.assertCountEqual(rm.get_roles("g3"), []) + self.assertEqual(rm.get_roles("u1"), ["g1"]) + self.assertEqual(rm.get_roles("u2"), ["g1"]) + self.assertEqual(rm.get_roles("u3"), ["g2"]) + self.assertEqual(rm.get_roles("u4"), ["g3"]) + self.assertEqual(rm.get_roles("g1"), []) + self.assertEqual(rm.get_roles("g2"), []) + self.assertEqual(rm.get_roles("g3"), []) rm.clear() @@ -110,19 +110,21 @@ def test_role(self): self.assertTrue(rm.has_link("g1", "any_group")) self.assertTrue(rm.has_link("g2", "any_group")) - self.assertCountEqual(rm.get_roles("u1"), ["g1", "any_user"]) - self.assertCountEqual(rm.get_roles("u2"), ["g2", "g1", r"g\d+", "any_user"]) - self.assertCountEqual(rm.get_roles(r"u\d+"), ["any_user"]) - self.assertCountEqual(rm.get_roles("u3"), ["any_user"]) - self.assertCountEqual(rm.get_roles("g1"), ["any_group"]) - self.assertCountEqual(rm.get_roles("g2"), ["any_group"]) + self.assertEqual(sorted(rm.get_roles("u1")), sorted(["g1", "any_user"])) + self.assertEqual( + sorted(rm.get_roles("u2")), sorted(["g2", "g1", r"g\d+", "any_user"]) + ) + self.assertEqual(rm.get_roles(r"u\d+"), ["any_user"]) + self.assertEqual(rm.get_roles("u3"), ["any_user"]) + self.assertEqual(rm.get_roles("g1"), ["any_group"]) + self.assertEqual(rm.get_roles("g2"), ["any_group"]) rm.delete_link(r"u\d+", "any_user") rm.delete_link(r"g\d+", "any_group") rm.delete_link("u1", "g1") rm.add_link("u1", "g2") - self.assertCountEqual(rm.get_roles("u1"), ["g2"]) + self.assertEqual(rm.get_roles("u1"), ["g2"]) rm.clear() diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 0659e353..05196f6e 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -33,8 +33,9 @@ def test_add_role_for_user(self): get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") ) e.add_role_for_user("alice", "data1_admin") - self.assertCountEqual( - e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"] + self.assertEqual( + sorted(e.get_roles_for_user("alice")), + sorted(["data2_admin", "data1_admin"]), ) def test_delete_role_for_user(self): @@ -42,12 +43,13 @@ def test_delete_role_for_user(self): get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") ) e.add_role_for_user("alice", "data1_admin") - self.assertCountEqual( - e.get_roles_for_user("alice"), ["data2_admin", "data1_admin"] + self.assertEqual( + sorted(e.get_roles_for_user("alice")), + sorted(["data2_admin", "data1_admin"]), ) e.delete_role_for_user("alice", "data1_admin") - self.assertCountEqual(e.get_roles_for_user("alice"), ["data2_admin"]) + self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) def test_delete_roles_for_user(self): e = self.get_enforcer( @@ -144,16 +146,20 @@ def test_enforce_implicit_roles_api(self): get_examples("rbac_with_hierarchy_policy.csv"), ) - self.assertCountEqual( - e.get_permissions_for_user("alice"), [["alice", "data1", "read"]] + self.assertEqual( + sorted(e.get_permissions_for_user("alice")), + sorted([["alice", "data1", "read"]]), ) - self.assertCountEqual( - e.get_permissions_for_user("bob"), [["bob", "data2", "write"]] + self.assertEqual( + sorted(e.get_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), ) - self.assertCountEqual( - e.get_implicit_roles_for_user("alice"), - ["admin", "data1_admin", "data2_admin"], + self.assertEqual( + sorted(e.get_implicit_roles_for_user("alice")), + sorted( + ["admin", "data1_admin", "data2_admin"], + ), ) self.assertTrue(e.get_implicit_roles_for_user("bob") == []) @@ -163,12 +169,12 @@ def test_enforce_implicit_roles_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertCountEqual( + self.assertEqual( e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] ) - self.assertCountEqual( - e.get_implicit_roles_for_user("alice", "domain1"), - ["role:global_admin", "role:reader", "role:writer"], + self.assertEqual( + sorted(e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), ) def test_enforce_implicit_permissions_api(self): @@ -177,24 +183,29 @@ def test_enforce_implicit_permissions_api(self): get_examples("rbac_with_hierarchy_policy.csv"), ) - self.assertCountEqual( - e.get_permissions_for_user("alice"), [["alice", "data1", "read"]] + self.assertEqual( + sorted(e.get_permissions_for_user("alice")), + sorted([["alice", "data1", "read"]]), ) - self.assertCountEqual( - e.get_permissions_for_user("bob"), [["bob", "data2", "write"]] + self.assertEqual( + sorted(e.get_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), ) - self.assertCountEqual( - e.get_implicit_permissions_for_user("alice"), - [ - ["alice", "data1", "read"], - ["data1_admin", "data1", "read"], - ["data1_admin", "data1", "write"], - ["data2_admin", "data2", "read"], - ["data2_admin", "data2", "write"], - ], + self.assertEqual( + sorted(e.get_implicit_permissions_for_user("alice")), + sorted( + [ + ["alice", "data1", "read"], + ["data1_admin", "data1", "read"], + ["data1_admin", "data1", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ] + ), ) - self.assertCountEqual( - e.get_implicit_permissions_for_user("bob"), [["bob", "data2", "write"]] + self.assertEqual( + sorted(e.get_implicit_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), ) def test_enforce_implicit_permissions_api_with_domain(self): @@ -203,22 +214,24 @@ def test_enforce_implicit_permissions_api_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertCountEqual( + self.assertEqual( e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] ) - self.assertCountEqual( - e.get_implicit_roles_for_user("alice", "domain1"), - ["role:global_admin", "role:reader", "role:writer"], - ) - self.assertCountEqual( - e.get_implicit_permissions_for_user("alice", "domain1"), - [ - ["alice", "domain1", "data2", "read"], - ["role:reader", "domain1", "data1", "read"], - ["role:writer", "domain1", "data1", "write"], - ], + self.assertEqual( + sorted(e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), ) - self.assertCountEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) + self.assertEqual( + sorted(e.get_implicit_permissions_for_user("alice", "domain1")), + sorted( + [ + ["alice", "domain1", "data2", "read"], + ["role:reader", "domain1", "data1", "read"], + ["role:writer", "domain1", "data1", "write"], + ] + ), + ) + self.assertEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) def test_enforce_get_users_in_domain(self): e = self.get_enforcer( From db149b7d425de5ea96f46f86e52af48ce81f48ea Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 18 Nov 2021 13:58:45 +0000 Subject: [PATCH 192/349] chore(release): 1.9.7 [skip ci] ## [1.9.7](https://github.com/casbin/pycasbin/compare/v1.9.6...v1.9.7) (2021-11-18) ### Bug Fixes * reimplement default role manager ([b8f7fa7](https://github.com/casbin/pycasbin/commit/b8f7fa7fd515e70b685a4d044fa3d568935ae337)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3ba3687..1f1d464a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.9.7](https://github.com/casbin/pycasbin/compare/v1.9.6...v1.9.7) (2021-11-18) + + +### Bug Fixes + +* reimplement default role manager ([b8f7fa7](https://github.com/casbin/pycasbin/commit/b8f7fa7fd515e70b685a4d044fa3d568935ae337)) + ## [1.9.6](https://github.com/casbin/pycasbin/compare/v1.9.5...v1.9.6) (2021-11-12) diff --git a/setup.cfg b/setup.cfg index 8bc6fd91..0acd76d8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.6 +version = 1.9.7 From 2d0176990688df15a9d4d5ee4fed3a980da68cd6 Mon Sep 17 00:00:00 2001 From: sallycaoyu <72861247+sallycaoyu@users.noreply.github.com> Date: Sat, 20 Nov 2021 09:16:02 +0800 Subject: [PATCH 193/349] feat: port the test case of add_function() example to pycasbin (#217) Signed-off-by: sallycaoyu --- tests/test_enforcer.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index d3d24f54..5aa2fa29 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -127,6 +127,23 @@ def test_enforce_key_match2(self): self.assertTrue(e.enforce("alice", "/alice_data/resource", "GET")) self.assertTrue(e.enforce("alice", "/alice_data2/123/using/456", "GET")) + def test_enforce_key_match_custom_model(self): + e = self.get_enforcer( + get_examples('keymatch_custom_model.conf'), get_examples('keymatch2_policy.csv') + ) + + def custom_function(key1, key2): + if key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data/:resource": + return True + elif key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data2/:id/using/:resId": + return True + return False + + e.add_function("keyMatchCustom", custom_function) + + self.assertFalse(e.enforce("alice", "/alice_data2/myid", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data2/myid/using/res_id", "GET")) + def test_enforce_priority(self): e = self.get_enforcer( get_examples("priority_model.conf"), get_examples("priority_policy.csv") From 6c859127e9c3ed88c40ded71a28d7ab1032dd113 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 20 Nov 2021 01:18:19 +0000 Subject: [PATCH 194/349] chore(release): 1.10.0 [skip ci] # [1.10.0](https://github.com/casbin/pycasbin/compare/v1.9.7...v1.10.0) (2021-11-20) ### Features * port the test case of add_function() example to pycasbin ([#217](https://github.com/casbin/pycasbin/issues/217)) ([c19d065](https://github.com/casbin/pycasbin/commit/c19d0658687bfb4e0a59c7323024c2842b226d56)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f1d464a..60374b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.10.0](https://github.com/casbin/pycasbin/compare/v1.9.7...v1.10.0) (2021-11-20) + + +### Features + +* port the test case of add_function() example to pycasbin ([#217](https://github.com/casbin/pycasbin/issues/217)) ([c19d065](https://github.com/casbin/pycasbin/commit/c19d0658687bfb4e0a59c7323024c2842b226d56)) + ## [1.9.7](https://github.com/casbin/pycasbin/compare/v1.9.6...v1.9.7) (2021-11-18) diff --git a/setup.cfg b/setup.cfg index 0acd76d8..1ae98a1d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.9.7 +version = 1.10.0 From 74b8b9ae0f37c904cef927731dcb3962def9f2be Mon Sep 17 00:00:00 2001 From: sallycaoyu Date: Sun, 21 Nov 2021 03:03:39 +0800 Subject: [PATCH 195/349] fix: linter bug in test_enforcer.py Signed-off-by: sallycaoyu --- tests/test_enforcer.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 5aa2fa29..00d977ea 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -129,13 +129,20 @@ def test_enforce_key_match2(self): def test_enforce_key_match_custom_model(self): e = self.get_enforcer( - get_examples('keymatch_custom_model.conf'), get_examples('keymatch2_policy.csv') + get_examples("keymatch_custom_model.conf"), + get_examples("keymatch2_policy.csv"), ) def custom_function(key1, key2): - if key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data/:resource": + if ( + key1 == "/alice_data2/myid/using/res_id" + and key2 == "/alice_data/:resource" + ): return True - elif key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data2/:id/using/:resId": + elif ( + key1 == "/alice_data2/myid/using/res_id" + and key2 == "/alice_data2/:id/using/:resId" + ): return True return False From 7053fb81434511cf5a2ff55179b637dc03c7472c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 21 Nov 2021 01:45:53 +0000 Subject: [PATCH 196/349] chore(release): 1.10.1 [skip ci] ## [1.10.1](https://github.com/casbin/pycasbin/compare/v1.10.0...v1.10.1) (2021-11-21) ### Bug Fixes * linter bug in test_enforcer.py ([782f229](https://github.com/casbin/pycasbin/commit/782f229f3bc39d8743731a0ce860d827dba14dab)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60374b97..a93154ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.10.1](https://github.com/casbin/pycasbin/compare/v1.10.0...v1.10.1) (2021-11-21) + + +### Bug Fixes + +* linter bug in test_enforcer.py ([782f229](https://github.com/casbin/pycasbin/commit/782f229f3bc39d8743731a0ce860d827dba14dab)) + # [1.10.0](https://github.com/casbin/pycasbin/compare/v1.9.7...v1.10.0) (2021-11-20) diff --git a/setup.cfg b/setup.cfg index 1ae98a1d..88566aca 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.10.0 +version = 1.10.1 From 69c1b1c1a94d3cdd0812633277dc87148c81e0b9 Mon Sep 17 00:00:00 2001 From: tim Date: Fri, 19 Nov 2021 22:51:06 +0400 Subject: [PATCH 197/349] feat: ignore policies with domain when get_implicit_permissions_for_user Signed-off-by: tim --- casbin/enforcer.py | 9 ++++-- casbin/synced_enforcer.py | 6 ++-- ...c_with_domains_without_policy_matcher.conf | 14 +++++++++ ..._with_hierarchy_without_policy_domains.csv | 12 +++++++ tests/test_rbac_api.py | 31 +++++++++++++++++++ 5 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 examples/rbac_with_domains_without_policy_matcher.conf create mode 100644 examples/rbac_with_hierarchy_without_policy_domains.csv diff --git a/casbin/enforcer.py b/casbin/enforcer.py index ec4216e4..8183d5e4 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -136,7 +136,9 @@ def get_implicit_roles_for_user(self, name, domain=""): return res - def get_implicit_permissions_for_user(self, user, domain=""): + def get_implicit_permissions_for_user( + self, user, domain="", filter_policy_dom=True + ): """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. @@ -147,6 +149,9 @@ def get_implicit_permissions_for_user(self, user, domain=""): get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + + Inherited roles can be matched by domain. + filter_policy_dom: bool - For given *domain*, policies will be filtered by domain as well. Default = True """ roles = self.get_implicit_roles_for_user(user, domain) @@ -154,7 +159,7 @@ def get_implicit_permissions_for_user(self, user, domain=""): res = [] for role in roles: - if domain: + if domain and filter_policy_dom: permissions = self.get_permissions_for_user_in_domain(role, domain) else: permissions = self.get_permissions_for_user(role) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 3947806f..c1180360 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -443,7 +443,7 @@ def get_implicit_roles_for_user(self, name, *domain): with self._rl: return self._e.get_implicit_roles_for_user(name, *domain) - def get_implicit_permissions_for_user(self, user, *domain): + def get_implicit_permissions_for_user(self, user, *domain, filter_policy_dom=True): """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. @@ -456,7 +456,9 @@ def get_implicit_permissions_for_user(self, user, *domain): But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. """ with self._rl: - return self._e.get_implicit_permissions_for_user(user, *domain) + return self._e.get_implicit_permissions_for_user( + user, *domain, filter_policy_dom=filter_policy_dom + ) def get_implicit_users_for_permission(self, *permission): """ diff --git a/examples/rbac_with_domains_without_policy_matcher.conf b/examples/rbac_with_domains_without_policy_matcher.conf new file mode 100644 index 00000000..e7af2e24 --- /dev/null +++ b/examples/rbac_with_domains_without_policy_matcher.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, dom, 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.dom) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/examples/rbac_with_hierarchy_without_policy_domains.csv b/examples/rbac_with_hierarchy_without_policy_domains.csv new file mode 100644 index 00000000..c7d7108b --- /dev/null +++ b/examples/rbac_with_hierarchy_without_policy_domains.csv @@ -0,0 +1,12 @@ +# All policies domain *neutral*. Only roles are domain dependent + +p, role:reader, data1, read +p, role:writer, data1, write +p, alice, data2, read + +g, role:global_admin, role:reader, domain1 +g, role:global_admin, role:writer, domain1 + +g, alice, role:global_admin, domain1 + + diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 05196f6e..c37cf129 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -233,6 +233,37 @@ def test_enforce_implicit_permissions_api_with_domain(self): ) self.assertEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) + def test_enforce_implicit_permissions_api_with_domain_ignore_domain_policies_filter( + self, + ): + e = self.get_enforcer( + get_examples("rbac_with_domains_without_policy_matcher.conf"), + get_examples("rbac_with_hierarchy_without_policy_domains.csv"), + ) + + self.assertEqual( + e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] + ) + self.assertEqual( + sorted(e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), + ) + self.assertEqual( + sorted( + e.get_implicit_permissions_for_user( + "alice", "domain1", filter_policy_dom=False + ) + ), + sorted( + [ + ["alice", "data2", "read"], + ["role:reader", "data1", "read"], + ["role:writer", "data1", "write"], + ] + ), + ) + self.assertEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) + def test_enforce_get_users_in_domain(self): e = self.get_enforcer( get_examples("rbac_with_domains_model.conf"), From ae4e60c9d569f283b07fb49784b80759083c0e19 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 21 Nov 2021 06:03:30 +0000 Subject: [PATCH 198/349] chore(release): 1.11.0 [skip ci] # [1.11.0](https://github.com/casbin/pycasbin/compare/v1.10.1...v1.11.0) (2021-11-21) ### Features * ignore policies with domain when get_implicit_permissions_for_user ([5ca47b7](https://github.com/casbin/pycasbin/commit/5ca47b7224299d07c19d80c9e2e4846a4135f151)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a93154ae..1fe2e2f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.11.0](https://github.com/casbin/pycasbin/compare/v1.10.1...v1.11.0) (2021-11-21) + + +### Features + +* ignore policies with domain when get_implicit_permissions_for_user ([5ca47b7](https://github.com/casbin/pycasbin/commit/5ca47b7224299d07c19d80c9e2e4846a4135f151)) + ## [1.10.1](https://github.com/casbin/pycasbin/compare/v1.10.0...v1.10.1) (2021-11-21) diff --git a/setup.cfg b/setup.cfg index 88566aca..bd98dcc8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.10.1 +version = 1.11.0 From 06735036d0c7bb819ac9f87c8624691e7ab533a6 Mon Sep 17 00:00:00 2001 From: sallycaoyu Date: Sun, 21 Nov 2021 15:14:30 +0800 Subject: [PATCH 199/349] fix: add glob_match() test cases from Go to Python Signed-off-by: sallycaoyu --- tests/util/test_builtin_operators.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 11c3f512..533861c2 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -184,6 +184,20 @@ def test_glob_match(self): self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/*")) + def test_glob_match2(self): + # add missing tests from Go to Python + self.assertFalse(util.glob_match_func("/foo", "*/foo")) # different from Go + self.assertFalse(util.glob_match_func("/foo", "*/foo*")) # different from Go + self.assertFalse(util.glob_match_func("/foo", "*/foo/*")) + self.assertFalse(util.glob_match_func("/foo/bar", "*/foo")) + self.assertFalse(util.glob_match_func("/foo/bar", "*/foo*")) + self.assertFalse( + util.glob_match_func("/foo/bar", "*/foo/*") + ) # different from Go + self.assertFalse(util.glob_match_func("/foobar", "*/foo")) + self.assertFalse(util.glob_match_func("/foobar", "*/foo*")) # different from Go + self.assertFalse(util.glob_match_func("/foobar", "*/foo/*")) + def test_ip_match(self): self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.0/24")) self.assertFalse(util.ip_match_func("192.168.2.123", "192.168.3.0/24")) From f6a8e8011fcba923bc4da53879fcb3d3b50ebe6b Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 21 Nov 2021 09:50:46 +0000 Subject: [PATCH 200/349] chore(release): 1.11.1 [skip ci] ## [1.11.1](https://github.com/casbin/pycasbin/compare/v1.11.0...v1.11.1) (2021-11-21) ### Bug Fixes * add glob_match() test cases from Go to Python ([658d3bb](https://github.com/casbin/pycasbin/commit/658d3bb189749a06089c678f708d546e26aebe11)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fe2e2f7..d29b4aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.11.1](https://github.com/casbin/pycasbin/compare/v1.11.0...v1.11.1) (2021-11-21) + + +### Bug Fixes + +* add glob_match() test cases from Go to Python ([658d3bb](https://github.com/casbin/pycasbin/commit/658d3bb189749a06089c678f708d546e26aebe11)) + # [1.11.0](https://github.com/casbin/pycasbin/compare/v1.10.1...v1.11.0) (2021-11-21) diff --git a/setup.cfg b/setup.cfg index bd98dcc8..a557eb27 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.11.0 +version = 1.11.1 From 3b29d9aea87af243e9d64c5eed45f41e9f8639ce Mon Sep 17 00:00:00 2001 From: Tamerlan Abilov Date: Sat, 27 Nov 2021 19:56:03 +0400 Subject: [PATCH 201/349] feat: support matching functions to filter policies (#222) Signed-off-by: timabilov --- casbin/model/policy.py | 3 +- ..._with_domain_and_policy_pattern_model.conf | 14 +++ ..._with_domain_and_policy_pattern_policy.csv | 6 ++ examples/rbac_with_domains_policy.csv | 10 +-- tests/test_management_api.py | 87 +++++++++++++++++++ 5 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 examples/rbac_with_domain_and_policy_pattern_model.conf create mode 100644 examples/rbac_with_domain_and_policy_pattern_policy.csv diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 332ad146..175dbf3b 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -69,7 +69,8 @@ def get_filtered_policy(self, sec, ptype, field_index, *field_values): rule for rule in self[sec][ptype].policy if all( - value == "" or rule[field_index + i] == value + (callable(value) and value(rule[field_index + i])) + or (value == "" or rule[field_index + i] == value) for i, value in enumerate(field_values) ) ] diff --git a/examples/rbac_with_domain_and_policy_pattern_model.conf b/examples/rbac_with_domain_and_policy_pattern_model.conf new file mode 100644 index 00000000..9405d855 --- /dev/null +++ b/examples/rbac_with_domain_and_policy_pattern_model.conf @@ -0,0 +1,14 @@ +[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) && keyMatch2(r.dom, p.dom) && r.obj == p.obj && r.act == p.act \ No newline at end of file diff --git a/examples/rbac_with_domain_and_policy_pattern_policy.csv b/examples/rbac_with_domain_and_policy_pattern_policy.csv new file mode 100644 index 00000000..f273ee58 --- /dev/null +++ b/examples/rbac_with_domain_and_policy_pattern_policy.csv @@ -0,0 +1,6 @@ +p, admin, domain.*, data1, read +p, user, domain.1, data2, read +p, user, domain.1, data2, write + +g, alice, user, * +g, bob, admin, domain.3 \ No newline at end of file diff --git a/examples/rbac_with_domains_policy.csv b/examples/rbac_with_domains_policy.csv index 9db696d4..8558d171 100644 --- a/examples/rbac_with_domains_policy.csv +++ b/examples/rbac_with_domains_policy.csv @@ -1,6 +1,6 @@ -p, admin, domain1, data1, read -p, admin, domain1, data1, write -p, admin, domain2, data2, read -p, admin, domain2, data2, write -g, alice, admin, domain1 +p, 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, domain2 \ No newline at end of file diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 503d91d0..a97d0fdd 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -1,3 +1,5 @@ +from functools import partial + import casbin from tests.test_enforcer import get_examples, TestCaseBase @@ -99,6 +101,91 @@ def test_get_policy_api(self): self.assertTrue(e.has_grouping_policy(["alice", "data2_admin"])) self.assertFalse(e.has_grouping_policy(["bob", "data2_admin"])) + def test_get_policy_matching_function(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + + self.assertEqual( + e.get_policy(), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + km2_fn = casbin.util.key_match2_func + self.assertEqual( + e.get_filtered_grouping_policy(2, partial(km2_fn, "domain.3")), + [["alice", "user", "*"], ["bob", "admin", "domain.3"]], + ) + + self.assertEqual( + e.get_filtered_grouping_policy(2, partial(km2_fn, "domain.1")), + [["alice", "user", "*"]], + ) + + # first p record matches to domain.3 + self.assertEqual( + e.get_filtered_policy(1, partial(km2_fn, "domain.3")), + [["admin", "domain.*", "data1", "read"]], + ) + + # first and second p record should be matched to (.., domain.1, read) + self.assertEqual( + e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "read"), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ], + ) + + def test_get_policy_multiple_matching_functions(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + + self.assertEqual( + e.get_policy(), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + km2_fn = casbin.util.key_match2_func + + self.assertEqual( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.2"), lambda a: "data" in a + ), + [["admin", "domain.*", "data1", "read"]], + ) + + self.assertEqual( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read" + ), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ], + ) + + self.assertEqual( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.1"), "", "reading".startswith + ), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ], + ) + def test_modify_policy_api(self): e = self.get_enforcer( get_examples("rbac_model.conf"), From 44d4fb133c9cae9cbc90968628a023d82fb29c27 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 27 Nov 2021 15:58:40 +0000 Subject: [PATCH 202/349] chore(release): 1.12.0 [skip ci] # [1.12.0](https://github.com/casbin/pycasbin/compare/v1.11.1...v1.12.0) (2021-11-27) ### Features * support matching functions to filter policies ([#222](https://github.com/casbin/pycasbin/issues/222)) ([9d0528d](https://github.com/casbin/pycasbin/commit/9d0528db85b8aedfc6b337f3d98bad19c64103e7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d29b4aa1..c4a8be31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.12.0](https://github.com/casbin/pycasbin/compare/v1.11.1...v1.12.0) (2021-11-27) + + +### Features + +* support matching functions to filter policies ([#222](https://github.com/casbin/pycasbin/issues/222)) ([9d0528d](https://github.com/casbin/pycasbin/commit/9d0528db85b8aedfc6b337f3d98bad19c64103e7)) + ## [1.11.1](https://github.com/casbin/pycasbin/compare/v1.11.0...v1.11.1) (2021-11-21) diff --git a/setup.cfg b/setup.cfg index a557eb27..b4c180c2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.11.1 +version = 1.12.0 From 306e340042f0f8cc566c5cd5bc97a902a2db6faa Mon Sep 17 00:00:00 2001 From: Tamerlan Abilov Date: Wed, 8 Dec 2021 06:44:44 +0400 Subject: [PATCH 203/349] feat: get implicit permissions filter policies domain by matching function (#226) Signed-off-by: timabilov Co-authored-by: timabilov --- casbin/enforcer.py | 20 ++++-- ..._with_domain_and_policy_pattern_policy.csv | 1 + tests/test_management_api.py | 70 +++++++++++++------ tests/test_rbac_api.py | 37 ++++++++++ 4 files changed, 99 insertions(+), 29 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 8183d5e4..f45473fb 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,3 +1,5 @@ +from functools import partial + from casbin.management_enforcer import ManagementEnforcer from casbin.util import join_slice, array_remove_duplicates, set_subtract @@ -150,7 +152,10 @@ def get_implicit_permissions_for_user( get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. - Inherited roles can be matched by domain. + For given domain policies are filtered by corresponding domain matching function of DomainManager + Inherited roles can be matched by domain. For domain neutral policies set: + filter_policy_dom = False + filter_policy_dom: bool - For given *domain*, policies will be filtered by domain as well. Default = True """ roles = self.get_implicit_roles_for_user(user, domain) @@ -158,12 +163,15 @@ def get_implicit_permissions_for_user( roles.insert(0, user) res = [] - for role in roles: - if domain and filter_policy_dom: - permissions = self.get_permissions_for_user_in_domain(role, domain) - else: - permissions = self.get_permissions_for_user(role) + # policy domain should be matched by domain_match_fn of DomainManager + if domain: + domain = partial(self.get_role_manager().domain_matching_func, domain) + + for role in roles: + permissions = self.get_permissions_for_user_in_domain( + role, domain if filter_policy_dom else "" + ) res.extend(permissions) return res diff --git a/examples/rbac_with_domain_and_policy_pattern_policy.csv b/examples/rbac_with_domain_and_policy_pattern_policy.csv index f273ee58..8d7b056b 100644 --- a/examples/rbac_with_domain_and_policy_pattern_policy.csv +++ b/examples/rbac_with_domain_and_policy_pattern_policy.csv @@ -1,4 +1,5 @@ p, admin, domain.*, data1, read +p, user, domain.*, data3, read p, user, domain.1, data2, read p, user, domain.1, data2, write diff --git a/tests/test_management_api.py b/tests/test_management_api.py index a97d0fdd..812c769e 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -111,6 +111,7 @@ def test_get_policy_matching_function(self): e.get_policy(), [ ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], ["user", "domain.1", "data2", "read"], ["user", "domain.1", "data2", "write"], ], @@ -127,21 +128,26 @@ def test_get_policy_matching_function(self): [["alice", "user", "*"]], ) - # first p record matches to domain.3 + # first and second p record matches to domain.3 self.assertEqual( e.get_filtered_policy(1, partial(km2_fn, "domain.3")), - [["admin", "domain.*", "data1", "read"]], - ) - - # first and second p record should be matched to (.., domain.1, read) - self.assertEqual( - e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "read"), [ ["admin", "domain.*", "data1", "read"], - ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], ], ) + self.assertEqual( + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "read")), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] + ), + ) + def test_get_policy_multiple_matching_functions(self): e = self.get_enforcer( get_examples("rbac_with_domain_and_policy_pattern_model.conf"), @@ -152,6 +158,7 @@ def test_get_policy_multiple_matching_functions(self): e.get_policy(), [ ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], ["user", "domain.1", "data2", "read"], ["user", "domain.1", "data2", "write"], ], @@ -160,30 +167,47 @@ def test_get_policy_multiple_matching_functions(self): km2_fn = casbin.util.key_match2_func self.assertEqual( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.2"), lambda a: "data" in a + sorted( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.2"), lambda a: "data" in a + ) + ), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], + ] ), - [["admin", "domain.*", "data1", "read"]], ) self.assertEqual( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read" + sorted( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read" + ) + ), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] ), - [ - ["admin", "domain.*", "data1", "read"], - ["user", "domain.1", "data2", "read"], - ], ) self.assertEqual( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.1"), "", "reading".startswith + sorted( + e.get_filtered_policy( + 1, partial(km2_fn, "domain.1"), "", "reading".startswith + ) + ), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] ), - [ - ["admin", "domain.*", "data1", "read"], - ["user", "domain.1", "data2", "read"], - ], ) def test_modify_policy_api(self): diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index c37cf129..01d21492 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -233,6 +233,43 @@ def test_enforce_implicit_permissions_api_with_domain(self): ) self.assertEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) + def test_enforce_implicit_permissions_api_with_domain_matching_function(self): + + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + + e.get_role_manager().add_domain_matching_func(casbin.util.key_match2_func) + + self.assertEqual( + e.get_implicit_permissions_for_user("alice", "domain.3"), + [["user", "domain.*", "data3", "read"]], + ) + + self.assertEqual( + e.get_implicit_permissions_for_user("alice", "domain.1"), + [ + ["user", "domain.*", "data3", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + self.assertEqual( + e.get_implicit_permissions_for_user("bob", "domain.3"), + [["admin", "domain.*", "data1", "read"]], + ) + + self.assertEqual( + e.get_implicit_permissions_for_user("bob", "domain.2"), + [], + ) + + self.assertEqual( + sorted(e.get_implicit_permissions_for_user("bob", "domain.1")), [] + ) + def test_enforce_implicit_permissions_api_with_domain_ignore_domain_policies_filter( self, ): From 31db4067e8369ebd8684be304ba2cb805865d7d7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 8 Dec 2021 02:47:12 +0000 Subject: [PATCH 204/349] chore(release): 1.13.0 [skip ci] # [1.13.0](https://github.com/casbin/pycasbin/compare/v1.12.0...v1.13.0) (2021-12-08) ### Features * get implicit permissions filter policies domain by matching function ([#226](https://github.com/casbin/pycasbin/issues/226)) ([e16dfb3](https://github.com/casbin/pycasbin/commit/e16dfb3c2992b0195b99b2aa07f30569d5d273af)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4a8be31..31c42e8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.13.0](https://github.com/casbin/pycasbin/compare/v1.12.0...v1.13.0) (2021-12-08) + + +### Features + +* get implicit permissions filter policies domain by matching function ([#226](https://github.com/casbin/pycasbin/issues/226)) ([e16dfb3](https://github.com/casbin/pycasbin/commit/e16dfb3c2992b0195b99b2aa07f30569d5d273af)) + # [1.12.0](https://github.com/casbin/pycasbin/compare/v1.11.1...v1.12.0) (2021-11-27) diff --git a/setup.cfg b/setup.cfg index b4c180c2..67aaab02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.12.0 +version = 1.13.0 From e9bf5f7736cb523dc89991a580df508066d9900c Mon Sep 17 00:00:00 2001 From: Bingchang Chen <19990626.love@163.com> Date: Fri, 10 Dec 2021 00:10:09 +0800 Subject: [PATCH 205/349] feat: casbin_js_get_permission_for_user (#229) Signed-off-by: abingcbc --- casbin/__init__.py | 1 + casbin/frontend.py | 14 ++++++++++++++ casbin/model/model.py | 30 ++++++++++++++++++++++++++++++ tests/test_frontend.py | 26 ++++++++++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 casbin/frontend.py create mode 100644 tests/test_frontend.py diff --git a/casbin/__init__.py b/casbin/__init__.py index 86f8a949..b838ce20 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -5,3 +5,4 @@ from .persist import * from .effect import * from .model import * +from .frontend import * diff --git a/casbin/frontend.py b/casbin/frontend.py new file mode 100644 index 00000000..c13851ec --- /dev/null +++ b/casbin/frontend.py @@ -0,0 +1,14 @@ +import json + + +def casbin_js_get_permission_for_user(e, user): + model = e.get_model() + m = {} + m["m"] = model.to_text() + policies = [] + for p_type in model["p"].keys(): + policy = model.get_policy("p", p_type) + for p in policy: + policies.append([p_type] + p) + m["p"] = policies + return json.dumps(m) diff --git a/casbin/model/model.py b/casbin/model/model.py index af083a79..93321594 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -98,3 +98,33 @@ def sort_policies_by_priority(self): assertion.policy_map[",".join(policy)] = i return None + + def to_text(self): + s = [] + + def write_string(sec): + for p_type in self[sec]: + value = self[sec][p_type].value + s.append( + "{} = {}\n".format( + sec, value.replace("p_", "p.").replace("r_", "r.") + ) + ) + + s.append("[request_definition]\n") + write_string("r") + s.append("[policy_definition]\n") + write_string("p") + if "g" in self.keys(): + s.append("[role_definition]\n") + for p_type in self["g"]: + s.append("{} = {}\n".format(p_type, self["g"][p_type].value)) + s.append("[policy_effect]\n") + write_string("e") + s.append("[matchers]\n") + write_string("m") + + # remove last \n + s[-1] = s[-1].strip() + + return "".join(s) diff --git a/tests/test_frontend.py b/tests/test_frontend.py new file mode 100644 index 00000000..8ffdf73b --- /dev/null +++ b/tests/test_frontend.py @@ -0,0 +1,26 @@ +import json +import re +import casbin +from unittest import TestCase +from tests.test_enforcer import get_examples + + +class TestFrontend(TestCase): + def test_casbin_js_get_permission_for_user(self): + e = casbin.SyncedEnforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + received = json.loads(casbin.casbin_js_get_permission_for_user(e, "alice")) + with open(get_examples("rbac_model.conf"), "r") as file: + expected_model_str = file.read() + self.assertEqual(received["m"], re.sub("\n+", "\n", expected_model_str)) + + with open(get_examples("rbac_with_hierarchy_policy.csv"), "r") as file: + expected_policies_str = file.read() + expected_policy_item = re.split(r",|\n", expected_policies_str) + i = 0 + for s_arr in received["p"]: + for s in s_arr: + self.assertEqual(s.strip(), expected_policy_item[i].strip()) + i += 1 From fae0ff6cfa0c9f318cf23510614fd78cfdf3ffc5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 9 Dec 2021 16:12:41 +0000 Subject: [PATCH 206/349] chore(release): 1.14.0 [skip ci] # [1.14.0](https://github.com/casbin/pycasbin/compare/v1.13.0...v1.14.0) (2021-12-09) ### Features * casbin_js_get_permission_for_user ([#229](https://github.com/casbin/pycasbin/issues/229)) ([075ca10](https://github.com/casbin/pycasbin/commit/075ca1078ea850498bc058767524468cdf05458d)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31c42e8a..0fdfe4f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.14.0](https://github.com/casbin/pycasbin/compare/v1.13.0...v1.14.0) (2021-12-09) + + +### Features + +* casbin_js_get_permission_for_user ([#229](https://github.com/casbin/pycasbin/issues/229)) ([075ca10](https://github.com/casbin/pycasbin/commit/075ca1078ea850498bc058767524468cdf05458d)) + # [1.13.0](https://github.com/casbin/pycasbin/compare/v1.12.0...v1.13.0) (2021-12-08) diff --git a/setup.cfg b/setup.cfg index 67aaab02..314e151b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.13.0 +version = 1.14.0 From 36a9b620107f287d0599a9a207f6679a177712e9 Mon Sep 17 00:00:00 2001 From: Bingchang Chen <19990626.love@163.com> Date: Sat, 11 Dec 2021 16:33:32 +0800 Subject: [PATCH 207/349] feat: subject priority (#231) Signed-off-by: abingcbc --- casbin/core_enforcer.py | 2 + casbin/model/model.py | 75 +++++++++++++++++++ examples/subject_priority_model.conf | 14 ++++ .../subject_priority_model_with_domain.conf | 14 ++++ examples/subject_priority_policy.csv | 16 ++++ .../subject_priority_policy_with_domain.csv | 7 ++ tests/test_enforcer.py | 16 ++++ 7 files changed, 144 insertions(+) create mode 100644 examples/subject_priority_model.conf create mode 100644 examples/subject_priority_model_with_domain.conf create mode 100644 examples/subject_priority_policy.csv create mode 100644 examples/subject_priority_policy_with_domain.csv diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 496588b3..a5e75580 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -189,6 +189,8 @@ def load_policy(self): self.adapter.load_policy(new_model) + self.model.sort_policies_by_subject_hierarchy() + new_model.sort_policies_by_priority() self.init_rm_map() diff --git a/casbin/model/model.py b/casbin/model/model.py index 93321594..fd4e775d 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -2,6 +2,9 @@ from casbin import util, config from .policy import Policy +DEFAULT_DOMAIN = "" +DEFAULT_SEPARATOR = "::" + class Model(Policy): @@ -99,6 +102,78 @@ def sort_policies_by_priority(self): return None + def sort_policies_by_subject_hierarchy(self): + if self["e"]["e"].value != "subjectPriority(p_eft) || deny": + return + + sub_index = 0 + domain_index = -1 + for ptype, assertion in self["p"].items(): + for index, token in enumerate(assertion.tokens): + if token == "{}_dom".format(ptype): + domain_index = index + break + + subject_hierarchy_map = self.get_subject_hierarchy_map( + self["g"]["g"].policy + ) + + def compare_policy(policy): + domain = DEFAULT_DOMAIN + if domain_index != -1: + domain = policy[domain_index] + name = self.get_name_with_domain(domain, policy[sub_index]) + return subject_hierarchy_map[name] + + assertion.policy = sorted( + assertion.policy, key=compare_policy, reverse=True + ) + for i, policy in enumerate(assertion.policy): + assertion.policy_map[",".join(policy)] = i + + def get_subject_hierarchy_map(self, policies): + subject_hierarchy_map = {} + # Tree structure of role + policy_map = {} + for policy in policies: + if len(policy) < 2: + raise RuntimeError("policy g expect 2 more params") + domain = DEFAULT_DOMAIN + if len(policy) != 2: + domain = policy[2] + child = self.get_name_with_domain(domain, policy[0]) + parent = self.get_name_with_domain(domain, policy[1]) + if parent not in policy_map.keys(): + policy_map[parent] = [child] + else: + policy_map[parent].append(child) + if child not in subject_hierarchy_map.keys(): + subject_hierarchy_map[child] = 0 + if parent not in subject_hierarchy_map.keys(): + subject_hierarchy_map[parent] = 0 + subject_hierarchy_map[child] = 1 + # Use queues for levelOrder + queue = [] + for k, v in subject_hierarchy_map.items(): + root = k + if v != 0: + continue + lv = 0 + queue.append(root) + while len(queue) != 0: + sz = len(queue) + for _ in range(sz): + node = queue.pop(0) + subject_hierarchy_map[node] = lv + if node in policy_map.keys(): + for child in policy_map[node]: + queue.append(child) + lv += 1 + return subject_hierarchy_map + + def get_name_with_domain(self, domain, name): + return "{}{}{}".format(domain, DEFAULT_SEPARATOR, name) + def to_text(self): s = [] diff --git a/examples/subject_priority_model.conf b/examples/subject_priority_model.conf new file mode 100644 index 00000000..77b8c4eb --- /dev/null +++ b/examples/subject_priority_model.conf @@ -0,0 +1,14 @@ +[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.act \ No newline at end of file diff --git a/examples/subject_priority_model_with_domain.conf b/examples/subject_priority_model_with_domain.conf new file mode 100644 index 00000000..92820ce0 --- /dev/null +++ b/examples/subject_priority_model_with_domain.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, dom, act + +[policy_definition] +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.act \ No newline at end of file diff --git a/examples/subject_priority_policy.csv b/examples/subject_priority_policy.csv new file mode 100644 index 00000000..24a44223 --- /dev/null +++ b/examples/subject_priority_policy.csv @@ -0,0 +1,16 @@ +p, 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, subscriber \ No newline at end of file diff --git a/examples/subject_priority_policy_with_domain.csv b/examples/subject_priority_policy_with_domain.csv new file mode 100644 index 00000000..c4859ecd --- /dev/null +++ b/examples/subject_priority_policy_with_domain.csv @@ -0,0 +1,7 @@ +p, 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, domain2 \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 00d977ea..284c8756 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -198,6 +198,22 @@ def test_enforce_priority_indeterminate(self): ) self.assertFalse(e.enforce("alice", "data1", "read")) + def test_enforce_subpriority(self): + e = self.get_enforcer( + get_examples("subject_priority_model.conf"), + get_examples("subject_priority_policy.csv"), + ) + self.assertTrue(e.enforce("jane", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "read")) + + def test_enforce_subpriority_with_domain(self): + e = self.get_enforcer( + get_examples("subject_priority_model_with_domain.conf"), + get_examples("subject_priority_policy_with_domain.csv"), + ) + self.assertTrue(e.enforce("alice", "data1", "domain1", "write")) + self.assertTrue(e.enforce("bob", "data2", "domain2", "write")) + def test_multiple_policy_definitions(self): e = self.get_enforcer( From fda7c7b34a3777fb591e8e48a7035d352244b6a2 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 11 Dec 2021 08:36:58 +0000 Subject: [PATCH 208/349] chore(release): 1.15.0 [skip ci] # [1.15.0](https://github.com/casbin/pycasbin/compare/v1.14.0...v1.15.0) (2021-12-11) ### Features * subject priority ([#231](https://github.com/casbin/pycasbin/issues/231)) ([b38169d](https://github.com/casbin/pycasbin/commit/b38169dbf3d122e17d764e058ef13915a1861eec)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fdfe4f8..23080b0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.15.0](https://github.com/casbin/pycasbin/compare/v1.14.0...v1.15.0) (2021-12-11) + + +### Features + +* subject priority ([#231](https://github.com/casbin/pycasbin/issues/231)) ([b38169d](https://github.com/casbin/pycasbin/commit/b38169dbf3d122e17d764e058ef13915a1861eec)) + # [1.14.0](https://github.com/casbin/pycasbin/compare/v1.13.0...v1.14.0) (2021-12-09) diff --git a/setup.cfg b/setup.cfg index 314e151b..29d5a7d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.14.0 +version = 1.15.0 From 6368ec927e74d74a97cbfe465b1a899e813ed4c7 Mon Sep 17 00:00:00 2001 From: Stonex <43725202+sheny1xuan@users.noreply.github.com> Date: Mon, 13 Dec 2021 11:15:34 +0800 Subject: [PATCH 209/349] perf: add pytest-benchmark and judge whether use regex in rabc (#228) Signed-off-by: stonex <1479765922@qq.com> --- .github/workflows/build.yml | 8 + .../rbac/default_role_manager/role_manager.py | 26 +-- tests/benchmarks/__init__.py | 0 tests/benchmarks/benchmark_model.py | 153 ++++++++++++++++++ tests/test_model_benchmark.py | 53 ------ 5 files changed, 175 insertions(+), 65 deletions(-) create mode 100644 tests/benchmarks/__init__.py create mode 100644 tests/benchmarks/benchmark_model.py delete mode 100644 tests/test_model_benchmark.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index be120595..f7d90964 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -28,10 +28,18 @@ jobs: pip install -r requirements.txt pip install -r requirements_dev.txt pip install coveralls + pip install pytest + pip install pytest-benchmark - name: Run tests run: coverage run -m unittest discover -s tests -t tests + - name: Run benchmark + run: python3 -m pytest + --benchmark-verbose + --benchmark-columns=mean,stddev,iqr,ops,rounds + tests/benchmarks/benchmark_model.py + - name: Upload coverage data to coveralls.io run: coveralls --service=github env: diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index ddd45751..c7e0ce7b 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -67,7 +67,7 @@ class RoleManager(RM): def __init__(self, max_hierarchy_level=10): self.logger = logging.getLogger(__name__) self.max_hierarchy_level = max_hierarchy_level - self.matching_func = lambda name1, name2: name1 == name2 + self.matching_func = None self.all_links = list() self.all_roles = dict() @@ -100,8 +100,9 @@ def _matching_roles(self, name, match_order=MatchOrder.STR_PATTERN): def _get_role(self, name): if name not in self.all_roles: role = Role(name) - for pattern_role in self._matching_roles(name): - role.copy_from(pattern_role) + if self.matching_func != None: + for pattern_role in self._matching_roles(name): + role.copy_from(pattern_role) self.all_roles[name] = role return self.all_roles[name] @@ -124,15 +125,16 @@ def add_link(self, name1, name2, *domain): user.add_role(role) - for r in self.all_roles.values(): - if r.name != user.name and self._matching_fn( - user.name, r.name, MatchOrder.PATTERN_STR - ): - r.add_role(role) - if r.name != role.name and self._matching_fn( - role.name, r.name, MatchOrder.PATTERN_STR - ): - role.add_role(r) + if self.matching_func != None: + for r in self.all_roles.values(): + if r.name != user.name and self._matching_fn( + user.name, r.name, MatchOrder.PATTERN_STR + ): + r.add_role(role) + if r.name != role.name and self._matching_fn( + role.name, r.name, MatchOrder.PATTERN_STR + ): + role.add_role(r) def delete_link(self, name1, name2, *domain): if Link(name1, name2) not in self.all_links: diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/benchmarks/benchmark_model.py b/tests/benchmarks/benchmark_model.py new file mode 100644 index 00000000..e0b54fae --- /dev/null +++ b/tests/benchmarks/benchmark_model.py @@ -0,0 +1,153 @@ +import os +import casbin + + +def raw_enforce(sub, obj, act): + policy = [["alice", "data1", "read"], ["bob", "data2", "write"]] + for rule in policy: + if sub == rule[0] and obj == rule[1] and act == rule[2]: + return True + + return False + + +def get_examples(path): + examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../../examples/" + return os.path.abspath(examples_path + path) + + +def get_enforcer(model=None, adapter=None): + return casbin.Enforcer( + model, + adapter, + ) + + +def test_benchmark_raw(benchmark): + @benchmark + def benchmark_raw(): + raw_enforce("alice", "data1", "read") + + +def test_benchmark_basic_model(benchmark): + e = get_enforcer(get_examples("basic_model.conf"), get_examples("basic_policy.csv")) + + @benchmark + def benchmark_basic_model(): + e.enforce("alice", "data1", "read") + + +def test_benchmark_rbac_model(benchmark): + e = get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + + @benchmark + def benchmark_rbac_model(): + e.enforce("alice", "data1", "read") + + +def test_benchmark_rbac_model_small(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + + e.add_policies( + {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(100)} + ) + e.add_grouping_policies( + {("user" + str(i), "group" + str(int(i / 10))) for i in range(1000)} + ) + + @benchmark + def benchmark_rbac_model(): + e.enforce("user501", "data9", "read") + + +def test_benchmark_rbac_model_medium(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + + e.add_policies( + {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(1000)} + ) + e.add_grouping_policies( + {("user" + str(i), "group" + str(int(i / 10))) for i in range(10000)} + ) + + @benchmark + def benchmark_rbac_model(): + e.enforce("user501", "data9", "read") + + +def test_benchmark_rbac_model_large(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + + e.add_policies( + {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(10000)} + ) + e.add_grouping_policies( + {("user" + str(i), "group" + str(int(i / 10))) for i in range(100000)} + ) + + @benchmark + def benchmark_rbac_model(): + e.enforce("user501", "data9", "read") + + +def test_benchmark_rbac_model_with_resource_roles(benchmark): + e = get_enforcer( + get_examples("rbac_with_resource_roles_model.conf"), + get_examples("rbac_with_resource_roles_policy.csv"), + ) + + @benchmark + def benchmark_rbac_model_with_resource_roles(): + e.enforce("alice", "data1", "read") + + +def test_benchmark_rbac_model_with_domains(benchmark): + e = get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + + @benchmark + def benchmark_rbac_model_with_domains(): + e.enforce("alice", "domain1", "data1", "read") + + +def test_benchmark_abac_model(benchmark): + e = get_enforcer(get_examples("abac_model.conf")) + sub = "alice" + obj = {"Owner": "alice", "id": "data1"} + + @benchmark + def benchmark_abac_model(): + e.enforce(sub, obj, "read") + + +def test_benchmark_rbac_with_deny(benchmark): + e = get_enforcer( + get_examples("rbac_with_deny_model.conf"), + get_examples("rbac_with_deny_policy.csv"), + ) + + @benchmark + def benchmark_rbac_with_deny(): + e.enforce("alice", "data1", "read") + + +def test_benchmark_prioriry(benchmark): + e = get_enforcer( + get_examples("priority_model.conf"), get_examples("priority_policy.csv") + ) + + @benchmark + def benchmark_rbac_with_deny(): + e.enforce("alice", "data1", "read") + + +def test_benchmark_keymatch(benchmark): + e = get_enforcer( + get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv") + ) + + @benchmark + def benchmark_keymatch(): + e.enforce("alice", "/alice_data/resource1", "GET") diff --git a/tests/test_model_benchmark.py b/tests/test_model_benchmark.py deleted file mode 100644 index 8e03a4c2..00000000 --- a/tests/test_model_benchmark.py +++ /dev/null @@ -1,53 +0,0 @@ -import casbin -import datetime -import logging -import sys -from tests.test_enforcer import get_examples, TestCaseBase -from unittest import TestCase - -log = logging.getLogger(__name__) -loglevel = logging.WARNING -logging.basicConfig(level=loglevel) - - -def get_function_name(): - return sys._getframe(2).f_code.co_name - - -def print_time_diff(start, end, time): - ms = (end - start).total_seconds() * 1000 / time - log.warning("%s %f ms" % (get_function_name(), ms)) - - -class TestModelBenchmark(TestCaseBase): - def test_benchmark_basic_model(self): - e = self.get_enforcer( - get_examples("basic_model.conf"), get_examples("basic_policy.csv") - ) - - time = 10000 - start = datetime.datetime.now() - for i in range(0, time): - e.enforce("alice", "data1", "read") - end = datetime.datetime.now() - print_time_diff(start, end, time) - - def test_benchmark_rbac_model(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) - - time = 10000 - start = datetime.datetime.now() - for i in range(0, time): - e.enforce("alice", "data2", "read") - end = datetime.datetime.now() - print_time_diff(start, end, time) - - -class TestModelBenchmarkSynced(TestModelBenchmark): - def get_enforcer(self, model=None, adapter=None): - return casbin.SyncedEnforcer( - model, - adapter, - ) From 863b0188ffa740e7d06dc1cc621827378571af1f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 13 Dec 2021 03:17:52 +0000 Subject: [PATCH 210/349] chore(release): 1.15.1 [skip ci] ## [1.15.1](https://github.com/casbin/pycasbin/compare/v1.15.0...v1.15.1) (2021-12-13) ### Performance Improvements * add pytest-benchmark and judge whether use regex in rabc ([#228](https://github.com/casbin/pycasbin/issues/228)) ([252c288](https://github.com/casbin/pycasbin/commit/252c288e20c138b97b7cd58aa3c47d4434b0c742)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23080b0c..36a5b082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.15.1](https://github.com/casbin/pycasbin/compare/v1.15.0...v1.15.1) (2021-12-13) + + +### Performance Improvements + +* add pytest-benchmark and judge whether use regex in rabc ([#228](https://github.com/casbin/pycasbin/issues/228)) ([252c288](https://github.com/casbin/pycasbin/commit/252c288e20c138b97b7cd58aa3c47d4434b0c742)) + # [1.15.0](https://github.com/casbin/pycasbin/compare/v1.14.0...v1.15.0) (2021-12-11) diff --git a/setup.cfg b/setup.cfg index 29d5a7d1..c32dd544 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.0 +version = 1.15.1 From 46dada7c45ae48b0cafa728b5ce3f7b048aacdd0 Mon Sep 17 00:00:00 2001 From: Bingchang Chen <19990626.love@163.com> Date: Wed, 15 Dec 2021 08:53:23 +0800 Subject: [PATCH 211/349] fix: Apache 2.0 license headers (#233) Signed-off-by: abingcbc --- casbin/__init__.py | 14 ++++++++++++++ casbin/config/__init__.py | 14 ++++++++++++++ casbin/config/config.py | 14 ++++++++++++++ casbin/core_enforcer.py | 14 ++++++++++++++ casbin/distributed_enforcer.py | 14 ++++++++++++++ casbin/effect/__init__.py | 14 ++++++++++++++ casbin/effect/default_effectors.py | 14 ++++++++++++++ casbin/effect/effector.py | 15 +++++++++++++++ casbin/enforcer.py | 14 ++++++++++++++ casbin/frontend.py | 14 ++++++++++++++ casbin/internal_enforcer.py | 14 ++++++++++++++ casbin/management_enforcer.py | 14 ++++++++++++++ casbin/model/__init__.py | 14 ++++++++++++++ casbin/model/assertion.py | 14 ++++++++++++++ casbin/model/function.py | 14 ++++++++++++++ casbin/model/model.py | 14 ++++++++++++++ casbin/model/policy.py | 14 ++++++++++++++ casbin/model/policy_op.py | 14 ++++++++++++++ casbin/persist/__init__.py | 14 ++++++++++++++ casbin/persist/adapter.py | 15 +++++++++++++++ casbin/persist/adapter_filtered.py | 14 ++++++++++++++ casbin/persist/adapters/__init__.py | 14 ++++++++++++++ casbin/persist/adapters/adapter_filtered.py | 14 ++++++++++++++ casbin/persist/adapters/file_adapter.py | 14 ++++++++++++++ casbin/persist/adapters/update_adapter.py | 15 +++++++++++++++ casbin/persist/batch_adapter.py | 14 ++++++++++++++ casbin/persist/dispatcher.py | 15 +++++++++++++++ casbin/rbac/__init__.py | 14 ++++++++++++++ casbin/rbac/default_role_manager/__init__.py | 14 ++++++++++++++ casbin/rbac/default_role_manager/role_manager.py | 14 ++++++++++++++ casbin/rbac/role_manager.py | 15 +++++++++++++++ casbin/synced_enforcer.py | 14 ++++++++++++++ casbin/util/__init__.py | 14 ++++++++++++++ casbin/util/builtin_operators.py | 14 ++++++++++++++ casbin/util/expression.py | 14 ++++++++++++++ casbin/util/rwlock.py | 14 ++++++++++++++ casbin/util/util.py | 14 ++++++++++++++ setup.py | 14 ++++++++++++++ tests/__init__.py | 13 +++++++++++++ tests/benchmarks/__init__.py | 13 +++++++++++++ tests/benchmarks/benchmark_model.py | 14 ++++++++++++++ tests/config/__init__.py | 13 +++++++++++++ tests/config/test_config.py | 14 ++++++++++++++ tests/model/__init__.py | 13 +++++++++++++ tests/model/test_policy.py | 14 ++++++++++++++ tests/rbac/__init__.py | 13 +++++++++++++ tests/rbac/test_role_manager.py | 14 ++++++++++++++ tests/test_distributed_api.py | 14 ++++++++++++++ tests/test_enforcer.py | 14 ++++++++++++++ tests/test_filter.py | 14 ++++++++++++++ tests/test_frontend.py | 14 ++++++++++++++ tests/test_management_api.py | 14 ++++++++++++++ tests/test_rbac_api.py | 14 ++++++++++++++ tests/util/__init__.py | 13 +++++++++++++ tests/util/test_builtin_operators.py | 14 ++++++++++++++ tests/util/test_rwlock.py | 14 ++++++++++++++ tests/util/test_util.py | 14 ++++++++++++++ 57 files changed, 797 insertions(+) diff --git a/casbin/__init__.py b/casbin/__init__.py index b838ce20..0e159ba8 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .enforcer import * from .synced_enforcer import SyncedEnforcer from .distributed_enforcer import DistributedEnforcer diff --git a/casbin/config/__init__.py b/casbin/config/__init__.py index cca5d9bd..fd34cf7f 100644 --- a/casbin/config/__init__.py +++ b/casbin/config/__init__.py @@ -1 +1,15 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .config import Config diff --git a/casbin/config/config.py b/casbin/config/config.py index 9dba5b3e..ced352c0 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from io import StringIO diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index a5e75580..31ba25d5 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import logging, copy from casbin.effect import Effector, get_effector, effect_to_bool diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 6ae14e6e..b01778d3 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin import SyncedEnforcer import logging diff --git a/casbin/effect/__init__.py b/casbin/effect/__init__.py index 88bc31da..ede8aa8f 100644 --- a/casbin/effect/__init__.py +++ b/casbin/effect/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .default_effectors import ( AllowOverrideEffector, DenyOverrideEffector, diff --git a/casbin/effect/default_effectors.py b/casbin/effect/default_effectors.py index 9e00ee41..7e5ae578 100644 --- a/casbin/effect/default_effectors.py +++ b/casbin/effect/default_effectors.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .effector import Effector diff --git a/casbin/effect/effector.py b/casbin/effect/effector.py index 45106db4..71251ded 100644 --- a/casbin/effect/effector.py +++ b/casbin/effect/effector.py @@ -1,3 +1,18 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + class Effector: """Effector is the interface for Casbin effectors.""" diff --git a/casbin/enforcer.py b/casbin/enforcer.py index f45473fb..636baf0c 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from functools import partial from casbin.management_enforcer import ManagementEnforcer diff --git a/casbin/frontend.py b/casbin/frontend.py index c13851ec..7ec7d788 100644 --- a/casbin/frontend.py +++ b/casbin/frontend.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import json diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 87879544..2a15fe0a 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin.core_enforcer import CoreEnforcer from casbin.model.policy_op import PolicyOp diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 6870cb99..3f1155f9 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin.internal_enforcer import InternalEnforcer from casbin.model.policy_op import PolicyOp diff --git a/casbin/model/__init__.py b/casbin/model/__init__.py index 2bdda40b..5491ba83 100644 --- a/casbin/model/__init__.py +++ b/casbin/model/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .assertion import Assertion from .model import Model from .policy import Policy diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index c2f52c68..174cbe31 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import logging from casbin.model.policy_op import PolicyOp diff --git a/casbin/model/function.py b/casbin/model/function.py index aedc9ef7..fdd4a227 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin import util diff --git a/casbin/model/model.py b/casbin/model/model.py index fd4e775d..c0b8d9c5 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from . import Assertion from casbin import util, config from .policy import Policy diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 175dbf3b..7c77cc74 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import logging DEFAULT_SEP = "," diff --git a/casbin/model/policy_op.py b/casbin/model/policy_op.py index 7ef171da..a05f765e 100644 --- a/casbin/model/policy_op.py +++ b/casbin/model/policy_op.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import enum diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index 0c04a1c4..78c13538 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .adapter import * from .adapter_filtered import * from .batch_adapter import * diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 567bd770..0d291b3b 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -1,3 +1,18 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + def load_policy_line(line, model): """loads a text line as a policy rule to model.""" diff --git a/casbin/persist/adapter_filtered.py b/casbin/persist/adapter_filtered.py index 5cd1038c..dd3ab897 100644 --- a/casbin/persist/adapter_filtered.py +++ b/casbin/persist/adapter_filtered.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .adapter import Adapter """ FilteredAdapter is the interface for Casbin adapters supporting filtered policies.""" diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index 804ae9ca..5bee4fcf 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .file_adapter import FileAdapter from .adapter_filtered import FilteredAdapter from .update_adapter import UpdateAdapter diff --git a/casbin/persist/adapters/adapter_filtered.py b/casbin/persist/adapters/adapter_filtered.py index b37a0da0..571ee7d1 100644 --- a/casbin/persist/adapters/adapter_filtered.py +++ b/casbin/persist/adapters/adapter_filtered.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin import persist from casbin import model from .file_adapter import FileAdapter diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index 8dc8d667..0e807598 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from casbin import persist import os diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index b9356ec6..590c1891 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -1,3 +1,18 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + class UpdateAdapter: """UpdateAdapter is the interface for Casbin adapters with add update policy function.""" diff --git a/casbin/persist/batch_adapter.py b/casbin/persist/batch_adapter.py index 8ccec0f3..c6688163 100644 --- a/casbin/persist/batch_adapter.py +++ b/casbin/persist/batch_adapter.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .adapter import Adapter """BatchAdapter is the interface for Casbin adapters with multiple add and remove policy functions.""" diff --git a/casbin/persist/dispatcher.py b/casbin/persist/dispatcher.py index 6ea589c1..51aa4a86 100644 --- a/casbin/persist/dispatcher.py +++ b/casbin/persist/dispatcher.py @@ -1,3 +1,18 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + class Dispatcher: """Dispatcher is the interface for pycasbin dispatcher""" diff --git a/casbin/rbac/__init__.py b/casbin/rbac/__init__.py index cd3c1521..b82b0732 100644 --- a/casbin/rbac/__init__.py +++ b/casbin/rbac/__init__.py @@ -1 +1,15 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .role_manager import RoleManager diff --git a/casbin/rbac/default_role_manager/__init__.py b/casbin/rbac/default_role_manager/__init__.py index bf5c0470..45e45fde 100644 --- a/casbin/rbac/default_role_manager/__init__.py +++ b/casbin/rbac/default_role_manager/__init__.py @@ -1 +1,15 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .role_manager import RoleManager, DomainManager diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index c7e0ce7b..e6c7cbd6 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import logging from collections import namedtuple from enum import Enum diff --git a/casbin/rbac/role_manager.py b/casbin/rbac/role_manager.py index 9c1cefc4..09153e7a 100644 --- a/casbin/rbac/role_manager.py +++ b/casbin/rbac/role_manager.py @@ -1,3 +1,18 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + class RoleManager: """provides interface to define the operations for managing roles.""" diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index c1180360..716e9680 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import threading import time diff --git a/casbin/util/__init__.py b/casbin/util/__init__.py index 92b6317c..f1cb7fab 100644 --- a/casbin/util/__init__.py +++ b/casbin/util/__init__.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from .builtin_operators import * from .expression import * from .util import * diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index cf478864..5541974a 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import ipaddress import re diff --git a/casbin/util/expression.py b/casbin/util/expression.py index 6f519c63..350f6f11 100644 --- a/casbin/util/expression.py +++ b/casbin/util/expression.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from simpleeval import EvalWithCompoundTypes import ast diff --git a/casbin/util/rwlock.py b/casbin/util/rwlock.py index 105312e1..0c0355df 100644 --- a/casbin/util/rwlock.py +++ b/casbin/util/rwlock.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from threading import RLock, Condition # This implementation was adapted from https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock diff --git a/casbin/util/util.py b/casbin/util/util.py index 3003af67..9b817604 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from collections import OrderedDict import re diff --git a/setup.py b/setup.py index 86fb8896..d13f3b9f 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import setuptools from os import path diff --git a/tests/__init__.py b/tests/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/benchmarks/__init__.py +++ b/tests/benchmarks/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/benchmarks/benchmark_model.py b/tests/benchmarks/benchmark_model.py index e0b54fae..5fb17a16 100644 --- a/tests/benchmarks/benchmark_model.py +++ b/tests/benchmarks/benchmark_model.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import os import casbin diff --git a/tests/config/__init__.py b/tests/config/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/config/__init__.py +++ b/tests/config/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/config/test_config.py b/tests/config/test_config.py index c7413f93..96c41bec 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import os from casbin.config import Config from unittest import TestCase diff --git a/tests/model/__init__.py b/tests/model/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/model/__init__.py +++ b/tests/model/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/model/test_policy.py b/tests/model/test_policy.py index 757c9e53..855eb841 100644 --- a/tests/model/test_policy.py +++ b/tests/model/test_policy.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from unittest import TestCase from casbin.model import Model diff --git a/tests/rbac/__init__.py b/tests/rbac/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/rbac/__init__.py +++ b/tests/rbac/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py index 8c3fe929..9e0327ef 100644 --- a/tests/rbac/test_role_manager.py +++ b/tests/rbac/test_role_manager.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from unittest import TestCase from casbin.rbac import default_role_manager from casbin.util import regex_match_func diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py index 9704dab6..84ef9900 100644 --- a/tests/test_distributed_api.py +++ b/tests/test_distributed_api.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import casbin from tests.test_enforcer import get_examples, TestCaseBase diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 284c8756..45f9022c 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import os import time from unittest import TestCase diff --git a/tests/test_filter.py b/tests/test_filter.py index 72dfce54..6633b229 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import casbin from unittest import TestCase from tests.test_enforcer import get_examples diff --git a/tests/test_frontend.py b/tests/test_frontend.py index 8ffdf73b..cfe1f6cb 100644 --- a/tests/test_frontend.py +++ b/tests/test_frontend.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import json import re import casbin diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 812c769e..7bcfcd2b 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from functools import partial import casbin diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 01d21492..62c18b30 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + import casbin from tests.test_enforcer import get_examples, TestCaseBase diff --git a/tests/util/__init__.py b/tests/util/__init__.py index e69de29b..dcfb1ee3 100644 --- a/tests/util/__init__.py +++ b/tests/util/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 533861c2..e981f8e8 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from unittest import TestCase from casbin import util diff --git a/tests/util/test_rwlock.py b/tests/util/test_rwlock.py index f7309f8f..d4a248a6 100644 --- a/tests/util/test_rwlock.py +++ b/tests/util/test_rwlock.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from unittest import TestCase from casbin.util.rwlock import RWLockWrite from concurrent.futures import ThreadPoolExecutor diff --git a/tests/util/test_util.py b/tests/util/test_util.py index 0b4be0e4..c60ff230 100644 --- a/tests/util/test_util.py +++ b/tests/util/test_util.py @@ -1,3 +1,17 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + from unittest import TestCase from casbin import util From 87f0ca2605e62cfb0193f316bdeda326934abfc8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 15 Dec 2021 01:00:25 +0000 Subject: [PATCH 212/349] chore(release): 1.15.2 [skip ci] ## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-15) ### Bug Fixes * Apache 2.0 license headers ([#233](https://github.com/casbin/pycasbin/issues/233)) ([16e4f0a](https://github.com/casbin/pycasbin/commit/16e4f0a0f82ac93688e1b735b3da173929524107)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36a5b082..89f5be7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-15) + + +### Bug Fixes + +* Apache 2.0 license headers ([#233](https://github.com/casbin/pycasbin/issues/233)) ([16e4f0a](https://github.com/casbin/pycasbin/commit/16e4f0a0f82ac93688e1b735b3da173929524107)) + ## [1.15.1](https://github.com/casbin/pycasbin/compare/v1.15.0...v1.15.1) (2021-12-13) diff --git a/setup.cfg b/setup.cfg index c32dd544..e0f07a9f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.1 +version = 1.15.2 From 1b96599ec499538d3b07ff6f6ce2c366170fb98d Mon Sep 17 00:00:00 2001 From: Tamerlan Abilov Date: Fri, 17 Dec 2021 06:05:36 +0400 Subject: [PATCH 213/349] fix: disabled casbin should allow all requests (#235) Signed-off-by: timabilov Co-authored-by: timabilov --- casbin/core_enforcer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 31ba25d5..7c16f9df 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -335,7 +335,7 @@ def enforce_ex(self, *rvals): mtype = "m" if not self.enabled: - return [False, []] + return [True, []] functions = self.fm.get_functions() From bbbc940e078abe6455cd1c2d6534868d2121e9a8 Mon Sep 17 00:00:00 2001 From: abichinger Date: Fri, 17 Dec 2021 03:06:20 +0100 Subject: [PATCH 214/349] perf: judge whether use domain_matching_func (#232) Signed-off-by: Andreas Bichinger --- casbin/enforcer.py | 5 +-- .../rbac/default_role_manager/role_manager.py | 33 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 636baf0c..8c3d01f3 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -179,8 +179,9 @@ def get_implicit_permissions_for_user( res = [] # policy domain should be matched by domain_match_fn of DomainManager - if domain: - domain = partial(self.get_role_manager().domain_matching_func, domain) + domain_matching_func = self.get_role_manager().domain_matching_func + if domain and domain_matching_func != None: + domain = partial(domain_matching_func, domain) for role in roles: permissions = self.get_permissions_for_user_in_domain( diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index e6c7cbd6..ffc48688 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -82,6 +82,7 @@ def __init__(self, max_hierarchy_level=10): self.logger = logging.getLogger(__name__) self.max_hierarchy_level = max_hierarchy_level self.matching_func = None + self.domain_matching_func = None self.all_links = list() self.all_roles = dict() @@ -214,7 +215,8 @@ def __init__(self, max_hierarchy_level=10): self.logger = logging.getLogger(__name__) self.all_links = dict() self.max_hierarchy_level = max_hierarchy_level - self.domain_matching_func = lambda domain1, domain2: domain1 == domain2 + self.matching_func = None + self.domain_matching_func = None self.matching_func = lambda name1, name2: name1 == name2 def add_matching_func(self, fn): @@ -243,11 +245,14 @@ def _get_links(self, *domain): def _get_role_manager(self, *domain): domain1 = self._get_domain(*domain) - domain_links = [] + domain_links = self.all_links.get(domain1, []) - for domain2, links in self.all_links.items(): - if match_error_handler(self.domain_matching_func, domain1, domain2): - domain_links = domain_links + links + if self.domain_matching_func != None: + for domain2, links in self.all_links.items(): + if domain1 != domain2 and match_error_handler( + self.domain_matching_func, domain1, domain2 + ): + domain_links = domain_links + links rm = RoleManager(max_hierarchy_level=self.max_hierarchy_level) rm.add_matching_func(self.matching_func) @@ -303,13 +308,19 @@ def _get_role_manager(self, *domain): def _affected_role_managers(self, *domain): domain_pattern = self._get_domain(*domain) - return [ - self.rm_map[domain_str] - for domain_str in self.rm_map.keys() - if match_error_handler( - self.domain_matching_func, domain_str, domain_pattern + + if self.domain_matching_func != None: + return [ + self.rm_map[domain_str] + for domain_str in self.rm_map.keys() + if match_error_handler( + self.domain_matching_func, domain_str, domain_pattern + ) + ] + else: + return ( + [self.rm_map[domain_pattern]] if domain_pattern in self.rm_map else [] ) - ] def add_matching_func(self, fn): super().add_matching_func(fn) From 571bf8c3d3cfff0472472742169edd153ef4761e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 17 Dec 2021 02:09:22 +0000 Subject: [PATCH 215/349] chore(release): 1.15.2 [skip ci] ## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-17) ### Bug Fixes * Apache 2.0 license headers ([#233](https://github.com/casbin/pycasbin/issues/233)) ([16e4f0a](https://github.com/casbin/pycasbin/commit/16e4f0a0f82ac93688e1b735b3da173929524107)) * disabled casbin should allow all requests ([#235](https://github.com/casbin/pycasbin/issues/235)) ([ecd4ff8](https://github.com/casbin/pycasbin/commit/ecd4ff8cbee8dede7923eb7d617cc251e0de5582)) ### Performance Improvements * judge whether use domain_matching_func ([#232](https://github.com/casbin/pycasbin/issues/232)) ([bb12c0f](https://github.com/casbin/pycasbin/commit/bb12c0f03c1a30cfb8e3cf3e77b4d1b6c0aad1d4)) --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f5be7b..a0edc89d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Semantic Versioning Changelog +## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-17) + + +### Bug Fixes + +* Apache 2.0 license headers ([#233](https://github.com/casbin/pycasbin/issues/233)) ([16e4f0a](https://github.com/casbin/pycasbin/commit/16e4f0a0f82ac93688e1b735b3da173929524107)) +* disabled casbin should allow all requests ([#235](https://github.com/casbin/pycasbin/issues/235)) ([ecd4ff8](https://github.com/casbin/pycasbin/commit/ecd4ff8cbee8dede7923eb7d617cc251e0de5582)) + + +### Performance Improvements + +* judge whether use domain_matching_func ([#232](https://github.com/casbin/pycasbin/issues/232)) ([bb12c0f](https://github.com/casbin/pycasbin/commit/bb12c0f03c1a30cfb8e3cf3e77b4d1b6c0aad1d4)) + ## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-15) From a5716bead3338497877c3edaeaffda1c52f9f6a6 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Mon, 20 Dec 2021 10:32:06 +0800 Subject: [PATCH 216/349] Delete FUNDING.yml --- .github/FUNDING.yml | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 96603a39..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,8 +0,0 @@ -# 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 -custom: # Replace with a single custom sponsorship URL From 07f2277ebac47e65d68bc9ad3b8e23833fd825db Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 2 Jan 2022 15:12:03 +0800 Subject: [PATCH 217/349] Add link to README. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index f7b7d4b6..e38b9df4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ PyCasbin [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) +💖 [**Looking for an open-source identity and access management solution like Okta, Auth0, Keycloak ? Learn more about: Casdoor**](https://casdoor.org/) + +casdoor + **News**: Async version PyCasbin can be found at: https://pypi.org/project/asynccasbin/ **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ From bb6e3e3f60938a027af4f2141f23f1e4fc75ed80 Mon Sep 17 00:00:00 2001 From: Jon Date: Thu, 13 Jan 2022 21:40:49 +0800 Subject: [PATCH 218/349] fix: #239, not allowing more than 9 types of policies/requests --- casbin/util/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/util/util.py b/casbin/util/util.py index 9b817604..1e138900 100644 --- a/casbin/util/util.py +++ b/casbin/util/util.py @@ -20,14 +20,14 @@ def escape_assertion(s): """escapes the dots in the assertion, because the expression evaluation doesn't support such variable names.""" - eval_p = re.search(r"\bp(\d?)\.", s) + eval_p = re.search(r"\bp(\d*)\.", s) if eval_p is not None: p_suffix = eval_p.group(1) p_before = re.compile(f"\\bp{p_suffix}\\.") p_after = f"p{p_suffix}_" s = re.sub(p_before, p_after, s) - eval_r = re.search(r"\br(\d?)\.", s) + eval_r = re.search(r"\br(\d*)\.", s) if eval_r is not None: r_suffix = eval_r.group(1) r_before = re.compile(f"\\br{r_suffix}\\.") From f2c9186f41ac2676b51f98794bfd3c8db4188481 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 13 Jan 2022 13:45:29 +0000 Subject: [PATCH 219/349] chore(release): 1.15.3 [skip ci] ## [1.15.3](https://github.com/casbin/pycasbin/compare/v1.15.2...v1.15.3) (2022-01-13) ### Bug Fixes * [#239](https://github.com/casbin/pycasbin/issues/239), not allowing more than 9 types of policies/requests ([dcf4392](https://github.com/casbin/pycasbin/commit/dcf43924bbdd59901d1b6632a5a1796ad546cc09)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0edc89d..1b5085a6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.15.3](https://github.com/casbin/pycasbin/compare/v1.15.2...v1.15.3) (2022-01-13) + + +### Bug Fixes + +* [#239](https://github.com/casbin/pycasbin/issues/239), not allowing more than 9 types of policies/requests ([dcf4392](https://github.com/casbin/pycasbin/commit/dcf43924bbdd59901d1b6632a5a1796ad546cc09)) + ## [1.15.2](https://github.com/casbin/pycasbin/compare/v1.15.1...v1.15.2) (2021-12-17) diff --git a/setup.cfg b/setup.cfg index e0f07a9f..5385ecda 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.2 +version = 1.15.3 From 932064fa9aae99e1cba197cc3ec70fd3f8e9d5c6 Mon Sep 17 00:00:00 2001 From: etienne-lebrun <90252101+etienne-lebrun@users.noreply.github.com> Date: Thu, 13 Jan 2022 15:36:01 +0100 Subject: [PATCH 220/349] fix: #238, avoid calling has_eval in enforcing loop (#240) --- casbin/core_enforcer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 7c16f9df..dc8086f7 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -366,8 +366,8 @@ def enforce_ex(self, *rvals): raise RuntimeError("invalid request size") exp_string = self.model["m"][mtype].value - has_eval = util.has_eval(exp_string) - if not has_eval: + exp_has_eval = util.has_eval(exp_string) + if not exp_has_eval: expression = self._get_expression(exp_string, functions) policy_effects = set() @@ -385,7 +385,7 @@ def enforce_ex(self, *rvals): p_parameters = dict(zip(p_tokens, pvals)) parameters = dict(r_parameters, **p_parameters) - if util.has_eval(exp_string): + if exp_has_eval: rule_names = util.get_eval_value(exp_string) rules = [ util.escape_assertion(p_parameters[rule_name]) @@ -427,7 +427,7 @@ def enforce_ex(self, *rvals): break else: - if has_eval: + if exp_has_eval: raise RuntimeError( "please make sure rule exists in policy when using eval() in matcher" ) From bc189ecefd7fe144ec7a6ebbc82a39a4d8f5c577 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 13 Jan 2022 14:43:27 +0000 Subject: [PATCH 221/349] chore(release): 1.15.4 [skip ci] ## [1.15.4](https://github.com/casbin/pycasbin/compare/v1.15.3...v1.15.4) (2022-01-13) ### Bug Fixes * [#238](https://github.com/casbin/pycasbin/issues/238), avoid calling has_eval in enforcing loop ([#240](https://github.com/casbin/pycasbin/issues/240)) ([8188b27](https://github.com/casbin/pycasbin/commit/8188b274e7954838c0717c10f2b772b47130b1e8)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b5085a6..be9a0d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.15.4](https://github.com/casbin/pycasbin/compare/v1.15.3...v1.15.4) (2022-01-13) + + +### Bug Fixes + +* [#238](https://github.com/casbin/pycasbin/issues/238), avoid calling has_eval in enforcing loop ([#240](https://github.com/casbin/pycasbin/issues/240)) ([8188b27](https://github.com/casbin/pycasbin/commit/8188b274e7954838c0717c10f2b772b47130b1e8)) + ## [1.15.3](https://github.com/casbin/pycasbin/compare/v1.15.2...v1.15.3) (2022-01-13) diff --git a/setup.cfg b/setup.cfg index 5385ecda..fd4cdd74 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.3 +version = 1.15.4 From 442b657afe36efdf6ecc9701c4510383202f0a70 Mon Sep 17 00:00:00 2001 From: Bingchang Chen <19990626.love@163.com> Date: Sat, 16 Apr 2022 17:51:07 +0800 Subject: [PATCH 222/349] fix: load policy override role manager (#252) Signed-off-by: abingcbc --- casbin/core_enforcer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index dc8086f7..7a9f7251 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -207,7 +207,6 @@ def load_policy(self): new_model.sort_policies_by_priority() - self.init_rm_map() self.model.print_policy() if self.auto_build_role_links: From ff67d6e3ab289cc31c67666ead7e5811ff8bcab1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 16 Apr 2022 09:53:55 +0000 Subject: [PATCH 223/349] chore(release): 1.15.5 [skip ci] ## [1.15.5](https://github.com/casbin/pycasbin/compare/v1.15.4...v1.15.5) (2022-04-16) ### Bug Fixes * load policy override role manager ([#252](https://github.com/casbin/pycasbin/issues/252)) ([97280c5](https://github.com/casbin/pycasbin/commit/97280c549b7a151a60d15fea63f4e0b141eb2c24)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be9a0d86..c622351b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.15.5](https://github.com/casbin/pycasbin/compare/v1.15.4...v1.15.5) (2022-04-16) + + +### Bug Fixes + +* load policy override role manager ([#252](https://github.com/casbin/pycasbin/issues/252)) ([97280c5](https://github.com/casbin/pycasbin/commit/97280c549b7a151a60d15fea63f4e0b141eb2c24)) + ## [1.15.4](https://github.com/casbin/pycasbin/compare/v1.15.3...v1.15.4) (2022-01-13) diff --git a/setup.cfg b/setup.cfg index fd4cdd74..998fb870 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.4 +version = 1.15.5 From 5b4d8aaeb4961d070ba7b731ed137c1eb0a585cd Mon Sep 17 00:00:00 2001 From: Shivansh Yadav Date: Wed, 27 Apr 2022 08:03:15 +0530 Subject: [PATCH 224/349] feat: update_filtered_policies (#249) --- casbin/internal_enforcer.py | 31 +++++++++++++++++++++++ casbin/management_enforcer.py | 14 ++++++++++ casbin/persist/adapters/update_adapter.py | 8 ++++++ tests/test_management_api.py | 17 +++++++++++++ 4 files changed, 70 insertions(+) diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 2a15fe0a..a038a517 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -88,6 +88,37 @@ def _update_policies(self, sec, ptype, old_rules, new_rules): return rules_updated + def _update_filtered_policies( + self, sec, ptype, new_rules, field_index, *field_values + ): + """_update_filtered_policies deletes old rules and adds new rules.""" + + old_rules = self.model.get_filtered_policy( + sec, ptype, field_index, *field_values + ) + + if self.adapter and self.auto_save: + try: + old_rules = self.adapter.update_filtered_policies( + sec, ptype, new_rules, field_index, *field_values + ) + except: + pass + + if not old_rules: + return False + + is_rule_changed = self.model.remove_policies(sec, ptype, old_rules) + self.model.add_policies(sec, ptype, new_rules) + is_rule_changed = is_rule_changed and len(new_rules) != 0 + if not is_rule_changed: + return is_rule_changed + if sec == "g": + self.build_role_links() + if self.watcher: + self.watcher.update() + return is_rule_changed + def _remove_policy(self, sec, ptype, rule): """removes a rule from the current policy.""" rule_removed = self.model.remove_policy(sec, ptype, rule) diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 3f1155f9..e90b286b 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -151,6 +151,20 @@ def update_named_policies(self, ptype, old_rules, new_rules): """updates authorization rules from the current named policy.""" return self._update_policies("p", ptype, old_rules, new_rules) + def update_filtered_policies(self, new_rules, field_index, *field_values): + """update_filtered_policies deletes old rules and adds new rules.""" + return self.update_filtered_named_policies( + "p", new_rules, field_index, *field_values + ) + + def update_filtered_named_policies( + self, ptype, new_rules, field_index, *field_values + ): + """update_filtered_named_policies deletes old rules and adds new rules.""" + return self._update_filtered_policies( + "p", ptype, new_rules, field_index, *field_values + ) + def remove_policy(self, *params): """removes an authorization rule from the current policy.""" return self.remove_named_policy("p", *params) diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index 590c1891..1ac09a3f 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -28,3 +28,11 @@ def update_policies(self, sec, ptype, old_rules, new_rules): UpdatePolicies updates some policy rules to storage, like db, redis. """ pass + + def update_filtered_policies( + self, sec, ptype, new_rules, field_index, *field_values + ): + """ + update_filtered_policies deletes old rules and adds new rules. + """ + pass diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 7bcfcd2b..3e61d204 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -115,6 +115,23 @@ def test_get_policy_api(self): self.assertTrue(e.has_grouping_policy(["alice", "data2_admin"])) self.assertFalse(e.has_grouping_policy(["bob", "data2_admin"])) + def test_update_filtered_policies(self): + e = casbin.Enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + + e.update_filtered_policies( + [ + ["data2_admin", "data3", "read"], + ["data2_admin", "data3", "write"], + ], + 0, + "data2_admin", + ) + self.assertTrue(e.enforce("data2_admin", "data3", "write")) + self.assertTrue(e.enforce("data2_admin", "data3", "read")) + def test_get_policy_matching_function(self): e = self.get_enforcer( get_examples("rbac_with_domain_and_policy_pattern_model.conf"), From 501b75d06d63edc7d8c681a46daf68c2ab8de8f8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 27 Apr 2022 02:36:34 +0000 Subject: [PATCH 225/349] chore(release): 1.16.0 [skip ci] # [1.16.0](https://github.com/casbin/pycasbin/compare/v1.15.5...v1.16.0) (2022-04-27) ### Features * update_filtered_policies ([#249](https://github.com/casbin/pycasbin/issues/249)) ([ca84dac](https://github.com/casbin/pycasbin/commit/ca84dac37292457b12647611e1f6a63358daf9c6)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c622351b..71c21eb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.16.0](https://github.com/casbin/pycasbin/compare/v1.15.5...v1.16.0) (2022-04-27) + + +### Features + +* update_filtered_policies ([#249](https://github.com/casbin/pycasbin/issues/249)) ([ca84dac](https://github.com/casbin/pycasbin/commit/ca84dac37292457b12647611e1f6a63358daf9c6)) + ## [1.15.5](https://github.com/casbin/pycasbin/compare/v1.15.4...v1.15.5) (2022-04-16) diff --git a/setup.cfg b/setup.cfg index 998fb870..1bafd15e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.15.5 +version = 1.16.0 From 474639bc6bb16f5d7e4907308cd737b8361220c1 Mon Sep 17 00:00:00 2001 From: Shivansh Yadav Date: Fri, 29 Apr 2022 21:15:36 +0530 Subject: [PATCH 226/349] fix: add lint config (#255) --- .github/workflows/build.yml | 7 +- casbin/config/config.py | 12 +- casbin/core_enforcer.py | 20 +--- casbin/distributed_enforcer.py | 28 ++--- casbin/enforcer.py | 8 +- casbin/internal_enforcer.py | 38 ++----- casbin/management_enforcer.py | 36 ++---- casbin/model/assertion.py | 12 +- casbin/model/model.py | 18 +-- casbin/model/policy.py | 21 +--- casbin/persist/adapters/update_adapter.py | 4 +- casbin/persist/watcher_ex.py | 4 +- .../rbac/default_role_manager/role_manager.py | 42 ++----- casbin/synced_enforcer.py | 24 +--- pyproject.toml | 2 + tests/benchmarks/benchmark_model.py | 32 ++---- tests/config/test_config.py | 8 +- tests/rbac/test_role_manager.py | 12 +- tests/test_distributed_api.py | 12 +- tests/test_enforcer.py | 54 +++------ tests/test_filter.py | 24 +--- tests/test_management_api.py | 34 ++---- tests/test_rbac_api.py | 70 +++--------- tests/util/test_builtin_operators.py | 104 ++++-------------- tests/util/test_util.py | 36 ++---- 25 files changed, 164 insertions(+), 498 deletions(-) create mode 100644 pyproject.toml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7d90964..876f0471 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -53,13 +53,18 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 + with: + fetch-depth: 0 - name: Super-Linter - uses: github/super-linter@v4.2.2 + uses: github/super-linter@v4.9.2 env: + VALIDATE_ALL_CODEBASE: false VALIDATE_PYTHON_BLACK: true DEFAULT_BRANCH: master GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + LINTER_RULES_PATH: / + PYTHON_BLACK_CONFIG_FILE: pyproject.toml coveralls: name: Indicate completion to coveralls.io diff --git a/casbin/config/config.py b/casbin/config/config.py index ced352c0..56c6ba30 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -77,11 +77,7 @@ def _parse_buffer(self, f): break line = line.strip() - if ( - "" == line - or self.DEFAULT_COMMENT == line[0:1] - or self.DEFAULT_COMMENT_SEM == line[0:1] - ): + if "" == line or self.DEFAULT_COMMENT == line[0:1] or self.DEFAULT_COMMENT_SEM == line[0:1]: can_write = True continue elif "[" == line[0:1] and "]" == line[-1]: @@ -107,11 +103,7 @@ def _write(self, section, line_num, b): option_val = buf.split("=", 1) if len(option_val) != 2: - raise RuntimeError( - "parse the content error : line {} , {} = ?".format( - line_num, option_val[0] - ) - ) + raise RuntimeError("parse the content error : line {} , {} = ?".format(line_num, option_val[0])) option = option_val[0].strip() value = option_val[1].strip() diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 7a9f7251..ed4fd889 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -79,11 +79,7 @@ def init_with_adapter(self, model_path, adapter=None): def init_with_model_and_adapter(self, m, adapter=None): """initializes an enforcer with a model and a database adapter.""" - if ( - not isinstance(m, Model) - or adapter is not None - and not isinstance(adapter, Adapter) - ): + if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter): raise RuntimeError("Invalid parameters for enforcer.") self.adapter = adapter @@ -386,10 +382,7 @@ def enforce_ex(self, *rvals): if exp_has_eval: rule_names = util.get_eval_value(exp_string) - rules = [ - util.escape_assertion(p_parameters[rule_name]) - for rule_name in rule_names - ] + rules = [util.escape_assertion(p_parameters[rule_name]) for rule_name in rule_names] exp_with_rule = util.replace_eval(exp_string, rules) expression = self._get_expression(exp_with_rule, functions) @@ -418,18 +411,13 @@ def enforce_ex(self, *rvals): else: policy_effects.add(Effector.ALLOW) - if ( - self.eft.intermediate_effect(policy_effects) - != Effector.INDETERMINATE - ): + if self.eft.intermediate_effect(policy_effects) != Effector.INDETERMINATE: explain_index = i break else: if exp_has_eval: - raise RuntimeError( - "please make sure rule exists in policy when using eval() in matcher" - ) + raise RuntimeError("please make sure rule exists in policy when using eval() in matcher") parameters = r_parameters.copy() diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index b01778d3..51b58d23 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -49,9 +49,7 @@ def add_policy_self(self, should_persist, sec, ptype, rules): if sec == "g": try: - self.build_incremental_role_links( - PolicyOp.Policy_add, ptype, no_exists_policy - ) + self.build_incremental_role_links(PolicyOp.Policy_add, ptype, no_exists_policy) except Exception as e: self.logger.log("An exception occurred: " + e) return no_exists_policy @@ -81,9 +79,7 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): return effected - def remove_filtered_policy_self( - self, should_persist, sec, ptype, field_index, *field_values - ): + def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, *field_values): """ remove_filtered_policy_self provides a method for dispatcher to remove an authorization rule from the current policy,field filters can be specified. @@ -91,21 +87,15 @@ def remove_filtered_policy_self( """ if should_persist: try: - self.adapter.remove_filtered_policy( - sec, ptype, field_index, field_values - ) + self.adapter.remove_filtered_policy(sec, ptype, field_index, field_values) except Exception as e: self.logger.log("An exception occurred: " + e) - effects = self.get_model().remove_filtered_policy_returns_effects( - sec, ptype, field_index, *field_values - ) + effects = self.get_model().remove_filtered_policy_returns_effects(sec, ptype, field_index, *field_values) if sec == "g": try: - self.build_incremental_role_links( - PolicyOp.Policy_remove, ptype, effects - ) + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, effects) except Exception as e: self.logger.log("An exception occurred: " + e) return effects @@ -143,16 +133,12 @@ def update_policy_self(self, should_persist, sec, ptype, old_rule, new_rule): if sec == "g": try: - self.build_incremental_role_links( - PolicyOp.Policy_remove, ptype, [old_rule] - ) + self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, [old_rule]) except Exception as e: return False try: - self.build_incremental_role_links( - PolicyOp.Policy_add, ptype, [new_rule] - ) + self.build_incremental_role_links(PolicyOp.Policy_add, ptype, [new_rule]) except Exception as e: return False diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 8c3d01f3..d1aabca6 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -152,9 +152,7 @@ def get_implicit_roles_for_user(self, name, domain=""): return res - def get_implicit_permissions_for_user( - self, user, domain="", filter_policy_dom=True - ): + def get_implicit_permissions_for_user(self, user, domain="", filter_policy_dom=True): """ gets implicit permissions for a user or role. Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. @@ -184,9 +182,7 @@ def get_implicit_permissions_for_user( domain = partial(domain_matching_func, domain) for role in roles: - permissions = self.get_permissions_for_user_in_domain( - role, domain if filter_policy_dom else "" - ) + permissions = self.get_permissions_for_user_in_domain(role, domain if filter_policy_dom else "") res.extend(permissions) return res diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index a038a517..48e49362 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -88,20 +88,14 @@ def _update_policies(self, sec, ptype, old_rules, new_rules): return rules_updated - def _update_filtered_policies( - self, sec, ptype, new_rules, field_index, *field_values - ): + def _update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): """_update_filtered_policies deletes old rules and adds new rules.""" - old_rules = self.model.get_filtered_policy( - sec, ptype, field_index, *field_values - ) + old_rules = self.model.get_filtered_policy(sec, ptype, field_index, *field_values) if self.adapter and self.auto_save: try: - old_rules = self.adapter.update_filtered_policies( - sec, ptype, new_rules, field_index, *field_values - ) + old_rules = self.adapter.update_filtered_policies(sec, ptype, new_rules, field_index, *field_values) except: pass @@ -154,19 +148,12 @@ def _remove_policies(self, sec, ptype, rules): def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): """removes rules based on field filters from the current policy.""" - rule_removed = self.model.remove_filtered_policy( - sec, ptype, field_index, *field_values - ) + rule_removed = self.model.remove_filtered_policy(sec, ptype, field_index, *field_values) if not rule_removed: return rule_removed if self.adapter and self.auto_save: - if ( - self.adapter.remove_filtered_policy( - sec, ptype, field_index, *field_values - ) - is False - ): + if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: return False if self.watcher: @@ -174,23 +161,14 @@ def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): return rule_removed - def _remove_filtered_policy_returns_effects( - self, sec, ptype, field_index, *field_values - ): + def _remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field_values): """removes rules based on field filters from the current policy.""" - rule_removed = self.model.remove_filtered_policy_returns_effects( - sec, ptype, field_index, *field_values - ) + rule_removed = self.model.remove_filtered_policy_returns_effects(sec, ptype, field_index, *field_values) if len(rule_removed) == 0: return rule_removed if self.adapter and self.auto_save: - if ( - self.adapter.remove_filtered_policy( - sec, ptype, field_index, *field_values - ) - is False - ): + if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: return False if self.watcher: diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index e90b286b..0112aeb1 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -153,17 +153,11 @@ def update_named_policies(self, ptype, old_rules, new_rules): def update_filtered_policies(self, new_rules, field_index, *field_values): """update_filtered_policies deletes old rules and adds new rules.""" - return self.update_filtered_named_policies( - "p", new_rules, field_index, *field_values - ) + return self.update_filtered_named_policies("p", new_rules, field_index, *field_values) - def update_filtered_named_policies( - self, ptype, new_rules, field_index, *field_values - ): + def update_filtered_named_policies(self, ptype, new_rules, field_index, *field_values): """update_filtered_named_policies deletes old rules and adds new rules.""" - return self._update_filtered_policies( - "p", ptype, new_rules, field_index, *field_values - ) + return self._update_filtered_policies("p", ptype, new_rules, field_index, *field_values) def remove_policy(self, *params): """removes an authorization rule from the current policy.""" @@ -243,9 +237,7 @@ def add_named_grouping_policy(self, ptype, *params): rules.append(list(params)) if self.auto_build_role_links: - self.model.build_incremental_role_links( - self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules - ) + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules) return rule_added def add_named_grouping_policies(self, ptype, rules): @@ -255,9 +247,7 @@ def add_named_grouping_policies(self, ptype, rules): Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" rules_added = self._add_policies("g", ptype, rules) if self.auto_build_role_links: - self.model.build_incremental_role_links( - self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules - ) + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules) return rules_added @@ -271,9 +261,7 @@ def remove_grouping_policies(self, rules): def remove_filtered_grouping_policy(self, field_index, *field_values): """removes a role inheritance rule from the current policy, field filters can be specified.""" - return self.remove_filtered_named_grouping_policy( - "g", field_index, *field_values - ) + return self.remove_filtered_named_grouping_policy("g", field_index, *field_values) def remove_named_grouping_policy(self, ptype, *params): """removes a role inheritance rule from the current named policy.""" @@ -288,9 +276,7 @@ def remove_named_grouping_policy(self, ptype, *params): rules.append(list(params)) if self.auto_build_role_links: - self.model.build_incremental_role_links( - self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules - ) + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) return rule_removed def remove_named_grouping_policies(self, ptype, rules): @@ -298,17 +284,13 @@ def remove_named_grouping_policies(self, ptype, rules): rules_removed = self._remove_policies("g", ptype, rules) if self.auto_build_role_links: - self.model.build_incremental_role_links( - self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules - ) + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) return rules_removed def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" - rule_removed = self._remove_filtered_policy_returns_effects( - "g", ptype, field_index, *field_values - ) + rule_removed = self._remove_filtered_policy_returns_effects("g", ptype, field_index, *field_values) if self.auto_build_role_links: self.model.build_incremental_role_links( diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 174cbe31..faf23976 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -31,15 +31,11 @@ def build_role_links(self, rm): self.rm = rm count = self.value.count("_") if count < 2: - raise RuntimeError( - 'the number of "_" in role definition should be at least 2' - ) + raise RuntimeError('the number of "_" in role definition should be at least 2') for rule in self.policy: if len(rule) < count: - raise RuntimeError( - "grouping policy elements do not meet role definition" - ) + raise RuntimeError("grouping policy elements do not meet role definition") if len(rule) > count: rule = rule[:count] @@ -52,9 +48,7 @@ def build_incremental_role_links(self, rm, op, rules): self.rm = rm count = self.value.count("_") if count < 2: - raise RuntimeError( - 'the number of "_" in role definition should be at least 2' - ) + raise RuntimeError('the number of "_" in role definition should be at least 2') for rule in rules: if len(rule) < count: raise TypeError("grouping policy elements do not meet role definition") diff --git a/casbin/model/model.py b/casbin/model/model.py index c0b8d9c5..a65509b6 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -107,9 +107,7 @@ def sort_policies_by_priority(self): if assertion.priority_index == -1: continue - assertion.policy = sorted( - assertion.policy, key=lambda x: x[assertion.priority_index] - ) + assertion.policy = sorted(assertion.policy, key=lambda x: x[assertion.priority_index]) for i, policy in enumerate(assertion.policy): assertion.policy_map[",".join(policy)] = i @@ -128,9 +126,7 @@ def sort_policies_by_subject_hierarchy(self): domain_index = index break - subject_hierarchy_map = self.get_subject_hierarchy_map( - self["g"]["g"].policy - ) + subject_hierarchy_map = self.get_subject_hierarchy_map(self["g"]["g"].policy) def compare_policy(policy): domain = DEFAULT_DOMAIN @@ -139,9 +135,7 @@ def compare_policy(policy): name = self.get_name_with_domain(domain, policy[sub_index]) return subject_hierarchy_map[name] - assertion.policy = sorted( - assertion.policy, key=compare_policy, reverse=True - ) + assertion.policy = sorted(assertion.policy, key=compare_policy, reverse=True) for i, policy in enumerate(assertion.policy): assertion.policy_map[",".join(policy)] = i @@ -194,11 +188,7 @@ def to_text(self): def write_string(sec): for p_type in self[sec]: value = self[sec][p_type].value - s.append( - "{} = {}\n".format( - sec, value.replace("p_", "p.").replace("r_", "r.") - ) - ) + s.append("{} = {}\n".format(sec, value.replace("p_", "p.").replace("r_", "r."))) s.append("[request_definition]\n") write_string("r") diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 7c77cc74..27a6a19d 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -83,8 +83,7 @@ def get_filtered_policy(self, sec, ptype, field_index, *field_values): rule for rule in self[sec][ptype].policy if all( - (callable(value) and value(rule[field_index + i])) - or (value == "" or rule[field_index + i] == value) + (callable(value) and value(rule[field_index + i])) or (value == "" or rule[field_index + i] == value) for i, value in enumerate(field_values) ) ] @@ -195,9 +194,7 @@ def update_policies(self, sec, ptype, old_rules, new_rules): if old_rule[priority_index] == new_rule[priority_index]: ast.policy[idx] = new_rule else: - raise Exception( - "New rule should have the same priority with old rule." - ) + raise Exception("New rule should have the same priority with old rule.") else: for idx, old_rule, new_rule in zip(old_rules_index, old_rules, new_rules): ast.policy[idx] = new_rule @@ -234,9 +231,7 @@ def remove_policies_with_effected(self, sec, ptype, rules): return effected - def remove_filtered_policy_returns_effects( - self, sec, ptype, field_index, *field_values - ): + def remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field_values): """ remove_filtered_policy_returns_effects removes policy rules based on field filters from the model. """ @@ -251,10 +246,7 @@ def remove_filtered_policy_returns_effects( return [] for rule in self[sec][ptype].policy: - if all( - value == "" or rule[field_index + i] == value - for i, value in enumerate(field_values) - ): + if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)): effects.append(rule) else: tmp.append(rule) @@ -274,10 +266,7 @@ def remove_filtered_policy(self, sec, ptype, field_index, *field_values): return res for rule in self[sec][ptype].policy: - if all( - value == "" or rule[field_index + i] == value - for i, value in enumerate(field_values) - ): + if all(value == "" or rule[field_index + i] == value for i, value in enumerate(field_values)): res = True else: tmp.append(rule) diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index 1ac09a3f..94a8c016 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -29,9 +29,7 @@ def update_policies(self, sec, ptype, old_rules, new_rules): """ pass - def update_filtered_policies( - self, sec, ptype, new_rules, field_index, *field_values - ): + def update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): """ update_filtered_policies deletes old rules and adds new rules. """ diff --git a/casbin/persist/watcher_ex.py b/casbin/persist/watcher_ex.py index ea063354..b627b135 100644 --- a/casbin/persist/watcher_ex.py +++ b/casbin/persist/watcher_ex.py @@ -35,9 +35,7 @@ def update_for_remove_policy(self, sec: str, ptype: str, *params: str): """ pass - def update_for_remove_filtered_policy( - self, sec: str, ptype: str, field_index: int, *field_values: str - ): + def update_for_remove_filtered_policy(self, sec: str, ptype: str, field_index: int, *field_values: str): """ update_for_remove_filtered_policy calls the update callback of other instances to synchronize their policy. It is called after Enforcer.RemoveFilteredNamedGroupingPolicy() diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index ffc48688..5ccd23a8 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -97,9 +97,9 @@ def _matching_fn(self, str1, str2, match_order=MatchOrder.STR_PATTERN): if match_order == MatchOrder.PATTERN_STR: return match_error_handler(self.matching_func, str2, str1) elif match_order == MatchOrder.PATTERN_PATTERN: - return match_error_handler( - self.matching_func, str1, str2 - ) or match_error_handler(self.matching_func, str2, str1) + return match_error_handler(self.matching_func, str1, str2) or match_error_handler( + self.matching_func, str2, str1 + ) else: # match_order == MatchOrder.STR_PATTERN return match_error_handler(self.matching_func, str1, str2) @@ -142,20 +142,14 @@ def add_link(self, name1, name2, *domain): if self.matching_func != None: for r in self.all_roles.values(): - if r.name != user.name and self._matching_fn( - user.name, r.name, MatchOrder.PATTERN_STR - ): + if r.name != user.name and self._matching_fn(user.name, r.name, MatchOrder.PATTERN_STR): r.add_role(role) - if r.name != role.name and self._matching_fn( - role.name, r.name, MatchOrder.PATTERN_STR - ): + if r.name != role.name and self._matching_fn(role.name, r.name, MatchOrder.PATTERN_STR): role.add_role(r) def delete_link(self, name1, name2, *domain): if Link(name1, name2) not in self.all_links: - raise RuntimeError( - f"error: link between {name1} and {name2} does not exist" - ) + raise RuntimeError(f"error: link between {name1} and {name2} does not exist") self.all_links.remove(Link(name1, name2)) user = self._get_role(name1) @@ -163,13 +157,9 @@ def delete_link(self, name1, name2, *domain): user.remove_role(role) for r in self.all_roles.values(): - if r.name != user.name and self._matching_fn( - user.name, r.name, MatchOrder.PATTERN_STR - ): + if r.name != user.name and self._matching_fn(user.name, r.name, MatchOrder.PATTERN_STR): r.remove_role(role) - if r.name != role.name and self._matching_fn( - role.name, r.name, MatchOrder.PATTERN_STR - ): + if r.name != role.name and self._matching_fn(role.name, r.name, MatchOrder.PATTERN_STR): role.remove_role(r) def has_link(self, name1, name2, *domain): @@ -249,9 +239,7 @@ def _get_role_manager(self, *domain): if self.domain_matching_func != None: for domain2, links in self.all_links.items(): - if domain1 != domain2 and match_error_handler( - self.domain_matching_func, domain1, domain2 - ): + if domain1 != domain2 and match_error_handler(self.domain_matching_func, domain1, domain2): domain_links = domain_links + links rm = RoleManager(max_hierarchy_level=self.max_hierarchy_level) @@ -270,9 +258,7 @@ def add_link(self, name1, name2, *domain): def delete_link(self, name1, name2, *domain): links = self._get_links(*domain) if Link(name1, name2) not in links: - raise RuntimeError( - f"error: link between {name1} and {name2} does not exist" - ) + raise RuntimeError(f"error: link between {name1} and {name2} does not exist") links.remove(Link(name1, name2)) def has_link(self, name1, name2, *domain): @@ -313,14 +299,10 @@ def _affected_role_managers(self, *domain): return [ self.rm_map[domain_str] for domain_str in self.rm_map.keys() - if match_error_handler( - self.domain_matching_func, domain_str, domain_pattern - ) + if match_error_handler(self.domain_matching_func, domain_str, domain_pattern) ] else: - return ( - [self.rm_map[domain_pattern]] if domain_pattern in self.rm_map else [] - ) + return [self.rm_map[domain_pattern]] if domain_pattern in self.rm_map else [] def add_matching_func(self, fn): super().add_matching_func(fn) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 716e9680..48a758c6 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -62,9 +62,7 @@ def start_auto_load_policy(self, interval): if self.is_auto_loading_running(): return self._auto_loading.value = True - self._auto_loading_thread = threading.Thread( - target=self._auto_load_policy, args=[interval], daemon=True - ) + self._auto_loading_thread = threading.Thread(target=self._auto_load_policy, args=[interval], daemon=True) self._auto_loading_thread.start() def stop_auto_load_policy(self): @@ -235,9 +233,7 @@ def get_named_grouping_policy(self, ptype): def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """gets all the role inheritance rules in the policy, field filters can be specified.""" with self._rl: - return self._e.get_filtered_named_grouping_policy( - ptype, field_index, *field_values - ) + return self._e.get_filtered_named_grouping_policy(ptype, field_index, *field_values) def has_policy(self, *params): """determines whether an authorization rule exists.""" @@ -283,9 +279,7 @@ def remove_named_policy(self, ptype, *params): def remove_filtered_named_policy(self, ptype, field_index, *field_values): """removes an authorization rule from the current named policy, field filters can be specified.""" with self._wl: - return self._e.remove_filtered_named_policy( - ptype, field_index, *field_values - ) + return self._e.remove_filtered_named_policy(ptype, field_index, *field_values) def has_grouping_policy(self, *params): """determines whether a role inheritance rule exists.""" @@ -331,9 +325,7 @@ def remove_named_grouping_policy(self, ptype, *params): def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): """removes a role inheritance rule from the current named policy, field filters can be specified.""" with self._wl: - return self._e.remove_filtered_named_grouping_policy( - ptype, field_index, *field_values - ) + return self._e.remove_filtered_named_grouping_policy(ptype, field_index, *field_values) def add_function(self, name, func): """adds a customized function.""" @@ -470,9 +462,7 @@ def get_implicit_permissions_for_user(self, user, *domain, filter_policy_dom=Tru But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. """ with self._rl: - return self._e.get_implicit_permissions_for_user( - user, *domain, filter_policy_dom=filter_policy_dom - ) + return self._e.get_implicit_permissions_for_user(user, *domain, filter_policy_dom=filter_policy_dom) def get_implicit_users_for_permission(self, *permission): """ @@ -602,9 +592,7 @@ def remove_named_grouping_policies(self, ptype, rules): return self._e.remove_named_grouping_policies(ptype, rules) def build_incremental_role_links(self, op, ptype, rules): - self.get_model().build_incremental_role_links( - self.get_role_manager(), op, "g", ptype, rules - ) + self.get_model().build_incremental_role_links(self.get_role_manager(), op, "g", ptype, rules) def new_enforce_context(self, suffix: str) -> "EnforceContext": return self._e.new_enforce_context(suffix) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e34796ec --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 120 \ No newline at end of file diff --git a/tests/benchmarks/benchmark_model.py b/tests/benchmarks/benchmark_model.py index 5fb17a16..7d65c98a 100644 --- a/tests/benchmarks/benchmark_model.py +++ b/tests/benchmarks/benchmark_model.py @@ -62,12 +62,8 @@ def benchmark_rbac_model(): def test_benchmark_rbac_model_small(benchmark): e = get_enforcer(get_examples("rbac_model.conf")) - e.add_policies( - {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(100)} - ) - e.add_grouping_policies( - {("user" + str(i), "group" + str(int(i / 10))) for i in range(1000)} - ) + e.add_policies({("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(100)}) + e.add_grouping_policies({("user" + str(i), "group" + str(int(i / 10))) for i in range(1000)}) @benchmark def benchmark_rbac_model(): @@ -77,12 +73,8 @@ def benchmark_rbac_model(): def test_benchmark_rbac_model_medium(benchmark): e = get_enforcer(get_examples("rbac_model.conf")) - e.add_policies( - {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(1000)} - ) - e.add_grouping_policies( - {("user" + str(i), "group" + str(int(i / 10))) for i in range(10000)} - ) + e.add_policies({("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(1000)}) + e.add_grouping_policies({("user" + str(i), "group" + str(int(i / 10))) for i in range(10000)}) @benchmark def benchmark_rbac_model(): @@ -92,12 +84,8 @@ def benchmark_rbac_model(): def test_benchmark_rbac_model_large(benchmark): e = get_enforcer(get_examples("rbac_model.conf")) - e.add_policies( - {("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(10000)} - ) - e.add_grouping_policies( - {("user" + str(i), "group" + str(int(i / 10))) for i in range(100000)} - ) + e.add_policies({("group" + str(i), "data" + str(int(i / 10)), "read") for i in range(10000)}) + e.add_grouping_policies({("user" + str(i), "group" + str(int(i / 10))) for i in range(100000)}) @benchmark def benchmark_rbac_model(): @@ -148,9 +136,7 @@ def benchmark_rbac_with_deny(): def test_benchmark_prioriry(benchmark): - e = get_enforcer( - get_examples("priority_model.conf"), get_examples("priority_policy.csv") - ) + e = get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) @benchmark def benchmark_rbac_with_deny(): @@ -158,9 +144,7 @@ def benchmark_rbac_with_deny(): def test_benchmark_keymatch(benchmark): - e = get_enforcer( - get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv") - ) + e = get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) @benchmark def benchmark_keymatch(): diff --git a/tests/config/test_config.py b/tests/config/test_config.py index 96c41bec..3d9d80e2 100644 --- a/tests/config/test_config.py +++ b/tests/config/test_config.py @@ -54,12 +54,8 @@ def test_new_config(self): self.assertEqual(config.get("multi5::name"), "r.sub==p.sub && r.obj==p.obj") self.assertEqual(config.get_bool("multi5::name"), False) - self.assertEqual( - config.get_string("multi5::name"), "r.sub==p.sub && r.obj==p.obj" - ) - self.assertEqual( - config.get_strings("multi5::name"), ["r.sub==p.sub && r.obj==p.obj"] - ) + self.assertEqual(config.get_string("multi5::name"), "r.sub==p.sub && r.obj==p.obj") + self.assertEqual(config.get_strings("multi5::name"), ["r.sub==p.sub && r.obj==p.obj"]) with self.assertRaises(ValueError): config.get_int("multi5::name") with self.assertRaises(ValueError): diff --git a/tests/rbac/test_role_manager.py b/tests/rbac/test_role_manager.py index 9e0327ef..40c2c1f0 100644 --- a/tests/rbac/test_role_manager.py +++ b/tests/rbac/test_role_manager.py @@ -95,9 +95,7 @@ def test_role(self): rm.clear() - match_fn = ( - lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False - ) + match_fn = lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False rm.add_matching_func(match_fn) @@ -125,9 +123,7 @@ def test_role(self): self.assertTrue(rm.has_link("g2", "any_group")) self.assertEqual(sorted(rm.get_roles("u1")), sorted(["g1", "any_user"])) - self.assertEqual( - sorted(rm.get_roles("u2")), sorted(["g2", "g1", r"g\d+", "any_user"]) - ) + self.assertEqual(sorted(rm.get_roles("u2")), sorted(["g2", "g1", r"g\d+", "any_user"])) self.assertEqual(rm.get_roles(r"u\d+"), ["any_user"]) self.assertEqual(rm.get_roles("u3"), ["any_user"]) self.assertEqual(rm.get_roles("g1"), ["any_group"]) @@ -307,9 +303,7 @@ def test_domain_role(self): self.assertTrue(rm.has_link("u4", "admin", "domain2")) rm.clear() - match_fn = ( - lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False - ) + match_fn = lambda name1, name2: True if re.match("^" + name2 + "$", name1) else False rm.add_domain_matching_func(match_fn) rm.add_link("alice", "user", ".*") diff --git a/tests/test_distributed_api.py b/tests/test_distributed_api.py index 84ef9900..205db5d8 100644 --- a/tests/test_distributed_api.py +++ b/tests/test_distributed_api.py @@ -24,9 +24,7 @@ def get_enforcer(self, model=None, adapter=None): ) def test(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.add_policy_self( False, @@ -50,12 +48,8 @@ def test(self): self.assertTrue(e.enforce("alice", "data2", "read")) self.assertTrue(e.enforce("alice", "data2", "write")) - e.update_policy_self( - False, "p", "p", ["alice", "data1", "read"], ["alice", "data1", "write"] - ) - e.update_policy_self( - False, "g", "g", ["alice", "data2_admin"], ["tom", "alice"] - ) + e.update_policy_self(False, "p", "p", ["alice", "data1", "read"], ["alice", "data1", "write"]) + e.update_policy_self(False, "g", "g", ["alice", "data2_admin"], ["tom", "alice"]) self.assertFalse(e.enforce("alice", "data1", "read")) self.assertTrue(e.enforce("alice", "data1", "write")) diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 45f9022c..c1591b4f 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -55,13 +55,9 @@ def test_enforce_ex_basic(self): get_examples("basic_model.conf"), get_examples("basic_policy.csv"), ) - self.assertTupleEqual( - e.enforce_ex("alice", "data1", "read"), (True, ["alice", "data1", "read"]) - ) + self.assertTupleEqual(e.enforce_ex("alice", "data1", "read"), (True, ["alice", "data1", "read"])) self.assertTupleEqual(e.enforce_ex("alice", "data2", "read"), (False, [])) - self.assertTupleEqual( - e.enforce_ex("bob", "data2", "write"), (True, ["bob", "data2", "write"]) - ) + self.assertTupleEqual(e.enforce_ex("bob", "data2", "write"), (True, ["bob", "data2", "write"])) self.assertTupleEqual(e.enforce_ex("bob", "data1", "write"), (False, [])) def test_model_set_load(self): @@ -92,9 +88,7 @@ def test_enforcer_basic_without_spaces(self): self.assertTrue(e.enforce("bob", "data2", "write")) def test_enforce_basic_with_root(self): - e = self.get_enforcer( - get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv") - ) + e = self.get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) self.assertTrue(e.enforce("root", "any", "any")) def test_enforce_basic_without_resources(self): @@ -118,16 +112,12 @@ def test_enforce_basic_without_users(self): self.assertFalse(e.enforce("data2", "read")) def test_enforce_ip_match(self): - e = self.get_enforcer( - get_examples("ipmatch_model.conf"), get_examples("ipmatch_policy.csv") - ) + e = self.get_enforcer(get_examples("ipmatch_model.conf"), get_examples("ipmatch_policy.csv")) self.assertTrue(e.enforce("192.168.2.1", "data1", "read")) self.assertFalse(e.enforce("192.168.3.1", "data1", "read")) def test_enforce_key_match(self): - e = self.get_enforcer( - get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv") - ) + e = self.get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) self.assertTrue(e.enforce("alice", "/alice_data/test", "GET")) self.assertFalse(e.enforce("alice", "/bob_data/test", "GET")) self.assertTrue(e.enforce("cathy", "/cathy_data", "GET")) @@ -135,9 +125,7 @@ def test_enforce_key_match(self): self.assertFalse(e.enforce("cathy", "/cathy_data/12", "POST")) def test_enforce_key_match2(self): - e = self.get_enforcer( - get_examples("keymatch2_model.conf"), get_examples("keymatch2_policy.csv") - ) + e = self.get_enforcer(get_examples("keymatch2_model.conf"), get_examples("keymatch2_policy.csv")) self.assertTrue(e.enforce("alice", "/alice_data/resource", "GET")) self.assertTrue(e.enforce("alice", "/alice_data2/123/using/456", "GET")) @@ -148,15 +136,9 @@ def test_enforce_key_match_custom_model(self): ) def custom_function(key1, key2): - if ( - key1 == "/alice_data2/myid/using/res_id" - and key2 == "/alice_data/:resource" - ): + if key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data/:resource": return True - elif ( - key1 == "/alice_data2/myid/using/res_id" - and key2 == "/alice_data2/:id/using/:resId" - ): + elif key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data2/:id/using/:resId": return True return False @@ -166,9 +148,7 @@ def custom_function(key1, key2): self.assertTrue(e.enforce("alice", "/alice_data2/myid/using/res_id", "GET")) def test_enforce_priority(self): - e = self.get_enforcer( - get_examples("priority_model.conf"), get_examples("priority_policy.csv") - ) + e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) self.assertTrue(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("alice", "data1", "write")) self.assertFalse(e.enforce("alice", "data2", "read")) @@ -248,22 +228,16 @@ def test_multiple_policy_definitions(self): self.assertFalse(e.enforce(enforce_context, sub1, "/data2", "read")) def test_enforce_rbac(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertTrue(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("bob", "data1", "read")) self.assertTrue(e.enforce("bob", "data2", "write")) self.assertTrue(e.enforce("alice", "data2", "read")) self.assertTrue(e.enforce("alice", "data2", "write")) - self.assertFalse( - e.enforce("bogus", "data2", "write") - ) # test non-existant subject + self.assertFalse(e.enforce("bogus", "data2", "write")) # test non-existant subject def test_enforce_rbac_empty_policy(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("empty_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) self.assertFalse(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("bob", "data1", "read")) self.assertFalse(e.enforce("bob", "data2", "write")) @@ -363,9 +337,7 @@ def test_enforce_abac_log_enabled(self): self.assertTrue(e.enforce(sub, obj, "write")) def test_abac_with_sub_rule(self): - e = self.get_enforcer( - get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv") - ) + e = self.get_enforcer(get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv")) sub1 = TestSub("alice", 16) sub2 = TestSub("bob", 20) diff --git a/tests/test_filter.py b/tests/test_filter.py index 6633b229..75151609 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -25,16 +25,12 @@ class Filter: class TestFilteredAdapter(TestCase): def test_init_filtered_adapter(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("rbac_with_domains_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) self.assertFalse(e.has_policy(["admin", "domain1", "data1", "read"])) def test_load_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("rbac_with_domains_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -63,9 +59,7 @@ def test_load_filtered_policy(self): e.get_adapter().save_policy(e.get_model()) def test_append_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("rbac_with_domains_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -98,9 +92,7 @@ def test_append_filtered_policy(self): self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) def test_filtered_policy_invalid_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("rbac_with_domains_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = ["", "domain1"] @@ -108,9 +100,7 @@ def test_filtered_policy_invalid_filter(self): e.load_filtered_policy(filter) def test_filtered_policy_empty_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("rbac_with_domains_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) try: @@ -145,9 +135,7 @@ def test_filtered_adapter_empty_filepath(self): e.load_filtered_policy(None) def test_filtered_adapter_invalid_filepath(self): - adapter = casbin.persist.adapters.FilteredAdapter( - get_examples("does_not_exist_policy.csv") - ) + adapter = casbin.persist.adapters.FilteredAdapter(get_examples("does_not_exist_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 3e61d204..098c8d05 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -52,17 +52,13 @@ def test_get_policy_api(self): ], ) - self.assertEqual( - e.get_filtered_policy(0, "alice"), [["alice", "data1", "read"]] - ) + self.assertEqual(e.get_filtered_policy(0, "alice"), [["alice", "data1", "read"]]) self.assertEqual(e.get_filtered_policy(0, "bob"), [["bob", "data2", "write"]]) self.assertEqual( e.get_filtered_policy(0, "data2_admin"), [["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"]], ) - self.assertEqual( - e.get_filtered_policy(1, "data1"), [["alice", "data1", "read"]] - ) + self.assertEqual(e.get_filtered_policy(1, "data1"), [["alice", "data1", "read"]]) self.assertEqual( e.get_filtered_policy(1, "data2"), [ @@ -99,14 +95,10 @@ def test_get_policy_api(self): self.assertFalse(e.has_policy(["alice", "data2", "read"])) self.assertFalse(e.has_policy(["bob", "data3", "write"])) self.assertEqual(e.get_grouping_policy(), [["alice", "data2_admin"]]) - self.assertEqual( - e.get_filtered_grouping_policy(0, "alice"), [["alice", "data2_admin"]] - ) + self.assertEqual(e.get_filtered_grouping_policy(0, "alice"), [["alice", "data2_admin"]]) self.assertEqual(e.get_filtered_grouping_policy(0, "bob"), []) self.assertEqual(e.get_filtered_grouping_policy(1, "data1_admin"), []) - self.assertEqual( - e.get_filtered_grouping_policy(1, "data2_admin"), [["alice", "data2_admin"]] - ) + self.assertEqual(e.get_filtered_grouping_policy(1, "data2_admin"), [["alice", "data2_admin"]]) # Note: "" (empty string) in fieldValues means matching all values. self.assertEqual( e.get_filtered_grouping_policy(0, "", "data2_admin"), @@ -198,11 +190,7 @@ def test_get_policy_multiple_matching_functions(self): km2_fn = casbin.util.key_match2_func self.assertEqual( - sorted( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.2"), lambda a: "data" in a - ) - ), + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.2"), lambda a: "data" in a)), sorted( [ ["admin", "domain.*", "data1", "read"], @@ -212,11 +200,7 @@ def test_get_policy_multiple_matching_functions(self): ) self.assertEqual( - sorted( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read" - ) - ), + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read")), sorted( [ ["admin", "domain.*", "data1", "read"], @@ -227,11 +211,7 @@ def test_get_policy_multiple_matching_functions(self): ) self.assertEqual( - sorted( - e.get_filtered_policy( - 1, partial(km2_fn, "domain.1"), "", "reading".startswith - ) - ), + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "reading".startswith)), sorted( [ ["admin", "domain.*", "data1", "read"], diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 62c18b30..2f5d3bc7 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -18,9 +18,7 @@ class TestRbacApi(TestCaseBase): def test_get_roles_for_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) self.assertEqual(e.get_roles_for_user("bob"), []) @@ -28,24 +26,18 @@ def test_get_roles_for_user(self): self.assertEqual(e.get_roles_for_user("non_exist"), []) def test_get_users_for_role(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertEqual(e.get_users_for_role("data2_admin"), ["alice"]) def test_has_role_for_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) self.assertTrue(e.has_role_for_user("alice", "data2_admin")) self.assertFalse(e.has_role_for_user("alice", "data1_admin")) def test_add_role_for_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.add_role_for_user("alice", "data1_admin") self.assertEqual( sorted(e.get_roles_for_user("alice")), @@ -53,9 +45,7 @@ def test_add_role_for_user(self): ) def test_delete_role_for_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.add_role_for_user("alice", "data1_admin") self.assertEqual( sorted(e.get_roles_for_user("alice")), @@ -66,23 +56,17 @@ def test_delete_role_for_user(self): self.assertEqual(e.get_roles_for_user("alice"), ["data2_admin"]) def test_delete_roles_for_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_roles_for_user("alice") self.assertEqual(e.get_roles_for_user("alice"), []) def test_delete_user(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_user("alice") self.assertEqual(e.get_roles_for_user("alice"), []) def test_delete_role(self): - e = self.get_enforcer( - get_examples("rbac_model.conf"), get_examples("rbac_policy.csv") - ) + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) e.delete_role("data2_admin") self.assertTrue(e.enforce("alice", "data1", "read")) self.assertFalse(e.enforce("alice", "data1", "write")) @@ -183,9 +167,7 @@ def test_enforce_implicit_roles_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertEqual( - e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] - ) + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) self.assertEqual( sorted(e.get_implicit_roles_for_user("alice", "domain1")), sorted(["role:global_admin", "role:reader", "role:writer"]), @@ -228,9 +210,7 @@ def test_enforce_implicit_permissions_api_with_domain(self): get_examples("rbac_with_hierarchy_with_domains_policy.csv"), ) - self.assertEqual( - e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] - ) + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) self.assertEqual( sorted(e.get_implicit_roles_for_user("alice", "domain1")), sorted(["role:global_admin", "role:reader", "role:writer"]), @@ -280,9 +260,7 @@ def test_enforce_implicit_permissions_api_with_domain_matching_function(self): [], ) - self.assertEqual( - sorted(e.get_implicit_permissions_for_user("bob", "domain.1")), [] - ) + self.assertEqual(sorted(e.get_implicit_permissions_for_user("bob", "domain.1")), []) def test_enforce_implicit_permissions_api_with_domain_ignore_domain_policies_filter( self, @@ -292,19 +270,13 @@ def test_enforce_implicit_permissions_api_with_domain_ignore_domain_policies_fil get_examples("rbac_with_hierarchy_without_policy_domains.csv"), ) - self.assertEqual( - e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"] - ) + self.assertEqual(e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) self.assertEqual( sorted(e.get_implicit_roles_for_user("alice", "domain1")), sorted(["role:global_admin", "role:reader", "role:writer"]), ) self.assertEqual( - sorted( - e.get_implicit_permissions_for_user( - "alice", "domain1", filter_policy_dom=False - ) - ), + sorted(e.get_implicit_permissions_for_user("alice", "domain1", filter_policy_dom=False)), sorted( [ ["alice", "data2", "read"], @@ -384,18 +356,10 @@ def test_implicit_user_api(self): get_examples("rbac_with_hierarchy_policy.csv"), ) - self.assertEqual( - ["alice"], e.get_implicit_users_for_permission("data1", "read") - ) - self.assertEqual( - ["alice"], e.get_implicit_users_for_permission("data1", "write") - ) - self.assertEqual( - ["alice"], e.get_implicit_users_for_permission("data2", "read") - ) - self.assertEqual( - ["alice", "bob"], e.get_implicit_users_for_permission("data2", "write") - ) + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "read")) + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data1", "write")) + self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read")) + self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) def test_domain_match_model(self): e = self.get_enforcer( diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index e981f8e8..2bfdbe86 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -36,31 +36,23 @@ def test_key_match2(self): self.assertTrue(util.key_match2_func("/foo", "/foo")) self.assertTrue(util.key_match2_func("/foo", "/foo*")) self.assertFalse(util.key_match2_func("/foo", "/foo/*")) - self.assertFalse( - util.key_match2_func("/foo/bar", "/foo") - ) # different with KeyMatch. + self.assertFalse(util.key_match2_func("/foo/bar", "/foo")) # different with KeyMatch. self.assertFalse(util.key_match2_func("/foo/bar", "/foo*")) self.assertTrue(util.key_match2_func("/foo/bar", "/foo/*")) - self.assertFalse( - util.key_match2_func("/foobar", "/foo") - ) # different with KeyMatch. + self.assertFalse(util.key_match2_func("/foobar", "/foo")) # different with KeyMatch. self.assertFalse(util.key_match2_func("/foobar", "/foo*")) self.assertFalse(util.key_match2_func("/foobar", "/foo/*")) self.assertFalse(util.key_match2_func("/", "/:resource")) self.assertTrue(util.key_match2_func("/resource1", "/:resource")) self.assertFalse(util.key_match2_func("/myid", "/:id/using/:resId")) - self.assertTrue( - util.key_match2_func("/myid/using/myresid", "/:id/using/:resId") - ) + self.assertTrue(util.key_match2_func("/myid/using/myresid", "/:id/using/:resId")) self.assertFalse(util.key_match2_func("/proxy/myid", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/res", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/proxy/myid/res/res2", "/proxy/:id/*")) - self.assertTrue( - util.key_match2_func("/proxy/myid/res/res2/res3", "/proxy/:id/*") - ) + self.assertTrue(util.key_match2_func("/proxy/myid/res/res2/res3", "/proxy/:id/*")) self.assertFalse(util.key_match2_func("/proxy/", "/proxy/:id/*")) self.assertTrue(util.key_match2_func("/alice", "/:id")) @@ -84,71 +76,31 @@ def test_key_match3(self): self.assertFalse(util.key_match3_func("/", "/{resource}")) self.assertTrue(util.key_match3_func("/resource1", "/{resource}")) self.assertFalse(util.key_match3_func("/myid", "/{id}/using/{resId}")) - self.assertTrue( - util.key_match3_func("/myid/using/myresid", "/{id}/using/{resId}") - ) + self.assertTrue(util.key_match3_func("/myid/using/myresid", "/{id}/using/{resId}")) self.assertFalse(util.key_match3_func("/proxy/myid", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/res", "/proxy/{id}/*")) self.assertTrue(util.key_match3_func("/proxy/myid/res/res2", "/proxy/{id}/*")) - self.assertTrue( - util.key_match3_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*") - ) + self.assertTrue(util.key_match3_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*")) self.assertFalse(util.key_match3_func("/proxy/", "/proxy/{id}/*")) - self.assertFalse( - util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}") - ) + self.assertFalse(util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}")) def test_key_match4(self): - self.assertTrue( - util.key_match4_func("/parent/123/child/123", "/parent/{id}/child/{id}") - ) - self.assertFalse( - util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{id}") - ) - - self.assertTrue( - util.key_match4_func( - "/parent/123/child/123", "/parent/{id}/child/{another_id}" - ) - ) - self.assertTrue( - util.key_match4_func( - "/parent/123/child/456", "/parent/{id}/child/{another_id}" - ) - ) - - self.assertTrue( - util.key_match4_func( - "/parent/123/child/456", "/parent/{id}/child/{another_id}" - ) - ) - self.assertFalse( - util.key_match4_func( - "/parent/123/child/123/book/456", "/parent/{id}/child/{id}/book/{id}" - ) - ) - self.assertFalse( - util.key_match4_func( - "/parent/123/child/456/book/123", "/parent/{id}/child/{id}/book/{id}" - ) - ) - self.assertFalse( - util.key_match4_func( - "/parent/123/child/456/book/", "/parent/{id}/child/{id}/book/{id}" - ) - ) - self.assertFalse( - util.key_match4_func( - "/parent/123/child/456", "/parent/{id}/child/{id}/book/{id}" - ) - ) - - self.assertFalse( - util.key_match4_func("/parent/123/child/123", "/parent/{i/d}/child/{i/d}") - ) + self.assertTrue(util.key_match4_func("/parent/123/child/123", "/parent/{id}/child/{id}")) + self.assertFalse(util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{id}")) + + self.assertTrue(util.key_match4_func("/parent/123/child/123", "/parent/{id}/child/{another_id}")) + self.assertTrue(util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{another_id}")) + + self.assertTrue(util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{another_id}")) + self.assertFalse(util.key_match4_func("/parent/123/child/123/book/456", "/parent/{id}/child/{id}/book/{id}")) + self.assertFalse(util.key_match4_func("/parent/123/child/456/book/123", "/parent/{id}/child/{id}/book/{id}")) + self.assertFalse(util.key_match4_func("/parent/123/child/456/book/", "/parent/{id}/child/{id}/book/{id}")) + self.assertFalse(util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{id}/book/{id}")) + + self.assertFalse(util.key_match4_func("/parent/123/child/123", "/parent/{i/d}/child/{i/d}")) def test_regex_match(self): self.assertTrue(util.regex_match_func("/topic/create", "/topic/create")) @@ -157,15 +109,9 @@ def test_regex_match(self): self.assertFalse(util.regex_match_func("/topic/edit", "/topic/edit/[0-9]+")) self.assertTrue(util.regex_match_func("/topic/edit/123", "/topic/edit/[0-9]+")) self.assertFalse(util.regex_match_func("/topic/edit/abc", "/topic/edit/[0-9]+")) - self.assertFalse( - util.regex_match_func("/foo/delete/123", "/topic/delete/[0-9]+") - ) - self.assertTrue( - util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+") - ) - self.assertFalse( - util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+") - ) + self.assertFalse(util.regex_match_func("/foo/delete/123", "/topic/delete/[0-9]+")) + self.assertTrue(util.regex_match_func("/topic/delete/0", "/topic/delete/[0-9]+")) + self.assertFalse(util.regex_match_func("/topic/edit/123s", "/topic/delete/[0-9]+")) def test_glob_match(self): self.assertTrue(util.glob_match_func("/foo", "/foo")) @@ -205,9 +151,7 @@ def test_glob_match2(self): self.assertFalse(util.glob_match_func("/foo", "*/foo/*")) self.assertFalse(util.glob_match_func("/foo/bar", "*/foo")) self.assertFalse(util.glob_match_func("/foo/bar", "*/foo*")) - self.assertFalse( - util.glob_match_func("/foo/bar", "*/foo/*") - ) # different from Go + self.assertFalse(util.glob_match_func("/foo/bar", "*/foo/*")) # different from Go self.assertFalse(util.glob_match_func("/foobar", "*/foo")) self.assertFalse(util.glob_match_func("/foobar", "*/foo*")) # different from Go self.assertFalse(util.glob_match_func("/foobar", "*/foo/*")) diff --git a/tests/util/test_util.py b/tests/util/test_util.py index c60ff230..1300c72d 100644 --- a/tests/util/test_util.py +++ b/tests/util/test_util.py @@ -18,28 +18,20 @@ class TestUtil(TestCase): def test_remove_comments(self): - self.assertEqual( - util.remove_comments("r.act == p.act # comments"), "r.act == p.act" - ) - self.assertEqual( - util.remove_comments("r.act == p.act#comments"), "r.act == p.act" - ) + self.assertEqual(util.remove_comments("r.act == p.act # comments"), "r.act == p.act") + self.assertEqual(util.remove_comments("r.act == p.act#comments"), "r.act == p.act") self.assertEqual(util.remove_comments("r.act == p.act###"), "r.act == p.act") self.assertEqual(util.remove_comments("### comments"), "") self.assertEqual(util.remove_comments("r.act == p.act"), "r.act == p.act") def test_escape_assertion(self): self.assertEqual( - util.escape_assertion( - "m = r.sub == p.sub && r.obj == p.obj && r.act == p.act" - ), + util.escape_assertion("m = r.sub == p.sub && r.obj == p.obj && r.act == p.act"), "m = r_sub == p_sub && r_obj == p_obj && r_act == p_act", ) def test_array_remove_duplicates(self): - res = util.array_remove_duplicates( - ["data", "data1", "data2", "data1", "data2", "data3"] - ) + res = util.array_remove_duplicates(["data", "data1", "data2", "data1", "data2", "data3"]) self.assertEqual(res, ["data", "data1", "data2", "data3"]) def test_array_to_string(self): @@ -64,25 +56,15 @@ def test_has_eval(self): self.assertTrue(util.has_eval("eval(a) && eval(b) && a && b && c")) def test_replace_eval(self): - self.assertEqual( - util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c" - ) - self.assertEqual( - util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)" - ) + self.assertEqual(util.replace_eval("eval(a) && eval(b) && c", ["a", "b"]), "(a) && (b) && c") + self.assertEqual(util.replace_eval("a && eval(b) && eval(c)", ["b", "c"]), "a && (b) && (c)") def test_get_eval_value(self): self.assertEqual(util.get_eval_value("eval(a) && a && b && c"), ["a"]) self.assertEqual(util.get_eval_value("a && eval(a) && b && c"), ["a"]) + self.assertEqual(util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"]) + self.assertEqual(util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"]) self.assertEqual( - util.get_eval_value("eval(a) && eval(b) && a && b && c"), ["a", "b"] - ) - self.assertEqual( - util.get_eval_value("a && eval(a) && eval(b) && b && c"), ["a", "b"] - ) - self.assertEqual( - util.get_eval_value( - "eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)" - ), + util.get_eval_value("eval(p.sub_rule) || p.obj == r.obj && eval(p.domain_rule)"), ["p.sub_rule", "p.domain_rule"], ) From 737adc40bd4d7f8b0b1026f2a818c3f928555bfa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 29 Apr 2022 15:49:57 +0000 Subject: [PATCH 227/349] chore(release): 1.16.1 [skip ci] ## [1.16.1](https://github.com/casbin/pycasbin/compare/v1.16.0...v1.16.1) (2022-04-29) ### Bug Fixes * add lint config ([#255](https://github.com/casbin/pycasbin/issues/255)) ([4890433](https://github.com/casbin/pycasbin/commit/4890433f8d828ca94233b0fe005af56617409b64)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c21eb4..c9c13d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.1](https://github.com/casbin/pycasbin/compare/v1.16.0...v1.16.1) (2022-04-29) + + +### Bug Fixes + +* add lint config ([#255](https://github.com/casbin/pycasbin/issues/255)) ([4890433](https://github.com/casbin/pycasbin/commit/4890433f8d828ca94233b0fe005af56617409b64)) + # [1.16.0](https://github.com/casbin/pycasbin/compare/v1.15.5...v1.16.0) (2022-04-27) diff --git a/setup.cfg b/setup.cfg index 1bafd15e..8ccca183 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.0 +version = 1.16.1 From 871b9b2ce66fd3fc99a9780480232366800829fd Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sat, 30 Apr 2022 12:02:50 +0800 Subject: [PATCH 228/349] fix: allow permissions management to use additional p-types (#256) * fix: allow permissions management to use additional p-types * code reuse and change a function name --- casbin/enforcer.py | 30 +++++++++++++++++-- casbin/synced_enforcer.py | 22 ++++++++++++++ examples/rbac_with_multiple_policy_model.conf | 16 ++++++++++ examples/rbac_with_multiple_policy_policy.csv | 8 +++++ tests/test_rbac_api.py | 25 ++++++++++++++++ 5 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 examples/rbac_with_multiple_policy_model.conf create mode 100644 examples/rbac_with_multiple_policy_policy.csv diff --git a/casbin/enforcer.py b/casbin/enforcer.py index d1aabca6..aab27333 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -164,6 +164,26 @@ def get_implicit_permissions_for_user(self, user, domain="", filter_policy_dom=T get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + For given domain policies are filtered by corresponding domain matching function of DomainManager + Inherited roles can be matched by domain. For domain neutral policies set: + filter_policy_dom = False + + filter_policy_dom: bool - For given *domain*, policies will be filtered by domain as well. Default = True + """ + return self.get_named_implicit_permissions_for_user("p", user, domain, filter_policy_dom) + + def get_named_implicit_permissions_for_user(self, ptype, user, domain="", filter_policy_dom=True): + """ + gets implicit permissions for a user or role by named policy. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + For given domain policies are filtered by corresponding domain matching function of DomainManager Inherited roles can be matched by domain. For domain neutral policies set: filter_policy_dom = False @@ -182,7 +202,9 @@ def get_implicit_permissions_for_user(self, user, domain="", filter_policy_dom=T domain = partial(domain_matching_func, domain) for role in roles: - permissions = self.get_permissions_for_user_in_domain(role, domain if filter_policy_dom else "") + permissions = self.get_named_permissions_for_user_in_domain( + ptype, role, domain if filter_policy_dom else "" + ) res.extend(permissions) return res @@ -235,4 +257,8 @@ def delete_roles_for_user_in_domain(self, user, role, domain): def get_permissions_for_user_in_domain(self, user, domain): """gets permissions for a user or role inside domain.""" - return self.get_filtered_policy(0, user, domain) + return self.get_named_permissions_for_user_in_domain("p", user, domain) + + def get_named_permissions_for_user_in_domain(self, ptype, user, domain): + """gets permissions for a user or role with named policy inside domain.""" + return self.get_filtered_named_policy(ptype, 0, user, domain) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 48a758c6..8c063d6e 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -464,6 +464,23 @@ def get_implicit_permissions_for_user(self, user, *domain, filter_policy_dom=Tru with self._rl: return self._e.get_implicit_permissions_for_user(user, *domain, filter_policy_dom=filter_policy_dom) + def get_named_implicit_permissions_for_user(self, ptype, user, *domain, filter_policy_dom=True): + """ + gets implicit permissions for a user or role by named policy. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + """ + with self._rl: + return self._e.get_named_implicit_permissions_for_user( + ptype, user, *domain, filter_policy_dom=filter_policy_dom + ) + def get_implicit_users_for_permission(self, *permission): """ gets implicit users for a permission. @@ -505,6 +522,11 @@ def get_permissions_for_user_in_domain(self, user, domain): with self._rl: return self._e.get_permissions_for_user_in_domain(user, domain) + def get_named_permissions_for_user_in_domain(self, ptype, user, domain): + """gets permissions for a user or role by named policy inside domain.""" + with self._rl: + return self._e.get_named_permissions_for_user_in_domain(ptype, user, domain) + def enable_auto_build_role_links(self, auto_build_role_links): """controls whether to rebuild the role inheritance relations when a role is added or deleted.""" with self._wl: diff --git a/examples/rbac_with_multiple_policy_model.conf b/examples/rbac_with_multiple_policy_model.conf new file mode 100644 index 00000000..6a7fb091 --- /dev/null +++ b/examples/rbac_with_multiple_policy_model.conf @@ -0,0 +1,16 @@ +[request_definition] +r = user, thing, action + +[policy_definition] +p = role, thing, action +p2 = role, action + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = g(r.user, p.role) && r.thing == p.thing && r.action == p.action +m2 = g(r.user, p2.role) && r.action == p.action + +[role_definition] +g = _,_ \ No newline at end of file diff --git a/examples/rbac_with_multiple_policy_policy.csv b/examples/rbac_with_multiple_policy_policy.csv new file mode 100644 index 00000000..18e9cbc2 --- /dev/null +++ b/examples/rbac_with_multiple_policy_policy.csv @@ -0,0 +1,8 @@ +p, user, /data, GET +p, admin, /data, POST + +p2, user, view +p2, admin, create + +g, admin, user +g, alice, admin \ No newline at end of file diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 2f5d3bc7..50fbd20b 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -204,6 +204,31 @@ def test_enforce_implicit_permissions_api(self): sorted([["bob", "data2", "write"]]), ) + def test_enforce_implicit_permissions_api_with_multiple_policy(self): + e = self.get_enforcer( + get_examples("rbac_with_multiple_policy_model.conf"), + get_examples("rbac_with_multiple_policy_policy.csv"), + ) + + self.assertEqual( + sorted(e.get_named_implicit_permissions_for_user("p", "alice")), + sorted( + [ + ["user", "/data", "GET"], + ["admin", "/data", "POST"], + ] + ), + ) + self.assertEqual( + sorted(e.get_named_implicit_permissions_for_user("p2", "alice")), + sorted( + [ + ["user", "view"], + ["admin", "create"], + ] + ), + ) + def test_enforce_implicit_permissions_api_with_domain(self): e = self.get_enforcer( get_examples("rbac_with_domains_model.conf"), From 1679b70ad2c636048171b8e93bc2053b1e9fcf32 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Apr 2022 04:05:52 +0000 Subject: [PATCH 229/349] chore(release): 1.16.2 [skip ci] ## [1.16.2](https://github.com/casbin/pycasbin/compare/v1.16.1...v1.16.2) (2022-04-30) ### Bug Fixes * allow permissions management to use additional p-types ([#256](https://github.com/casbin/pycasbin/issues/256)) ([37892e1](https://github.com/casbin/pycasbin/commit/37892e1b1578db42543ec81e0120ebda3492afc7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c13d02..7b806ed4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.2](https://github.com/casbin/pycasbin/compare/v1.16.1...v1.16.2) (2022-04-30) + + +### Bug Fixes + +* allow permissions management to use additional p-types ([#256](https://github.com/casbin/pycasbin/issues/256)) ([37892e1](https://github.com/casbin/pycasbin/commit/37892e1b1578db42543ec81e0120ebda3492afc7)) + ## [1.16.1](https://github.com/casbin/pycasbin/compare/v1.16.0...v1.16.1) (2022-04-29) diff --git a/setup.cfg b/setup.cfg index 8ccca183..7f12c0b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.1 +version = 1.16.2 From 0328f7dd8d36a6b86191310d9dfa4a8cc9ab6c96 Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sat, 30 Apr 2022 12:07:41 +0800 Subject: [PATCH 230/349] fix: different behavior of glob_match from other casbin (#254) * fix: different behavior of glob_match from other casbin * squash test --- casbin/util/builtin_operators.py | 114 ++++++++++++++++++++++++++- requirements.txt | 3 +- tests/util/test_builtin_operators.py | 56 ++++++++++--- 3 files changed, 155 insertions(+), 18 deletions(-) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 5541974a..adaf2d1a 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -15,8 +15,6 @@ import ipaddress import re -from wcmatch import pathlib - KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+}(.*?)") KEY_MATCH4_PATTERN = re.compile(r"{([^/]+)}") @@ -151,9 +149,119 @@ def regex_match_func(*args): return regex_match(name1, name2) +def range_match(pattern, pattern_index, test): + """check the if char `test` in string is match with the scope of [...] in pattern""" + + pattern_len = len(pattern) + if pattern_index == pattern_len: + return -1 + negate = pattern[pattern_index] == "!" or pattern[pattern_index] == "^" + if negate: + pattern_index += 1 + ok = 0 + while True: + if pattern_index == pattern_len: + break + c = pattern[pattern_index] + pattern_index += 1 + if c == "]": + break + if c == "\\": + if pattern_index == pattern_len: + return -1 + c = pattern[pattern_index] + pattern_index += 1 + if ( + pattern_index != pattern_len + and pattern[pattern_index] == "-" + and pattern_index + 1 != pattern_len + and pattern[pattern_index + 1] != "]" + ): + c2 = pattern[pattern_index + 1] + pattern_index += 2 + if c2 == "\\": + if pattern_index == pattern_len: + return -1 + c2 = pattern[pattern_index] + pattern_index += 1 + if c <= test <= c2: + ok = 1 + elif c == test: + ok = 1 + + if ok == negate: + return -1 + else: + return pattern_index + + def glob_match(string, pattern): """determines whether string matches the pattern in glob expression.""" - return pathlib.Path(string).globmatch(pattern) + + pattern_len = len(pattern) + string_len = len(string) + if pattern_len == 0: + return string_len == 0 + pattern_index = 0 + string_index = 0 + while True: + if pattern_index == pattern_len: + return string_len == string_index + c = pattern[pattern_index] + pattern_index += 1 + if c == "?": + if string_index == string_len: + return False + if string[string_index] == "/": + return False + string_index += 1 + continue + if c == "*": + while (pattern_index != pattern_len) and (c == "*"): + c = pattern[pattern_index] + pattern_index += 1 + if pattern_index == pattern_len: + return string.find("/", string_index) == -1 + else: + if c == "/": + string_index = string.find("/", string_index) + if string_index == -1: + return False + else: + string_index += 1 + # General case, use recursion. + while string_index != string_len: + if glob_match(string[string_index:], pattern[pattern_index:]): + return True + if string[string_index] == "/": + break + string_index += 1 + continue + if c == "[": + if string_index == string_len: + return False + if string[string_index] == "/": + return False + pattern_index = range_match(pattern, pattern_index, string[string_index]) + if pattern_index == -1: + return False + string_index += 1 + continue + if c == "\\": + if pattern_index == pattern_len: + c = "\\" + else: + c = pattern[pattern_index] + pattern_index += 1 + # fall through + # other cases and c == "\\" + if string_index == string_len: + return False + else: + if c == string[string_index]: + string_index += 1 + else: + return False def glob_match_func(*args): diff --git a/requirements.txt b/requirements.txt index 4ff6dd17..2518a967 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -simpleeval >= 0.9.11 -wcmatch >= 8.1.2 \ No newline at end of file +simpleeval >= 0.9.11 \ No newline at end of file diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 2bfdbe86..7e11e834 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -123,7 +123,15 @@ def test_glob_match(self): self.assertFalse(util.glob_match_func("/foobar", "/foo")) self.assertTrue(util.glob_match_func("/foobar", "/foo*")) self.assertFalse(util.glob_match_func("/foobar", "/foo/*")) - + self.assertTrue(util.glob_match_func("/foo", "*/foo")) + self.assertTrue(util.glob_match_func("/foo", "*/foo*")) + self.assertFalse(util.glob_match_func("/foo", "*/foo/*")) + self.assertFalse(util.glob_match_func("/foo/bar", "*/foo")) + self.assertFalse(util.glob_match_func("/foo/bar", "*/foo*")) + self.assertTrue(util.glob_match_func("/foo/bar", "*/foo/*")) + self.assertFalse(util.glob_match_func("/foobar", "*/foo")) + self.assertTrue(util.glob_match_func("/foobar", "*/foo*")) + self.assertFalse(util.glob_match_func("/foobar", "*/foo/*")) self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo")) self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/foo", "*/foo/*")) @@ -133,7 +141,6 @@ def test_glob_match(self): self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo")) self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/foobar", "*/foo/*")) - self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foo", "*/foo/*")) @@ -144,17 +151,40 @@ def test_glob_match(self): self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo*")) self.assertFalse(util.glob_match_func("/prefix/subprefix/foobar", "*/foo/*")) - def test_glob_match2(self): - # add missing tests from Go to Python - self.assertFalse(util.glob_match_func("/foo", "*/foo")) # different from Go - self.assertFalse(util.glob_match_func("/foo", "*/foo*")) # different from Go - self.assertFalse(util.glob_match_func("/foo", "*/foo/*")) - self.assertFalse(util.glob_match_func("/foo/bar", "*/foo")) - self.assertFalse(util.glob_match_func("/foo/bar", "*/foo*")) - self.assertFalse(util.glob_match_func("/foo/bar", "*/foo/*")) # different from Go - self.assertFalse(util.glob_match_func("/foobar", "*/foo")) - self.assertFalse(util.glob_match_func("/foobar", "*/foo*")) # different from Go - self.assertFalse(util.glob_match_func("/foobar", "*/foo/*")) + self.assertTrue(util.glob_match_func("/f", "/?")) + self.assertTrue(util.glob_match_func("/foobar", "/foo?ar")) + self.assertFalse(util.glob_match_func("/fooar", "/foo?ar")) + self.assertTrue(util.glob_match_func("/foobbar", "/foo??ar")) + self.assertTrue(util.glob_match_func("/foobbbbar", "/foo????ar")) + self.assertTrue(util.glob_match_func("/foobar", "/foo[bc]ar")) + self.assertFalse(util.glob_match_func("/fooaar", "/foo[bc]ar")) + self.assertFalse(util.glob_match_func("/foodar", "/foo[bc]ar")) + self.assertTrue(util.glob_match_func("/foobar", "/foo[b-b]ar")) + self.assertFalse(util.glob_match_func("/fooaar", "/foo[b-c]ar")) + self.assertTrue(util.glob_match_func("/foobar", "/foo[b-c]ar")) + self.assertTrue(util.glob_match_func("/foocar", "/foo[b-c]ar")) + self.assertFalse(util.glob_match_func("/foodar", "/foo[b-c]ar")) + self.assertTrue(util.glob_match_func("/foo1ar", "/foo[!234]ar")) + self.assertFalse(util.glob_match_func("/foo3ar", "/foo[!234]ar")) + self.assertTrue(util.glob_match_func("/foo5ar", "/foo[!234]ar")) + self.assertTrue(util.glob_match_func("/foo1ar", "/foo[!2-5]ar")) + self.assertFalse(util.glob_match_func("/foo2ar", "/foo[!2-5]ar")) + self.assertTrue(util.glob_match_func("/foo1ar", "/foo[^234]ar")) + self.assertFalse(util.glob_match_func("/foo3ar", "/foo[^234]ar")) + self.assertTrue(util.glob_match_func("/foo5ar", "/foo[^234]ar")) + self.assertTrue(util.glob_match_func("/foo1ar", "/foo[^2-5]ar")) + self.assertFalse(util.glob_match_func("/foo2ar", "/foo[^2-5]ar")) + + self.assertTrue(util.glob_match_func("\\", "\\\\")) + self.assertTrue(util.glob_match_func("/a", "/\\a")) + self.assertTrue(util.glob_match_func("/*", "/\\*")) + self.assertFalse(util.glob_match_func("a", "\\?")) + self.assertTrue(util.glob_match_func("?", "\\?")) + self.assertTrue(util.glob_match_func("\n", "\n")) + self.assertFalse(util.glob_match_func("\n", "\\n")) + self.assertTrue(util.glob_match_func("[", "\\[")) + self.assertTrue(util.glob_match_func("*", "\\*")) + self.assertTrue(util.glob_match_func("\\*", "\\\\\\*")) def test_ip_match(self): self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.0/24")) From 99d508f3e23e319c0f788829d5e19e75b5cd40ae Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Apr 2022 04:10:44 +0000 Subject: [PATCH 231/349] chore(release): 1.16.3 [skip ci] ## [1.16.3](https://github.com/casbin/pycasbin/compare/v1.16.2...v1.16.3) (2022-04-30) ### Bug Fixes * different behavior of glob_match from other casbin ([#254](https://github.com/casbin/pycasbin/issues/254)) ([49b6a04](https://github.com/casbin/pycasbin/commit/49b6a0467ff4e84838a4b61ee79fe66af0e9de25)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b806ed4..09f846e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.3](https://github.com/casbin/pycasbin/compare/v1.16.2...v1.16.3) (2022-04-30) + + +### Bug Fixes + +* different behavior of glob_match from other casbin ([#254](https://github.com/casbin/pycasbin/issues/254)) ([49b6a04](https://github.com/casbin/pycasbin/commit/49b6a0467ff4e84838a4b61ee79fe66af0e9de25)) + ## [1.16.2](https://github.com/casbin/pycasbin/compare/v1.16.1...v1.16.2) (2022-04-30) diff --git a/setup.cfg b/setup.cfg index 7f12c0b8..1a0f6d9a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.2 +version = 1.16.3 From 80b66c8e641589a3c285423b6785772fb2556d59 Mon Sep 17 00:00:00 2001 From: cs1137195420 <46164668+cs1137195420@users.noreply.github.com> Date: Fri, 13 May 2022 10:08:16 +0800 Subject: [PATCH 232/349] fix: avoid raising exception in remove_grouping_policy (#257) * fix issue #250 * fix issue #250 --- casbin/management_enforcer.py | 6 +++--- casbin/rbac/default_role_manager/role_manager.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 0112aeb1..12480d77 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -275,7 +275,7 @@ def remove_named_grouping_policy(self, ptype, *params): rule_removed = self._remove_policy("g", ptype, list(params)) rules.append(list(params)) - if self.auto_build_role_links: + if self.auto_build_role_links and rule_removed: self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) return rule_removed @@ -283,7 +283,7 @@ def remove_named_grouping_policies(self, ptype, rules): """removes role inheritance rules from the current named policy.""" rules_removed = self._remove_policies("g", ptype, rules) - if self.auto_build_role_links: + if self.auto_build_role_links and rules_removed: self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) return rules_removed @@ -292,7 +292,7 @@ def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_value """removes a role inheritance rule from the current named policy, field filters can be specified.""" rule_removed = self._remove_filtered_policy_returns_effects("g", ptype, field_index, *field_values) - if self.auto_build_role_links: + if self.auto_build_role_links and rule_removed: self.model.build_incremental_role_links( self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rule_removed ) diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 5ccd23a8..e6f203d2 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -149,7 +149,7 @@ def add_link(self, name1, name2, *domain): def delete_link(self, name1, name2, *domain): if Link(name1, name2) not in self.all_links: - raise RuntimeError(f"error: link between {name1} and {name2} does not exist") + return self.all_links.remove(Link(name1, name2)) user = self._get_role(name1) From 86bae969331e7a5cb6efcb6b4538dae5c006f14e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 13 May 2022 02:12:13 +0000 Subject: [PATCH 233/349] chore(release): 1.16.4 [skip ci] ## [1.16.4](https://github.com/casbin/pycasbin/compare/v1.16.3...v1.16.4) (2022-05-13) ### Bug Fixes * avoid raising exception in remove_grouping_policy ([#257](https://github.com/casbin/pycasbin/issues/257)) ([39e17b4](https://github.com/casbin/pycasbin/commit/39e17b45cf8880a0db9e30f84def55801bf199d7)), closes [#250](https://github.com/casbin/pycasbin/issues/250) [#250](https://github.com/casbin/pycasbin/issues/250) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f846e9..b62f0376 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.4](https://github.com/casbin/pycasbin/compare/v1.16.3...v1.16.4) (2022-05-13) + + +### Bug Fixes + +* avoid raising exception in remove_grouping_policy ([#257](https://github.com/casbin/pycasbin/issues/257)) ([39e17b4](https://github.com/casbin/pycasbin/commit/39e17b45cf8880a0db9e30f84def55801bf199d7)), closes [#250](https://github.com/casbin/pycasbin/issues/250) [#250](https://github.com/casbin/pycasbin/issues/250) + ## [1.16.3](https://github.com/casbin/pycasbin/compare/v1.16.2...v1.16.3) (2022-04-30) diff --git a/setup.cfg b/setup.cfg index 1a0f6d9a..bad01b6a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.3 +version = 1.16.4 From 5d3468e33e6e58891c678c21efa25775224932f2 Mon Sep 17 00:00:00 2001 From: cs1137195420 <46164668+cs1137195420@users.noreply.github.com> Date: Fri, 20 May 2022 20:55:00 +0800 Subject: [PATCH 234/349] fix: avoid duplicate adapter entries (#259) * fix duplicate adapter entries * fix: duplicate adapter entries --- casbin/persist/adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 0d291b3b..9e4217f4 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -32,7 +32,7 @@ def load_policy_line(line, model): if key not in model.model[sec].keys(): return - model.model[sec][key].policy.append(tokens[1:]) + model.add_policy(sec, key, tokens[1:]) class Adapter: From 3844b785647d1e2a5eb277f74a233c8dd7ad72fe Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 20 May 2022 12:58:51 +0000 Subject: [PATCH 235/349] chore(release): 1.16.5 [skip ci] ## [1.16.5](https://github.com/casbin/pycasbin/compare/v1.16.4...v1.16.5) (2022-05-20) ### Bug Fixes * avoid duplicate adapter entries ([#259](https://github.com/casbin/pycasbin/issues/259)) ([8d74386](https://github.com/casbin/pycasbin/commit/8d7438687518f58ba945c822b36d1c9e5188316e)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b62f0376..701051cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.5](https://github.com/casbin/pycasbin/compare/v1.16.4...v1.16.5) (2022-05-20) + + +### Bug Fixes + +* avoid duplicate adapter entries ([#259](https://github.com/casbin/pycasbin/issues/259)) ([8d74386](https://github.com/casbin/pycasbin/commit/8d7438687518f58ba945c822b36d1c9e5188316e)) + ## [1.16.4](https://github.com/casbin/pycasbin/compare/v1.16.3...v1.16.4) (2022-05-13) diff --git a/setup.cfg b/setup.cfg index bad01b6a..8a9472e7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.4 +version = 1.16.5 From 42d8693c790f0b6e5fb49d1c779a97ab732af0ae Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Wed, 22 Jun 2022 15:53:57 +0800 Subject: [PATCH 236/349] fix: Set the default log level to warning (#261) * fix: update core_enforcer.py * fix: update core_enforcer.py --- casbin/core_enforcer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index ed4fd889..5f401eb3 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -52,6 +52,8 @@ class CoreEnforcer: def __init__(self, model=None, adapter=None): self.logger = logging.getLogger(__name__) + # if want to see more detail logs, change log level to info or debug + self.logger.setLevel(logging.WARNING) if isinstance(model, str): if isinstance(adapter, str): self.init_with_file(model, adapter) @@ -443,7 +445,8 @@ def enforce_ex(self, *rvals): if result: self.logger.info(req_str) else: - # leaving this in error for now, if it's very noise this can be changed to info or debug + # leaving this in error for now, if it's very noise this can be changed to info or debug, + # or change the log level self.logger.error(req_str) explain_rule = [] From 99e3e628fad7990f5f92df765d56fb47276eea29 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 22 Jun 2022 07:57:49 +0000 Subject: [PATCH 237/349] chore(release): 1.16.6 [skip ci] ## [1.16.6](https://github.com/casbin/pycasbin/compare/v1.16.5...v1.16.6) (2022-06-22) ### Bug Fixes * Set the default log level to warning ([#261](https://github.com/casbin/pycasbin/issues/261)) ([9a48e5d](https://github.com/casbin/pycasbin/commit/9a48e5d89a49c09fcd6bde445a1f11aaca1da5b9)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 701051cd..28710937 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.6](https://github.com/casbin/pycasbin/compare/v1.16.5...v1.16.6) (2022-06-22) + + +### Bug Fixes + +* Set the default log level to warning ([#261](https://github.com/casbin/pycasbin/issues/261)) ([9a48e5d](https://github.com/casbin/pycasbin/commit/9a48e5d89a49c09fcd6bde445a1f11aaca1da5b9)) + ## [1.16.5](https://github.com/casbin/pycasbin/compare/v1.16.4...v1.16.5) (2022-05-20) diff --git a/setup.cfg b/setup.cfg index 8a9472e7..3a3fc6dd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.5 +version = 1.16.6 From 3d6adf6ca95ed41463a552ac7572c7432fac2573 Mon Sep 17 00:00:00 2001 From: Yibo He <1137195420@qq.com> Date: Sun, 26 Jun 2022 14:21:04 +0800 Subject: [PATCH 238/349] fix: add import statement in tests module (#264) --- tests/__init__.py | 12 ++++++++++++ tests/benchmarks/__init__.py | 2 ++ tests/config/__init__.py | 2 ++ tests/model/__init__.py | 2 ++ tests/rbac/__init__.py | 2 ++ tests/util/__init__.py | 4 ++++ 6 files changed, 24 insertions(+) diff --git a/tests/__init__.py b/tests/__init__.py index dcfb1ee3..e4493206 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -11,3 +11,15 @@ # 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. + +from .test_distributed_api import TestDistributedApi +from .test_enforcer import * +from .test_filter import TestFilteredAdapter +from .test_frontend import TestFrontend +from .test_management_api import TestManagementApi, TestManagementApiSynced +from .test_rbac_api import TestRbacApi, TestRbacApiSynced +from . import benchmarks +from . import config +from . import model +from . import rbac +from . import util diff --git a/tests/benchmarks/__init__.py b/tests/benchmarks/__init__.py index dcfb1ee3..0a2216ff 100644 --- a/tests/benchmarks/__init__.py +++ b/tests/benchmarks/__init__.py @@ -11,3 +11,5 @@ # 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. + +from .benchmark_model import * diff --git a/tests/config/__init__.py b/tests/config/__init__.py index dcfb1ee3..abb6b12e 100644 --- a/tests/config/__init__.py +++ b/tests/config/__init__.py @@ -11,3 +11,5 @@ # 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. + +from .test_config import TestConfig diff --git a/tests/model/__init__.py b/tests/model/__init__.py index dcfb1ee3..528fed7a 100644 --- a/tests/model/__init__.py +++ b/tests/model/__init__.py @@ -11,3 +11,5 @@ # 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. + +from .test_policy import TestPolicy diff --git a/tests/rbac/__init__.py b/tests/rbac/__init__.py index dcfb1ee3..b0964285 100644 --- a/tests/rbac/__init__.py +++ b/tests/rbac/__init__.py @@ -11,3 +11,5 @@ # 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. + +from .test_role_manager import TestRoleManager, TestDomainManager diff --git a/tests/util/__init__.py b/tests/util/__init__.py index dcfb1ee3..9ff3711b 100644 --- a/tests/util/__init__.py +++ b/tests/util/__init__.py @@ -11,3 +11,7 @@ # 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. + +from .test_builtin_operators import TestBuiltinOperators +from .test_rwlock import TestRWLock +from .test_util import TestUtil From 2252d6e28e64be239f00b6c4351fd1d7b503f736 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 26 Jun 2022 06:24:34 +0000 Subject: [PATCH 239/349] chore(release): 1.16.7 [skip ci] ## [1.16.7](https://github.com/casbin/pycasbin/compare/v1.16.6...v1.16.7) (2022-06-26) ### Bug Fixes * add import statement in tests module ([#264](https://github.com/casbin/pycasbin/issues/264)) ([8d83b84](https://github.com/casbin/pycasbin/commit/8d83b8461af74b9dd39e2e9652921d969d735c7c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28710937..e3551c1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.7](https://github.com/casbin/pycasbin/compare/v1.16.6...v1.16.7) (2022-06-26) + + +### Bug Fixes + +* add import statement in tests module ([#264](https://github.com/casbin/pycasbin/issues/264)) ([8d83b84](https://github.com/casbin/pycasbin/commit/8d83b8461af74b9dd39e2e9652921d969d735c7c)) + ## [1.16.6](https://github.com/casbin/pycasbin/compare/v1.16.5...v1.16.6) (2022-06-22) diff --git a/setup.cfg b/setup.cfg index 3a3fc6dd..99b93b0e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.6 +version = 1.16.7 From 7b5a26952eb606e68b6a5cd03d5ea4d1a10d16a7 Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sun, 26 Jun 2022 14:34:42 +0800 Subject: [PATCH 240/349] fix: add dependency for benchmark (#262) --- requirements_dev.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 4efdb2ca..53cde028 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,2 +1,4 @@ -r requirements.txt -black==21.6b0 \ No newline at end of file +black==21.6b0 +pytest==7.0.1 +pytest-benchmark==3.4.1 \ No newline at end of file From d99dbe7e645ed0a41d5f14285f245c50135485e0 Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sun, 26 Jun 2022 14:38:13 +0800 Subject: [PATCH 241/349] fix: add more example of globmatch and add unit test for it (#263) --- examples/globmatch_model.conf | 11 +++++++++++ examples/globmatch_policy.csv | 5 +++++ tests/test_enforcer.py | 13 +++++++++++++ 3 files changed, 29 insertions(+) create mode 100644 examples/globmatch_model.conf create mode 100644 examples/globmatch_policy.csv diff --git a/examples/globmatch_model.conf b/examples/globmatch_model.conf new file mode 100644 index 00000000..fd3f1fe1 --- /dev/null +++ b/examples/globmatch_model.conf @@ -0,0 +1,11 @@ +[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 && globMatch(r.obj, p.obj) && regexMatch(r.act, p.act) \ No newline at end of file diff --git a/examples/globmatch_policy.csv b/examples/globmatch_policy.csv new file mode 100644 index 00000000..51144af5 --- /dev/null +++ b/examples/globmatch_policy.csv @@ -0,0 +1,5 @@ +p, alice, /alice_data/*, GET +p, alice, /alice_data/???, POST + +p, bob, /alice_data/[1-9], GET +p, bob, /bob_data/[!1-9], POST \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index c1591b4f..f056936b 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -147,6 +147,19 @@ def custom_function(key1, key2): self.assertFalse(e.enforce("alice", "/alice_data2/myid", "GET")) self.assertTrue(e.enforce("alice", "/alice_data2/myid/using/res_id", "GET")) + def test_enforce_glob_match(self): + e = self.get_enforcer( + get_examples("globmatch_model.conf"), + get_examples("globmatch_policy.csv"), + ) + + self.assertTrue(e.enforce("alice", "/alice_data/test_all", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data/123", "POST")) + self.assertTrue(e.enforce("bob", "/alice_data/1", "GET")) + self.assertFalse(e.enforce("bob", "/alice_data/0", "GET")) + self.assertTrue(e.enforce("bob", "/bob_data/0", "POST")) + self.assertFalse(e.enforce("bob", "/bob_data/1", "POST")) + def test_enforce_priority(self): e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) self.assertTrue(e.enforce("alice", "data1", "read")) From e8f67a01c25446347318abfd53f60c5f6d839caf Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 26 Jun 2022 06:42:22 +0000 Subject: [PATCH 242/349] chore(release): 1.16.8 [skip ci] ## [1.16.8](https://github.com/casbin/pycasbin/compare/v1.16.7...v1.16.8) (2022-06-26) ### Bug Fixes * add dependency for benchmark ([#262](https://github.com/casbin/pycasbin/issues/262)) ([4f3e71f](https://github.com/casbin/pycasbin/commit/4f3e71fadb17cf41381e3315b795a23e007bf23b)) * add more example of globmatch and add unit test for it ([#263](https://github.com/casbin/pycasbin/issues/263)) ([42d65d2](https://github.com/casbin/pycasbin/commit/42d65d2b74b22cc1b10b9dc96a8fc960e8af3f72)) --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3551c1e..3812d1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Semantic Versioning Changelog +## [1.16.8](https://github.com/casbin/pycasbin/compare/v1.16.7...v1.16.8) (2022-06-26) + + +### Bug Fixes + +* add dependency for benchmark ([#262](https://github.com/casbin/pycasbin/issues/262)) ([4f3e71f](https://github.com/casbin/pycasbin/commit/4f3e71fadb17cf41381e3315b795a23e007bf23b)) +* add more example of globmatch and add unit test for it ([#263](https://github.com/casbin/pycasbin/issues/263)) ([42d65d2](https://github.com/casbin/pycasbin/commit/42d65d2b74b22cc1b10b9dc96a8fc960e8af3f72)) + ## [1.16.7](https://github.com/casbin/pycasbin/compare/v1.16.6...v1.16.7) (2022-06-26) diff --git a/setup.cfg b/setup.cfg index 99b93b0e..e77c50a7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.7 +version = 1.16.8 From 7f285f181cee5d8e4bbb2f8045117be8c5fc1fbf Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sun, 10 Jul 2022 20:19:50 +0800 Subject: [PATCH 243/349] fix: add benchmark for glob match (#265) --- tests/benchmarks/benchmark_model.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/benchmarks/benchmark_model.py b/tests/benchmarks/benchmark_model.py index 7d65c98a..02895ff3 100644 --- a/tests/benchmarks/benchmark_model.py +++ b/tests/benchmarks/benchmark_model.py @@ -149,3 +149,11 @@ def test_benchmark_keymatch(benchmark): @benchmark def benchmark_keymatch(): e.enforce("alice", "/alice_data/resource1", "GET") + + +def test_benchmark_globmatch(benchmark): + e = get_enforcer(get_examples("globmatch_model.conf"), get_examples("globmatch_policy.csv")) + + @benchmark + def benchmark_globmatch(): + e.enforce("alice", "/alice_data/resource1", "GET") From 42df75c9c2b4e10a932d1946d066b9e9500e6ace Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 10 Jul 2022 12:23:09 +0000 Subject: [PATCH 244/349] chore(release): 1.16.9 [skip ci] ## [1.16.9](https://github.com/casbin/pycasbin/compare/v1.16.8...v1.16.9) (2022-07-10) ### Bug Fixes * add benchmark for glob match ([#265](https://github.com/casbin/pycasbin/issues/265)) ([0c6b09f](https://github.com/casbin/pycasbin/commit/0c6b09fbd342a29913ec0fda3550fc8271f287b3)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3812d1ec..03084e5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.9](https://github.com/casbin/pycasbin/compare/v1.16.8...v1.16.9) (2022-07-10) + + +### Bug Fixes + +* add benchmark for glob match ([#265](https://github.com/casbin/pycasbin/issues/265)) ([0c6b09f](https://github.com/casbin/pycasbin/commit/0c6b09fbd342a29913ec0fda3550fc8271f287b3)) + ## [1.16.8](https://github.com/casbin/pycasbin/compare/v1.16.7...v1.16.8) (2022-06-26) diff --git a/setup.cfg b/setup.cfg index e77c50a7..cc31d5c7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.8 +version = 1.16.9 From 5d949e471bf4c4f8f192868200a6ba1d260cf69d Mon Sep 17 00:00:00 2001 From: Ter Date: Mon, 11 Jul 2022 18:23:52 +0800 Subject: [PATCH 245/349] test: update the benchmark test cases (#266) --- .github/workflows/build.yml | 4 +- .../rbac_with_pattern_large_scale_model.conf | 14 + .../rbac_with_pattern_large_scale_policy.csv | 3768 +++++++++++++++++ tests/benchmarks/benchmark_management_api.py | 101 + tests/benchmarks/benchmark_model.py | 84 +- tests/benchmarks/benchmark_role_manager.py | 187 + 6 files changed, 4152 insertions(+), 6 deletions(-) create mode 100644 examples/performance/rbac_with_pattern_large_scale_model.conf create mode 100644 examples/performance/rbac_with_pattern_large_scale_policy.csv create mode 100644 tests/benchmarks/benchmark_management_api.py create mode 100644 tests/benchmarks/benchmark_role_manager.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 876f0471..bcc467b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,11 +34,13 @@ jobs: - name: Run tests run: coverage run -m unittest discover -s tests -t tests - - name: Run benchmark + - name: Run benchmark run: python3 -m pytest --benchmark-verbose --benchmark-columns=mean,stddev,iqr,ops,rounds tests/benchmarks/benchmark_model.py + tests/benchmarks/benchmark_management_api.py + tests/benchmarks/benchmark_role_manager.py - name: Upload coverage data to coveralls.io run: coveralls --service=github diff --git a/examples/performance/rbac_with_pattern_large_scale_model.conf b/examples/performance/rbac_with_pattern_large_scale_model.conf new file mode 100644 index 00000000..19c40522 --- /dev/null +++ b/examples/performance/rbac_with_pattern_large_scale_model.conf @@ -0,0 +1,14 @@ +[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) \ No newline at end of file diff --git a/examples/performance/rbac_with_pattern_large_scale_policy.csv b/examples/performance/rbac_with_pattern_large_scale_policy.csv new file mode 100644 index 00000000..5e865a7b --- /dev/null +++ b/examples/performance/rbac_with_pattern_large_scale_policy.csv @@ -0,0 +1,3768 @@ +# 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/site005 \ No newline at end of file diff --git a/tests/benchmarks/benchmark_management_api.py b/tests/benchmarks/benchmark_management_api.py new file mode 100644 index 00000000..f336ed88 --- /dev/null +++ b/tests/benchmarks/benchmark_management_api.py @@ -0,0 +1,101 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + +import os +import random + +import casbin +from casbin import util + + +def get_examples(path): + examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../../examples/" + return os.path.abspath(examples_path + path) + + +def get_enforcer(model=None, adapter=None): + return casbin.Enforcer( + model, + adapter, + ) + + +def _benchmark_has_policy(benchmark, user_num): + e = get_enforcer(get_examples("basic_model.conf")) + + e.add_policies({(f"user{i}", f"data{i // 10}" "read") for i in range(user_num)}) + + @benchmark + def run_benchmark(): + e.has_policy(f"user{random.randint(0, user_num)}", f"data{random.randint(0, user_num) // 10}", "read") + + +def test_benchmark_has_policy_small(benchmark): + _benchmark_has_policy(benchmark, 100) + + +def test_benchmark_has_policy_medium(benchmark): + _benchmark_has_policy(benchmark, 1000) + + +def test_benchmark_has_policy_large(benchmark): + _benchmark_has_policy(benchmark, 10000) + + +def _benchmark_add_policy(benchmark, user_num): + e = get_enforcer(get_examples("basic_model.conf")) + + e.add_policies({(f"user{i}", f"data{i // 10}" "read") for i in range(user_num)}) + + @benchmark + def run_benchmark(): + e.add_policy( + f"user{random.randint(0, user_num) + user_num}", + f"data{(random.randint(0, user_num) + user_num) // 10}", + "read", + ) + + +def test_benchmark_add_policy_small(benchmark): + _benchmark_add_policy(benchmark, 100) + + +def test_benchmark_add_policy_medium(benchmark): + _benchmark_add_policy(benchmark, 1000) + + +def test_benchmark_add_policy_large(benchmark): + _benchmark_add_policy(benchmark, 10000) + + +def _benchmark_remove_policy(benchmark, user_num): + e = get_enforcer(get_examples("basic_model.conf")) + + e.add_policies({(f"user{i}", f"data{i // 10}" "read") for i in range(user_num)}) + + @benchmark + def run_benchmark(): + e.remove_policy(f"user{random.randint(0, user_num)}", f"data{random.randint(0, user_num) // 10}", "read") + + +def test_benchmark_remove_policy_small(benchmark): + _benchmark_remove_policy(benchmark, 100) + + +def test_benchmark_remove_policy_medium(benchmark): + _benchmark_remove_policy(benchmark, 1000) + + +def test_benchmark_remove_policy_large(benchmark): + _benchmark_remove_policy(benchmark, 10000) diff --git a/tests/benchmarks/benchmark_model.py b/tests/benchmarks/benchmark_model.py index 02895ff3..d613567f 100644 --- a/tests/benchmarks/benchmark_model.py +++ b/tests/benchmarks/benchmark_model.py @@ -14,6 +14,7 @@ import os import casbin +from casbin import util def raw_enforce(sub, obj, act): @@ -59,6 +60,52 @@ def benchmark_rbac_model(): e.enforce("alice", "data1", "read") +def _benchmark_rbac_model_sizes(benchmark, roles, resources, users): + e = get_enforcer(get_examples("rbac_model.conf")) + + e.add_policies( + { + ("group-has-a-very-long-name-" + str(i), "data-has-a-very-long-name-" + str(i % resources), "read") + for i in range(roles) + } + ) + e.add_grouping_policies( + { + ("user-has-a-very-long-name-" + str(i), "group-has-a-very-long-name-" + str(i % roles), "read") + for i in range(users) + } + ) + + requests_num = 17 + enforce_requests = [] + for i in range(requests_num): + user_num = users // requests_num * i + role_num = user_num % roles + resource_num = role_num % resources + if i % 2 == 0: + resource_num = (resource_num + 1) % resources + enforce_requests.append( + (f"user-has-a-very-long-name-{user_num}", f"data-has-a-very-long-name-{resource_num}", "read") + ) + + @benchmark + def run_benchmark(): + for request in enforce_requests: + _ = e.enforce(*request) + + +def test_benchmark_rbac_model_sizes_small(benchmark): + _benchmark_rbac_model_sizes(benchmark, 100, 10, 1000) + + +def test_benchmark_rbac_model_sizes_medium(benchmark): + _benchmark_rbac_model_sizes(benchmark, 1000, 100, 10000) + + +def test_benchmark_rbac_model_sizes_large(benchmark): + _benchmark_rbac_model_sizes(benchmark, 10000, 1000, 100000) + + def test_benchmark_rbac_model_small(benchmark): e = get_enforcer(get_examples("rbac_model.conf")) @@ -124,6 +171,27 @@ def benchmark_abac_model(): e.enforce(sub, obj, "read") +def test_benchmark_abac_rule_model(benchmark): + e = get_enforcer(get_examples("abac_rule_model.conf")) + sub = {"Name": "alice", "Age": 18} + obj = {"Owner": "alice", "id": "data1"} + + e.add_policies({("r.sub.Age > 20", f"data{i}", "read") for i in range(1000)}) + + @benchmark + def benchmark_abac_rule_model(): + ok = e.enforce(sub, obj, "read") + assert not ok + + +def test_benchmark_key_match_model(benchmark): + e = get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) + + @benchmark + def benchmark_keymatch(): + e.enforce("alice", "/alice_data/resource1", "GET") + + def test_benchmark_rbac_with_deny(benchmark): e = get_enforcer( get_examples("rbac_with_deny_model.conf"), @@ -135,7 +203,7 @@ def benchmark_rbac_with_deny(): e.enforce("alice", "data1", "read") -def test_benchmark_prioriry(benchmark): +def test_benchmark_priority_model(benchmark): e = get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) @benchmark @@ -143,12 +211,18 @@ def benchmark_rbac_with_deny(): e.enforce("alice", "data1", "read") -def test_benchmark_keymatch(benchmark): - e = get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) +def test_benchmark_rbac_model_with_domains_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_matching_func("g", util.key_match4_func) + e.build_role_links() @benchmark - def benchmark_keymatch(): - e.enforce("alice", "/alice_data/resource1", "GET") + def run_benchmark(): + _ = e.enforce("staffUser1001", "/orgs/1/sites/site001", "App001.Module001.Action1001") def test_benchmark_globmatch(benchmark): diff --git a/tests/benchmarks/benchmark_role_manager.py b/tests/benchmarks/benchmark_role_manager.py new file mode 100644 index 00000000..1889a9fd --- /dev/null +++ b/tests/benchmarks/benchmark_role_manager.py @@ -0,0 +1,187 @@ +# Copyright 2022 The casbin Authors. All Rights Reserved. +# +# 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. + +import os +import casbin +from casbin.rbac.default_role_manager import RoleManager +from casbin import util + + +def get_examples(path): + examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../../examples/" + return os.path.abspath(os.path.join(examples_path, path)) + + +def get_enforcer(model=None, adapter=None): + return casbin.Enforcer( + model, + adapter, + ) + + +def test_benchmark_role_manager_small(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + rm: RoleManager = e.get_role_manager() + + e.enable_auto_build_role_links(False) + + p_policies = [] + for i in range(100): + p_policies.append([f"group{i}", f"data{i}", "read"]) + + _ = e.add_policies(p_policies) + + g_policies = [] + for i in range(1000): + g_policies.append([f"user{i}", f"group{i // 10}"]) + + _ = e.add_grouping_policies(g_policies) + + @benchmark + def run_benchmark(): + for i in range(100): + _ = rm.has_link("user501", f"group{i}") + + +def test_benchmark_role_manager_medium(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + rm: RoleManager = e.get_role_manager() + + e.enable_auto_build_role_links(False) + + p_policies = [] + for i in range(1000): + p_policies.append([f"group{i}", f"data{i}", "read"]) + + _ = e.add_policies(p_policies) + + g_policies = [] + for i in range(1000): + g_policies.append([f"user{i}", f"group{i // 10}"]) + + _ = e.add_grouping_policies(g_policies) + + e.build_role_links() + + @benchmark + def run_benchmark(): + for i in range(1000): + _ = rm.has_link("user501", f"group{i}") + + +def test_benchmark_role_manager_large(benchmark): + e = get_enforcer(get_examples("rbac_model.conf")) + rm: RoleManager = e.get_role_manager() + + e.enable_auto_build_role_links(False) + + p_policies = [] + for i in range(10000): + p_policies.append([f"group{i}", f"data{i}", "read"]) + + _ = e.add_policies(p_policies) + + g_policies = [] + for i in range(100000): + g_policies.append([f"user{i}", f"group{i // 10}"]) + + _ = e.add_grouping_policies(g_policies) + + @benchmark + def run_benchmark(): + for i in range(10000): + _ = rm.has_link("user501", f"group{i}") + + +def test_benchmark_build_role_links_with_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_matching_func("g", util.key_match4_func) + + @benchmark + def run_benchmark(): + _ = e.build_role_links() + + +def test_benchmark_build_role_links_with_domain_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_domain_matching_func("g", util.key_match4_func) + + @benchmark + def run_benchmark(): + _ = e.build_role_links() + + +def test_benchmark_build_role_links_with_pattern_and_domain_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_matching_func("g", util.key_match4_func) + e.add_named_domain_matching_func("g", util.key_match4_func) + + @benchmark + def run_benchmark(): + _ = e.build_role_links() + + +def test_benchmark_has_link_with_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_matching_func("g", util.key_match4_func) + rm: RoleManager = e.rm_map["g"] + + @benchmark + def run_benchmark(): + _ = rm.has_link("staffUser1001", "staff001", "/orgs/1/sites/site001") + + +def test_benchmark_has_link_with_domain_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_domain_matching_func("g", util.key_match4_func) + rm: RoleManager = e.rm_map["g"] + + @benchmark + def run_benchmark(): + _ = rm.has_link("staffUser1001", "staff001", "/orgs/1/sites/site001") + + +def test_benchmark_has_link_with_pattern_and_domain_pattern_large(benchmark): + e = get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + + e.add_named_matching_func("g", util.key_match4_func) + e.add_named_domain_matching_func("g", util.key_match4_func) + rm: RoleManager = e.rm_map["g"] + + @benchmark + def run_benchmark(): + _ = rm.has_link("staffUser1001", "staff001", "/orgs/1/sites/site001") From cc9ea5895ad3c2f3c897dc951b0cb645d5cee71f Mon Sep 17 00:00:00 2001 From: Yibo He <1137195420@qq.com> Date: Sat, 30 Jul 2022 15:03:21 +0800 Subject: [PATCH 246/349] fix: add key_get() for builtin_operators (#267) --- casbin/util/builtin_operators.py | 66 ++++++++++++++++++++- tests/util/test_builtin_operators.py | 88 ++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index adaf2d1a..1b09d1ef 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -16,7 +16,7 @@ import re KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") -KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+}(.*?)") +KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+?}(.*?)") KEY_MATCH4_PATTERN = re.compile(r"{([^/]+)}") @@ -42,6 +42,22 @@ def key_match_func(*args): return key_match(name1, name2) +def key_get(key1, key2): + """ + key_get returns the matched part + For example, "/foo/bar/foo" matches "/foo/*" + "bar/foo" will been returned + """ + i = key2.find("*") + if i == -1: + return "" + + if len(key1) > i: + if key1[:i] == key2[:i]: + return key1[i:] + return "" + + def key_match2(key1, key2): """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" @@ -63,6 +79,30 @@ def key_match2_func(*args): return key_match2(name1, name2) +def key_get2(key1, key2, path_var): + """ + key_get2 returns value matched pattern + For example, "/resource1" matches "/:resource" + if the pathVar == "resource", then "resource1" will be returned + """ + key2 = key2.replace("/*", "/.*") + + keys = re.findall(":[^/]+", key2) + key2 = KEY_MATCH2_PATTERN.sub(r"\g<1>([^\/]+)\g<2>", key2, 0) + + if key2 == "*": + key2 = "(.*)" + + key2 = "^" + key2 + "$" + values = re.match(key2, key1) + if values is None: + return "" + for i, key in enumerate(keys): + if path_var == key[1:]: + return values.groups()[i] + return "" + + def key_match3(key1, key2): """determines 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}" @@ -81,6 +121,30 @@ def key_match3_func(*args): return key_match3(name1, name2) +def key_get3(key1, key2, path_var): + """ + key_get3 returns value matched pattern + For example, "project/proj_project1_admin/" matches "project/proj_{project}_admin/" + if the pathVar == "project", then "project1" will be returned + """ + key2 = key2.replace("/*", "/.*") + + keys = re.findall(r"{[^/]+?}", key2) + key2 = KEY_MATCH3_PATTERN.sub(r"\g<1>([^/]+?)\g<2>", key2, 0) + + if key2 == "*": + key2 = "(.*)" + + key2 = "^" + key2 + "$" + values = re.match(key2, key1) + if values is None: + return "" + for i, key in enumerate(keys): + if path_var == key[1 : len(key) - 1]: + return values.groups()[i] + return "" + + def key_match4(key1: str, key2: str) -> bool: """ key_match4 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *. diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 7e11e834..2dacf0b2 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -31,6 +31,17 @@ def test_key_match(self): self.assertFalse(util.key_match2_func("/alice/all", "/:/all")) + def test_key_get(self): + self.assertEqual(util.key_get("/foo", "/foo"), "") + self.assertEqual(util.key_get("/foo", "/foo*"), "") + self.assertEqual(util.key_get("/foo", "/foo/*"), "") + self.assertEqual(util.key_get("/foo/bar", "/foo"), "") + self.assertEqual(util.key_get("/foo/bar", "/foo*"), "/bar") + self.assertEqual(util.key_get("/foo/bar", "/foo/*"), "bar") + self.assertEqual(util.key_get("/foobar", "/foo"), "") + self.assertEqual(util.key_get("/foobar", "/foo*"), "bar") + self.assertEqual(util.key_get("/foobar", "/foo/*"), "") + def test_key_match2(self): self.assertFalse(util.key_match2_func("/foo", "/")) self.assertTrue(util.key_match2_func("/foo", "/foo")) @@ -62,6 +73,38 @@ def test_key_match2(self): self.assertFalse(util.key_match2_func("/alice/all", "/:/all")) + def test_key_get2(self): + self.assertEqual(util.key_get2("/foo", "/foo", "id"), "") + self.assertEqual(util.key_get2("/foo", "/foo*", "id"), "") + self.assertEqual(util.key_get2("/foo", "/foo/*", "id"), "") + self.assertEqual(util.key_get2("/foo/bar", "/foo", "id"), "") + self.assertEqual(util.key_get2("/foo/bar", "/foo*", "id"), "") + self.assertEqual(util.key_get2("/foo/bar", "/foo/*", "id"), "") + self.assertEqual(util.key_get2("/foobar", "/foo", "id"), "") + self.assertEqual(util.key_get2("/foobar", "/foo*", "id"), "") + self.assertEqual(util.key_get2("/foobar", "/foo/*", "id"), "") + + self.assertEqual(util.key_get2("/", "/:resource", "resource"), "") + self.assertEqual(util.key_get2("/resource1", "/:resource", "resource"), "resource1") + self.assertEqual(util.key_get2("/myid", "/:id/using/:resId", "id"), "") + self.assertEqual(util.key_get2("/myid/using/myresid", "/:id/using/:resId", "id"), "myid") + self.assertEqual(util.key_get2("/myid/using/myresid", "/:id/using/:resId", "resId"), "myresid") + + self.assertEqual(util.key_get2("/proxy/myid", "/proxy/:id/*", "id"), "") + self.assertEqual(util.key_get2("/proxy/myid/", "/proxy/:id/*", "id"), "myid") + self.assertEqual(util.key_get2("/proxy/myid/res", "/proxy/:id/*", "id"), "myid") + self.assertEqual(util.key_get2("/proxy/myid/res/res2", "/proxy/:id/*", "id"), "myid") + self.assertEqual(util.key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/*", "id"), "myid") + self.assertEqual(util.key_get2("/proxy/myid/res/res2/res3", "/proxy/:id/res/*", "id"), "myid") + self.assertEqual(util.key_get2("/proxy/", "/proxy/:id/*", "id"), "") + + self.assertEqual(util.key_get2("/alice", "/:id", "id"), "alice") + self.assertEqual(util.key_get2("/alice/all", "/:id/all", "id"), "alice") + self.assertEqual(util.key_get2("/alice", "/:id/all", "id"), "") + self.assertEqual(util.key_get2("/alice/all", "/:id", "id"), "") + + self.assertEqual(util.key_get2("/alice/all", "/:/all", ""), "") + def test_key_match3(self): self.assertTrue(util.key_match3_func("/foo", "/foo")) self.assertTrue(util.key_match3_func("/foo", "/foo*")) @@ -87,6 +130,51 @@ def test_key_match3(self): self.assertFalse(util.key_match3_func("/myid/using/myresid", "/{id/using/{resId}")) + def test_key_get3(self): + self.assertEqual(util.key_get3("/foo", "/foo", "id"), "") + self.assertEqual(util.key_get3("/foo", "/foo*", "id"), "") + self.assertEqual(util.key_get3("/foo", "/foo/*", "id"), "") + self.assertEqual(util.key_get3("/foo/bar", "/foo", "id"), "") + self.assertEqual(util.key_get3("/foo/bar", "/foo*", "id"), "") + self.assertEqual(util.key_get3("/foo/bar", "/foo/*", "id"), "") + self.assertEqual(util.key_get3("/foobar", "/foo", "id"), "") + self.assertEqual(util.key_get3("/foobar", "/foo*", "id"), "") + self.assertEqual(util.key_get3("/foobar", "/foo/*", "id"), "") + + self.assertEqual(util.key_get3("/", "/{resource}", "resource"), "") + self.assertEqual(util.key_get3("/resource1", "/{resource}", "resource"), "resource1") + self.assertEqual(util.key_get3("/myid", "/{id}/using/{resId}", "id"), "") + self.assertEqual(util.key_get3("/myid/using/myresid", "/{id}/using/{resId}", "id"), "myid") + self.assertEqual(util.key_get3("/myid/using/myresid", "/{id}/using/{resId}", "resId"), "myresid") + + self.assertEqual(util.key_get3("/proxy/myid", "/proxy/{id}/*", "id"), "") + self.assertEqual(util.key_get3("/proxy/myid/", "/proxy/{id}/*", "id"), "myid") + self.assertEqual(util.key_get3("/proxy/myid/res", "/proxy/{id}/*", "id"), "myid") + self.assertEqual(util.key_get3("/proxy/myid/res/res2", "/proxy/{id}/*", "id"), "myid") + self.assertEqual(util.key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*", "id"), "myid") + self.assertEqual(util.key_get3("/proxy/", "/proxy/{id}/*", "id"), "") + + self.assertEqual( + util.key_get3("/api/group1_group_name/project1_admin/info", "/api/{proj}_admin/info", "proj"), "" + ) + self.assertEqual(util.key_get3("/{id/using/myresid", "/{id/using/{resId}", "resId"), "myresid") + self.assertEqual(util.key_get3("/{id/using/myresid/status}", "/{id/using/{resId}/status}", "resId"), "myresid") + + self.assertEqual(util.key_get3("/proxy/myid/res/res2/res3", "/proxy/{id}/*/{res}", "res"), "res3") + self.assertEqual(util.key_get3("/api/project1_admin/info", "/api/{proj}_admin/info", "proj"), "project1") + self.assertEqual( + util.key_get3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", "g"), + "group1", + ) + self.assertEqual( + util.key_get3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", "gn"), + "group_name", + ) + self.assertEqual( + util.key_get3("/api/group1_group_name/project1_admin/info", "/api/{g}_{gn}/{proj}_admin/info", "proj"), + "project1", + ) + def test_key_match4(self): self.assertTrue(util.key_match4_func("/parent/123/child/123", "/parent/{id}/child/{id}")) self.assertFalse(util.key_match4_func("/parent/123/child/456", "/parent/{id}/child/{id}")) From 66bcef435d236801f8739bc263fc92ccac1df9a6 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Jul 2022 07:08:04 +0000 Subject: [PATCH 247/349] chore(release): 1.16.10 [skip ci] ## [1.16.10](https://github.com/casbin/pycasbin/compare/v1.16.9...v1.16.10) (2022-07-30) ### Bug Fixes * add key_get() for builtin_operators ([#267](https://github.com/casbin/pycasbin/issues/267)) ([29d3e39](https://github.com/casbin/pycasbin/commit/29d3e3944953590eaea59a9a0ab042d097d09d97)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03084e5e..af9984ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.10](https://github.com/casbin/pycasbin/compare/v1.16.9...v1.16.10) (2022-07-30) + + +### Bug Fixes + +* add key_get() for builtin_operators ([#267](https://github.com/casbin/pycasbin/issues/267)) ([29d3e39](https://github.com/casbin/pycasbin/commit/29d3e3944953590eaea59a9a0ab042d097d09d97)) + ## [1.16.9](https://github.com/casbin/pycasbin/compare/v1.16.8...v1.16.9) (2022-07-10) diff --git a/setup.cfg b/setup.cfg index cc31d5c7..c0a6e8b3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.9 +version = 1.16.10 From f4928e68d69e9b135edf7e967281d60eb99d3e7f Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sat, 30 Jul 2022 16:09:04 +0800 Subject: [PATCH 248/349] fix: update support python version (#268) * fix: update support python version * Update build.yml Co-authored-by: Yang Luo --- .github/workflows/build.yml | 2 +- setup.py | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bcc467b8..8041c5ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: ['3.7', '3.8', '3.9', '3.10'] os: [ubuntu-18.04, macOS-latest, windows-latest] steps: diff --git a/setup.py b/setup.py index d13f3b9f..3dcc8f66 100644 --- a/setup.py +++ b/setup.py @@ -52,12 +52,11 @@ python_requires=">=3.3", license="Apache 2.0", classifiers=[ - "Programming Language :: Python :: 3.3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], From da4ecec513d7d5a2e346f48adbfb52506f6844b5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Jul 2022 08:12:28 +0000 Subject: [PATCH 249/349] chore(release): 1.16.11 [skip ci] ## [1.16.11](https://github.com/casbin/pycasbin/compare/v1.16.10...v1.16.11) (2022-07-30) ### Bug Fixes * update support python version ([#268](https://github.com/casbin/pycasbin/issues/268)) ([b4ddcbb](https://github.com/casbin/pycasbin/commit/b4ddcbb7a36896b231f589ea04271e52630e7bb2)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af9984ed..4c1be03e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.16.11](https://github.com/casbin/pycasbin/compare/v1.16.10...v1.16.11) (2022-07-30) + + +### Bug Fixes + +* update support python version ([#268](https://github.com/casbin/pycasbin/issues/268)) ([b4ddcbb](https://github.com/casbin/pycasbin/commit/b4ddcbb7a36896b231f589ea04271e52630e7bb2)) + ## [1.16.10](https://github.com/casbin/pycasbin/compare/v1.16.9...v1.16.10) (2022-07-30) diff --git a/setup.cfg b/setup.cfg index c0a6e8b3..d85c1e07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.10 +version = 1.16.11 From 2913bdad88abf4745c4646da2ef96bd44b9697f2 Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Mon, 8 Aug 2022 14:07:01 +0800 Subject: [PATCH 250/349] feat: add watcher-ex call (#269) --- casbin/core_enforcer.py | 5 ++++- casbin/internal_enforcer.py | 25 ++++++++++++++++++++----- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 5f401eb3..a4280678 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -263,7 +263,10 @@ def save_policy(self): self.adapter.save_policy(self.model) if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_save_policy", None)): + self.watcher.update_for_save_policy(self.model) + else: + self.watcher.update() def enable_enforce(self, enabled=True): """changes the enforcing state of Casbin, diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 48e49362..fb63b5d2 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -32,7 +32,10 @@ def _add_policy(self, sec, ptype, rule): return False if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_add_policy", None)): + self.watcher.update_for_add_policy(sec, ptype, rule) + else: + self.watcher.update() return rule_added @@ -50,7 +53,10 @@ def _add_policies(self, sec, ptype, rules): return False if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_add_policies", None)): + self.watcher.update_for_add_policies(sec, ptype, rules) + else: + self.watcher.update() return rules_added @@ -124,7 +130,10 @@ def _remove_policy(self, sec, ptype, rule): return False if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_remove_policy", None)): + self.watcher.update_for_remove_policy(sec, ptype, rule) + else: + self.watcher.update() return rule_removed @@ -142,7 +151,10 @@ def _remove_policies(self, sec, ptype, rules): return False if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_remove_policies", None)): + self.watcher.update_for_remove_policies(sec, ptype, rules) + else: + self.watcher.update() return rules_removed @@ -157,7 +169,10 @@ def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): return False if self.watcher: - self.watcher.update() + if callable(getattr(self.watcher, "update_for_remove_filtered_policy", None)): + self.watcher.update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) + else: + self.watcher.update() return rule_removed From 4a669150074bacbdf840379e29991636049ec19c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 8 Aug 2022 06:14:03 +0000 Subject: [PATCH 251/349] chore(release): 1.17.0 [skip ci] # [1.17.0](https://github.com/casbin/pycasbin/compare/v1.16.11...v1.17.0) (2022-08-08) ### Features * add watcher-ex call ([#269](https://github.com/casbin/pycasbin/issues/269)) ([f4bc0c0](https://github.com/casbin/pycasbin/commit/f4bc0c05b6578c6d08410410d7bc54dfd690e6e5)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c1be03e..5b93b2e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.17.0](https://github.com/casbin/pycasbin/compare/v1.16.11...v1.17.0) (2022-08-08) + + +### Features + +* add watcher-ex call ([#269](https://github.com/casbin/pycasbin/issues/269)) ([f4bc0c0](https://github.com/casbin/pycasbin/commit/f4bc0c05b6578c6d08410410d7bc54dfd690e6e5)) + ## [1.16.11](https://github.com/casbin/pycasbin/compare/v1.16.10...v1.16.11) (2022-07-30) diff --git a/setup.cfg b/setup.cfg index d85c1e07..c3626b42 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.16.11 +version = 1.17.0 From bc5318a7e52a925f3223d69922d1fbd413d65bad Mon Sep 17 00:00:00 2001 From: Nekotoxin Date: Sun, 28 Aug 2022 20:34:01 +0800 Subject: [PATCH 252/349] fix: some typos (#270) --- casbin/management_enforcer.py | 6 +++--- casbin/synced_enforcer.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index 12480d77..faacdf8e 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -213,7 +213,7 @@ def add_grouping_policy(self, *params): return self.add_named_grouping_policy("g", *params) def add_grouping_policies(self, rules): - """adds role inheritance rulea to the current policy. + """adds role inheritance rules to the current policy. If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule. @@ -241,7 +241,7 @@ def add_named_grouping_policy(self, ptype, *params): return rule_added def add_named_grouping_policies(self, ptype, rules): - """ "adds named role inheritance rules to the current policy. + """adds named role inheritance rules to the current policy. If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule.""" @@ -256,7 +256,7 @@ def remove_grouping_policy(self, *params): return self.remove_named_grouping_policy("g", *params) def remove_grouping_policies(self, rules): - """removes role inheritance rulea from the current policy.""" + """removes role inheritance rules from the current policy.""" return self.remove_named_grouping_policies("g", rules) def remove_filtered_grouping_policy(self, field_index, *field_values): diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 8c063d6e..59823184 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -587,7 +587,7 @@ def remove_named_policies(self, ptype, rules): return self._e.remove_named_policies(ptype, rules) def add_grouping_policies(self, rules): - """adds role inheritance rulea to the current policy. + """adds role inheritance rules to the current policy. If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. Otherwise the function returns true for the corresponding policy rule by adding the new rule. @@ -604,7 +604,7 @@ def add_named_grouping_policies(self, ptype, rules): return self._e.add_named_grouping_policies(ptype, rules) def remove_grouping_policies(self, rules): - """removes role inheritance rulea from the current policy.""" + """removes role inheritance rules from the current policy.""" with self._wl: return self._e.addremove_grouping_policies_policies(rules) From 8afbb61caf68458a0c2d8cb5d975a84bcb61a70d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 28 Aug 2022 12:37:38 +0000 Subject: [PATCH 253/349] chore(release): 1.17.1 [skip ci] ## [1.17.1](https://github.com/casbin/pycasbin/compare/v1.17.0...v1.17.1) (2022-08-28) ### Bug Fixes * some typos ([#270](https://github.com/casbin/pycasbin/issues/270)) ([7705e25](https://github.com/casbin/pycasbin/commit/7705e25fe33d59877d54116ad9855795ebaeaf51)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b93b2e5..609ae8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.1](https://github.com/casbin/pycasbin/compare/v1.17.0...v1.17.1) (2022-08-28) + + +### Bug Fixes + +* some typos ([#270](https://github.com/casbin/pycasbin/issues/270)) ([7705e25](https://github.com/casbin/pycasbin/commit/7705e25fe33d59877d54116ad9855795ebaeaf51)) + # [1.17.0](https://github.com/casbin/pycasbin/compare/v1.16.11...v1.17.0) (2022-08-08) diff --git a/setup.cfg b/setup.cfg index c3626b42..3f2f8a0a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.0 +version = 1.17.1 From f0afaa4d24ac97f60cd024e18b69028ed91709d9 Mon Sep 17 00:00:00 2001 From: stuartbeattie84 <62883733+stuartbeattie84@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:35:39 +0100 Subject: [PATCH 254/349] fix: catch exceptions in synced enforcer thread (#272) --- casbin/synced_enforcer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 59823184..c93b4f2b 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -55,7 +55,10 @@ def is_auto_loading_running(self): def _auto_load_policy(self, interval): while self.is_auto_loading_running(): time.sleep(interval) - self.load_policy() + try: + self.load_policy() + except Exception as e: + self.logger.error(str(e)) def start_auto_load_policy(self, interval): """starts a thread that will call load_policy every interval seconds""" From e8c2639db4b0374db781be6379aa3065fd638710 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 20 Oct 2022 15:39:53 +0000 Subject: [PATCH 255/349] chore(release): 1.17.2 [skip ci] ## [1.17.2](https://github.com/casbin/pycasbin/compare/v1.17.1...v1.17.2) (2022-10-20) ### Bug Fixes * catch exceptions in synced enforcer thread ([#272](https://github.com/casbin/pycasbin/issues/272)) ([ff35627](https://github.com/casbin/pycasbin/commit/ff3562764343f08030fe13fcd884bbe169945b87)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 609ae8e7..139898ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.2](https://github.com/casbin/pycasbin/compare/v1.17.1...v1.17.2) (2022-10-20) + + +### Bug Fixes + +* catch exceptions in synced enforcer thread ([#272](https://github.com/casbin/pycasbin/issues/272)) ([ff35627](https://github.com/casbin/pycasbin/commit/ff3562764343f08030fe13fcd884bbe169945b87)) + ## [1.17.1](https://github.com/casbin/pycasbin/compare/v1.17.0...v1.17.1) (2022-08-28) diff --git a/setup.cfg b/setup.cfg index 3f2f8a0a..627488d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.1 +version = 1.17.2 From d633b1fa3aa25c0457859267151d48d0e936e474 Mon Sep 17 00:00:00 2001 From: Xhy-5000 <45428960+Xhy-5000@users.noreply.github.com> Date: Tue, 1 Nov 2022 01:11:13 -0400 Subject: [PATCH 256/349] fix: load_policy changed the old model (#277) --- casbin/core_enforcer.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index a4280678..f9531438 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -194,18 +194,18 @@ def init_rm_map(self): def load_policy(self): """reloads the policy from file/database.""" need_to_rebuild = False - new_model = copy.copy(self.model) + new_model = copy.deepcopy(self.model) new_model.clear_policy() try: self.adapter.load_policy(new_model) - self.model.sort_policies_by_subject_hierarchy() + new_model.sort_policies_by_subject_hierarchy() new_model.sort_policies_by_priority() - self.model.print_policy() + new_model.print_policy() if self.auto_build_role_links: @@ -214,7 +214,6 @@ def load_policy(self): rm.clear() new_model.build_role_links(self.rm_map) - self.build_role_links() self.model = new_model From 3c445f864fb668e2902195cd94a8eb5949b7d867 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 1 Nov 2022 05:15:16 +0000 Subject: [PATCH 257/349] chore(release): 1.17.3 [skip ci] ## [1.17.3](https://github.com/casbin/pycasbin/compare/v1.17.2...v1.17.3) (2022-11-01) ### Bug Fixes * load_policy changed the old model ([#277](https://github.com/casbin/pycasbin/issues/277)) ([f247231](https://github.com/casbin/pycasbin/commit/f247231f1f18aae87773c02739d0b15fb9164c0a)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 139898ee..9634ea19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.3](https://github.com/casbin/pycasbin/compare/v1.17.2...v1.17.3) (2022-11-01) + + +### Bug Fixes + +* load_policy changed the old model ([#277](https://github.com/casbin/pycasbin/issues/277)) ([f247231](https://github.com/casbin/pycasbin/commit/f247231f1f18aae87773c02739d0b15fb9164c0a)) + ## [1.17.2](https://github.com/casbin/pycasbin/compare/v1.17.1...v1.17.2) (2022-10-20) diff --git a/setup.cfg b/setup.cfg index 627488d1..44bd9369 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.2 +version = 1.17.3 From d372c9e39555f55c41b35237afe1204861bbd462 Mon Sep 17 00:00:00 2001 From: stuartbeattie84 <62883733+stuartbeattie84@users.noreply.github.com> Date: Tue, 1 Nov 2022 13:53:18 +0000 Subject: [PATCH 258/349] fix: fix bug in autoloader error handling (#275) * Update synced_enforcer.py * Fixed bug in autoloader Fixed a bug when autoloader attempts to log an exeption - SyncedEnforcer doesn't have a logger of its own, needs to use its Enforcer's logger. --- casbin/synced_enforcer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index c93b4f2b..b1bc6085 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -58,7 +58,7 @@ def _auto_load_policy(self, interval): try: self.load_policy() except Exception as e: - self.logger.error(str(e)) + self._e.logger.error(repr(e)) def start_auto_load_policy(self, interval): """starts a thread that will call load_policy every interval seconds""" From 015d7cc1ef50ecd2a0beaa13e29a915d991fbeae Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 1 Nov 2022 13:57:29 +0000 Subject: [PATCH 259/349] chore(release): 1.17.4 [skip ci] ## [1.17.4](https://github.com/casbin/pycasbin/compare/v1.17.3...v1.17.4) (2022-11-01) ### Bug Fixes * fix bug in autoloader error handling ([#275](https://github.com/casbin/pycasbin/issues/275)) ([e6f03ce](https://github.com/casbin/pycasbin/commit/e6f03ce165d447534dcd2149928115eb5afed51e)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9634ea19..5ec91254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.4](https://github.com/casbin/pycasbin/compare/v1.17.3...v1.17.4) (2022-11-01) + + +### Bug Fixes + +* fix bug in autoloader error handling ([#275](https://github.com/casbin/pycasbin/issues/275)) ([e6f03ce](https://github.com/casbin/pycasbin/commit/e6f03ce165d447534dcd2149928115eb5afed51e)) + ## [1.17.3](https://github.com/casbin/pycasbin/compare/v1.17.2...v1.17.3) (2022-11-01) diff --git a/setup.cfg b/setup.cfg index 44bd9369..2fd443cb 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.3 +version = 1.17.4 From 6bebebd89052a6e90fb54e1107521003f07c1ed4 Mon Sep 17 00:00:00 2001 From: Zeeland <287017217@qq.com> Date: Mon, 21 Nov 2022 00:36:57 +0800 Subject: [PATCH 260/349] docs: click Persistence jump to related catalogue (#279) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e38b9df4..dc0a474c 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ import casbin e = casbin.Enforcer("path/to/model.conf", "path/to/policy.csv") ``` -Note: you can also initialize an enforcer with policy in DB instead of file, see [Persistence](#persistence) section for details. +Note: you can also initialize an enforcer with policy in DB instead of file, see [Policy persistence](#policy-persistence) section for details. 2. Add an enforcement hook into your code right before the access happens: From edbfa8fdb9dd9a47d621e7faaac8df0249923d17 Mon Sep 17 00:00:00 2001 From: r4wand <26229485+r4wand@users.noreply.github.com> Date: Thu, 1 Dec 2022 05:27:57 +0300 Subject: [PATCH 261/349] docs: updated broken links (#281) * docs: updated broken links * Update README.md Co-authored-by: hsluoyz --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dc0a474c..01c7883b 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ pip install casbin ## Documentation -https://casbin.org/docs/en/overview +https://casbin.org/docs/overview ## Online editor @@ -152,7 +152,7 @@ You can also use the online editor (http://casbin.org/editor/) to write your Cas ## Tutorials -https://casbin.org/docs/en/tutorials +https://casbin.org/docs/tutorials ## Get started @@ -205,15 +205,15 @@ We also provide a web-based UI for model management and policy management: ## Policy persistence -https://casbin.org/docs/en/adapters +https://casbin.org/docs/adapters ## Role manager -https://casbin.org/docs/en/role-managers +https://casbin.org/docs/role-managers ## Benchmarks -https://casbin.org/docs/en/benchmark +https://casbin.org/docs/benchmark ## Examples @@ -233,11 +233,11 @@ Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/ex ## Middlewares -Authz middlewares for web frameworks: https://casbin.org/docs/en/middlewares +Authz middlewares for web frameworks: https://casbin.org/docs/middlewares ## Our adopters -https://casbin.org/docs/en/adopters +https://casbin.org/docs/adopters ## Contributors From b5630438e55470e61640adb2f1cc32e9bc0b28a2 Mon Sep 17 00:00:00 2001 From: Zeeland <287017217@qq.com> Date: Mon, 5 Dec 2022 14:55:34 +0800 Subject: [PATCH 262/349] fix: polish import grammar and remove some useless package (#282) * fix: polish import grammar and remove some useless package * fix: remove a blank line --- casbin/core_enforcer.py | 3 ++- casbin/internal_enforcer.py | 1 - casbin/persist/adapters/adapter_filtered.py | 1 - tests/benchmarks/benchmark_management_api.py | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index f9531438..64e5d8e7 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, copy +import logging +import copy from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index fb63b5d2..66264b7a 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -13,7 +13,6 @@ # limitations under the License. from casbin.core_enforcer import CoreEnforcer -from casbin.model.policy_op import PolicyOp class InternalEnforcer(CoreEnforcer): diff --git a/casbin/persist/adapters/adapter_filtered.py b/casbin/persist/adapters/adapter_filtered.py index 571ee7d1..0fa85820 100644 --- a/casbin/persist/adapters/adapter_filtered.py +++ b/casbin/persist/adapters/adapter_filtered.py @@ -13,7 +13,6 @@ # limitations under the License. from casbin import persist -from casbin import model from .file_adapter import FileAdapter import os diff --git a/tests/benchmarks/benchmark_management_api.py b/tests/benchmarks/benchmark_management_api.py index f336ed88..2a89ee2d 100644 --- a/tests/benchmarks/benchmark_management_api.py +++ b/tests/benchmarks/benchmark_management_api.py @@ -16,7 +16,6 @@ import random import casbin -from casbin import util def get_examples(path): From 1b8ee3d95d059f6a4ed3978b64c5ea8c4e670c59 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 5 Dec 2022 06:59:25 +0000 Subject: [PATCH 263/349] chore(release): 1.17.5 [skip ci] ## [1.17.5](https://github.com/casbin/pycasbin/compare/v1.17.4...v1.17.5) (2022-12-05) ### Bug Fixes * polish import grammar and remove some useless package ([#282](https://github.com/casbin/pycasbin/issues/282)) ([be53e0b](https://github.com/casbin/pycasbin/commit/be53e0b9e0d8863919de4cdd5f0185f5eea0779d)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec91254..fec0aa6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.5](https://github.com/casbin/pycasbin/compare/v1.17.4...v1.17.5) (2022-12-05) + + +### Bug Fixes + +* polish import grammar and remove some useless package ([#282](https://github.com/casbin/pycasbin/issues/282)) ([be53e0b](https://github.com/casbin/pycasbin/commit/be53e0b9e0d8863919de4cdd5f0185f5eea0779d)) + ## [1.17.4](https://github.com/casbin/pycasbin/compare/v1.17.3...v1.17.4) (2022-11-01) diff --git a/setup.cfg b/setup.cfg index 2fd443cb..4b0954cd 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.4 +version = 1.17.5 From d1a625975ababaa114705f32e4997e3ad3b822b5 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Thu, 9 Feb 2023 19:21:53 +0800 Subject: [PATCH 264/349] Fix CI node-version to 18 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8041c5ad..e3f40f20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -94,7 +94,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v2 with: - node-version: '16' + node-version: '18' - name: Setup run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi From 5ef8f2f0097284dc8dda98d66b6b22267aab2fb5 Mon Sep 17 00:00:00 2001 From: Guilhem Villemin Date: Thu, 9 Feb 2023 12:25:52 +0100 Subject: [PATCH 265/349] fix: fix typo in remove_grouping_policies() API (#287) --- casbin/synced_enforcer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index b1bc6085..5e981e7d 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -609,7 +609,7 @@ def add_named_grouping_policies(self, ptype, rules): def remove_grouping_policies(self, rules): """removes role inheritance rules from the current policy.""" with self._wl: - return self._e.addremove_grouping_policies_policies(rules) + return self._e.remove_grouping_policies(rules) def remove_named_grouping_policies(self, ptype, rules): """removes role inheritance rules from the current named policy.""" From f0e46df4d3131d36744ad0627dadac1ab88bb677 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 9 Feb 2023 11:31:28 +0000 Subject: [PATCH 266/349] chore(release): 1.17.6 [skip ci] ## [1.17.6](https://github.com/casbin/pycasbin/compare/v1.17.5...v1.17.6) (2023-02-09) ### Bug Fixes * fix typo in remove_grouping_policies() API ([#287](https://github.com/casbin/pycasbin/issues/287)) ([747f71d](https://github.com/casbin/pycasbin/commit/747f71d2f629affa4aa0d15115e5cb61b4f4b732)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fec0aa6f..3746b699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.17.6](https://github.com/casbin/pycasbin/compare/v1.17.5...v1.17.6) (2023-02-09) + + +### Bug Fixes + +* fix typo in remove_grouping_policies() API ([#287](https://github.com/casbin/pycasbin/issues/287)) ([747f71d](https://github.com/casbin/pycasbin/commit/747f71d2f629affa4aa0d15115e5cb61b4f4b732)) + ## [1.17.5](https://github.com/casbin/pycasbin/compare/v1.17.4...v1.17.5) (2022-12-05) diff --git a/setup.cfg b/setup.cfg index 4b0954cd..f5712376 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.5 +version = 1.17.6 From f5bdad681931b34ca6687ce9dcdba096c569ed78 Mon Sep 17 00:00:00 2001 From: hsluoyz Date: Tue, 7 Mar 2023 20:55:17 +0800 Subject: [PATCH 267/349] feat: revert "fix: avoid duplicate adapter entries (#259)" (#290) This reverts commit 8d7438687518f58ba945c822b36d1c9e5188316e. --- casbin/persist/adapter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 9e4217f4..0d291b3b 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -32,7 +32,7 @@ def load_policy_line(line, model): if key not in model.model[sec].keys(): return - model.add_policy(sec, key, tokens[1:]) + model.model[sec][key].policy.append(tokens[1:]) class Adapter: From 335ed21eda1aa969951d5240422ba2d3e33e27c4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 7 Mar 2023 13:00:38 +0000 Subject: [PATCH 268/349] chore(release): 1.18.0 [skip ci] # [1.18.0](https://github.com/casbin/pycasbin/compare/v1.17.6...v1.18.0) (2023-03-07) ### Features * revert "fix: avoid duplicate adapter entries ([#259](https://github.com/casbin/pycasbin/issues/259))" ([#290](https://github.com/casbin/pycasbin/issues/290)) ([b573041](https://github.com/casbin/pycasbin/commit/b573041270acbc9e217ee673792b31e74239b7c7)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3746b699..b31bbb15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.18.0](https://github.com/casbin/pycasbin/compare/v1.17.6...v1.18.0) (2023-03-07) + + +### Features + +* revert "fix: avoid duplicate adapter entries ([#259](https://github.com/casbin/pycasbin/issues/259))" ([#290](https://github.com/casbin/pycasbin/issues/290)) ([b573041](https://github.com/casbin/pycasbin/commit/b573041270acbc9e217ee673792b31e74239b7c7)) + ## [1.17.6](https://github.com/casbin/pycasbin/compare/v1.17.5...v1.17.6) (2023-02-09) diff --git a/setup.cfg b/setup.cfg index f5712376..614b79a5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.17.6 +version = 1.18.0 From 3a62a0886d00e6822e05bef46fcab2429c95a0f0 Mon Sep 17 00:00:00 2001 From: Terry Gao Date: Sat, 1 Apr 2023 17:55:20 +0800 Subject: [PATCH 269/349] fix: support escaping comma inside quote in load_policy_line()'s CSV parsing (#292) Signed-off-by: terry-xuan-gao --- casbin/persist/adapter.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 0d291b3b..b07b2141 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -22,7 +22,25 @@ def load_policy_line(line, model): if line[:1] == "#": return - tokens = [token.strip() for token in line.split(",")] + stack = [] + tokens = [] + for c in line: + if c == "[": + stack.append(c) + tokens[-1] += "[" + elif c == "]": + stack.pop() + tokens[-1] += "]" + elif c == "," and len(stack) == 0: + tokens.append("") + else: + if len(tokens) == 0: + tokens.append(c) + else: + tokens[-1] += c + + tokens = [x.strip() for x in tokens] + key = tokens[0] sec = key[0] From f12359d2cdba9332329ba6aebcf535cb26abf2dd Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 1 Apr 2023 09:58:55 +0000 Subject: [PATCH 270/349] chore(release): 1.18.1 [skip ci] ## [1.18.1](https://github.com/casbin/pycasbin/compare/v1.18.0...v1.18.1) (2023-04-01) ### Bug Fixes * support escaping comma inside quote in load_policy_line()'s CSV parsing ([#292](https://github.com/casbin/pycasbin/issues/292)) ([51e6e7c](https://github.com/casbin/pycasbin/commit/51e6e7c34d8ebcef666d34a6f10f457bfc0c31d0)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b31bbb15..f50f6eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.18.1](https://github.com/casbin/pycasbin/compare/v1.18.0...v1.18.1) (2023-04-01) + + +### Bug Fixes + +* support escaping comma inside quote in load_policy_line()'s CSV parsing ([#292](https://github.com/casbin/pycasbin/issues/292)) ([51e6e7c](https://github.com/casbin/pycasbin/commit/51e6e7c34d8ebcef666d34a6f10f457bfc0c31d0)) + # [1.18.0](https://github.com/casbin/pycasbin/compare/v1.17.6...v1.18.0) (2023-03-07) diff --git a/setup.cfg b/setup.cfg index 614b79a5..42040816 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.18.0 +version = 1.18.1 From 306f873ef343de8a142ad3c8badec9505d8ae4e4 Mon Sep 17 00:00:00 2001 From: Daniel Flynn Date: Thu, 6 Apr 2023 12:08:22 -0500 Subject: [PATCH 271/349] fix: load policy rule without splitting custom functions (#293) * fix: load policy rule without splitting custom functions * fix: Update adapter.py * build: update python and os for tests * fix: update CI --------- Co-authored-by: hsluoyz --- .github/workflows/build.yml | 4 ++-- casbin/persist/adapter.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e3f40f20..4114fa08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,8 +11,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.7', '3.8', '3.9', '3.10'] - os: [ubuntu-18.04, macOS-latest, windows-latest] + python-version: ['3.9', '3.10', '3.11'] + os: [ubuntu-latest, macOS-latest, windows-latest] steps: - name: Checkout diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index b07b2141..2c9b9a38 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -25,12 +25,12 @@ def load_policy_line(line, model): stack = [] tokens = [] for c in line: - if c == "[": + if c == "[" or c == "(": stack.append(c) - tokens[-1] += "[" - elif c == "]": + tokens[-1] += c + elif c == "]" or c == ")": stack.pop() - tokens[-1] += "]" + tokens[-1] += c elif c == "," and len(stack) == 0: tokens.append("") else: From f063cbc670c68957214b630e23b5d6968e15299a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 6 Apr 2023 17:12:42 +0000 Subject: [PATCH 272/349] chore(release): 1.18.2 [skip ci] ## [1.18.2](https://github.com/casbin/pycasbin/compare/v1.18.1...v1.18.2) (2023-04-06) ### Bug Fixes * load policy rule without splitting custom functions ([#293](https://github.com/casbin/pycasbin/issues/293)) ([fbc4261](https://github.com/casbin/pycasbin/commit/fbc42616620fcbe089cfcf459ec9eaa441fc33e1)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f50f6eaa..5da39226 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.18.2](https://github.com/casbin/pycasbin/compare/v1.18.1...v1.18.2) (2023-04-06) + + +### Bug Fixes + +* load policy rule without splitting custom functions ([#293](https://github.com/casbin/pycasbin/issues/293)) ([fbc4261](https://github.com/casbin/pycasbin/commit/fbc42616620fcbe089cfcf459ec9eaa441fc33e1)) + ## [1.18.1](https://github.com/casbin/pycasbin/compare/v1.18.0...v1.18.1) (2023-04-01) diff --git a/setup.cfg b/setup.cfg index 42040816..344b829a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.18.1 +version = 1.18.2 From 44e4cec6e12e5a5500033ed697896531d394d86b Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 22 May 2023 16:40:04 +1000 Subject: [PATCH 273/349] fix: Exclude tests from package (#297) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3dcc8f66..8353aade 100644 --- a/setup.py +++ b/setup.py @@ -47,7 +47,7 @@ "access control", "permission", ], - packages=setuptools.find_packages(exclude=("tests",)), + packages=setuptools.find_packages(exclude=("tests", "tests.*")), install_requires=install_requires, python_requires=">=3.3", license="Apache 2.0", From 5b48029861a488e0ca4a65bc6f92ea270b6462e0 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Mon, 22 May 2023 16:41:25 +1000 Subject: [PATCH 274/349] fix: Stop including README as top-level data file in package (#296) --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 8353aade..c81fcaac 100644 --- a/setup.py +++ b/setup.py @@ -60,5 +60,4 @@ "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ], - data_files=[desc_file], ) From b25c96b71b9eb0660fdc88a478e25eacc4bf0840 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 22 May 2023 06:46:18 +0000 Subject: [PATCH 275/349] chore(release): 1.18.3 [skip ci] ## [1.18.3](https://github.com/casbin/pycasbin/compare/v1.18.2...v1.18.3) (2023-05-22) ### Bug Fixes * Exclude tests from package ([#297](https://github.com/casbin/pycasbin/issues/297)) ([9b014a2](https://github.com/casbin/pycasbin/commit/9b014a226058947424f87408ea14176f20183031)) * Stop including README as top-level data file in package ([#296](https://github.com/casbin/pycasbin/issues/296)) ([e85e9b9](https://github.com/casbin/pycasbin/commit/e85e9b91aca3007fc4aab292689302eaae27cf02)) --- CHANGELOG.md | 8 ++++++++ setup.cfg | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5da39226..7e6e6d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Semantic Versioning Changelog +## [1.18.3](https://github.com/casbin/pycasbin/compare/v1.18.2...v1.18.3) (2023-05-22) + + +### Bug Fixes + +* Exclude tests from package ([#297](https://github.com/casbin/pycasbin/issues/297)) ([9b014a2](https://github.com/casbin/pycasbin/commit/9b014a226058947424f87408ea14176f20183031)) +* Stop including README as top-level data file in package ([#296](https://github.com/casbin/pycasbin/issues/296)) ([e85e9b9](https://github.com/casbin/pycasbin/commit/e85e9b91aca3007fc4aab292689302eaae27cf02)) + ## [1.18.2](https://github.com/casbin/pycasbin/compare/v1.18.1...v1.18.2) (2023-04-06) diff --git a/setup.cfg b/setup.cfg index 344b829a..6d8504b7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.18.2 +version = 1.18.3 From 5ad87442b55e062e0a89222b64463a7d441d0cc3 Mon Sep 17 00:00:00 2001 From: BustDot Date: Wed, 24 May 2023 16:52:12 +0800 Subject: [PATCH 276/349] feat: Add batch_enforce() API (#298) * feat: Add batch_enforce() API * feat: Add batch_enforce() API * feat: Add batch_enforce() API * Update core_enforcer.py --------- Co-authored-by: hsluoyz --- casbin/core_enforcer.py | 8 ++++++++ casbin/synced_enforcer.py | 7 +++++++ tests/test_enforcer.py | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 64e5d8e7..d2955efa 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -458,6 +458,14 @@ def enforce_ex(self, *rvals): return result, explain_rule + def batch_enforce(self, rvals): + """batch_enforce enforce in batches""" + results = [] + for request in rvals: + result = self.enforce(*request) + results.append(result) + return results + @staticmethod def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 5e981e7d..40f008d4 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -158,6 +158,13 @@ def enforce_ex(self, *rvals): with self._rl: return self._e.enforce_ex(*rvals) + def batch_enforce(self, rvals): + """batch_enforce enforce in batches, + input parameters are usually: [(sub, obj, act), (sub, obj, act), ...]. + """ + with self._rl: + return self._e.batch_enforce(rvals) + def get_all_subjects(self): """gets the list of subjects that show up in the current policy.""" with self._rl: diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index f056936b..cac51b3b 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -60,6 +60,24 @@ def test_enforce_ex_basic(self): self.assertTupleEqual(e.enforce_ex("bob", "data2", "write"), (True, ["bob", "data2", "write"])) self.assertTupleEqual(e.enforce_ex("bob", "data1", "write"), (False, [])) + def test_batch_enforce(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + results = [True, False, True, False] + self.assertEqual( + e.batch_enforce( + [ + ("alice", "data1", "read"), + ("alice", "data2", "read"), + ("bob", "data2", "write"), + ("bob", "data1", "write"), + ] + ), + results, + ) + def test_model_set_load(self): e = self.get_enforcer( get_examples("basic_model.conf"), From 3fbf3803a50c56d548745f8a0460513c76ef8814 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 24 May 2023 08:56:37 +0000 Subject: [PATCH 277/349] chore(release): 1.19.0 [skip ci] # [1.19.0](https://github.com/casbin/pycasbin/compare/v1.18.3...v1.19.0) (2023-05-24) ### Features * Add batch_enforce() API ([#298](https://github.com/casbin/pycasbin/issues/298)) ([361af9b](https://github.com/casbin/pycasbin/commit/361af9bc76492b975d454d9f479b3703248fc509)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e6e6d47..4502148c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.19.0](https://github.com/casbin/pycasbin/compare/v1.18.3...v1.19.0) (2023-05-24) + + +### Features + +* Add batch_enforce() API ([#298](https://github.com/casbin/pycasbin/issues/298)) ([361af9b](https://github.com/casbin/pycasbin/commit/361af9bc76492b975d454d9f479b3703248fc509)) + ## [1.18.3](https://github.com/casbin/pycasbin/compare/v1.18.2...v1.18.3) (2023-05-22) diff --git a/setup.cfg b/setup.cfg index 6d8504b7..2d9a56d1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.18.3 +version = 1.19.0 From 4d191fe51416870daef77d76ddc0a9b211e0b863 Mon Sep 17 00:00:00 2001 From: YunShu Date: Thu, 6 Jul 2023 18:17:02 +0800 Subject: [PATCH 278/349] docs: replace gitter links with discord (#302) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01c7883b..3a93ff95 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ PyCasbin [![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) [![Pyversions](https://img.shields.io/pypi/pyversions/casbin.svg)](https://pypi.org/project/casbin/) [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/casbin/lobby) +[![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN) 💖 [**Looking for an open-source identity and access management solution like Okta, Auth0, Keycloak ? Learn more about: Casdoor**](https://casdoor.org/) From 02e0b78c7f33bf1692b9c16bd91ba74ebcbb2c2d Mon Sep 17 00:00:00 2001 From: BustDot Date: Fri, 7 Jul 2023 14:31:58 +0800 Subject: [PATCH 279/349] feat: add auto_notify_watcher to core enforcer (#303) * feat: add auto_notify_watcher * feat: add unit test for auto_notify_watcher * feat: lint code --- casbin/core_enforcer.py | 6 ++ casbin/internal_enforcer.py | 18 ++-- tests/test_watcher_ex.py | 189 ++++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+), 9 deletions(-) create mode 100644 tests/test_watcher_ex.py diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index d2955efa..4668f3db 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -50,6 +50,7 @@ class CoreEnforcer: enabled = False auto_save = False auto_build_role_links = False + auto_notify_watcher = False def __init__(self, model=None, adapter=None): self.logger = logging.getLogger(__name__) @@ -106,6 +107,7 @@ def _initialize(self): self.enabled = True self.auto_save = True self.auto_build_role_links = True + self.auto_notify_watcher = True self.init_rm_map() @@ -283,6 +285,10 @@ def enable_auto_build_role_links(self, auto_build_role_links): """controls whether to rebuild the role inheritance relations when a role is added or deleted.""" self.auto_build_role_links = auto_build_role_links + def enable_auto_notify_watcher(self, auto_notify_watcher): + """controls whether to save a policy rule automatically notify the watcher when it is added or removed.""" + self.auto_notify_watcher = auto_notify_watcher + def build_role_links(self): """manually rebuild the role inheritance relations.""" diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index 66264b7a..d5ec77b8 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -30,7 +30,7 @@ def _add_policy(self, sec, ptype, rule): if self.adapter.add_policy(sec, ptype, rule) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: if callable(getattr(self.watcher, "update_for_add_policy", None)): self.watcher.update_for_add_policy(sec, ptype, rule) else: @@ -51,7 +51,7 @@ def _add_policies(self, sec, ptype, rules): if self.adapter.add_policies(sec, ptype, rules) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: if callable(getattr(self.watcher, "update_for_add_policies", None)): self.watcher.update_for_add_policies(sec, ptype, rules) else: @@ -71,7 +71,7 @@ def _update_policy(self, sec, ptype, old_rule, new_rule): if self.adapter.update_policy(sec, ptype, old_rule, new_rule) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: self.watcher.update() return rule_updated @@ -88,7 +88,7 @@ def _update_policies(self, sec, ptype, old_rules, new_rules): if self.adapter.update_policies(sec, ptype, old_rules, new_rules) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: self.watcher.update() return rules_updated @@ -114,7 +114,7 @@ def _update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_v return is_rule_changed if sec == "g": self.build_role_links() - if self.watcher: + if self.watcher and self.auto_notify_watcher: self.watcher.update() return is_rule_changed @@ -128,7 +128,7 @@ def _remove_policy(self, sec, ptype, rule): if self.adapter.remove_policy(sec, ptype, rule) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: if callable(getattr(self.watcher, "update_for_remove_policy", None)): self.watcher.update_for_remove_policy(sec, ptype, rule) else: @@ -149,7 +149,7 @@ def _remove_policies(self, sec, ptype, rules): if self.adapter.remove_policies(sec, ptype, rules) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: if callable(getattr(self.watcher, "update_for_remove_policies", None)): self.watcher.update_for_remove_policies(sec, ptype, rules) else: @@ -167,7 +167,7 @@ def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: if callable(getattr(self.watcher, "update_for_remove_filtered_policy", None)): self.watcher.update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) else: @@ -185,7 +185,7 @@ def _remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *fiel if self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) is False: return False - if self.watcher: + if self.watcher and self.auto_notify_watcher: self.watcher.update() return rule_removed diff --git a/tests/test_watcher_ex.py b/tests/test_watcher_ex.py new file mode 100644 index 00000000..e98a12fb --- /dev/null +++ b/tests/test_watcher_ex.py @@ -0,0 +1,189 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +import casbin +from tests.test_enforcer import get_examples, TestCaseBase + + +class SampleWatcher: + def __init__(self): + self.callback = None + self.notify_message = None + + def close(self): + pass + + def set_update_callback(self, callback): + """ + sets the callback function to be called when the policy is updated + :param callable callback: callback(event) + - event: event received from the rabbitmq + :return: + """ + self.callback = callback + + def update(self, msg): + """ + update the policy + """ + self.notify_message = msg + return True + + def update_for_add_policy(self, section, ptype, *params): + """ + update for add policy + :param section: section + :param ptype: policy type + :param params: other params + :return: True if updated + """ + message = "called add policy" + return self.update(message) + + def update_for_remove_policy(self, section, ptype, *params): + """ + update for remove policy + :param section: section + :param ptype: policy type + :param params: other params + :return: True if updated + """ + message = "called remove policy" + return self.update(message) + + def update_for_remove_filtered_policy(self, section, ptype, field_index, *params): + """ + update for remove filtered policy + :param section: section + :param ptype: policy type + :param field_index: field index + :param params: other params + :return: + """ + message = "called remove filtered policy" + return self.update(message) + + def update_for_save_policy(self, model: casbin.Model): + """ + update for save policy + :param model: casbin model + :return: + """ + message = "called save policy" + return self.update(message) + + def update_for_add_policies(self, section, ptype, *params): + """ + update for add policies + :param section: section + :param ptype: policy type + :param params: other params + :return: + """ + message = "called add policies" + return self.update(message) + + def update_for_remove_policies(self, section, ptype, *params): + """ + update for remove policies + :param section: section + :param ptype: policy type + :param params: other params + :return: + """ + message = "called remove policies" + return self.update(message) + + def start_watch(self): + """ + starts the watch thread + :return: + """ + pass + + +class TestWatcherEx(TestCaseBase): + def get_enforcer(self, model=None, adapter=None): + return casbin.Enforcer( + model, + adapter, + ) + + def test_auto_notify_enabled(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + w = SampleWatcher() + e.set_watcher(w) + e.enable_auto_notify_watcher(True) + + e.save_policy() + self.assertEqual(w.notify_message, "called save policy") + + e.add_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, "called add policy") + + e.remove_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, "called remove policy") + + e.remove_filtered_policy(1, "data1") + self.assertEqual(w.notify_message, "called remove filtered policy") + + rules = [ + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ] + e.add_policies(rules) + self.assertEqual(w.notify_message, "called add policies") + + e.remove_policies(rules) + self.assertEqual(w.notify_message, "called remove policies") + + def test_auto_notify_disabled(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + w = SampleWatcher() + e.set_watcher(w) + e.enable_auto_notify_watcher(False) + + e.save_policy() + self.assertEqual(w.notify_message, "called save policy") + + w.notify_message = None + + e.add_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, None) + + e.remove_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, None) + + e.remove_filtered_policy(1, "data1") + self.assertEqual(w.notify_message, None) + + rules = [ + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ] + e.add_policies(rules) + self.assertEqual(w.notify_message, None) + + e.remove_policies(rules) + self.assertEqual(w.notify_message, None) From 014d8af96b991283b9572bc00c4ee26961bb3227 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 7 Jul 2023 06:35:50 +0000 Subject: [PATCH 280/349] chore(release): 1.20.0 [skip ci] # [1.20.0](https://github.com/casbin/pycasbin/compare/v1.19.0...v1.20.0) (2023-07-07) ### Features * add auto_notify_watcher to core enforcer ([#303](https://github.com/casbin/pycasbin/issues/303)) ([15de222](https://github.com/casbin/pycasbin/commit/15de2223d08e4d53555e747a4e549c327344412c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4502148c..2e36a678 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.20.0](https://github.com/casbin/pycasbin/compare/v1.19.0...v1.20.0) (2023-07-07) + + +### Features + +* add auto_notify_watcher to core enforcer ([#303](https://github.com/casbin/pycasbin/issues/303)) ([15de222](https://github.com/casbin/pycasbin/commit/15de2223d08e4d53555e747a4e549c327344412c)) + # [1.19.0](https://github.com/casbin/pycasbin/compare/v1.18.3...v1.19.0) (2023-05-24) diff --git a/setup.cfg b/setup.cfg index 2d9a56d1..ed0190df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.19.0 +version = 1.20.0 From 79ebbb08ed8591d6aeb0836c8d83e44bbd721d5c Mon Sep 17 00:00:00 2001 From: BustDot Date: Mon, 10 Jul 2023 10:38:26 +0800 Subject: [PATCH 281/349] feat: add string adapter (#304) --- casbin/persist/adapters/string_adapter.py | 67 ++++++++++++++++++ tests/persist/__init__.py | 0 tests/persist/test_string_adapter.py | 83 +++++++++++++++++++++++ 3 files changed, 150 insertions(+) create mode 100644 casbin/persist/adapters/string_adapter.py create mode 100644 tests/persist/__init__.py create mode 100644 tests/persist/test_string_adapter.py diff --git a/casbin/persist/adapters/string_adapter.py b/casbin/persist/adapters/string_adapter.py new file mode 100644 index 00000000..0076caee --- /dev/null +++ b/casbin/persist/adapters/string_adapter.py @@ -0,0 +1,67 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from casbin import persist, load_policy_line +import os + +from casbin.util import util + + +class StringAdapter(persist.Adapter): + """the string adapter for Casbin. + It can load policy from string or save policy to string. + """ + + _file_path = "" + + def __init__(self, line): + self.line = line + + def load_policy(self, model): + """loads all policy rules from the storage.""" + if self.line == "": + raise RuntimeError("invalid line, line cannot be empty") + + strs = self.line.split("\n") + for s in strs: + if s == "": + continue + load_policy_line(s, model) + + def save_policy(self, model): + """saves all policy rules to the storage.""" + tmp = [] + for ptype, ast in model["p"].items(): + for rule in ast.policy: + tmp.append(ptype + ", " + util.array_to_string(rule) + "\n") + + for ptype, ast in model["g"].items(): + for rule in ast.policy: + tmp.append(ptype + ", " + util.array_to_string(rule) + "\n") + + self.line = "".join(tmp).rstrip("\n") + + def add_policy(self, sec, ptype, rule): + """adds a policy rule to the storage.""" + raise RuntimeError("not implemented") + + def remove_policy(self, sec, ptype, rule): + """removes a policy rule from the storage.""" + raise RuntimeError("not implemented") + + def remove_filtered_policy(self, sec, ptype, field_index, *field_values): + """removes policy rules that match the filter from the storage. + This is part of the Auto-Save feature. + """ + raise RuntimeError("not implemented") diff --git a/tests/persist/__init__.py b/tests/persist/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/persist/test_string_adapter.py b/tests/persist/test_string_adapter.py new file mode 100644 index 00000000..b0a4b8e9 --- /dev/null +++ b/tests/persist/test_string_adapter.py @@ -0,0 +1,83 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. +from casbin import Model +from casbin.persist.adapters.string_adapter import StringAdapter +from tests import TestCaseBase + + +class TestStringAdapter(TestCaseBase): + def test_key_match_rbac(self): + conf = """ +[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) && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) +""" + line = """ +p, alice, /alice_data/*, (GET)|(POST) +p, alice, /alice_data/resource1, POST +p, data_group_admin, /admin/*, POST +p, data_group_admin, /bob_data/*, POST +g, alice, data_group_admin +""" + adapter = StringAdapter(line) + model = Model() + model.load_model_from_text(conf) + e = self.get_enforcer(model, adapter) + sub = "alice" + obj = "/alice_data/login" + act = "POST" + self.assertTrue(e.enforce(sub, obj, act)) + + def test_string_rbac(self): + conf = """ +[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 +""" + line = """ +p, alice, data1, read +p, data_group_admin, data3, read +p, data_group_admin, data3, write +g, alice, data_group_admin +""" + adapter = StringAdapter(line) + model = Model() + model.load_model_from_text(conf) + e = self.get_enforcer(model, adapter) + sub = "alice" + obj = "data1" + act = "read" + self.assertTrue(e.enforce(sub, obj, act)) From 4687c9ba89ac8beb7df9a0a86fa52602a1eb9ac4 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 10 Jul 2023 02:41:30 +0000 Subject: [PATCH 282/349] chore(release): 1.21.0 [skip ci] # [1.21.0](https://github.com/casbin/pycasbin/compare/v1.20.0...v1.21.0) (2023-07-10) ### Features * add string adapter ([#304](https://github.com/casbin/pycasbin/issues/304)) ([784a46f](https://github.com/casbin/pycasbin/commit/784a46f8f403cf18572c22c8779dc1e3dca315ca)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e36a678..4e0e20fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.21.0](https://github.com/casbin/pycasbin/compare/v1.20.0...v1.21.0) (2023-07-10) + + +### Features + +* add string adapter ([#304](https://github.com/casbin/pycasbin/issues/304)) ([784a46f](https://github.com/casbin/pycasbin/commit/784a46f8f403cf18572c22c8779dc1e3dca315ca)) + # [1.20.0](https://github.com/casbin/pycasbin/compare/v1.19.0...v1.20.0) (2023-07-07) diff --git a/setup.cfg b/setup.cfg index ed0190df..02afc091 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.20.0 +version = 1.21.0 From 3f9475d79d05a158dc0d0ebcd8781491cfe9048a Mon Sep 17 00:00:00 2001 From: BustDot Date: Fri, 14 Jul 2023 13:47:17 +0800 Subject: [PATCH 283/349] feat: update logger for more precise log level and format control (#306) * feat: add log config * feat: add enable_log for enforcer init * feat: lint code --- casbin/core_enforcer.py | 16 ++++-- casbin/distributed_enforcer.py | 17 +++---- casbin/model/assertion.py | 2 +- casbin/model/policy.py | 2 +- .../rbac/default_role_manager/role_manager.py | 4 +- casbin/util/log.py | 50 +++++++++++++++++++ 6 files changed, 73 insertions(+), 18 deletions(-) create mode 100644 casbin/util/log.py diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 4668f3db..b6c7468f 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -21,6 +21,7 @@ from casbin.persist.adapters import FileAdapter from casbin.rbac import default_role_manager from casbin.util import generate_g_function, SimpleEval, util +from casbin.util.log import configure_logging class EnforceContext: @@ -52,10 +53,8 @@ class CoreEnforcer: auto_build_role_links = False auto_notify_watcher = False - def __init__(self, model=None, adapter=None): - self.logger = logging.getLogger(__name__) - # if want to see more detail logs, change log level to info or debug - self.logger.setLevel(logging.WARNING) + def __init__(self, model=None, adapter=None, enable_log=False): + self.logger = logging.getLogger("casbin.enforcer") if isinstance(model, str): if isinstance(adapter, str): self.init_with_file(model, adapter) @@ -68,6 +67,9 @@ def __init__(self, model=None, adapter=None): else: self.init_with_model_and_adapter(model, adapter) + if enable_log: + configure_logging() + def init_with_file(self, model_path, policy_path): """initializes an enforcer with a model file and a policy file.""" a = FileAdapter(policy_path) @@ -89,7 +91,6 @@ def init_with_model_and_adapter(self, m, adapter=None): self.adapter = adapter self.model = m - m.logger = self.logger self.model.print_model() self.fm = FunctionMap.load_function_map() @@ -472,6 +473,11 @@ def batch_enforce(self, rvals): results.append(result) return results + @staticmethod + def configure_logging(logging_config=None): + """configure_logging configure the default logger for casbin""" + configure_logging(logging_config) + @staticmethod def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 51b58d23..8703ab50 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -24,7 +24,6 @@ class DistributedEnforcer(SyncedEnforcer): """DistributedEnforcer wraps SyncedEnforcer for dispatcher.""" def __init__(self, model=None, adapter=None): - self.logger = logging.getLogger(__name__) SyncedEnforcer.__init__(self, model, adapter) def add_policy_self(self, should_persist, sec, ptype, rules): @@ -43,7 +42,7 @@ def add_policy_self(self, should_persist, sec, ptype, rules): if isinstance(self.adapter, batch_adapter): self.adapter.add_policies(sec, ptype, rules) except Exception as e: - self.logger.log("An error occurred: " + e) + self._e.logger.error("An error occurred: " + e) self.get_model().add_policies(sec, ptype, no_exists_policy) @@ -51,7 +50,7 @@ def add_policy_self(self, should_persist, sec, ptype, rules): try: self.build_incremental_role_links(PolicyOp.Policy_add, ptype, no_exists_policy) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) return no_exists_policy return no_exists_policy @@ -66,7 +65,7 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): if isinstance(self.adapter, batch_adapter): self.adapter.remove_policy(sec, ptype, rules) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) effected = self.get_model().remove_policies_with_effected(sec, ptype, rules) @@ -74,7 +73,7 @@ def remove_policy_self(self, should_persist, sec, ptype, rules): try: self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, rules) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) return effected return effected @@ -89,7 +88,7 @@ def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, * try: self.adapter.remove_filtered_policy(sec, ptype, field_index, field_values) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) effects = self.get_model().remove_filtered_policy_returns_effects(sec, ptype, field_index, *field_values) @@ -97,7 +96,7 @@ def remove_filtered_policy_self(self, should_persist, sec, ptype, field_index, * try: self.build_incremental_role_links(PolicyOp.Policy_remove, ptype, effects) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) return effects return effects @@ -110,7 +109,7 @@ def clear_policy_self(self, should_persist): try: self.adapter.save_policy(None) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) self.get_model().clear_policy() @@ -123,7 +122,7 @@ def update_policy_self(self, should_persist, sec, ptype, old_rule, new_rule): if isinstance(self.adapter, update_adapter): self.adapter.update_policy(sec, ptype, old_rule, new_rule) except Exception as e: - self.logger.log("An exception occurred: " + e) + self._e.logger.error("An exception occurred: " + e) return False rule_updated = self.get_model().update_policy(sec, ptype, old_rule, new_rule) diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index faf23976..3e92d11c 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -18,7 +18,7 @@ class Assertion: def __init__(self): - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger("casbin.policy") self.key = "" self.value = "" self.tokens = [] diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 27a6a19d..55f7a681 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -19,7 +19,7 @@ class Policy: def __init__(self): - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger("casbin.policy") self.model = {} def __getitem__(self, item): diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index e6f203d2..0fea3e8b 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -79,7 +79,7 @@ class RoleManager(RM): """provides a default implementation for the RoleManager interface""" def __init__(self, max_hierarchy_level=10): - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger("casbin.role") self.max_hierarchy_level = max_hierarchy_level self.matching_func = None self.domain_matching_func = None @@ -202,7 +202,7 @@ def print_roles(self): class DomainManagerBase(RM): def __init__(self, max_hierarchy_level=10): - self.logger = logging.getLogger(__name__) + self.logger = logging.getLogger("casbin.role") self.all_links = dict() self.max_hierarchy_level = max_hierarchy_level self.matching_func = None diff --git a/casbin/util/log.py b/casbin/util/log.py new file mode 100644 index 00000000..6fe692f7 --- /dev/null +++ b/casbin/util/log.py @@ -0,0 +1,50 @@ +import logging +import logging.config + + +# Default logging for Casbin. +DEFAULT_LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "casbin_formatter": { + "format": "{asctime} {message}", + "style": "{", + } + }, + "handlers": { + "console": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "casbin_formatter", + }, + }, + "loggers": { + "casbin": { + "handlers": ["console"], + "level": "INFO", + }, + "casbin.policy": { + "handlers": ["console"], + "level": "WARNING", + "propagate": False, + }, + "casbin.enforcer": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + "casbin.role": { + "handlers": ["console"], + "level": "INFO", + "propagate": False, + }, + }, +} + + +def configure_logging(logging_config=None): + if logging_config: + logging.config.dictConfig(logging_config) + else: + logging.config.dictConfig(DEFAULT_LOGGING) From 97378d36891fc4d3765957b7794725cc9e069f4d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 14 Jul 2023 05:52:28 +0000 Subject: [PATCH 284/349] chore(release): 1.22.0 [skip ci] # [1.22.0](https://github.com/casbin/pycasbin/compare/v1.21.0...v1.22.0) (2023-07-14) ### Features * update logger for more precise log level and format control ([#306](https://github.com/casbin/pycasbin/issues/306)) ([5c688d7](https://github.com/casbin/pycasbin/commit/5c688d7db2c7f5139e2192a0a21d3b2ba3f0807b)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e0e20fa..09637dfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.22.0](https://github.com/casbin/pycasbin/compare/v1.21.0...v1.22.0) (2023-07-14) + + +### Features + +* update logger for more precise log level and format control ([#306](https://github.com/casbin/pycasbin/issues/306)) ([5c688d7](https://github.com/casbin/pycasbin/commit/5c688d7db2c7f5139e2192a0a21d3b2ba3f0807b)) + # [1.21.0](https://github.com/casbin/pycasbin/compare/v1.20.0...v1.21.0) (2023-07-10) diff --git a/setup.cfg b/setup.cfg index 02afc091..c01828a4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.21.0 +version = 1.22.0 From bc097a8b3f1e01782f542abc5068f4b7506f73f3 Mon Sep 17 00:00:00 2001 From: AmisAdmin <34331297+amisadmin@users.noreply.github.com> Date: Wed, 2 Aug 2023 14:04:22 +0800 Subject: [PATCH 285/349] Fixed using the rbac model subjectPriority policy, loading the policy failed when the role was not associated with the user (#308) --- casbin/model/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/model/model.py b/casbin/model/model.py index a65509b6..188b9f56 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -133,7 +133,7 @@ def compare_policy(policy): if domain_index != -1: domain = policy[domain_index] name = self.get_name_with_domain(domain, policy[sub_index]) - return subject_hierarchy_map[name] + return subject_hierarchy_map.get(name, 0) assertion.policy = sorted(assertion.policy, key=compare_policy, reverse=True) for i, policy in enumerate(assertion.policy): From 0c72faa4d6a03b4c4b59ea40dac6f518fa2f5070 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Mon, 7 Aug 2023 00:05:43 +0800 Subject: [PATCH 286/349] feat: add AsyncEnforcer to pycasbin (#307) * feat: add async_internal_enforcer * feat: add async_enforcer * feat: add AsyncEnforcer to __init__.py * feat: add unittest for AsyncEnforcer * feat: lint code * feat: lint code * feat: lint code --- casbin/__init__.py | 1 + casbin/async_enforcer.py | 255 ++++++++++ casbin/async_internal_enforcer.py | 291 ++++++++++++ casbin/async_management_enforcer.py | 304 ++++++++++++ casbin/persist/adapters/async_file_adapter.py | 81 ++++ tests/test_enforcer.py | 449 +++++++++++++++++- tests/test_management_api.py | 295 ++++++++++++ tests/test_rbac_api.py | 436 +++++++++++++++++ 8 files changed, 2111 insertions(+), 1 deletion(-) create mode 100644 casbin/async_enforcer.py create mode 100644 casbin/async_internal_enforcer.py create mode 100644 casbin/async_management_enforcer.py create mode 100644 casbin/persist/adapters/async_file_adapter.py diff --git a/casbin/__init__.py b/casbin/__init__.py index 0e159ba8..b0cd34d5 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -15,6 +15,7 @@ from .enforcer import * from .synced_enforcer import SyncedEnforcer from .distributed_enforcer import DistributedEnforcer +from .async_enforcer import AsyncEnforcer from . import util from .persist import * from .effect import * diff --git a/casbin/async_enforcer.py b/casbin/async_enforcer.py new file mode 100644 index 00000000..ce6740aa --- /dev/null +++ b/casbin/async_enforcer.py @@ -0,0 +1,255 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from functools import partial + +from casbin.async_management_enforcer import AsyncManagementEnforcer +from casbin.util import join_slice, array_remove_duplicates, set_subtract + + +class AsyncEnforcer(AsyncManagementEnforcer): + """ + AsyncEnforcer = AsyncManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API + """ + + async def get_roles_for_user(self, name): + """gets the roles that a user has.""" + return self.model.model["g"]["g"].rm.get_roles(name) + + async def get_users_for_role(self, name): + """gets the users that has a role.""" + return self.model.model["g"]["g"].rm.get_users(name) + + async def has_role_for_user(self, name, role): + """determines whether a user has a role.""" + roles = await self.get_roles_for_user(name) + return any(r == role for r in roles) + + async def add_role_for_user(self, user, role): + """ + async adds a role for a user. + Returns false if the user already has the role (aka not affected). + """ + return await self.add_grouping_policy(user, role) + + async def delete_role_for_user(self, user, role): + """ + async deletes a role for a user. + Returns false if the user does not have the role (aka not affected). + """ + return await self.remove_grouping_policy(user, role) + + async def delete_roles_for_user(self, user): + """ + async deletes all roles for a user. + Returns false if the user does not have any roles (aka not affected). + """ + return await self.remove_filtered_grouping_policy(0, user) + + async def delete_user(self, user): + """ + async deletes a user. + Returns false if the user does not exist (aka not affected). + """ + res1 = await self.remove_filtered_grouping_policy(0, user) + + res2 = await self.remove_filtered_policy(0, user) + return res1 or res2 + + async def delete_role(self, role): + """ + async deletes a role. + Returns false if the role does not exist (aka not affected). + """ + res1 = await self.remove_filtered_grouping_policy(1, role) + + res2 = await self.remove_filtered_policy(0, role) + return res1 or res2 + + async def delete_permission(self, *permission): + """ + async deletes a permission. + Returns false if the permission does not exist (aka not affected). + """ + return await self.remove_filtered_policy(1, *permission) + + async def add_permission_for_user(self, user, *permission): + """ + async adds a permission for a user or role. + Returns false if the user or role already has the permission (aka not affected). + """ + return await self.add_policy(join_slice(user, *permission)) + + async def delete_permission_for_user(self, user, *permission): + """ + async deletes a permission for a user or role. + Returns false if the user or role does not have the permission (aka not affected). + """ + return await self.remove_policy(join_slice(user, *permission)) + + async def delete_permissions_for_user(self, user): + """ + async deletes permissions for a user or role. + Returns false if the user or role does not have any permissions (aka not affected). + """ + return await self.remove_filtered_policy(0, user) + + async def get_permissions_for_user(self, user): + """ + gets permissions for a user or role. + """ + return self.get_filtered_policy(0, user) + + async def has_permission_for_user(self, user, *permission): + """ + determines whether a user has a permission. + """ + return self.has_policy(join_slice(user, *permission)) + + async def get_implicit_roles_for_user(self, name, domain=""): + """ + gets implicit roles that a user has. + Compared to get_roles_for_user(), this function retrieves indirect roles besides direct roles. + For example: + g, alice, role:admin + g, role:admin, role:user + + get_roles_for_user("alice") can only get: ["role:admin"]. + But get_implicit_roles_for_user("alice") will get: ["role:admin", "role:user"]. + """ + res = [] + queue = [name] + + while queue: + name = queue.pop(0) + + for rm in self.rm_map.values(): + roles = rm.get_roles(name, domain) + for r in roles: + if r not in res: + res.append(r) + queue.append(r) + + return res + + async def get_implicit_permissions_for_user(self, user, domain="", filter_policy_dom=True): + """ + gets implicit permissions for a user or role. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + + For given domain policies are filtered by corresponding domain matching function of DomainManager + Inherited roles can be matched by domain. For domain neutral policies set: + filter_policy_dom = False + + filter_policy_dom: bool - For given *domain*, policies will be filtered by domain as well. Default = True + """ + return await self.get_named_implicit_permissions_for_user("p", user, domain, filter_policy_dom) + + async def get_named_implicit_permissions_for_user(self, ptype, user, domain="", filter_policy_dom=True): + """ + gets implicit permissions for a user or role by named policy. + Compared to get_permissions_for_user(), this function retrieves permissions for inherited roles. + For example: + p, admin, data1, read + p, alice, data2, read + g, alice, admin + + get_permissions_for_user("alice") can only get: [["alice", "data2", "read"]]. + But get_implicit_permissions_for_user("alice") will get: [["admin", "data1", "read"], ["alice", "data2", "read"]]. + + For given domain policies are filtered by corresponding domain matching function of DomainManager + Inherited roles can be matched by domain. For domain neutral policies set: + filter_policy_dom = False + + filter_policy_dom: bool - For given *domain*, policies will be filtered by domain as well. Default = True + """ + roles = await self.get_implicit_roles_for_user(user, domain) + + roles.insert(0, user) + + res = [] + + # policy domain should be matched by domain_match_fn of DomainManager + domain_matching_func = self.get_role_manager().domain_matching_func + if domain and domain_matching_func != None: + domain = partial(domain_matching_func, domain) + + for role in roles: + permissions = await self.get_named_permissions_for_user_in_domain( + ptype, role, domain if filter_policy_dom else "" + ) + res.extend(permissions) + + return res + + async def get_implicit_users_for_permission(self, *permission): + """ + gets implicit users for a permission. + For example: + p, admin, data1, read + p, bob, data1, read + g, alice, admin + + get_implicit_users_for_permission("data1", "read") will get: ["alice", "bob"]. + Note: only users will be returned, roles (2nd arg in "g") will be excluded. + """ + p_subjects = self.get_all_subjects() + g_inherit = self.model.get_values_for_field_in_policy("g", "g", 1) + g_subjects = self.model.get_values_for_field_in_policy("g", "g", 0) + subjects = array_remove_duplicates(g_subjects + p_subjects) + + res = list() + subjects = set_subtract(subjects, g_inherit) + + for user in subjects: + req = join_slice(user, *permission) + allowed = self.enforce(*req) + + if allowed: + res.append(user) + + return res + + async def get_roles_for_user_in_domain(self, name, domain): + """gets the roles that a user has inside a domain.""" + return self.model.model["g"]["g"].rm.get_roles(name, domain) + + async def get_users_for_role_in_domain(self, name, domain): + """gets the users that has a role inside a domain.""" + return self.model.model["g"]["g"].rm.get_users(name, domain) + + async def add_role_for_user_in_domain(self, user, role, domain): + """async adds a role for a user inside a domain.""" + """Returns false if the user already has the role (aka not affected).""" + return await self.add_grouping_policy(user, role, domain) + + async def delete_roles_for_user_in_domain(self, user, role, domain): + """async deletes a role for a user inside a domain.""" + """Returns false if the user does not have any roles (aka not affected).""" + return await self.remove_filtered_grouping_policy(0, user, role, domain) + + async def get_permissions_for_user_in_domain(self, user, domain): + """gets permissions for a user or role inside domain.""" + return await self.get_named_permissions_for_user_in_domain("p", user, domain) + + async def get_named_permissions_for_user_in_domain(self, ptype, user, domain): + """gets permissions for a user or role with named policy inside domain.""" + return self.get_filtered_named_policy(ptype, 0, user, domain) diff --git a/casbin/async_internal_enforcer.py b/casbin/async_internal_enforcer.py new file mode 100644 index 00000000..9950ece6 --- /dev/null +++ b/casbin/async_internal_enforcer.py @@ -0,0 +1,291 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. +import copy + +from casbin.model import Model, FunctionMap +from casbin.persist import Adapter +from casbin.core_enforcer import CoreEnforcer +from casbin.persist.adapters.async_file_adapter import AsyncFileAdapter + + +class AsyncInternalEnforcer(CoreEnforcer): + """ + AsyncInternalEnforcer = CoreEnforcer + Async Internal API. + """ + + def init_with_file(self, model_path, policy_path): + """initializes an enforcer with a model file and a policy file.""" + a = AsyncFileAdapter(policy_path) + self.init_with_adapter(model_path, a) + + def init_with_model_and_adapter(self, m, adapter=None): + """initializes an enforcer with a model and a database adapter.""" + + if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter): + raise RuntimeError("Invalid parameters for enforcer.") + + self.adapter = adapter + + self.model = m + self.model.print_model() + self.fm = FunctionMap.load_function_map() + + self._initialize() + + async def load_policy(self): + """async reloads the policy from file/database.""" + need_to_rebuild = False + new_model = copy.deepcopy(self.model) + new_model.clear_policy() + + try: + await self.adapter.load_policy(new_model) + + new_model.sort_policies_by_subject_hierarchy() + + new_model.sort_policies_by_priority() + + new_model.print_policy() + + if self.auto_build_role_links: + need_to_rebuild = True + for rm in self.rm_map.values(): + rm.clear() + + new_model.build_role_links(self.rm_map) + + self.model = new_model + + except Exception as e: + if self.auto_build_role_links and need_to_rebuild: + self.build_role_links() + + raise e + + async def load_filtered_policy(self, filter): + """async reloads a filtered policy from file/database.""" + self.model.clear_policy() + + if not hasattr(self.adapter, "is_filtered"): + raise ValueError("filtered policies are not supported by this adapter") + + await self.adapter.load_filtered_policy(self.model, filter) + + self.model.sort_policies_by_priority() + + self.init_rm_map() + self.model.print_policy() + if self.auto_build_role_links: + self.build_role_links() + + async def load_increment_filtered_policy(self, filter): + """async append a filtered policy from file/database.""" + if not hasattr(self.adapter, "is_filtered"): + raise ValueError("filtered policies are not supported by this adapter") + + self.adapter.load_filtered_policy(self.model, filter) + self.model.print_policy() + if self.auto_build_role_links: + self.build_role_links() + + async def save_policy(self): + if self.is_filtered(): + raise RuntimeError("cannot save a filtered policy") + + await self.adapter.save_policy(self.model) + + if self.watcher: + if callable(getattr(self.watcher, "update_for_save_policy", None)): + self.watcher.update_for_save_policy(self.model) + else: + self.watcher.update() + + async def _add_policy(self, sec, ptype, rule): + """async adds a rule to the current policy.""" + rule_added = self.model.add_policy(sec, ptype, rule) + if not rule_added: + return rule_added + + if self.adapter and self.auto_save: + result = await self.adapter.add_policy(sec, ptype, rule) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + if callable(getattr(self.watcher, "update_for_add_policy", None)): + self.watcher.update_for_add_policy(sec, ptype, rule) + else: + self.watcher.update() + + return rule_added + + async def _add_policies(self, sec, ptype, rules): + """async adds rules to the current policy.""" + rules_added = self.model.add_policies(sec, ptype, rules) + if not rules_added: + return rules_added + + if self.adapter and self.auto_save: + if hasattr(self.adapter, "add_policies") is False: + return False + + result = await self.adapter.add_policies(sec, ptype, rules) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + if callable(getattr(self.watcher, "update_for_add_policies", None)): + self.watcher.update_for_add_policies(sec, ptype, rules) + else: + self.watcher.update() + + return rules_added + + async def _update_policy(self, sec, ptype, old_rule, new_rule): + """async updates a rule from the current policy.""" + rule_updated = self.model.update_policy(sec, ptype, old_rule, new_rule) + + if not rule_updated: + return rule_updated + + if self.adapter and self.auto_save: + + result = await self.adapter.update_policy(sec, ptype, old_rule, new_rule) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + self.watcher.update() + + return rule_updated + + async def _update_policies(self, sec, ptype, old_rules, new_rules): + """async updates rules from the current policy.""" + rules_updated = self.model.update_policies(sec, ptype, old_rules, new_rules) + + if not rules_updated: + return rules_updated + + if self.adapter and self.auto_save: + result = await self.adapter.update_policies(sec, ptype, old_rules, new_rules) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + self.watcher.update() + + return rules_updated + + async def _update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): + """async deletes old rules and adds new rules.""" + + old_rules = self.model.get_filtered_policy(sec, ptype, field_index, *field_values) + + if self.adapter and self.auto_save: + try: + old_rules = await self.adapter.update_filtered_policies( + sec, ptype, new_rules, field_index, *field_values + ) + except: + pass + + if not old_rules: + return False + + is_rule_changed = self.model.remove_policies(sec, ptype, old_rules) + self.model.add_policies(sec, ptype, new_rules) + is_rule_changed = is_rule_changed and len(new_rules) != 0 + if not is_rule_changed: + return is_rule_changed + if sec == "g": + self.build_role_links() + if self.watcher and self.auto_notify_watcher: + self.watcher.update() + return is_rule_changed + + async def _remove_policy(self, sec, ptype, rule): + """async removes a rule from the current policy.""" + rule_removed = self.model.remove_policy(sec, ptype, rule) + if not rule_removed: + return rule_removed + + if self.adapter and self.auto_save: + result = await self.adapter.remove_policy(sec, ptype, rule) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + if callable(getattr(self.watcher, "update_for_remove_policy", None)): + self.watcher.update_for_remove_policy(sec, ptype, rule) + else: + self.watcher.update() + + return rule_removed + + async def _remove_policies(self, sec, ptype, rules): + """async RemovePolicies removes policy rules from the model.""" + rules_removed = self.model.remove_policies(sec, ptype, rules) + if not rules_removed: + return rules_removed + + if self.adapter and self.auto_save: + if hasattr(self.adapter, "remove_policies") is False: + return False + + result = await self.adapter.remove_policies(sec, ptype, rules) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + if callable(getattr(self.watcher, "update_for_remove_policies", None)): + self.watcher.update_for_remove_policies(sec, ptype, rules) + else: + self.watcher.update() + + return rules_removed + + async def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): + """async removes rules based on field filters from the current policy.""" + rule_removed = self.model.remove_filtered_policy(sec, ptype, field_index, *field_values) + if not rule_removed: + return rule_removed + + if self.adapter and self.auto_save: + result = await self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + if callable(getattr(self.watcher, "update_for_remove_filtered_policy", None)): + self.watcher.update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) + else: + self.watcher.update() + + return rule_removed + + async def _remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *field_values): + """async removes rules based on field filters from the current policy.""" + rule_removed = self.model.remove_filtered_policy_returns_effects(sec, ptype, field_index, *field_values) + if len(rule_removed) == 0: + return rule_removed + + if self.adapter and self.auto_save: + result = await self.adapter.remove_filtered_policy(sec, ptype, field_index, *field_values) + if result is False: + return False + + if self.watcher and self.auto_notify_watcher: + self.watcher.update() + + return rule_removed diff --git a/casbin/async_management_enforcer.py b/casbin/async_management_enforcer.py new file mode 100644 index 00000000..ef0a5ca8 --- /dev/null +++ b/casbin/async_management_enforcer.py @@ -0,0 +1,304 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. +from casbin.async_internal_enforcer import AsyncInternalEnforcer +from casbin.model.policy_op import PolicyOp + + +class AsyncManagementEnforcer(AsyncInternalEnforcer): + """ + AsyncManagementEnforcer = AsyncInternalEnforcer + AsyncManagement API. + """ + + def get_all_subjects(self): + """gets the list of subjects that show up in the current policy.""" + return self.get_all_named_subjects("p") + + def get_all_named_subjects(self, ptype): + """gets the list of subjects that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy("p", ptype, 0) + + def get_all_objects(self): + """gets the list of objects that show up in the current policy.""" + return self.get_all_named_objects("p") + + def get_all_named_objects(self, ptype): + """gets the list of objects that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy("p", ptype, 1) + + def get_all_actions(self): + """gets the list of actions that show up in the current policy.""" + return self.get_all_named_actions("p") + + def get_all_named_actions(self, ptype): + """gets the list of actions that show up in the current named policy.""" + return self.model.get_values_for_field_in_policy("p", ptype, 2) + + def get_all_roles(self): + """gets the list of roles that show up in the current named policy.""" + return self.get_all_named_roles("g") + + def get_all_named_roles(self, ptype): + """gets all the authorization rules in the policy.""" + return self.model.get_values_for_field_in_policy("g", ptype, 1) + + def get_policy(self): + """gets all the authorization rules in the policy.""" + return self.get_named_policy("p") + + def get_filtered_policy(self, field_index, *field_values): + """gets all the authorization rules in the policy, field filters can be specified.""" + return self.get_filtered_named_policy("p", field_index, *field_values) + + def get_named_policy(self, ptype): + """gets all the authorization rules in the named policy.""" + return self.model.get_policy("p", ptype) + + def get_filtered_named_policy(self, ptype, field_index, *field_values): + """gets all the authorization rules in the named policy, field filters can be specified.""" + return self.model.get_filtered_policy("p", ptype, field_index, *field_values) + + def get_grouping_policy(self): + """gets all the role inheritance rules in the policy.""" + return self.get_named_grouping_policy("g") + + def get_filtered_grouping_policy(self, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + return self.get_filtered_named_grouping_policy("g", field_index, *field_values) + + def get_named_grouping_policy(self, ptype): + """gets all the role inheritance rules in the policy.""" + return self.model.get_policy("g", ptype) + + def get_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """gets all the role inheritance rules in the policy, field filters can be specified.""" + return self.model.get_filtered_policy("g", ptype, field_index, *field_values) + + def has_policy(self, *params): + """determines whether an authorization rule exists.""" + return self.has_named_policy("p", *params) + + def has_named_policy(self, ptype, *params): + """determines whether a named authorization rule exists.""" + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + return self.model.has_policy("p", ptype, str_slice) + + return self.model.has_policy("p", ptype, list(params)) + + async def add_policy(self, *params): + """async 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. + """ + return await self.add_named_policy("p", *params) + + async def add_policies(self, rules): + """async 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. + """ + return await self.add_named_policies("p", rules) + + async def add_named_policy(self, ptype, *params): + """async 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. + """ + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_added = await self._add_policy("p", ptype, str_slice) + else: + rule_added = await self._add_policy("p", ptype, list(params)) + + return rule_added + + async def add_named_policies(self, ptype, rules): + """async 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. + """ + return await self._add_policies("p", ptype, rules) + + async def update_policy(self, old_rule, new_rule): + """async updates an authorization rule from the current policy.""" + return await self.update_named_policy("p", old_rule, new_rule) + + async def update_policies(self, old_rules, new_rules): + """async updates authorization rules from the current policy.""" + return await self.update_named_policies("p", old_rules, new_rules) + + async def update_named_policy(self, ptype, old_rule, new_rule): + """async updates an authorization rule from the current named policy.""" + return await self._update_policy("p", ptype, old_rule, new_rule) + + async def update_named_policies(self, ptype, old_rules, new_rules): + """async updates authorization rules from the current named policy.""" + return await self._update_policies("p", ptype, old_rules, new_rules) + + async def update_filtered_policies(self, new_rules, field_index, *field_values): + """async update_filtered_policies deletes old rules and adds new rules.""" + return await self.update_filtered_named_policies("p", new_rules, field_index, *field_values) + + async def update_filtered_named_policies(self, ptype, new_rules, field_index, *field_values): + """async update_filtered_named_policies deletes old rules and adds new rules.""" + return await self._update_filtered_policies("p", ptype, new_rules, field_index, *field_values) + + async def remove_policy(self, *params): + """async removes an authorization rule from the current policy.""" + return await self.remove_named_policy("p", *params) + + async def remove_policies(self, rules): + """async removes authorization rules from the current policy.""" + return await self.remove_named_policies("p", rules) + + async def remove_filtered_policy(self, field_index, *field_values): + """async removes an authorization rule from the current policy, field filters can be specified.""" + return await self.remove_filtered_named_policy("p", field_index, *field_values) + + async def remove_named_policy(self, ptype, *params): + """async removes an authorization rule from the current named policy.""" + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_removed = await self._remove_policy("p", ptype, str_slice) + else: + rule_removed = await self._remove_policy("p", ptype, list(params)) + + return rule_removed + + async def remove_named_policies(self, ptype, rules): + """async removes authorization rules from the current named policy.""" + return await self._remove_policies("p", ptype, rules) + + async def remove_filtered_named_policy(self, ptype, field_index, *field_values): + """async removes an authorization rule from the current named policy, field filters can be specified.""" + return await self._remove_filtered_policy("p", ptype, field_index, *field_values) + + def has_grouping_policy(self, *params): + """determines whether a role inheritance rule exists.""" + + return self.has_named_grouping_policy("g", *params) + + def has_named_grouping_policy(self, ptype, *params): + """determines whether a named role inheritance rule exists.""" + + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + return self.model.has_policy("g", ptype, str_slice) + + return self.model.has_policy("g", ptype, list(params)) + + async def add_grouping_policy(self, *params): + """async 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. + """ + return await self.add_named_grouping_policy("g", *params) + + async def add_grouping_policies(self, rules): + """async adds role inheritance rules to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise, the function returns true for the corresponding policy rule by adding the new rule. + """ + return await self.add_named_grouping_policies("g", rules) + + async def add_named_grouping_policy(self, ptype, *params): + """async 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. + """ + + rules = [] + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_added = await self._add_policy("g", ptype, str_slice) + rules.append(str_slice) + else: + rule_added = await self._add_policy("g", ptype, list(params)) + rules.append(list(params)) + + if self.auto_build_role_links: + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules) + return rule_added + + async def add_named_grouping_policies(self, ptype, rules): + """async adds named role inheritance rules to the current policy. + + If the rule already exists, the function returns false for the corresponding policy rule and the rule will not be added. + Otherwise, the function returns true for the corresponding policy rule by adding the new rule. + """ + rules_added = await self._add_policies("g", ptype, rules) + if self.auto_build_role_links: + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules) + + return rules_added + + async def remove_grouping_policy(self, *params): + """async removes a role inheritance rule from the current policy.""" + return await self.remove_named_grouping_policy("g", *params) + + async def remove_grouping_policies(self, rules): + """async removes role inheritance rules from the current policy.""" + return await self.remove_named_grouping_policies("g", rules) + + async def remove_filtered_grouping_policy(self, field_index, *field_values): + """async removes a role inheritance rule from the current policy, field filters can be specified.""" + return await self.remove_filtered_named_grouping_policy("g", field_index, *field_values) + + async def remove_named_grouping_policy(self, ptype, *params): + """async removes a role inheritance rule from the current named policy.""" + + rules = [] + if len(params) == 1 and isinstance(params[0], list): + str_slice = params[0] + rule_removed = await self._remove_policy("g", ptype, str_slice) + rules.append(str_slice) + else: + rule_removed = await self._remove_policy("g", ptype, list(params)) + rules.append(list(params)) + + if self.auto_build_role_links and rule_removed: + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) + return rule_removed + + async def remove_named_grouping_policies(self, ptype, rules): + """async removes role inheritance rules from the current named policy.""" + rules_removed = await self._remove_policies("g", ptype, rules) + + if self.auto_build_role_links and rules_removed: + self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rules) + + return rules_removed + + async def remove_filtered_named_grouping_policy(self, ptype, field_index, *field_values): + """async removes a role inheritance rule from the current named policy, field filters can be specified.""" + rule_removed = await self._remove_filtered_policy_returns_effects("g", ptype, field_index, *field_values) + + if self.auto_build_role_links and rule_removed: + self.model.build_incremental_role_links( + self.rm_map[ptype], PolicyOp.Policy_remove, "g", ptype, rule_removed + ) + return rule_removed + + def add_function(self, name, func): + """adds a customized function.""" + self.fm.add_function(name, func) diff --git a/casbin/persist/adapters/async_file_adapter.py b/casbin/persist/adapters/async_file_adapter.py new file mode 100644 index 00000000..27341310 --- /dev/null +++ b/casbin/persist/adapters/async_file_adapter.py @@ -0,0 +1,81 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from casbin import persist +import os + + +class AsyncFileAdapter(persist.Adapter): + """the async file adapter for Casbin. + It can load policy from file or save policy to file. + """ + + _file_path = "" + + def __init__(self, file_path): + self._file_path = file_path + + async def load_policy(self, model): + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + + self._load_policy_file(model) + + async def save_policy(self, model): + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + + self._save_policy_file(model) + + def _load_policy_file(self, model): + with open(self._file_path, "rb") as file: + line = file.readline() + while line: + persist.load_policy_line(line.decode().strip(), model) + line = file.readline() + + def _save_policy_file(self, model): + with open(self._file_path, "w") as file: + lines = [] + + if "p" in model.model.keys(): + for key, ast in model.model["p"].items(): + for pvals in ast.policy: + lines.append(key + ", " + ", ".join(pvals)) + + if "g" in model.model.keys(): + for key, ast in model.model["g"].items(): + for pvals in ast.policy: + lines.append(key + ", " + ", ".join(pvals)) + + for i, line in enumerate(lines): + if i != len(lines) - 1: + lines[i] += "\n" + + file.writelines(lines) + + async def add_policy(self, sec, ptype, rule): + pass + + async def add_policies(self, sec, ptype, rules): + pass + + async def remove_policy(self, sec, ptype, rule): + pass + + async def remove_policies(self, sec, ptype, rules): + pass + + async def remove_filtered_policy(self, sec, ptype, field_index, *field_values): + pass diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index cac51b3b..29e89ef5 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -14,7 +14,7 @@ import os import time -from unittest import TestCase +from unittest import TestCase, IsolatedAsyncioTestCase import casbin @@ -451,3 +451,450 @@ def test_auto_loading_policy(self): # thread needs a moment to exit time.sleep(10 / 1000) self.assertFalse(e.is_auto_loading_running()) + + +class TestConfigAsync(IsolatedAsyncioTestCase): + def get_enforcer(self, model=None, adapter=None): + return casbin.AsyncEnforcer( + model, + adapter, + ) + + async def test_enforcer_basic(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "write")) + + async def test_enforce_ex_basic(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + self.assertTupleEqual(e.enforce_ex("alice", "data1", "read"), (True, ["alice", "data1", "read"])) + self.assertTupleEqual(e.enforce_ex("alice", "data2", "read"), (False, [])) + self.assertTupleEqual(e.enforce_ex("bob", "data2", "write"), (True, ["bob", "data2", "write"])) + self.assertTupleEqual(e.enforce_ex("bob", "data1", "write"), (False, [])) + + async def test_batch_enforce(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + + results = [True, False, True, False] + self.assertEqual( + e.batch_enforce( + [ + ("alice", "data1", "read"), + ("alice", "data2", "read"), + ("bob", "data2", "write"), + ("bob", "data1", "write"), + ] + ), + results, + ) + + async def test_model_set_load(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + + if not isinstance(e, casbin.SyncedEnforcer): + e.set_model(None) + self.assertTrue(e.model is None) + # creating new model + e.load_model() + self.assertTrue(e.model is not None) + + async def test_enforcer_basic_without_spaces(self): + e = self.get_enforcer( + get_examples("basic_model_without_spaces.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + + async def test_enforce_basic_with_root(self): + e = self.get_enforcer(get_examples("basic_with_root_model.conf"), get_examples("basic_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("root", "any", "any")) + + async def test_enforce_basic_without_resources(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "read")) + self.assertFalse(e.enforce("alice", "write")) + self.assertTrue(e.enforce("bob", "write")) + self.assertFalse(e.enforce("bob", "read")) + + async def test_enforce_basic_without_users(self): + e = self.get_enforcer( + get_examples("basic_without_users_model.conf"), + get_examples("basic_without_users_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("data1", "read")) + self.assertFalse(e.enforce("data1", "write")) + self.assertTrue(e.enforce("data2", "write")) + self.assertFalse(e.enforce("data2", "read")) + + async def test_enforce_ip_match(self): + e = self.get_enforcer(get_examples("ipmatch_model.conf"), get_examples("ipmatch_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("192.168.2.1", "data1", "read")) + self.assertFalse(e.enforce("192.168.3.1", "data1", "read")) + + async def test_enforce_key_match(self): + e = self.get_enforcer(get_examples("keymatch_model.conf"), get_examples("keymatch_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "/alice_data/test", "GET")) + self.assertFalse(e.enforce("alice", "/bob_data/test", "GET")) + self.assertTrue(e.enforce("cathy", "/cathy_data", "GET")) + self.assertTrue(e.enforce("cathy", "/cathy_data", "POST")) + self.assertFalse(e.enforce("cathy", "/cathy_data/12", "POST")) + + async def test_enforce_key_match2(self): + e = self.get_enforcer(get_examples("keymatch2_model.conf"), get_examples("keymatch2_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "/alice_data/resource", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data2/123/using/456", "GET")) + + async def test_enforce_key_match_custom_model(self): + e = self.get_enforcer( + get_examples("keymatch_custom_model.conf"), + get_examples("keymatch2_policy.csv"), + ) + await e.load_policy() + + def custom_function(key1, key2): + if key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data/:resource": + return True + elif key1 == "/alice_data2/myid/using/res_id" and key2 == "/alice_data2/:id/using/:resId": + return True + return False + + e.add_function("keyMatchCustom", custom_function) + + self.assertFalse(e.enforce("alice", "/alice_data2/myid", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data2/myid/using/res_id", "GET")) + + async def test_enforce_glob_match(self): + e = self.get_enforcer( + get_examples("globmatch_model.conf"), + get_examples("globmatch_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "/alice_data/test_all", "GET")) + self.assertTrue(e.enforce("alice", "/alice_data/123", "POST")) + self.assertTrue(e.enforce("bob", "/alice_data/1", "GET")) + self.assertFalse(e.enforce("bob", "/alice_data/0", "GET")) + self.assertTrue(e.enforce("bob", "/bob_data/0", "POST")) + self.assertFalse(e.enforce("bob", "/bob_data/1", "POST")) + + async def test_enforce_priority(self): + e = self.get_enforcer(get_examples("priority_model.conf"), get_examples("priority_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertTrue(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + + async def test_enforce_priority_explicit(self): + e = self.get_enforcer( + get_examples("priority_model_explicit.conf"), + get_examples("priority_policy_explicit.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "read")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "write")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "read")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "write")) + + await e.add_policy("1", "bob", "data2", "write", "deny") + + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "read")) + self.assertFalse(e.enforce("data1_deny_group", "data1", "write")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "read")) + self.assertTrue(e.enforce("data2_allow_group", "data2", "write")) + + async def test_enforce_priority_indeterminate(self): + e = self.get_enforcer( + get_examples("priority_model.conf"), + get_examples("priority_indeterminate_policy.csv"), + ) + await e.load_policy() + + self.assertFalse(e.enforce("alice", "data1", "read")) + + async def test_enforce_subpriority(self): + e = self.get_enforcer( + get_examples("subject_priority_model.conf"), + get_examples("subject_priority_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("jane", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "read")) + + async def test_enforce_subpriority_with_domain(self): + e = self.get_enforcer( + get_examples("subject_priority_model_with_domain.conf"), + get_examples("subject_priority_policy_with_domain.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "domain1", "write")) + self.assertTrue(e.enforce("bob", "data2", "domain2", "write")) + + async def test_multiple_policy_definitions(self): + + e = self.get_enforcer( + get_examples("multiple_policy_definitions_model.conf"), + get_examples("multiple_policy_definitions_policy.csv"), + ) + await e.load_policy() + + enforce_context = e.new_enforce_context("2") + enforce_context.etype = "e" + + sub1 = TestSub("alice", 70) + sub2 = TestSub("bob", 30) + + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce(enforce_context, sub1, "/data1", "read")) + self.assertTrue(e.enforce(enforce_context, sub2, "/data1", "read")) + self.assertFalse(e.enforce(enforce_context, sub2, "/data1", "write")) + self.assertFalse(e.enforce(enforce_context, sub1, "/data2", "read")) + + async def test_enforce_rbac(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("bogus", "data2", "write")) # test non-existant subject + + async def test_enforce_rbac_empty_policy(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("empty_policy.csv")) + await e.load_policy() + + self.assertFalse(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data2", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + + async def test_enforce_rbac_with_deny(self): + e = self.get_enforcer( + get_examples("rbac_with_deny_model.conf"), + get_examples("rbac_with_deny_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + + async def test_enforce_rbac_with_domains(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "write")) + + self.assertFalse(e.enforce("bob", "domain2", "data1", "read")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "write")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) + + async def test_enforce_rbac_with_not_deny(self): + e = self.get_enforcer( + get_examples("rbac_with_not_deny_model.conf"), + get_examples("rbac_with_deny_policy.csv"), + ) + await e.load_policy() + + self.assertFalse(e.enforce("alice", "data2", "write")) + + async def test_enforce_rbac_with_resource_roles(self): + e = self.get_enforcer( + get_examples("rbac_with_resource_roles_model.conf"), + get_examples("rbac_with_resource_roles_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + + async def test_enforce_rbac_with_pattern(self): + e = self.get_enforcer( + get_examples("rbac_with_pattern_model.conf"), + get_examples("rbac_with_pattern_policy.csv"), + ) + await e.load_policy() + + # set matching function to key_match2 + e.add_named_matching_func("g2", casbin.util.key_match2) + + self.assertTrue(e.enforce("alice", "/book/1", "GET")) + self.assertTrue(e.enforce("alice", "/book/2", "GET")) + self.assertTrue(e.enforce("alice", "/pen/1", "GET")) + self.assertFalse(e.enforce("alice", "/pen/2", "GET")) + self.assertFalse(e.enforce("bob", "/book/1", "GET")) + self.assertFalse(e.enforce("bob", "/book/2", "GET")) + self.assertTrue(e.enforce("bob", "/pen/1", "GET")) + self.assertTrue(e.enforce("bob", "/pen/2", "GET")) + + # replace key_match2 with key_match3 + e.add_named_matching_func("g2", casbin.util.key_match3) + self.assertTrue(e.enforce("alice", "/book2/1", "GET")) + self.assertTrue(e.enforce("alice", "/book2/2", "GET")) + self.assertTrue(e.enforce("alice", "/pen2/1", "GET")) + self.assertFalse(e.enforce("alice", "/pen2/2", "GET")) + self.assertFalse(e.enforce("bob", "/book2/1", "GET")) + self.assertFalse(e.enforce("bob", "/book2/2", "GET")) + self.assertTrue(e.enforce("bob", "/pen2/1", "GET")) + self.assertTrue(e.enforce("bob", "/pen2/2", "GET")) + + async def test_rbac_with_multipy_matched_pattern(self): + e = self.get_enforcer( + get_examples("rbac_with_multiply_matched_pattern.conf"), + get_examples("rbac_with_multiply_matched_pattern.csv"), + ) + await e.load_policy() + + e.add_named_matching_func("g2", casbin.util.glob_match) + + self.assertTrue(e.enforce("root@localhost", "/", "org.create")) + + async def test_enforce_abac_log_enabled(self): + e = self.get_enforcer(get_examples("abac_model.conf")) + sub = "alice" + obj = {"Owner": "alice", "id": "data1"} + self.assertTrue(e.enforce(sub, obj, "write")) + + async def test_abac_with_sub_rule(self): + e = self.get_enforcer(get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv")) + await e.load_policy() + + sub1 = TestSub("alice", 16) + sub2 = TestSub("bob", 20) + sub3 = TestSub("alice", 65) + + self.assertFalse(e.enforce(sub1, "/data1", "read")) + self.assertFalse(e.enforce(sub1, "/data2", "read")) + self.assertFalse(e.enforce(sub1, "/data1", "write")) + self.assertTrue(e.enforce(sub1, "/data2", "write")) + + self.assertTrue(e.enforce(sub2, "/data1", "read")) + self.assertFalse(e.enforce(sub2, "/data2", "read")) + self.assertFalse(e.enforce(sub2, "/data1", "write")) + self.assertTrue(e.enforce(sub2, "/data2", "write")) + + self.assertTrue(e.enforce(sub3, "/data1", "read")) + self.assertFalse(e.enforce(sub3, "/data2", "read")) + self.assertFalse(e.enforce(sub3, "/data1", "write")) + self.assertFalse(e.enforce(sub3, "/data2", "write")) + + async def test_abac_with_multiple_sub_rules(self): + e = self.get_enforcer( + get_examples("abac_multiple_rules_model.conf"), + get_examples("abac_multiple_rules_policy.csv"), + ) + await e.load_policy() + + sub1 = TestSub("alice", 16) + sub2 = TestSub("alice", 20) + sub3 = TestSub("bob", 65) + sub4 = TestSub("bob", 35) + + self.assertFalse(e.enforce(sub1, "/data1", "read")) + self.assertFalse(e.enforce(sub1, "/data2", "read")) + self.assertFalse(e.enforce(sub1, "/data1", "write")) + self.assertFalse(e.enforce(sub1, "/data2", "write")) + + self.assertTrue(e.enforce(sub2, "/data1", "read")) + self.assertFalse(e.enforce(sub2, "/data2", "read")) + self.assertFalse(e.enforce(sub2, "/data1", "write")) + self.assertFalse(e.enforce(sub2, "/data2", "write")) + + self.assertFalse(e.enforce(sub3, "/data1", "read")) + self.assertFalse(e.enforce(sub3, "/data2", "read")) + self.assertFalse(e.enforce(sub3, "/data1", "write")) + self.assertFalse(e.enforce(sub3, "/data2", "write")) + + self.assertFalse(e.enforce(sub4, "/data1", "read")) + self.assertFalse(e.enforce(sub4, "/data2", "read")) + self.assertFalse(e.enforce(sub4, "/data1", "write")) + self.assertTrue(e.enforce(sub4, "/data2", "write")) + + async def test_matcher_using_in_operator_bracket(self): + e = self.get_enforcer( + get_examples("rbac_model_matcher_using_in_op_bracket.conf"), + get_examples("rbac_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data3", "scribble")) + self.assertFalse(e.enforce("alice", "data4", "scribble")) diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 098c8d05..84f55225 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -13,6 +13,7 @@ # limitations under the License. from functools import partial +from unittest import IsolatedAsyncioTestCase import casbin from tests.test_enforcer import get_examples, TestCaseBase @@ -311,3 +312,297 @@ def get_enforcer(self, model=None, adapter=None): model, adapter, ) + + +class TestManagementApiAsync(IsolatedAsyncioTestCase): + def get_enforcer(self, model=None, adapter=None): + return casbin.AsyncEnforcer( + model, + adapter, + ) + + async def test_get_list(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + # True, + ) + await e.load_policy() + + self.assertEqual(e.get_all_subjects(), ["alice", "bob", "data2_admin"]) + self.assertEqual(e.get_all_objects(), ["data1", "data2"]) + self.assertEqual(e.get_all_actions(), ["read", "write"]) + self.assertEqual(e.get_all_roles(), ["data2_admin"]) + + async def test_get_policy_api(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + await e.load_policy() + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + + self.assertEqual(e.get_filtered_policy(0, "alice"), [["alice", "data1", "read"]]) + self.assertEqual(e.get_filtered_policy(0, "bob"), [["bob", "data2", "write"]]) + self.assertEqual( + e.get_filtered_policy(0, "data2_admin"), + [["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"]], + ) + self.assertEqual(e.get_filtered_policy(1, "data1"), [["alice", "data1", "read"]]) + self.assertEqual( + e.get_filtered_policy(1, "data2"), + [ + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + self.assertEqual( + e.get_filtered_policy(2, "read"), + [["alice", "data1", "read"], ["data2_admin", "data2", "read"]], + ) + self.assertEqual( + e.get_filtered_policy(2, "write"), + [["bob", "data2", "write"], ["data2_admin", "data2", "write"]], + ) + self.assertEqual( + e.get_filtered_policy(0, "data2_admin", "data2"), + [["data2_admin", "data2", "read"], ["data2_admin", "data2", "write"]], + ) + + # Note: "" (empty string) in fieldValues means matching all values. + self.assertEqual( + e.get_filtered_policy(0, "data2_admin", "", "read"), + [["data2_admin", "data2", "read"]], + ) + self.assertEqual( + e.get_filtered_policy(1, "data2", "write"), + [["bob", "data2", "write"], ["data2_admin", "data2", "write"]], + ) + + self.assertTrue(e.has_policy(["alice", "data1", "read"])) + self.assertTrue(e.has_policy(["bob", "data2", "write"])) + self.assertFalse(e.has_policy(["alice", "data2", "read"])) + self.assertFalse(e.has_policy(["bob", "data3", "write"])) + self.assertEqual(e.get_grouping_policy(), [["alice", "data2_admin"]]) + self.assertEqual(e.get_filtered_grouping_policy(0, "alice"), [["alice", "data2_admin"]]) + self.assertEqual(e.get_filtered_grouping_policy(0, "bob"), []) + self.assertEqual(e.get_filtered_grouping_policy(1, "data1_admin"), []) + self.assertEqual(e.get_filtered_grouping_policy(1, "data2_admin"), [["alice", "data2_admin"]]) + # Note: "" (empty string) in fieldValues means matching all values. + self.assertEqual( + e.get_filtered_grouping_policy(0, "", "data2_admin"), + [["alice", "data2_admin"]], + ) + self.assertTrue(e.has_grouping_policy(["alice", "data2_admin"])) + self.assertFalse(e.has_grouping_policy(["bob", "data2_admin"])) + + async def test_update_filtered_policies(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + await e.load_policy() + + await e.update_filtered_policies( + [ + ["data2_admin", "data3", "read"], + ["data2_admin", "data3", "write"], + ], + 0, + "data2_admin", + ) + self.assertTrue(e.enforce("data2_admin", "data3", "write")) + self.assertTrue(e.enforce("data2_admin", "data3", "read")) + + async def test_get_policy_matching_function(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + e.get_policy(), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + km2_fn = casbin.util.key_match2_func + self.assertEqual( + e.get_filtered_grouping_policy(2, partial(km2_fn, "domain.3")), + [["alice", "user", "*"], ["bob", "admin", "domain.3"]], + ) + + self.assertEqual( + e.get_filtered_grouping_policy(2, partial(km2_fn, "domain.1")), + [["alice", "user", "*"]], + ) + + # first and second p record matches to domain.3 + self.assertEqual( + e.get_filtered_policy(1, partial(km2_fn, "domain.3")), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], + ], + ) + + self.assertEqual( + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "read")), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] + ), + ) + + async def test_get_policy_multiple_matching_functions(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + e.get_policy(), + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + km2_fn = casbin.util.key_match2_func + + self.assertEqual( + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.2"), lambda a: "data" in a)), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.*", "data3", "read"], + ] + ), + ) + + self.assertEqual( + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), lambda a: "data" in a, "read")), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] + ), + ) + + self.assertEqual( + sorted(e.get_filtered_policy(1, partial(km2_fn, "domain.1"), "", "reading".startswith)), + sorted( + [ + ["admin", "domain.*", "data1", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.*", "data3", "read"], + ] + ), + ) + + async def test_modify_policy_api(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + # True, + ) + await e.load_policy() + + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ], + ) + + await e.add_policy("eve", "data3", "read") + await e.add_named_policy("p", ["eve", "data3", "write"]) + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ], + ) + + rules = [ + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ] + + named_policies = [ + ["jack", "data4", "write"], + ["katy", "data4", "read"], + ["leyo", "data4", "write"], + ["ham", "data4", "read"], + ] + await e.add_policies(rules) + await e.add_named_policies("p", named_policies) + + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ["jack", "data4", "write"], + ["katy", "data4", "read"], + ["leyo", "data4", "write"], + ["ham", "data4", "read"], + ], + ) + + await e.remove_policies(rules) + await e.remove_named_policies("p", named_policies) + + await e.add_named_policy("p", "testing") + self.assertEqual( + e.get_policy(), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ["eve", "data3", "read"], + ["eve", "data3", "write"], + ["testing"], + ], + ) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 50fbd20b..847ff0d1 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -11,6 +11,7 @@ # 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. +from unittest import IsolatedAsyncioTestCase import casbin from tests.test_enforcer import get_examples, TestCaseBase @@ -411,3 +412,438 @@ def get_enforcer(self, model=None, adapter=None): model, adapter, ) + + +class TestRbacApiAsync(IsolatedAsyncioTestCase): + def get_enforcer(self, model=None, adapter=None): + return casbin.AsyncEnforcer( + model, + adapter, + ) + + async def test_get_roles_for_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + self.assertEqual(await e.get_roles_for_user("alice"), ["data2_admin"]) + self.assertEqual(await e.get_roles_for_user("bob"), []) + self.assertEqual(await e.get_roles_for_user("data2_admin"), []) + self.assertEqual(await e.get_roles_for_user("non_exist"), []) + + async def test_get_users_for_role(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + self.assertEqual(await e.get_users_for_role("data2_admin"), ["alice"]) + + async def test_has_role_for_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + self.assertTrue(await e.has_role_for_user("alice", "data2_admin")) + self.assertFalse(await e.has_role_for_user("alice", "data1_admin")) + + async def test_add_role_for_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + await e.add_role_for_user("alice", "data1_admin") + self.assertEqual( + sorted(await e.get_roles_for_user("alice")), + sorted(["data2_admin", "data1_admin"]), + ) + + async def test_delete_role_for_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + await e.add_role_for_user("alice", "data1_admin") + self.assertEqual( + sorted(await e.get_roles_for_user("alice")), + sorted(["data2_admin", "data1_admin"]), + ) + + await e.delete_role_for_user("alice", "data1_admin") + self.assertEqual(await e.get_roles_for_user("alice"), ["data2_admin"]) + + async def test_delete_roles_for_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + await e.delete_roles_for_user("alice") + self.assertEqual(await e.get_roles_for_user("alice"), []) + + async def test_delete_user(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + await e.delete_user("alice") + self.assertEqual(await e.get_roles_for_user("alice"), []) + + async def test_delete_role(self): + e = self.get_enforcer(get_examples("rbac_model.conf"), get_examples("rbac_policy.csv")) + await e.load_policy() + + await e.delete_role("data2_admin") + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("bob", "data1", "read")) + self.assertFalse(e.enforce("bob", "data1", "write")) + self.assertFalse(e.enforce("bob", "data2", "read")) + self.assertTrue(e.enforce("bob", "data2", "write")) + + async def test_delete_permission(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + await e.delete_permission("read") + self.assertFalse(e.enforce("alice", "read")) + self.assertFalse(e.enforce("alice", "write")) + self.assertFalse(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) + + async def test_add_permission_for_user(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + await e.delete_permission("read") + await e.add_permission_for_user("bob", "read") + self.assertTrue(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) + + async def test_delete_permission_for_user(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + await e.add_permission_for_user("bob", "read") + + self.assertTrue(e.enforce("bob", "read")) + await e.delete_permission_for_user("bob", "read") + self.assertFalse(e.enforce("bob", "read")) + self.assertTrue(e.enforce("bob", "write")) + + async def test_delete_permissions_for_user(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + await e.delete_permissions_for_user("bob") + + self.assertTrue(e.enforce("alice", "read")) + self.assertFalse(e.enforce("bob", "read")) + self.assertFalse(e.enforce("bob", "write")) + + async def test_get_permissions_for_user(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_permissions_for_user("alice"), [["alice", "read"]]) + + async def test_has_permission_for_user(self): + e = self.get_enforcer( + get_examples("basic_without_resources_model.conf"), + get_examples("basic_without_resources_policy.csv"), + ) + await e.load_policy() + + self.assertTrue(await e.has_permission_for_user("alice", *["read"])) + self.assertFalse(await e.has_permission_for_user("alice", *["write"])) + self.assertFalse(await e.has_permission_for_user("bob", *["read"])) + self.assertTrue(await e.has_permission_for_user("bob", *["write"])) + + async def test_enforce_implicit_roles_api(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + sorted(await e.get_permissions_for_user("alice")), + sorted([["alice", "data1", "read"]]), + ) + self.assertEqual( + sorted(await e.get_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), + ) + + self.assertEqual( + sorted(await e.get_implicit_roles_for_user("alice")), + sorted( + ["admin", "data1_admin", "data2_admin"], + ), + ) + self.assertTrue(await e.get_implicit_roles_for_user("bob") == []) + + async def test_enforce_implicit_roles_with_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) + self.assertEqual( + sorted(await e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), + ) + + async def test_enforce_implicit_permissions_api(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + sorted(await e.get_permissions_for_user("alice")), + sorted([["alice", "data1", "read"]]), + ) + self.assertEqual( + sorted(await e.get_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), + ) + self.assertEqual( + sorted(await e.get_implicit_permissions_for_user("alice")), + sorted( + [ + ["alice", "data1", "read"], + ["data1_admin", "data1", "read"], + ["data1_admin", "data1", "write"], + ["data2_admin", "data2", "read"], + ["data2_admin", "data2", "write"], + ] + ), + ) + self.assertEqual( + sorted(await e.get_implicit_permissions_for_user("bob")), + sorted([["bob", "data2", "write"]]), + ) + + async def test_enforce_implicit_permissions_api_with_multiple_policy(self): + e = self.get_enforcer( + get_examples("rbac_with_multiple_policy_model.conf"), + get_examples("rbac_with_multiple_policy_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + sorted(await e.get_named_implicit_permissions_for_user("p", "alice")), + sorted( + [ + ["user", "/data", "GET"], + ["admin", "/data", "POST"], + ] + ), + ) + self.assertEqual( + sorted(await e.get_named_implicit_permissions_for_user("p2", "alice")), + sorted( + [ + ["user", "view"], + ["admin", "create"], + ] + ), + ) + + async def test_enforce_implicit_permissions_api_with_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_hierarchy_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) + self.assertEqual( + sorted(await e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), + ) + self.assertEqual( + sorted(await e.get_implicit_permissions_for_user("alice", "domain1")), + sorted( + [ + ["alice", "domain1", "data2", "read"], + ["role:reader", "domain1", "data1", "read"], + ["role:writer", "domain1", "data1", "write"], + ] + ), + ) + self.assertEqual(await e.get_implicit_permissions_for_user("bob", "domain1"), []) + + async def test_enforce_implicit_permissions_api_with_domain_matching_function(self): + + e = self.get_enforcer( + get_examples("rbac_with_domain_and_policy_pattern_model.conf"), + get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), + ) + await e.load_policy() + + e.get_role_manager().add_domain_matching_func(casbin.util.key_match2_func) + + self.assertEqual( + await e.get_implicit_permissions_for_user("alice", "domain.3"), + [["user", "domain.*", "data3", "read"]], + ) + + self.assertEqual( + await e.get_implicit_permissions_for_user("alice", "domain.1"), + [ + ["user", "domain.*", "data3", "read"], + ["user", "domain.1", "data2", "read"], + ["user", "domain.1", "data2", "write"], + ], + ) + + self.assertEqual( + await e.get_implicit_permissions_for_user("bob", "domain.3"), + [["admin", "domain.*", "data1", "read"]], + ) + + self.assertEqual( + await e.get_implicit_permissions_for_user("bob", "domain.2"), + [], + ) + + self.assertEqual(sorted(await e.get_implicit_permissions_for_user("bob", "domain.1")), []) + + async def test_enforce_implicit_permissions_api_with_domain_ignore_domain_policies_filter( + self, + ): + e = self.get_enforcer( + get_examples("rbac_with_domains_without_policy_matcher.conf"), + get_examples("rbac_with_hierarchy_without_policy_domains.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain1"), ["role:global_admin"]) + self.assertEqual( + sorted(await e.get_implicit_roles_for_user("alice", "domain1")), + sorted(["role:global_admin", "role:reader", "role:writer"]), + ) + self.assertEqual( + sorted(await e.get_implicit_permissions_for_user("alice", "domain1", filter_policy_dom=False)), + sorted( + [ + ["alice", "data2", "read"], + ["role:reader", "data1", "read"], + ["role:writer", "data1", "write"], + ] + ), + ) + self.assertEqual(await e.get_implicit_permissions_for_user("bob", "domain1"), []) + + async def test_enforce_get_users_in_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + print(e.get_users_for_role_in_domain("admin", "domain1")) + self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain1") == ["alice"]) + self.assertTrue(await e.get_users_for_role_in_domain("non_exist", "domain1") == []) + self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) + self.assertTrue(await e.get_users_for_role_in_domain("non_exist", "domain2") == []) + await e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + await e.add_role_for_user_in_domain("bob", "admin", "domain1") + self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain1") == ["bob"]) + self.assertTrue(await e.get_users_for_role_in_domain("non_exist", "domain1") == []) + self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) + self.assertTrue(await e.get_users_for_role_in_domain("non_exist", "domain2") == []) + + async def test_enforce_user_api_with_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_users_for_role_in_domain("admin", "domain1"), ["alice"]) + self.assertEqual(await e.get_users_for_role_in_domain("non_exist", "domain1"), []) + self.assertEqual(await e.get_users_for_role_in_domain("admin", "domain2"), ["bob"]) + self.assertEqual(await e.get_users_for_role_in_domain("non_exist", "domain2"), []) + + await e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + await e.add_role_for_user_in_domain("bob", "admin", "domain1") + + self.assertEqual(await e.get_users_for_role_in_domain("admin", "domain1"), ["bob"]) + self.assertEqual(await e.get_users_for_role_in_domain("non_exist", "domain1"), []) + self.assertEqual(await e.get_users_for_role_in_domain("admin", "domain2"), ["bob"]) + self.assertEqual(await e.get_users_for_role_in_domain("non_exist", "domain2"), []) + + async def test_enforce_get_roles_with_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain1"), ["admin"]) + self.assertEqual(await e.get_roles_for_user_in_domain("bob", "domain1"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("admin", "domain1"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("non_exist", "domain1"), []) + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain2"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("bob", "domain2"), ["admin"]) + self.assertEqual(await e.get_roles_for_user_in_domain("admin", "domain2"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("non_exist", "domain2"), []) + + await e.delete_roles_for_user_in_domain("alice", "admin", "domain1") + await e.add_role_for_user_in_domain("bob", "admin", "domain1") + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain1"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("bob", "domain1"), ["admin"]) + self.assertEqual(await e.get_roles_for_user_in_domain("admin", "domain1"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("non_exist", "domain1"), []) + + self.assertEqual(await e.get_roles_for_user_in_domain("alice", "domain2"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("bob", "domain2"), ["admin"]) + self.assertEqual(await e.get_roles_for_user_in_domain("admin", "domain2"), []) + self.assertEqual(await e.get_roles_for_user_in_domain("non_exist", "domain2"), []) + + async def test_implicit_user_api(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_with_hierarchy_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(["alice"], await e.get_implicit_users_for_permission("data1", "read")) + self.assertEqual(["alice"], await e.get_implicit_users_for_permission("data1", "write")) + self.assertEqual(["alice"], await e.get_implicit_users_for_permission("data2", "read")) + self.assertEqual(["alice", "bob"], await e.get_implicit_users_for_permission("data2", "write")) + + async def test_domain_match_model(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_pattern_model.conf"), + get_examples("rbac_with_domain_pattern_policy.csv"), + ) + await e.load_policy() + + e.get_role_manager().add_domain_matching_func(casbin.util.key_match2_func) + + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain1", "data2", "write")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "read")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "write")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "read")) + self.assertFalse(e.enforce("bob", "domain2", "data1", "write")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) + self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) From f34bd8646b370c0cdac7c607266479d6b4e1f818 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 6 Aug 2023 16:09:44 +0000 Subject: [PATCH 287/349] chore(release): 1.23.0 [skip ci] # [1.23.0](https://github.com/casbin/pycasbin/compare/v1.22.0...v1.23.0) (2023-08-06) ### Features * add AsyncEnforcer to pycasbin ([#307](https://github.com/casbin/pycasbin/issues/307)) ([4192dc2](https://github.com/casbin/pycasbin/commit/4192dc22dab3673b44c87d7dee059bd790c12c60)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09637dfc..cb6902da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.23.0](https://github.com/casbin/pycasbin/compare/v1.22.0...v1.23.0) (2023-08-06) + + +### Features + +* add AsyncEnforcer to pycasbin ([#307](https://github.com/casbin/pycasbin/issues/307)) ([4192dc2](https://github.com/casbin/pycasbin/commit/4192dc22dab3673b44c87d7dee059bd790c12c60)) + # [1.22.0](https://github.com/casbin/pycasbin/compare/v1.21.0...v1.22.0) (2023-07-14) diff --git a/setup.cfg b/setup.cfg index c01828a4..c91113c1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.22.0 +version = 1.23.0 From fea35086dee6e166433135efb7a7812ea3df764b Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Mon, 7 Aug 2023 12:04:41 +0800 Subject: [PATCH 288/349] docs: add async enforcer tutorials to README.md (#312) --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3a93ff95..cabed825 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ PyCasbin casdoor -**News**: Async version PyCasbin can be found at: https://pypi.org/project/asynccasbin/ +**News**: Async is now supported by Pycasbin >= 1.23.0! **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ @@ -44,6 +44,7 @@ production-ready | production-ready | beta-test | production-ready - [Policy management](#policy-management) - [Policy persistence](#policy-persistence) - [Role manager](#role-manager) +- [Async Enforcer](#async-enforcer) - [Benchmarks](#benchmarks) - [Examples](#examples) - [Middlewares](#middlewares) @@ -211,6 +212,63 @@ https://casbin.org/docs/adapters https://casbin.org/docs/role-managers +## Async Enforcer + +If your code use `async` / `await` and is heavily dependent on I/O operations, you can adopt Async Enforcer! + +1. Create an async engine and new a Casbin AsyncEnforcer with a model file and an async Pycasbin adapter: + +```python +import asyncio +import os + +import casbin +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker + +from casbin_async_sqlalchemy_adapter import Adapter, CasbinRule + + +async def get_enforcer(): + engine = create_async_engine("sqlite+aiosqlite://", future=True) + adapter = Adapter(engine) + await adapter.create_table() + + async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession) + async with async_session() as s: + s.add(CasbinRule(ptype="p", v0="alice", v1="data1", v2="read")) + s.add(CasbinRule(ptype="p", v0="bob", v1="data2", v2="write")) + s.add(CasbinRule(ptype="p", v0="data2_admin", v1="data2", v2="read")) + s.add(CasbinRule(ptype="p", v0="data2_admin", v1="data2", v2="write")) + s.add(CasbinRule(ptype="g", v0="alice", v1="data2_admin")) + await s.commit() + + e = casbin.AsyncEnforcer("path/to/model.conf", adapter) + await e.load_policy() + return e +``` + +Note: you can see all supported adapters in [Adapters | Casbin](https://casbin.org/docs/adapters). + +2. Add an enforcement hook into your code right before the access happens: + +```python +async def main(): + e = await get_enforcer() + if e.enforce("alice", "data1", "read"): + print("alice can read data1") + else: + print("alice can not read data1") +``` + +3. Run the code: + +```python +asyncio.run(main()) +``` + +4. Please refer to the ``tests`` files for more usage. + ## Benchmarks https://casbin.org/docs/benchmark From 647c4d6bb36bc5a839d250a23d9c91d603aebddc Mon Sep 17 00:00:00 2001 From: Aditya Nambiar Date: Wed, 9 Aug 2023 22:07:49 -0700 Subject: [PATCH 289/349] fix: fix bug in add policy for priority effectors (#313) --- casbin/model/policy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 55f7a681..ab446e39 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -118,11 +118,12 @@ def add_policy(self, sec, ptype, rule): print(e) if idx > idx_insert: + tmp = assertion.policy[i] assertion.policy[i] = assertion.policy[i - 1] + assertion.policy[i - 1] = tmp else: break - assertion.policy[i] = rule assertion.policy_map[DEFAULT_SEP.join(rule)] = i except Exception as e: From 19ea80aaffea31457b44fd4278bb594359708bd5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 10 Aug 2023 05:12:35 +0000 Subject: [PATCH 290/349] chore(release): 1.23.1 [skip ci] ## [1.23.1](https://github.com/casbin/pycasbin/compare/v1.23.0...v1.23.1) (2023-08-10) ### Bug Fixes * fix bug in add policy for priority effectors ([#313](https://github.com/casbin/pycasbin/issues/313)) ([8d30537](https://github.com/casbin/pycasbin/commit/8d30537149eeea433441ca62802f3bf06e1fd42c)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb6902da..2b3b2a2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.23.1](https://github.com/casbin/pycasbin/compare/v1.23.0...v1.23.1) (2023-08-10) + + +### Bug Fixes + +* fix bug in add policy for priority effectors ([#313](https://github.com/casbin/pycasbin/issues/313)) ([8d30537](https://github.com/casbin/pycasbin/commit/8d30537149eeea433441ca62802f3bf06e1fd42c)) + # [1.23.0](https://github.com/casbin/pycasbin/compare/v1.22.0...v1.23.0) (2023-08-06) diff --git a/setup.cfg b/setup.cfg index c91113c1..7268c25f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.23.0 +version = 1.23.1 From 4c5bf839e4427f72a49eca948446789d8ee40371 Mon Sep 17 00:00:00 2001 From: Jon Date: Tue, 22 Aug 2023 18:32:42 +0800 Subject: [PATCH 291/349] docs: Django authorization is coming --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cabed825..294904ce 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ PyCasbin casdoor +**News**: 🔥 How to use it with `Django` ? Try [Django Authorization](https://github.com/pycasbin/django-authorization), an authorization library for `Django` framework. + **News**: Async is now supported by Pycasbin >= 1.23.0! **News**: still worry about how to write the correct Casbin policy? ``Casbin online editor`` is coming to help! Try it at: http://casbin.org/editor/ From ddbb222860a735e64584602008af04586ac98f07 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Sun, 27 Aug 2023 22:57:49 +0800 Subject: [PATCH 292/349] feat: add field_index_map and constant (#314) * feat: add constants * feat: add field_index_map --- casbin/async_internal_enforcer.py | 12 ++++++++++-- casbin/constant/__init__.py | 0 casbin/constant/constants.py | 26 ++++++++++++++++++++++++++ casbin/effect/__init__.py | 15 +++++++++++---- casbin/internal_enforcer.py | 11 +++++++++-- casbin/model/assertion.py | 2 ++ casbin/model/model.py | 23 +++++++++++++++++++++-- casbin/synced_enforcer.py | 9 +++++++++ tests/test_rbac_api.py | 21 +++++++++++++++++++-- 9 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 casbin/constant/__init__.py create mode 100644 casbin/constant/constants.py diff --git a/casbin/async_internal_enforcer.py b/casbin/async_internal_enforcer.py index 9950ece6..e62a8bd2 100644 --- a/casbin/async_internal_enforcer.py +++ b/casbin/async_internal_enforcer.py @@ -13,9 +13,9 @@ # limitations under the License. import copy +from casbin.core_enforcer import CoreEnforcer from casbin.model import Model, FunctionMap from casbin.persist import Adapter -from casbin.core_enforcer import CoreEnforcer from casbin.persist.adapters.async_file_adapter import AsyncFileAdapter @@ -160,7 +160,6 @@ async def _update_policy(self, sec, ptype, old_rule, new_rule): return rule_updated if self.adapter and self.auto_save: - result = await self.adapter.update_policy(sec, ptype, old_rule, new_rule) if result is False: return False @@ -289,3 +288,12 @@ async def _remove_filtered_policy_returns_effects(self, sec, ptype, field_index, self.watcher.update() return rule_removed + + async def get_field_index(self, ptype, field): + """gets the index of the field name.""" + return self.model.get_field_index(ptype, field) + + async def set_field_index(self, ptype, field, index): + """sets the index of the field name.""" + assertion = self.model["p"][ptype] + assertion.field_index_map[field] = index diff --git a/casbin/constant/__init__.py b/casbin/constant/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/casbin/constant/constants.py b/casbin/constant/constants.py new file mode 100644 index 00000000..c5b89b3e --- /dev/null +++ b/casbin/constant/constants.py @@ -0,0 +1,26 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +# Index constants +DOMAIN_INDEX = "dom" +SUBJECT_INDEX = "sub" +OBJECT_INDEX = "obj" +PRIORITY_INDEX = "priority" + +# Effect constants +ALLOW_OVERRIDE_EFFECT = "some(where (p_eft == allow))" +DENY_OVERRIDE_EFFECT = "!some(where (p_eft == deny))" +ALLOW_AND_DENY_EFFECT = "some(where (p_eft == allow)) && !some(where (p_eft == deny))" +PRIORITY_EFFECT = "priority(p_eft) || deny" +SUBJECT_PRIORITY_EFFECT = "subjectPriority(p_eft) || deny" diff --git a/casbin/effect/__init__.py b/casbin/effect/__init__.py index ede8aa8f..d32abb89 100644 --- a/casbin/effect/__init__.py +++ b/casbin/effect/__init__.py @@ -19,18 +19,25 @@ PriorityEffector, ) from .effector import Effector +from ..constant.constants import ( + ALLOW_OVERRIDE_EFFECT, + SUBJECT_PRIORITY_EFFECT, + PRIORITY_EFFECT, + DENY_OVERRIDE_EFFECT, + ALLOW_AND_DENY_EFFECT, +) def get_effector(expr): """creates an effector based on the current policy effect expression""" - if expr == "some(where (p_eft == allow))": + if expr == ALLOW_OVERRIDE_EFFECT: return AllowOverrideEffector() - elif expr == "!some(where (p_eft == deny))": + elif expr == DENY_OVERRIDE_EFFECT: return DenyOverrideEffector() - elif expr == "some(where (p_eft == allow)) && !some(where (p_eft == deny))": + elif expr == ALLOW_AND_DENY_EFFECT: return AllowAndDenyEffector() - elif expr == "priority(p_eft) || deny" or expr == "subjectPriority(p_eft) || deny": + elif expr == PRIORITY_EFFECT or expr == SUBJECT_PRIORITY_EFFECT: return PriorityEffector() else: raise RuntimeError("unsupported effect") diff --git a/casbin/internal_enforcer.py b/casbin/internal_enforcer.py index d5ec77b8..fe62071e 100644 --- a/casbin/internal_enforcer.py +++ b/casbin/internal_enforcer.py @@ -67,7 +67,6 @@ def _update_policy(self, sec, ptype, old_rule, new_rule): return rule_updated if self.adapter and self.auto_save: - if self.adapter.update_policy(sec, ptype, old_rule, new_rule) is False: return False @@ -84,7 +83,6 @@ def _update_policies(self, sec, ptype, old_rules, new_rules): return rules_updated if self.adapter and self.auto_save: - if self.adapter.update_policies(sec, ptype, old_rules, new_rules) is False: return False @@ -189,3 +187,12 @@ def _remove_filtered_policy_returns_effects(self, sec, ptype, field_index, *fiel self.watcher.update() return rule_removed + + def get_field_index(self, ptype, field): + """gets the index of the field name.""" + return self.model.get_field_index(ptype, field) + + def set_field_index(self, ptype, field, index): + """sets the index of the field name.""" + assertion = self.model["p"][ptype] + assertion.field_index_map[field] = index diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 3e92d11c..672d9454 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -13,6 +13,7 @@ # limitations under the License. import logging + from casbin.model.policy_op import PolicyOp @@ -26,6 +27,7 @@ def __init__(self): self.rm = None self.priority_index: int = -1 self.policy_map: dict = {} + self.field_index_map: dict = {} def build_role_links(self, rm): self.rm = rm diff --git a/casbin/model/model.py b/casbin/model/model.py index 188b9f56..143bd0a4 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from . import Assertion from casbin import util, config +from . import Assertion from .policy import Policy DEFAULT_DOMAIN = "" @@ -21,7 +21,6 @@ class Model(Policy): - section_name_map = { "r": "request_definition", "p": "policy_definition", @@ -207,3 +206,23 @@ def write_string(sec): s[-1] = s[-1].strip() return "".join(s) + + def get_field_index(self, ptype, field): + """get_field_index gets the index of the field for a ptype in a policy, + return -1 if the field does not exist.""" + assertion = self["p"][ptype] + if field in assertion.field_index_map: + return assertion.field_index_map[field] + + pattern = f"{ptype}_{field}" + index = -1 + for i, token in enumerate(assertion.tokens): + if token == pattern: + index = i + break + + if index == -1: + return index + + assertion.field_index_map[field] = index + return index diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 40f008d4..adcf3fce 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -628,3 +628,12 @@ def build_incremental_role_links(self, op, ptype, rules): def new_enforce_context(self, suffix: str) -> "EnforceContext": return self._e.new_enforce_context(suffix) + + def get_field_index(self, ptype, field): + """gets the index of the field name.""" + return self._e.model.get_field_index(ptype, field) + + def set_field_index(self, ptype, field, index): + """sets the index of the field name.""" + assertion = self._e.model["p"][ptype] + assertion.field_index_map[field] = index diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 847ff0d1..6c0ef439 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -14,6 +14,7 @@ from unittest import IsolatedAsyncioTestCase import casbin +from casbin.constant.constants import DOMAIN_INDEX from tests.test_enforcer import get_examples, TestCaseBase @@ -254,7 +255,6 @@ def test_enforce_implicit_permissions_api_with_domain(self): self.assertEqual(e.get_implicit_permissions_for_user("bob", "domain1"), []) def test_enforce_implicit_permissions_api_with_domain_matching_function(self): - e = self.get_enforcer( get_examples("rbac_with_domain_and_policy_pattern_model.conf"), get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), @@ -405,6 +405,15 @@ def test_domain_match_model(self): self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) + def test_set_field_index(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertEqual(e.get_field_index("p", DOMAIN_INDEX), 1) + e.set_field_index("p", DOMAIN_INDEX, 2) + self.assertEqual(e.get_field_index("p", DOMAIN_INDEX), 2) + class TestRbacApiSynced(TestRbacApi): def get_enforcer(self, model=None, adapter=None): @@ -686,7 +695,6 @@ async def test_enforce_implicit_permissions_api_with_domain(self): self.assertEqual(await e.get_implicit_permissions_for_user("bob", "domain1"), []) async def test_enforce_implicit_permissions_api_with_domain_matching_function(self): - e = self.get_enforcer( get_examples("rbac_with_domain_and_policy_pattern_model.conf"), get_examples("rbac_with_domain_and_policy_pattern_policy.csv"), @@ -847,3 +855,12 @@ async def test_domain_match_model(self): self.assertFalse(e.enforce("bob", "domain2", "data1", "write")) self.assertTrue(e.enforce("bob", "domain2", "data2", "read")) self.assertTrue(e.enforce("bob", "domain2", "data2", "write")) + + async def test_set_field_index(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertEqual(await e.get_field_index("p", DOMAIN_INDEX), 1) + await e.set_field_index("p", DOMAIN_INDEX, 2) + self.assertEqual(await e.get_field_index("p", DOMAIN_INDEX), 2) From 70d4ea9968780850163206601afe5d8f5c4fa8f3 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 27 Aug 2023 15:01:55 +0000 Subject: [PATCH 293/349] chore(release): 1.24.0 [skip ci] # [1.24.0](https://github.com/casbin/pycasbin/compare/v1.23.1...v1.24.0) (2023-08-27) ### Features * add field_index_map and constant ([#314](https://github.com/casbin/pycasbin/issues/314)) ([e42f3da](https://github.com/casbin/pycasbin/commit/e42f3da582f6d864c1c70b00679af9da560fe0f4)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b3b2a2a..cb763d7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.24.0](https://github.com/casbin/pycasbin/compare/v1.23.1...v1.24.0) (2023-08-27) + + +### Features + +* add field_index_map and constant ([#314](https://github.com/casbin/pycasbin/issues/314)) ([e42f3da](https://github.com/casbin/pycasbin/commit/e42f3da582f6d864c1c70b00679af9da560fe0f4)) + ## [1.23.1](https://github.com/casbin/pycasbin/compare/v1.23.0...v1.23.1) (2023-08-10) diff --git a/setup.cfg b/setup.cfg index 7268c25f..fc480ca2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.23.1 +version = 1.24.0 From f1af270b576923891eb119c5aeab1e0ca2f3e193 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Wed, 30 Aug 2023 12:42:09 +0800 Subject: [PATCH 294/349] feat: add key_match5 function for matcher (#315) --- casbin/model/function.py | 1 + casbin/util/builtin_operators.py | 27 ++++++++++++++++++++ tests/util/test_builtin_operators.py | 38 ++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/casbin/model/function.py b/casbin/model/function.py index fdd4a227..0d0b0c50 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -31,6 +31,7 @@ def load_function_map(): fm.add_function("keyMatch2", util.key_match2_func) fm.add_function("keyMatch3", util.key_match3_func) fm.add_function("keyMatch4", util.key_match4_func) + fm.add_function("keyMatch5", util.key_match5_func) fm.add_function("regexMatch", util.regex_match_func) fm.add_function("ipMatch", util.ip_match_func) fm.add_function("globMatch", util.glob_match_func) diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 1b09d1ef..84965f45 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -18,6 +18,7 @@ KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+?}(.*?)") KEY_MATCH4_PATTERN = re.compile(r"{([^/]+)}") +KEY_MATCH5_PATTERN = re.compile(r"{[^/]+}") def key_match(key1, key2): @@ -194,6 +195,32 @@ def key_match4_func(*args) -> bool: return key_match4(name1, name2) +def key_match5(key1: str, key2: str) -> bool: + """ + key_match5 determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a * + For example, + - "/foo/bar?status=1&type=2" matches "/foo/bar" + - "/parent/child1" and "/parent/child1" matches "/parent/*" + - "/parent/child1?status=1" matches "/parent/*" + """ + i = key1.find("?") + if i != -1: + key1 = key1[:i] + + key2 = key2.replace("/*", "/.*") + + key2 = KEY_MATCH5_PATTERN.sub(r"[^/]+", key2, 0) + + return regex_match(key1, "^" + key2 + "$") + + +def key_match5_func(*args) -> bool: + name1 = args[0] + name2 = args[1] + + return key_match5(name1, name2) + + def regex_match(key1, key2): """determines whether key1 matches the pattern of key2 in regular expression.""" diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index 2dacf0b2..b0b47da6 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -13,6 +13,7 @@ # limitations under the License. from unittest import TestCase + from casbin import util @@ -190,6 +191,43 @@ def test_key_match4(self): self.assertFalse(util.key_match4_func("/parent/123/child/123", "/parent/{i/d}/child/{i/d}")) + def test_key_match5_func(self): + self.assertTrue(util.key_match5_func("/parent/child?status=1&type=2", "/parent/child")) + self.assertFalse(util.key_match5_func("/parent?status=1&type=2", "/parent/child")) + + self.assertTrue(util.key_match5_func("/parent/child/?status=1&type=2", "/parent/child/")) + self.assertFalse(util.key_match5_func("/parent/child/?status=1&type=2", "/parent/child")) + self.assertFalse(util.key_match5_func("/parent/child?status=1&type=2", "/parent/child/")) + + self.assertTrue(util.key_match5_func("/foo", "/foo")) + self.assertTrue(util.key_match5_func("/foo", "/foo*")) + self.assertFalse(util.key_match5_func("/foo", "/foo/*")) + self.assertFalse(util.key_match5_func("/foo/bar", "/foo")) + self.assertFalse(util.key_match5_func("/foo/bar", "/foo*")) + self.assertTrue(util.key_match5_func("/foo/bar", "/foo/*")) + self.assertFalse(util.key_match5_func("/foobar", "/foo")) + self.assertFalse(util.key_match5_func("/foobar", "/foo*")) + self.assertFalse(util.key_match5_func("/foobar", "/foo/*")) + + self.assertFalse(util.key_match5_func("/", "/{resource}")) + self.assertTrue(util.key_match5_func("/resource1", "/{resource}")) + self.assertFalse(util.key_match5_func("/myid", "/{id}/using/{resId}")) + self.assertTrue(util.key_match5_func("/myid/using/myresid", "/{id}/using/{resId}")) + + self.assertFalse(util.key_match5_func("/proxy/myid", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res/res2", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res/res2/res3", "/proxy/{id}/*")) + self.assertFalse(util.key_match5_func("/proxy/", "/proxy/{id}/*")) + + self.assertFalse(util.key_match5_func("/proxy/myid?status=1&type=2", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res?status=1&type=2", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res/res2?status=1&type=2", "/proxy/{id}/*")) + self.assertTrue(util.key_match5_func("/proxy/myid/res/res2/res3?status=1&type=2", "/proxy/{id}/*")) + self.assertFalse(util.key_match5_func("/proxy/", "/proxy/{id}/*")) + def test_regex_match(self): self.assertTrue(util.regex_match_func("/topic/create", "/topic/create")) self.assertTrue(util.regex_match_func("/topic/create/123", "/topic/create")) From e0eb7b0c23f62ad73bf516ec89ba181d726cf5f5 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 30 Aug 2023 04:45:19 +0000 Subject: [PATCH 295/349] chore(release): 1.25.0 [skip ci] # [1.25.0](https://github.com/casbin/pycasbin/compare/v1.24.0...v1.25.0) (2023-08-30) ### Features * add key_match5 function for matcher ([#315](https://github.com/casbin/pycasbin/issues/315)) ([7d29109](https://github.com/casbin/pycasbin/commit/7d29109d67d8f60c6be9fca6b4aaff523a8c156e)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb763d7c..22b24e29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.25.0](https://github.com/casbin/pycasbin/compare/v1.24.0...v1.25.0) (2023-08-30) + + +### Features + +* add key_match5 function for matcher ([#315](https://github.com/casbin/pycasbin/issues/315)) ([7d29109](https://github.com/casbin/pycasbin/commit/7d29109d67d8f60c6be9fca6b4aaff523a8c156e)) + # [1.24.0](https://github.com/casbin/pycasbin/compare/v1.23.1...v1.24.0) (2023-08-27) diff --git a/setup.cfg b/setup.cfg index fc480ca2..5e613608 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.24.0 +version = 1.25.0 From 0eb8d27b9d0ccfa577fc374b392f4c853f6ecf90 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Thu, 31 Aug 2023 17:40:25 +0800 Subject: [PATCH 296/349] feat: add get_all_roles_by_domain api (#316) * feat: add get_all_roles_by_domain api * feat: use set to improve performance --- casbin/async_enforcer.py | 14 ++++++++++ casbin/enforcer.py | 14 ++++++++++ casbin/synced_enforcer.py | 6 +++++ examples/rbac_with_domains_policy2.csv | 9 +++++++ tests/test_rbac_api.py | 36 ++++++++++++++++++++++++++ 5 files changed, 79 insertions(+) create mode 100644 examples/rbac_with_domains_policy2.csv diff --git a/casbin/async_enforcer.py b/casbin/async_enforcer.py index ce6740aa..1535cb25 100644 --- a/casbin/async_enforcer.py +++ b/casbin/async_enforcer.py @@ -253,3 +253,17 @@ async def get_permissions_for_user_in_domain(self, user, domain): async def get_named_permissions_for_user_in_domain(self, ptype, user, domain): """gets permissions for a user or role with named policy inside domain.""" return self.get_filtered_named_policy(ptype, 0, user, domain) + + async def get_all_roles_by_domain(self, domain): + """gets all roles associated with the domain. + note: Not applicable to Domains with inheritance relationship (implicit roles)""" + g = self.model.model["g"]["g"] + policies = g.policy + roles = set() + for policy in policies: + if policy[len(policy) - 1] == domain: + role = policy[len(policy) - 2] + if role not in roles: + roles.add(role) + + return list(roles) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index aab27333..1b967182 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -262,3 +262,17 @@ def get_permissions_for_user_in_domain(self, user, domain): def get_named_permissions_for_user_in_domain(self, ptype, user, domain): """gets permissions for a user or role with named policy inside domain.""" return self.get_filtered_named_policy(ptype, 0, user, domain) + + def get_all_roles_by_domain(self, domain): + """gets all roles associated with the domain. + note: Not applicable to Domains with inheritance relationship (implicit roles)""" + g = self.model.model["g"]["g"] + policies = g.policy + roles = set() + for policy in policies: + if policy[len(policy) - 1] == domain: + role = policy[len(policy) - 2] + if role not in roles: + roles.add(role) + + return list(roles) diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index adcf3fce..0eb401cf 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -637,3 +637,9 @@ def set_field_index(self, ptype, field, index): """sets the index of the field name.""" assertion = self._e.model["p"][ptype] assertion.field_index_map[field] = index + + def get_all_roles_by_domain(self, domain): + """gets all roles associated with the domain. + note: Not applicable to Domains with inheritance relationship (implicit roles)""" + with self._rl: + return self._e.get_all_roles_by_domain(domain) diff --git a/examples/rbac_with_domains_policy2.csv b/examples/rbac_with_domains_policy2.csv new file mode 100644 index 00000000..baa06f06 --- /dev/null +++ b/examples/rbac_with_domains_policy2.csv @@ -0,0 +1,9 @@ +p, admin, domain1, data1, read +p, admin, domain1, data1, write +p, admin, domain2, data2, read +p, admin, domain2, data2, write +p, user, domain3, data2, read +g, alice, admin, domain1 +g, alice, admin, domain2 +g, bob, admin, domain2 +g, bob, user, domain3 diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 6c0ef439..82a3540c 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -376,6 +376,22 @@ def test_enforce_get_roles_with_domain(self): self.assertEqual(e.get_roles_for_user_in_domain("admin", "domain2"), []) self.assertEqual(e.get_roles_for_user_in_domain("non_exist", "domain2"), []) + def test_get_all_roles_by_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + self.assertEqual(e.get_all_roles_by_domain("domain1"), ["admin"]) + self.assertEqual(e.get_all_roles_by_domain("domain2"), ["admin"]) + + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy2.csv"), + ) + self.assertEqual(e.get_all_roles_by_domain("domain1"), ["admin"]) + self.assertEqual(e.get_all_roles_by_domain("domain2"), ["admin"]) + self.assertEqual(e.get_all_roles_by_domain("domain3"), ["user"]) + def test_implicit_user_api(self): e = self.get_enforcer( get_examples("rbac_model.conf"), @@ -824,6 +840,26 @@ async def test_enforce_get_roles_with_domain(self): self.assertEqual(await e.get_roles_for_user_in_domain("admin", "domain2"), []) self.assertEqual(await e.get_roles_for_user_in_domain("non_exist", "domain2"), []) + async def test_get_all_roles_by_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_all_roles_by_domain("domain1"), ["admin"]) + self.assertEqual(await e.get_all_roles_by_domain("domain2"), ["admin"]) + + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy2.csv"), + ) + await e.load_policy() + + self.assertEqual(await e.get_all_roles_by_domain("domain1"), ["admin"]) + self.assertEqual(await e.get_all_roles_by_domain("domain2"), ["admin"]) + self.assertEqual(await e.get_all_roles_by_domain("domain3"), ["user"]) + async def test_implicit_user_api(self): e = self.get_enforcer( get_examples("rbac_model.conf"), From 3151b4a1957f0cfb5106703a9656510b4e7db53d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 31 Aug 2023 09:47:00 +0000 Subject: [PATCH 297/349] chore(release): 1.26.0 [skip ci] # [1.26.0](https://github.com/casbin/pycasbin/compare/v1.25.0...v1.26.0) (2023-08-31) ### Features * add get_all_roles_by_domain api ([#316](https://github.com/casbin/pycasbin/issues/316)) ([22507ca](https://github.com/casbin/pycasbin/commit/22507ca7cc47746a03a97b1d051e209ea4ef96d1)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22b24e29..bcc64b0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.26.0](https://github.com/casbin/pycasbin/compare/v1.25.0...v1.26.0) (2023-08-31) + + +### Features + +* add get_all_roles_by_domain api ([#316](https://github.com/casbin/pycasbin/issues/316)) ([22507ca](https://github.com/casbin/pycasbin/commit/22507ca7cc47746a03a97b1d051e209ea4ef96d1)) + # [1.25.0](https://github.com/casbin/pycasbin/compare/v1.24.0...v1.25.0) (2023-08-30) diff --git a/setup.cfg b/setup.cfg index 5e613608..afc81190 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.25.0 +version = 1.26.0 From 76941778213a4875a3948dddbe821b141ac8e632 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Sun, 3 Sep 2023 17:26:57 +0800 Subject: [PATCH 298/349] feat: add get_implicit_users_for_resource api (#317) --- casbin/async_enforcer.py | 59 +++++++++++++++++++++++ casbin/enforcer.py | 59 +++++++++++++++++++++++ casbin/model/policy.py | 1 - casbin/synced_enforcer.py | 20 ++++++++ tests/test_rbac_api.py | 99 ++++++++++++++++++++++++++++++++++++++- 5 files changed, 236 insertions(+), 2 deletions(-) diff --git a/casbin/async_enforcer.py b/casbin/async_enforcer.py index 1535cb25..f61322f7 100644 --- a/casbin/async_enforcer.py +++ b/casbin/async_enforcer.py @@ -267,3 +267,62 @@ async def get_all_roles_by_domain(self, domain): roles.add(role) return list(roles) + + async def get_implicit_users_for_resource(self, resource): + """gets implicit user based on resource. + for example: + p, alice, data1, read + p, bob, data2, write + p, data2_admin, data2, read + p, data2_admin, data2, write + g, alice, data2_admin + get_implicit_users_for_resource("data2") will return [[bob data2 write] [alice data2 read] [alice data2 write]] + get_implicit_users_for_resource("data1") will return [[alice data1 read]] + Note: only users will be returned, roles (2nd arg in "g") will be excluded.""" + permissions = dict() + subject_index = await self.get_field_index("p", "sub") + object_index = await self.get_field_index("p", "obj") + rm = self.get_role_manager() + roles = self.get_all_roles() + + for rule in self.get_policy(): + if rule[object_index] == resource: + sub = rule[subject_index] + if sub not in roles: + permissions[tuple(rule)] = True + else: + users = rm.get_users(sub) + for user in users: + implicit_rule = rule.copy() + implicit_rule[subject_index] = user + permissions[tuple(implicit_rule)] = True + + permissions = [list(t) for t in (list(key) for key in permissions.keys())] + return permissions + + async def get_implicit_users_for_resource_by_domain(self, resource, domain): + """get implicit user based on resource and domain. + Compared to GetImplicitUsersForResource, domain is supported""" + permissions = dict() + subject_index = await self.get_field_index("p", "sub") + object_index = await self.get_field_index("p", "obj") + dom_index = await self.get_field_index("p", "dom") + rm = self.get_role_manager() + roles = await self.get_all_roles_by_domain(domain) + + for rule in self.get_policy(): + if rule[object_index] == resource: + sub = rule[subject_index] + if sub not in roles: + permissions[tuple(rule)] = True + else: + if domain != rule[dom_index]: + continue + users = rm.get_users(sub, domain) + for user in users: + implicit_rule = rule.copy() + implicit_rule[subject_index] = user + permissions[tuple(implicit_rule)] = True + + permissions = [list(t) for t in (list(key) for key in permissions.keys())] + return permissions diff --git a/casbin/enforcer.py b/casbin/enforcer.py index 1b967182..a1777f9c 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -276,3 +276,62 @@ def get_all_roles_by_domain(self, domain): roles.add(role) return list(roles) + + def get_implicit_users_for_resource(self, resource): + """gets implicit user based on resource. + for example: + p, alice, data1, read + p, bob, data2, write + p, data2_admin, data2, read + p, data2_admin, data2, write + g, alice, data2_admin + get_implicit_users_for_resource("data2") will return [[bob data2 write] [alice data2 read] [alice data2 write]] + get_implicit_users_for_resource("data1") will return [[alice data1 read]] + Note: only users will be returned, roles (2nd arg in "g") will be excluded.""" + permissions = dict() + subject_index = self.get_field_index("p", "sub") + object_index = self.get_field_index("p", "obj") + rm = self.get_role_manager() + roles = self.get_all_roles() + + for rule in self.get_policy(): + if rule[object_index] == resource: + sub = rule[subject_index] + if sub not in roles: + permissions[tuple(rule)] = True + else: + users = rm.get_users(sub) + for user in users: + implicit_rule = rule.copy() + implicit_rule[subject_index] = user + permissions[tuple(implicit_rule)] = True + + permissions = [list(t) for t in (list(key) for key in permissions.keys())] + return permissions + + def get_implicit_users_for_resource_by_domain(self, resource, domain): + """get implicit user based on resource and domain. + Compared to GetImplicitUsersForResource, domain is supported""" + permissions = dict() + subject_index = self.get_field_index("p", "sub") + object_index = self.get_field_index("p", "obj") + dom_index = self.get_field_index("p", "dom") + rm = self.get_role_manager() + roles = self.get_all_roles_by_domain(domain) + + for rule in self.get_policy(): + if rule[object_index] == resource: + sub = rule[subject_index] + if sub not in roles: + permissions[tuple(rule)] = True + else: + if domain != rule[dom_index]: + continue + users = rm.get_users(sub, domain) + for user in users: + implicit_rule = rule.copy() + implicit_rule[subject_index] = user + permissions[tuple(implicit_rule)] = True + + permissions = [list(t) for t in (list(key) for key in permissions.keys())] + return permissions diff --git a/casbin/model/policy.py b/casbin/model/policy.py index ab446e39..66f6986d 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -111,7 +111,6 @@ def add_policy(self, sec, ptype, rule): i = len(assertion.policy) - 1 for i in range(i, 0, -1): - try: idx = int(assertion.policy[i - 1][assertion.priority_index]) except Exception as e: diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 0eb401cf..5e457917 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -643,3 +643,23 @@ def get_all_roles_by_domain(self, domain): note: Not applicable to Domains with inheritance relationship (implicit roles)""" with self._rl: return self._e.get_all_roles_by_domain(domain) + + def get_implicit_users_for_resource(self, resource): + """gets implicit user based on resource. + for example: + p, alice, data1, read + p, bob, data2, write + p, data2_admin, data2, read + p, data2_admin, data2, write + g, alice, data2_admin + get_implicit_users_for_resource("data2") will return [[bob data2 write] [alice data2 read] [alice data2 write]] + get_implicit_users_for_resource("data1") will return [[alice data1 read]] + Note: only users will be returned, roles (2nd arg in "g") will be excluded.""" + with self._rl: + return self._e.get_implicit_users_for_resource(resource) + + def get_implicit_users_for_resource_by_domain(self, resource, domain): + """get implicit user based on resource and domain. + Compared to GetImplicitUsersForResource, domain is supported""" + with self._rl: + return self._e.get_implicit_users_for_resource_by_domain(resource, domain) diff --git a/tests/test_rbac_api.py b/tests/test_rbac_api.py index 82a3540c..6171feda 100644 --- a/tests/test_rbac_api.py +++ b/tests/test_rbac_api.py @@ -403,6 +403,53 @@ def test_implicit_user_api(self): self.assertEqual(["alice"], e.get_implicit_users_for_permission("data2", "read")) self.assertEqual(["alice", "bob"], e.get_implicit_users_for_permission("data2", "write")) + def test_get_implicit_users_for_resource(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + + self.assertEqual([["alice", "data1", "read"]], e.get_implicit_users_for_resource("data1")) + self.assertEqual( + [ + ["bob", "data2", "write"], + ["alice", "data2", "read"], + ["alice", "data2", "write"], + ], + e.get_implicit_users_for_resource("data2"), + ) + + # test duplicate permissions + e.add_grouping_policy("alice", "data2_admin_2") + e.add_policies([["data2_admin_2", "data2", "read"], ["data2_admin_2", "data2", "write"]]) + self.assertEqual( + [ + ["bob", "data2", "write"], + ["alice", "data2", "read"], + ["alice", "data2", "write"], + ], + e.get_implicit_users_for_resource("data2"), + ) + + def test_get_implicit_users_for_resource_by_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + + self.assertEqual( + [["alice", "domain1", "data1", "read"], ["alice", "domain1", "data1", "write"]], + e.get_implicit_users_for_resource_by_domain("data1", "domain1"), + ) + self.assertEqual( + [], + e.get_implicit_users_for_resource_by_domain("data2", "domain1"), + ) + self.assertEqual( + [["bob", "domain2", "data2", "read"], ["bob", "domain2", "data2", "write"]], + e.get_implicit_users_for_resource_by_domain("data2", "domain2"), + ) + def test_domain_match_model(self): e = self.get_enforcer( get_examples("rbac_with_domain_pattern_model.conf"), @@ -778,7 +825,6 @@ async def test_enforce_get_users_in_domain(self): ) await e.load_policy() - print(e.get_users_for_role_in_domain("admin", "domain1")) self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain1") == ["alice"]) self.assertTrue(await e.get_users_for_role_in_domain("non_exist", "domain1") == []) self.assertTrue(await e.get_users_for_role_in_domain("admin", "domain2") == ["bob"]) @@ -872,6 +918,57 @@ async def test_implicit_user_api(self): self.assertEqual(["alice"], await e.get_implicit_users_for_permission("data2", "read")) self.assertEqual(["alice", "bob"], await e.get_implicit_users_for_permission("data2", "write")) + async def test_get_implicit_users_for_resource(self): + e = self.get_enforcer( + get_examples("rbac_model.conf"), + get_examples("rbac_policy.csv"), + ) + await e.load_policy() + + self.assertEqual([["alice", "data1", "read"]], await e.get_implicit_users_for_resource("data1")) + result = await e.get_implicit_users_for_resource("data2") + self.assertEqual( + [ + ["bob", "data2", "write"], + ["alice", "data2", "read"], + ["alice", "data2", "write"], + ], + result, + ) + + # test duplicate permissions + await e.add_grouping_policy("alice", "data2_admin_2") + await e.add_policies([["data2_admin_2", "data2", "read"], ["data2_admin_2", "data2", "write"]]) + result = await e.get_implicit_users_for_resource("data2") + self.assertEqual( + [ + ["bob", "data2", "write"], + ["alice", "data2", "read"], + ["alice", "data2", "write"], + ], + result, + ) + + async def test_get_implicit_users_for_resource_by_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + ) + await e.load_policy() + + self.assertEqual( + [["alice", "domain1", "data1", "read"], ["alice", "domain1", "data1", "write"]], + await e.get_implicit_users_for_resource_by_domain("data1", "domain1"), + ) + self.assertEqual( + [], + await e.get_implicit_users_for_resource_by_domain("data2", "domain1"), + ) + self.assertEqual( + [["bob", "domain2", "data2", "read"], ["bob", "domain2", "data2", "write"]], + await e.get_implicit_users_for_resource_by_domain("data2", "domain2"), + ) + async def test_domain_match_model(self): e = self.get_enforcer( get_examples("rbac_with_domain_pattern_model.conf"), From 9139c57de6f41bd0d381453f8e239f16dbb5db0f Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 3 Sep 2023 09:31:33 +0000 Subject: [PATCH 299/349] chore(release): 1.27.0 [skip ci] # [1.27.0](https://github.com/casbin/pycasbin/compare/v1.26.0...v1.27.0) (2023-09-03) ### Features * add get_implicit_users_for_resource api ([#317](https://github.com/casbin/pycasbin/issues/317)) ([8172097](https://github.com/casbin/pycasbin/commit/817209768528f249644bbe7ac30f1d235bd192da)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc64b0d..fd4e5196 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.27.0](https://github.com/casbin/pycasbin/compare/v1.26.0...v1.27.0) (2023-09-03) + + +### Features + +* add get_implicit_users_for_resource api ([#317](https://github.com/casbin/pycasbin/issues/317)) ([8172097](https://github.com/casbin/pycasbin/commit/817209768528f249644bbe7ac30f1d235bd192da)) + # [1.26.0](https://github.com/casbin/pycasbin/compare/v1.25.0...v1.26.0) (2023-08-31) diff --git a/setup.cfg b/setup.cfg index afc81190..406024ad 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.26.0 +version = 1.27.0 From 25450264c1b3a03c7060d40ca8330fc5c16ca59a Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Sat, 16 Sep 2023 13:54:42 +0800 Subject: [PATCH 300/349] feat: port fastbin to casbin (#318) * feat: port fastbin * feat: implement FastEnforcer * fix: remove redundant init code --- casbin/__init__.py | 1 + casbin/core_enforcer.py | 6 +- casbin/distributed_enforcer.py | 6 +- casbin/fast_enforcer.py | 41 +++++++++++++ casbin/model/__init__.py | 4 +- casbin/model/model_fast.py | 36 ++++++++++++ casbin/model/policy_fast.py | 101 ++++++++++++++++++++++++++++++++ tests/__init__.py | 11 ++-- tests/model/__init__.py | 1 + tests/model/test_policy_fast.py | 101 ++++++++++++++++++++++++++++++++ tests/test_fast_enforcer.py | 80 +++++++++++++++++++++++++ 11 files changed, 373 insertions(+), 15 deletions(-) create mode 100644 casbin/fast_enforcer.py create mode 100644 casbin/model/model_fast.py create mode 100644 casbin/model/policy_fast.py create mode 100644 tests/model/test_policy_fast.py create mode 100644 tests/test_fast_enforcer.py diff --git a/casbin/__init__.py b/casbin/__init__.py index b0cd34d5..fa812588 100644 --- a/casbin/__init__.py +++ b/casbin/__init__.py @@ -15,6 +15,7 @@ from .enforcer import * from .synced_enforcer import SyncedEnforcer from .distributed_enforcer import DistributedEnforcer +from .fast_enforcer import FastEnforcer from .async_enforcer import AsyncEnforcer from . import util from .persist import * diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index b6c7468f..e90ecd45 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging import copy +import logging from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap @@ -202,7 +202,6 @@ def load_policy(self): new_model.clear_policy() try: - self.adapter.load_policy(new_model) new_model.sort_policies_by_subject_hierarchy() @@ -212,7 +211,6 @@ def load_policy(self): new_model.print_policy() if self.auto_build_role_links: - need_to_rebuild = True for rm in self.rm_map.values(): rm.clear() @@ -222,7 +220,6 @@ def load_policy(self): self.model = new_model except Exception as e: - if self.auto_build_role_links and need_to_rebuild: self.build_role_links() @@ -315,7 +312,6 @@ def add_named_domain_matching_func(self, ptype, fn): return False def new_enforce_context(self, suffix: str) -> EnforceContext: - return EnforceContext( rtype="r" + suffix, ptype="p" + suffix, diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index 8703ab50..a5e3b7b9 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -12,12 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from casbin import SyncedEnforcer -import logging - -from casbin.persist import batch_adapter from casbin.model.policy_op import PolicyOp +from casbin.persist import batch_adapter from casbin.persist.adapters import update_adapter +from casbin.synced_enforcer import SyncedEnforcer class DistributedEnforcer(SyncedEnforcer): diff --git a/casbin/fast_enforcer.py b/casbin/fast_enforcer.py new file mode 100644 index 00000000..72e38ce1 --- /dev/null +++ b/casbin/fast_enforcer.py @@ -0,0 +1,41 @@ +import logging +from typing import Sequence + +from casbin.enforcer import Enforcer +from casbin.model import Model, FastModel, fast_policy_filter, FunctionMap +from casbin.persist.adapters import FileAdapter +from casbin.util.log import configure_logging + + +class FastEnforcer(Enforcer): + _cache_key_order: Sequence[int] = None + + def __init__(self, model=None, adapter=None, enable_log=False, cache_key_order: Sequence[int] = None): + self._cache_key_order = cache_key_order + super().__init__(model, adapter, enable_log) + + def new_model(self, path="", text=""): + """creates a model.""" + if self._cache_key_order is None: + m = Model() + else: + m = FastModel(self._cache_key_order) + if len(path) > 0: + m.load_model(path) + else: + m.load_model_from_text(text) + + return m + + def enforce(self, *rvals): + """decides whether a "subject" can access a "object" with the operation "action", + input parameters are usually: (sub, obj, act). + """ + if FastEnforcer._cache_key_order is None: + result, _ = self.enforce_ex(*rvals) + else: + keys = [rvals[x] for x in self._cache_key_order] + with fast_policy_filter(self.model.model["p"]["p"].policy, *keys): + result, _ = self.enforce_ex(*rvals) + + return result diff --git a/casbin/model/__init__.py b/casbin/model/__init__.py index 5491ba83..c54d1402 100644 --- a/casbin/model/__init__.py +++ b/casbin/model/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. from .assertion import Assertion +from .function import FunctionMap from .model import Model +from .model_fast import FastModel from .policy import Policy -from .function import FunctionMap +from .policy_fast import FastPolicy, fast_policy_filter diff --git a/casbin/model/model_fast.py b/casbin/model/model_fast.py new file mode 100644 index 00000000..ed7a3d71 --- /dev/null +++ b/casbin/model/model_fast.py @@ -0,0 +1,36 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from typing import Any, Sequence + +from .model import Model +from .policy_fast import FastPolicy + + +class FastModel(Model): + _cache_key_order: Sequence[int] + + def __init__(self, cache_key_order: Sequence[int]) -> None: + super().__init__() + self._cache_key_order = cache_key_order + + def add_def(self, sec: str, key: str, value: Any) -> None: + super().add_def(sec, key, value) + if sec == "p" and key == "p": + self.model[sec][key].policy = FastPolicy(self._cache_key_order) + + def clear_policy(self) -> None: + """clears all current policy.""" + super().clear_policy() + self.model["p"]["p"].policy = FastPolicy(self._cache_key_order) diff --git a/casbin/model/policy_fast.py b/casbin/model/policy_fast.py new file mode 100644 index 00000000..acb7ddbb --- /dev/null +++ b/casbin/model/policy_fast.py @@ -0,0 +1,101 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from contextlib import contextmanager +from typing import Any, Container, Dict, Iterable, Iterator, Optional, Sequence, Set, cast + + +def in_cache(cache: Dict[str, Any], keys: Sequence[str]) -> Optional[Set[Sequence[str]]]: + if keys[0] in cache: + if len(keys) > 1: + return in_cache(cache[keys[-0]], keys[1:]) + return cast(Set[Sequence[str]], cache[keys[0]]) + else: + return None + + +class FastPolicy(Container[Sequence[str]]): + _cache: Dict[str, Any] + _current_filter: Optional[Set[Sequence[str]]] + _cache_key_order: Sequence[int] + + def __init__(self, cache_key_order: Sequence[int]) -> None: + self._cache = {} + self._current_filter = None + self._cache_key_order = cache_key_order + + def __iter__(self) -> Iterator[Sequence[str]]: + yield from self.__get_policy() + + def __len__(self) -> int: + return len(list(self.__get_policy())) + + def __contains__(self, item: object) -> bool: + if not isinstance(item, (list, tuple)) or len(self._cache_key_order) >= len(item): + return False + keys = [item[x] for x in self._cache_key_order] + exists = in_cache(self._cache, keys) + if not exists: + return False + return tuple(item) in exists + + def __getitem__(self, item: int) -> Sequence[str]: + for i, entry in enumerate(self): + if i == item: + return entry + raise KeyError("No such value exists") + + def append(self, item: Sequence[str]) -> None: + cache = self._cache + keys = [item[x] for x in self._cache_key_order] + + for key in keys[:-1]: + if key not in cache: + cache[key] = dict() + cache = cache[key] + if keys[-1] not in cache: + cache[keys[-1]] = set() + + cache[keys[-1]].add(tuple(item)) + + def remove(self, policy: Sequence[str]) -> bool: + keys = [policy[x] for x in self._cache_key_order] + exists = in_cache(self._cache, keys) + if not exists: + return True + + exists.remove(tuple(policy)) + return True + + def __get_policy(self) -> Iterable[Sequence[str]]: + if self._current_filter is not None: + return (list(x) for x in self._current_filter) + else: + return (list(v2) for v in self._cache.values() for v1 in v.values() for v2 in v1) + + def apply_filter(self, *keys: str) -> None: + value = in_cache(self._cache, keys) + self._current_filter = value or set() + + def clear_filter(self) -> None: + self._current_filter = None + + +@contextmanager +def fast_policy_filter(policy: FastPolicy, *keys: str) -> Iterator[None]: + try: + policy.apply_filter(*keys) + yield + finally: + policy.clear_filter() diff --git a/tests/__init__.py b/tests/__init__.py index e4493206..6e64b186 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,14 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +from . import benchmarks +from . import config +from . import model +from . import rbac +from . import util from .test_distributed_api import TestDistributedApi from .test_enforcer import * +from .test_fast_enforcer import TestFastEnforcer from .test_filter import TestFilteredAdapter from .test_frontend import TestFrontend from .test_management_api import TestManagementApi, TestManagementApiSynced from .test_rbac_api import TestRbacApi, TestRbacApiSynced -from . import benchmarks -from . import config -from . import model -from . import rbac -from . import util diff --git a/tests/model/__init__.py b/tests/model/__init__.py index 528fed7a..900ee454 100644 --- a/tests/model/__init__.py +++ b/tests/model/__init__.py @@ -13,3 +13,4 @@ # limitations under the License. from .test_policy import TestPolicy +from .test_policy_fast import TestContextManager, TestFastPolicy diff --git a/tests/model/test_policy_fast.py b/tests/model/test_policy_fast.py new file mode 100644 index 00000000..52cb1022 --- /dev/null +++ b/tests/model/test_policy_fast.py @@ -0,0 +1,101 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +from unittest import TestCase + +from casbin.model import FastPolicy, fast_policy_filter + + +class TestFastPolicy(TestCase): + def test_able_to_add_rules(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + + assert list(policy) == [["sub", "obj", "read"]] + + def test_does_not_add_duplicates(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + policy.append(["sub", "obj", "read"]) + + assert list(policy) == [["sub", "obj", "read"]] + + def test_can_remove_rules(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + policy.remove(["sub", "obj", "read"]) + + assert list(policy) == [] + + def test_returns_lengtt(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + + assert len(policy) == 1 + + def test_supports_in_keyword(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + + assert ["sub", "obj", "read"] in policy + + def test_supports_filters(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + policy.append(["sub", "obj", "read2"]) + policy.append(["sub", "obj2", "read2"]) + + policy.apply_filter("read2", "obj2") + + assert list(policy) == [["sub", "obj2", "read2"]] + + def test_clears_filters(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + policy.append(["sub", "obj", "read2"]) + policy.append(["sub", "obj2", "read2"]) + + policy.apply_filter("read2", "obj2") + policy.clear_filter() + + assert list(policy) == [ + ["sub", "obj", "read"], + ["sub", "obj", "read2"], + ["sub", "obj2", "read2"], + ] + + +class TestContextManager: + def test_fast_policy_filter(self) -> None: + policy = FastPolicy([2, 1]) + + policy.append(["sub", "obj", "read"]) + policy.append(["sub", "obj", "read2"]) + policy.append(["sub", "obj2", "read2"]) + + with fast_policy_filter(policy, "read2", "obj2"): + assert list(policy) == [["sub", "obj2", "read2"]] + + assert list(policy) == [ + ["sub", "obj", "read"], + ["sub", "obj", "read2"], + ["sub", "obj2", "read2"], + ] diff --git a/tests/test_fast_enforcer.py b/tests/test_fast_enforcer.py new file mode 100644 index 00000000..b2a2eccb --- /dev/null +++ b/tests/test_fast_enforcer.py @@ -0,0 +1,80 @@ +# Copyright 2023 The casbin Authors. All Rights Reserved. +# +# 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. + +import os +from typing import Sequence +from unittest import TestCase + +import casbin +from casbin import FastPolicy + + +def get_examples(path): + examples_path = os.path.split(os.path.realpath(__file__))[0] + "/../examples/" + return os.path.abspath(examples_path + path) + + +class TestCaseBase(TestCase): + def get_enforcer(self, model=None, adapter=None, cache_key_order: Sequence[int] = None): + return casbin.FastEnforcer( + model, + adapter, + cache_key_order=cache_key_order, + ) + + +class TestFastEnforcer(TestCaseBase): + def test_creates_proper_policy(self) -> None: + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + [2, 1], + ) + self.assertIsInstance(e.model.model["p"]["p"].policy, FastPolicy) + + def test_initializes_model(self) -> None: + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + [2, 1], + ) + self.assertEqual( + list(e.model.model["p"]["p"].policy), + [ + ["alice", "data1", "read"], + ["bob", "data2", "write"], + ], + ) + + def test_able_to_clear_policy(self) -> None: + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + [2, 1], + ) + + e.clear_policy() + + self.assertIsInstance(e.model.model["p"]["p"].policy, FastPolicy) + self.assertEqual(list(e.model.model["p"]["p"].policy), []) + + def test_able_to_enforce_rule(self) -> None: + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + [2, 1], + ) + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertFalse(e.enforce("alice2", "data1", "read")) From e02f41dfbade6fecf228e7d7445ea7084e3b00d0 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 16 Sep 2023 05:59:07 +0000 Subject: [PATCH 301/349] chore(release): 1.28.0 [skip ci] # [1.28.0](https://github.com/casbin/pycasbin/compare/v1.27.0...v1.28.0) (2023-09-16) ### Features * port fastbin to casbin ([#318](https://github.com/casbin/pycasbin/issues/318)) ([67537d6](https://github.com/casbin/pycasbin/commit/67537d6799d04266f5d27bc3f86fa44db1676821)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd4e5196..858c0750 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.28.0](https://github.com/casbin/pycasbin/compare/v1.27.0...v1.28.0) (2023-09-16) + + +### Features + +* port fastbin to casbin ([#318](https://github.com/casbin/pycasbin/issues/318)) ([67537d6](https://github.com/casbin/pycasbin/commit/67537d6799d04266f5d27bc3f86fa44db1676821)) + # [1.27.0](https://github.com/casbin/pycasbin/compare/v1.26.0...v1.27.0) (2023-09-03) diff --git a/setup.cfg b/setup.cfg index 406024ad..90f99960 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.27.0 +version = 1.28.0 From cba6ed9707dba47f892d27d481ca5b3bd6e3365d Mon Sep 17 00:00:00 2001 From: AmisAdmin <34331297+amisadmin@users.noreply.github.com> Date: Sat, 23 Sep 2023 21:53:19 +0800 Subject: [PATCH 302/349] feat: Fixed the `subjectPriority` sorting algorithm and support for checking the subject role link loop (#322) * fix: Fixed the `subjectPriority` sorting algorithm and support for checking the subject role link loop. * fix: Run black --- casbin/model/model.py | 63 ++++++++++++++++++++------------------- tests/model/test_model.py | 47 +++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 31 deletions(-) create mode 100644 tests/model/test_model.py diff --git a/casbin/model/model.py b/casbin/model/model.py index 143bd0a4..1b91ba54 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -134,14 +134,20 @@ def compare_policy(policy): name = self.get_name_with_domain(domain, policy[sub_index]) return subject_hierarchy_map.get(name, 0) - assertion.policy = sorted(assertion.policy, key=compare_policy, reverse=True) + assertion.policy = sorted(assertion.policy, key=compare_policy) for i, policy in enumerate(assertion.policy): assertion.policy_map[",".join(policy)] = i def get_subject_hierarchy_map(self, policies): - subject_hierarchy_map = {} - # Tree structure of role - policy_map = {} + """ + Get the subject hierarchy from the policy. + Select the lowest level subject in multiple rounds until all subjects are selected. + Return the subject hierarchy dictionary, the subject is the key, and the level is the value. + The level starts from 0 and increases in turn. The smaller the level, the higher the priority. + """ + # Init unsorted policy, and subject + unsorted_policy = [] + unsorted_sub = set() for policy in policies: if len(policy) < 2: raise RuntimeError("policy g expect 2 more params") @@ -150,33 +156,28 @@ def get_subject_hierarchy_map(self, policies): domain = policy[2] child = self.get_name_with_domain(domain, policy[0]) parent = self.get_name_with_domain(domain, policy[1]) - if parent not in policy_map.keys(): - policy_map[parent] = [child] - else: - policy_map[parent].append(child) - if child not in subject_hierarchy_map.keys(): - subject_hierarchy_map[child] = 0 - if parent not in subject_hierarchy_map.keys(): - subject_hierarchy_map[parent] = 0 - subject_hierarchy_map[child] = 1 - # Use queues for levelOrder - queue = [] - for k, v in subject_hierarchy_map.items(): - root = k - if v != 0: - continue - lv = 0 - queue.append(root) - while len(queue) != 0: - sz = len(queue) - for _ in range(sz): - node = queue.pop(0) - subject_hierarchy_map[node] = lv - if node in policy_map.keys(): - for child in policy_map[node]: - queue.append(child) - lv += 1 - return subject_hierarchy_map + unsorted_policy.append([child, parent]) + unsorted_sub.add(child) + unsorted_sub.add(parent) + # sort policy,and update sorted_sub_list + sorted_sub_list = [] + while len(unsorted_policy) > 0: + # get all parent subject + parent_sub = {p[1] for p in unsorted_policy if p[1] != ""} + # remove parent subject from unsorted_sub + sorted_sub = unsorted_sub - parent_sub + if not sorted_sub: + raise RuntimeError("cycle dependency in subject hierarchy.subjects: {}".format(unsorted_sub)) + # update sorted_sub_list + sorted_sub_list.append(sorted_sub) + # remove sorted subject, and update unsorted_policy + unsorted_policy = [p for p in unsorted_policy if p[0] not in sorted_sub] + # update unsorted_sub + unsorted_sub = unsorted_sub - sorted_sub + if len(unsorted_sub) > 0: + sorted_sub_list.append(unsorted_sub) + # Tree structure of subject + return {sub: i for i, subs in enumerate(sorted_sub_list) for sub in subs} def get_name_with_domain(self, domain, name): return "{}{}{}".format(domain, DEFAULT_SEPARATOR, name) diff --git a/tests/model/test_model.py b/tests/model/test_model.py new file mode 100644 index 00000000..99187555 --- /dev/null +++ b/tests/model/test_model.py @@ -0,0 +1,47 @@ +from unittest import TestCase + +from casbin import Model +from casbin.model.model import DEFAULT_DOMAIN + + +class TestModel(TestCase): + m = Model() + + def check_hierarchy(self, policies: list, subject_hierarchy_map: dict): + """check_hierarchy checks the hierarchy of the subject hierarchy map""" + for policy in policies: + if len(policy) < 2: + raise RuntimeError("policy g expect 2 more params") + domain = DEFAULT_DOMAIN + if len(policy) != 2: + domain = policy[2] + child = self.m.get_name_with_domain(domain, policy[0]) + parent = self.m.get_name_with_domain(domain, policy[1]) + assert subject_hierarchy_map[child] < subject_hierarchy_map[parent] + + def test_get_subject_hierarchy_map(self): + # test 1 + policies = [ + ["A1", "B1"], + ["A1", "B2"], + ["A2", "B3"], + ] + res = self.m.get_subject_hierarchy_map(policies) + self.check_hierarchy(policies, res) + # test 2 + policies = [ + ["A1", "B1"], + ["B1", "B2"], + ["B2", "B3"], + ["B1", "B4"], + ["A1", "B2"], + ] + res = self.m.get_subject_hierarchy_map(policies) + self.check_hierarchy(policies, res) + # test 3 + policies = [ + ["B1", "B2"], + ["B2", "B3"], + ["B3", "B1"], + ] + self.assertRaises(RuntimeError, self.m.get_subject_hierarchy_map, policies) From 7f77c9ff3072b39e4117e2fb3f8c3de3e25a3f91 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 23 Sep 2023 14:00:33 +0000 Subject: [PATCH 303/349] chore(release): 1.29.0 [skip ci] # [1.29.0](https://github.com/casbin/pycasbin/compare/v1.28.0...v1.29.0) (2023-09-23) ### Features * Fixed the `subjectPriority` sorting algorithm and support for checking the subject role link loop ([#322](https://github.com/casbin/pycasbin/issues/322)) ([f964e2a](https://github.com/casbin/pycasbin/commit/f964e2aecbee28e4f9b8d1b4fba0d68cad0879fa)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 858c0750..51dbd4c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.29.0](https://github.com/casbin/pycasbin/compare/v1.28.0...v1.29.0) (2023-09-23) + + +### Features + +* Fixed the `subjectPriority` sorting algorithm and support for checking the subject role link loop ([#322](https://github.com/casbin/pycasbin/issues/322)) ([f964e2a](https://github.com/casbin/pycasbin/commit/f964e2aecbee28e4f9b8d1b4fba0d68cad0879fa)) + # [1.28.0](https://github.com/casbin/pycasbin/compare/v1.27.0...v1.28.0) (2023-09-16) diff --git a/setup.cfg b/setup.cfg index 90f99960..de346eba 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.28.0 +version = 1.29.0 From f7b4942192fb8e5614143725262a7fe3dc96cd15 Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Sat, 23 Sep 2023 22:02:56 +0800 Subject: [PATCH 304/349] feat: disable logger when log is not enabled (#321) --- casbin/core_enforcer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index e90ecd45..b729745e 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -69,6 +69,8 @@ def __init__(self, model=None, adapter=None, enable_log=False): if enable_log: configure_logging() + else: + logging.disable(logging.CRITICAL) def init_with_file(self, model_path, policy_path): """initializes an enforcer with a model file and a policy file.""" From 6df894f540652c91146a4734484359a0a2622302 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 23 Sep 2023 14:08:15 +0000 Subject: [PATCH 305/349] chore(release): 1.30.0 [skip ci] # [1.30.0](https://github.com/casbin/pycasbin/compare/v1.29.0...v1.30.0) (2023-09-23) ### Features * disable logger when log is not enabled ([#321](https://github.com/casbin/pycasbin/issues/321)) ([952c208](https://github.com/casbin/pycasbin/commit/952c208a9d423a86ac4ae6c7fa5ed15bc94e00dc)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51dbd4c7..f00eeda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.30.0](https://github.com/casbin/pycasbin/compare/v1.29.0...v1.30.0) (2023-09-23) + + +### Features + +* disable logger when log is not enabled ([#321](https://github.com/casbin/pycasbin/issues/321)) ([952c208](https://github.com/casbin/pycasbin/commit/952c208a9d423a86ac4ae6c7fa5ed15bc94e00dc)) + # [1.29.0](https://github.com/casbin/pycasbin/compare/v1.28.0...v1.29.0) (2023-09-23) diff --git a/setup.cfg b/setup.cfg index de346eba..9f1f1f8d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.29.0 +version = 1.30.0 From 846a5d106b8ae6601d667c5de30392bbc3e2de3d Mon Sep 17 00:00:00 2001 From: Haozhe Pan Date: Sun, 24 Sep 2023 09:55:06 +0800 Subject: [PATCH 306/349] feat: add temporal role model (#320) --- casbin/core_enforcer.py | 75 +++++++- casbin/management_enforcer.py | 4 + casbin/model/assertion.py | 47 +++++ casbin/model/function.py | 1 + casbin/model/model.py | 18 ++ casbin/model/policy.py | 14 ++ casbin/rbac/__init__.py | 2 +- casbin/rbac/default_role_manager/__init__.py | 2 +- .../rbac/default_role_manager/role_manager.py | 174 ++++++++++++++++++ casbin/rbac/role_manager.py | 26 +++ casbin/synced_enforcer.py | 22 +++ casbin/util/builtin_operators.py | 44 +++++ ...domain_temporal_roles_condition_policy.csv | 15 ++ ...rbac_with_domain_temporal_roles_model.conf | 14 ++ ...rbac_with_domain_temporal_roles_policy.csv | 24 +++ ...c_with_temporal_roles_condition_policy.csv | 15 ++ examples/rbac_with_temporal_roles_model.conf | 14 ++ examples/rbac_with_temporal_roles_policy.csv | 24 +++ tests/test_enforcer.py | 138 +++++++++++++- tests/util/test_builtin_operators.py | 9 + 20 files changed, 670 insertions(+), 12 deletions(-) create mode 100644 examples/rbac_with_domain_temporal_roles_condition_policy.csv create mode 100644 examples/rbac_with_domain_temporal_roles_model.conf create mode 100644 examples/rbac_with_domain_temporal_roles_policy.csv create mode 100644 examples/rbac_with_temporal_roles_condition_policy.csv create mode 100644 examples/rbac_with_temporal_roles_model.conf create mode 100644 examples/rbac_with_temporal_roles_policy.csv diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index b729745e..8b4d7b0c 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -20,7 +20,7 @@ from casbin.persist import Adapter from casbin.persist.adapters import FileAdapter from casbin.rbac import default_role_manager -from casbin.util import generate_g_function, SimpleEval, util +from casbin.util import generate_g_function, SimpleEval, util, generate_conditional_g_function from casbin.util.log import configure_logging @@ -47,6 +47,7 @@ class CoreEnforcer: adapter = None watcher = None rm_map = None + cond_rm_map = None enabled = False auto_save = False @@ -104,6 +105,7 @@ def init_with_model_and_adapter(self, m, adapter=None): def _initialize(self): self.rm_map = dict() + self.cond_rm_map = dict() self.eft = get_effector(self.model["e"]["e"].value) self.watcher = None @@ -192,10 +194,26 @@ def init_rm_map(self): if "g" in self.model.keys(): for ptype in self.model["g"]: assertion = self.model["g"][ptype] - if assertion.value.count("_") == 2: - self.rm_map[ptype] = default_role_manager.RoleManager(10) - else: - self.rm_map[ptype] = default_role_manager.DomainManager(10) + if ptype in self.rm_map: + rm = self.rm_map[ptype] + rm.clear() + continue + + if len(assertion.tokens) <= 2 and len(assertion.params_tokens) == 0: + assertion.rm = default_role_manager.RoleManager(10) + self.rm_map[ptype] = assertion.rm + + if len(assertion.tokens) <= 2 and len(assertion.params_tokens) != 0: + assertion.cond_rm = default_role_manager.ConditionalRoleManager(10) + self.cond_rm_map[ptype] = assertion.cond_rm + + if len(assertion.tokens) > 2: + if len(assertion.params_tokens) == 0: + assertion.rm = default_role_manager.DomainManager(10) + self.rm_map[ptype] = assertion.rm + else: + assertion.cond_rm = default_role_manager.ConditionalDomainManager(10) + self.cond_rm_map[ptype] = assertion.cond_rm def load_policy(self): """reloads the policy from file/database.""" @@ -216,8 +234,13 @@ def load_policy(self): need_to_rebuild = True for rm in self.rm_map.values(): rm.clear() + if len(self.rm_map) != 0: + new_model.build_role_links(self.rm_map) - new_model.build_role_links(self.rm_map) + for crm in self.cond_rm_map.values(): + crm.clear() + if len(self.cond_rm_map) != 0: + new_model.build_conditional_role_links(self.cond_rm_map) self.model = new_model @@ -313,6 +336,40 @@ def add_named_domain_matching_func(self, ptype, fn): return False + def add_named_link_condition_func(self, ptype, user, role, fn): + """Add condition function fn for Link userName->roleName, + when fn returns true, Link is valid, otherwise invalid""" + if ptype in self.cond_rm_map: + rm = self.cond_rm_map[ptype] + rm.add_link_condition_func(user, role, fn) + return True + return False + + def add_named_domain_link_condition_func(self, ptype, user, role, domain, fn): + """Add condition function fn for Link userName-> {roleName, domain}, + when fn returns true, Link is valid, otherwise invalid""" + if ptype in self.cond_rm_map: + rm = self.cond_rm_map[ptype] + rm.add_domain_link_condition_func(user, role, domain, fn) + return True + return False + + def set_named_link_condition_func_params(self, ptype, user, role, *params): + """Sets the parameters of the condition function fn for Link userName->roleName""" + if ptype in self.cond_rm_map: + rm = self.cond_rm_map[ptype] + rm.set_link_condition_func_params(user, role, *params) + return True + return False + + def set_named_domain_link_condition_func_params(self, ptype, user, role, domain, *params): + """Sets the parameters of the condition function fn for Link userName->{roleName, domain}""" + if ptype in self.cond_rm_map: + rm = self.cond_rm_map[ptype] + rm.set_domain_link_condition_func_params(user, role, domain, *params) + return True + return False + def new_enforce_context(self, suffix: str) -> EnforceContext: return EnforceContext( rtype="r" + suffix, @@ -346,8 +403,10 @@ def enforce_ex(self, *rvals): if "g" in self.model.keys(): for key, ast in self.model["g"].items(): - rm = ast.rm - functions[key] = generate_g_function(rm) + if len(self.rm_map) != 0: + functions[key] = generate_g_function(ast.rm) + if len(self.cond_rm_map) != 0: + functions[key] = generate_conditional_g_function(ast.cond_rm) if len(rvals) != 0: if isinstance(rvals[0], EnforceContext): diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index faacdf8e..b374ce10 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -238,6 +238,10 @@ def add_named_grouping_policy(self, ptype, *params): if self.auto_build_role_links: self.model.build_incremental_role_links(self.rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules) + if ptype in self.cond_rm_map: + self.model.build_incremental_conditional_role_links( + self.cond_rm_map[ptype], PolicyOp.Policy_add, "g", ptype, rules + ) return rule_added def add_named_grouping_policies(self, ptype, rules): diff --git a/casbin/model/assertion.py b/casbin/model/assertion.py index 672d9454..120ce344 100644 --- a/casbin/model/assertion.py +++ b/casbin/model/assertion.py @@ -23,8 +23,10 @@ def __init__(self): self.key = "" self.value = "" self.tokens = [] + self.params_tokens = [] self.policy = [] self.rm = None + self.cond_rm = None self.priority_index: int = -1 self.policy_map: dict = {} self.field_index_map: dict = {} @@ -62,3 +64,48 @@ def build_incremental_role_links(self, rm, op, rules): rm.delete_link(rule[0], rule[1], *rule[2:]) else: raise TypeError("Invalid operation: " + str(op)) + + def build_incremental_conditional_role_links(self, cond_rm, op, rules): + self.cond_rm = cond_rm + count = self.value.count("_") + if count < 2: + raise RuntimeError('the number of "_" in role definition should be at least 2') + + for rule in rules: + if len(rule) < count: + raise TypeError("grouping policy elements do not meet role definition") + if len(rule) > count: + rule = rule[:count] + + domain_rule = rule[2 : len(self.tokens)] + + if op == PolicyOp.Policy_add: + self.add_conditional_role_link(rule, domain_rule) + elif op == PolicyOp.Policy_remove: + self.cond_rm.delete_link(rule[0], rule[1], *rule[2:]) + else: + raise TypeError("Invalid operation: " + str(op)) + + def build_conditional_role_links(self, cond_rm): + self.cond_rm = cond_rm + count = self.value.count("_") + if count < 2: + raise RuntimeError('the number of "_" in role definition should be at least 2') + for rule in self.policy: + if len(rule) < count: + raise TypeError("grouping policy elements do not meet role definition") + if len(rule) > count: + rule = rule[:count] + + domain_rule = rule[2 : len(self.tokens)] + + self.add_conditional_role_link(rule, domain_rule) + + def add_conditional_role_link(self, rule, domain_rule): + if not domain_rule: + self.cond_rm.add_link(rule[0], rule[1]) + self.cond_rm.set_link_condition_func_params(rule[0], rule[1], *rule[len(self.tokens) :]) + else: + domain = domain_rule[0] + self.cond_rm.add_link(rule[0], rule[1], domain) + self.cond_rm.set_domain_link_condition_func_params(rule[0], rule[1], domain, *rule[len(self.tokens) :]) diff --git a/casbin/model/function.py b/casbin/model/function.py index 0d0b0c50..8448a6b5 100644 --- a/casbin/model/function.py +++ b/casbin/model/function.py @@ -35,6 +35,7 @@ def load_function_map(): fm.add_function("regexMatch", util.regex_match_func) fm.add_function("ipMatch", util.ip_match_func) fm.add_function("globMatch", util.glob_match_func) + fm.add_function("timeMatch", util.time_match_func) return fm diff --git a/casbin/model/model.py b/casbin/model/model.py index 1b91ba54..e33fd715 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -11,6 +11,7 @@ # 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. +import re from casbin import util, config from . import Assertion @@ -18,6 +19,7 @@ DEFAULT_DOMAIN = "" DEFAULT_SEPARATOR = "::" +PARAMS_REGEX = re.compile(r"\((.*?)\)") class Model(Policy): @@ -34,6 +36,18 @@ def _load_assertion(self, cfg, sec, key): return self.add_def(sec, key, value) + def get_params_token(self, value): + """get_params_token Get params_token from Assertion.value""" + # Find the matching string using the regular expression + params_string = PARAMS_REGEX.search(value) + + if params_string is None: + return [] + + # Extract the captured group (inside parentheses) and split it by commas + params_string = params_string.group(1) + return [param.strip() for param in params_string.split(",")] + def add_def(self, sec, key, value): if value == "": return @@ -46,6 +60,10 @@ def add_def(self, sec, key, value): ast.tokens = ast.value.split(",") for i, token in enumerate(ast.tokens): ast.tokens[i] = key + "_" + token.strip() + elif "g" == sec: + ast.params_tokens = self.get_params_token(ast.value) + ast.tokens = ast.value.split(",") + ast.tokens = ast.tokens[: len(ast.tokens) - len(ast.params_tokens)] else: ast.value = util.remove_comments(util.escape_assertion(ast.value)) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 66f6986d..01a4d8a9 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -51,6 +51,20 @@ def build_incremental_role_links(self, rm, op, sec, ptype, rules): if sec == "g": self[sec].get(ptype).build_incremental_role_links(rm, op, rules) + def build_incremental_conditional_role_links(self, cond_rm, op, sec, ptype, rules): + if sec == "g": + return self[sec].get(ptype).build_incremental_conditional_role_links(cond_rm, op, rules) + return None + + def build_conditional_role_links(self, cond_rm_map): + if "g" not in self.keys(): + return + self.print_policy() + for ptype, ast in self["g"].items(): + cond_rm = cond_rm_map.get(ptype) + if cond_rm: + ast.build_conditional_role_links(cond_rm) + def print_policy(self): """Log using info""" diff --git a/casbin/rbac/__init__.py b/casbin/rbac/__init__.py index b82b0732..a2432c0c 100644 --- a/casbin/rbac/__init__.py +++ b/casbin/rbac/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .role_manager import RoleManager +from .role_manager import RoleManager, ConditionalRoleManager diff --git a/casbin/rbac/default_role_manager/__init__.py b/casbin/rbac/default_role_manager/__init__.py index 45e45fde..428ed855 100644 --- a/casbin/rbac/default_role_manager/__init__.py +++ b/casbin/rbac/default_role_manager/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .role_manager import RoleManager, DomainManager +from .role_manager import RoleManager, DomainManager, ConditionalRoleManager, ConditionalDomainManager diff --git a/casbin/rbac/default_role_manager/role_manager.py b/casbin/rbac/default_role_manager/role_manager.py index 0fea3e8b..5ea0605b 100644 --- a/casbin/rbac/default_role_manager/role_manager.py +++ b/casbin/rbac/default_role_manager/role_manager.py @@ -17,6 +17,7 @@ from enum import Enum from casbin.rbac import RoleManager as RM +from casbin.rbac import ConditionalRoleManager as CRM Link = namedtuple("Link", ["user", "role"]) @@ -32,6 +33,8 @@ def __init__(self, name): self.name = name self.roles = set() self.users = set() + self.link_condition_func_map = dict() + self.link_condition_func_params_map = dict() def add_role(self, role): self.roles.add(role) @@ -74,6 +77,21 @@ def get_roles(self): return roles + def add_link_condition_func(self, role, domain, fn): + self.link_condition_func_map[(role.name, domain)] = fn + + def get_link_condition_func(self, role, domain): + key = (role.name, domain) + return self.link_condition_func_map.get(key) + + def set_link_condition_func_params(self, role, domain, *params): + self.link_condition_func_params_map[(role.name, domain)] = params + + def get_link_condition_func_params(self, role, domain): + key = (role.name, domain) + params = self.link_condition_func_params_map.get(key) + return list(params) if params is not None else [] + class RoleManager(RM): """provides a default implementation for the RoleManager interface""" @@ -349,3 +367,159 @@ def match_error_handler(fn, key1, key2): return fn(key1, key2) except: return False + + +class ConditionalRoleManager(RoleManager, CRM): + def has_link(self, name1, name2, *domains): + """determines whether role: name1 inherits role: name2.""" + if name1 == name2 or (self.matching_func is not None and self._matching_fn(name1, name2)): + return True + + user = self._get_role(name1) + role = self._get_role(name2) + + return self._has_link(role.name, [user], self.max_hierarchy_level, *domains) + + def _has_link(self, target_name, roles, level, *domains): + """use the Breadth First Search algorithm to traverse the Role tree + Judging whether the user has a role (has link) is to judge whether the role node can be reached from the user node + """ + if level < 0 or len(roles) == 0: + return False + + next_roles = set() + for role in roles: + if target_name == role.name or ( + self.matching_func is not None and self._matching_fn(role.name, target_name) + ): + return True + + for next_role in role.roles: + linked_role = self.get_next_roles(role, next_role, domains) + next_roles.update(set(linked_role)) + + return self._has_link(target_name, next_roles, level - 1, *domains) + + def get_next_roles(self, current_role, next_role, domains): + pass_link_condition_func = True + next_roles = list() + if not domains: + link_condition_func = self.get_link_condition_func(current_role.name, next_role.name) + if link_condition_func is not None: + params = self.get_link_condition_func_params(current_role.name, next_role.name) + pass_link_condition_func = link_condition_func(*params) + else: + link_condition_func = self.get_domain_link_condition_func(current_role.name, next_role.name, domains[0]) + if link_condition_func is not None: + params = self.get_link_condition_func_params(current_role.name, next_role.name, domains) + pass_link_condition_func = link_condition_func(*params) + + if pass_link_condition_func: + next_roles.append(next_role) + + return next_roles + + def get_link_condition_func(self, user_name, role_name): + """get_link_condition_func get link_condition_func based on userName, roleName""" + return self.get_domain_link_condition_func(user_name, role_name, "") + + def get_domain_link_condition_func(self, user_name, role_name, domain): + """get_domain_link_condition_func get link_condition_func based on userName, roleName, domain""" + user = self._get_role(user_name) + role = self._get_role(role_name) + + return user.get_link_condition_func(role, domain) + + def get_link_condition_func_params(self, user_name, role_name, domains=None): + """get_link_condition_func_params gets parameters of link_condition_func based on userName, roleName, domain""" + if domains is None: + domains = [] + + user = self._get_role(user_name) + role = self._get_role(role_name) + + domain_name = "" + if len(domains) != 0: + domain_name = domains[0] + + return user.get_link_condition_func_params(role, domain_name) + + def add_link_condition_func(self, user_name, role_name, fn): + """add_link_condition_func is based on userName, roleName, add LinkConditionFunc""" + self.add_domain_link_condition_func(user_name, role_name, "", fn) + + def add_domain_link_condition_func(self, user_name, role_name, domain, fn): + """add_domain_link_condition_func is based on userName, roleName, domain, add LinkConditionFunc""" + user = self._get_role(user_name) + role = self._get_role(role_name) + user.add_link_condition_func(role, domain, fn) + + def set_link_condition_func_params(self, user_name, role_name, *params): + """set_link_condition_func_params sets parameters of LinkConditionFunc based on userName, roleName, domain""" + self.set_domain_link_condition_func_params(user_name, role_name, "", *params) + + def set_domain_link_condition_func_params(self, user_name, role_name, domain, *params): + """set_domain_link_condition_func_params sets parameters of LinkConditionFunc based on userName, roleName, domain""" + user = self._get_role(user_name) + role = self._get_role(role_name) + user.set_link_condition_func_params(role, domain, *params) + + +class ConditionalDomainManager(DomainManager, ConditionalRoleManager): + def _get_role_manager(self, *domain): + domain1 = self._get_domain(*domain) + if domain1 not in self.rm_map: + self.rm_map[domain1] = self._get_conditional_role_manager(*domain) + + return self.rm_map[domain1] + + def _get_conditional_role_manager(self, *domain, store=False): + domain1 = self._get_domain(*domain) + domain_links = self.all_links.get(domain1, []) + + rm = self.rm_map.get(domain1, None) + + if rm is None: + rm = ConditionalRoleManager(max_hierarchy_level=self.max_hierarchy_level) + if store: + self.rm_map[domain1] = rm + if self.domain_matching_func is not None: + for domain2, links in self.all_links.items(): + if domain1 != domain2 and match_error_handler(self.domain_matching_func, domain1, domain2): + domain_links = domain_links + links + + rm.add_matching_func(self.matching_func) + for link in domain_links: + rm.add_link(link[0], link[1]) + return rm + + def has_link(self, name1, name2, *domain): + domain = self._get_domain(*domain) + rm = self._get_conditional_role_manager(domain) + return rm.has_link(name1, name2, domain) + + def add_link(self, name1, name2, *domain): + domain = self._get_domain(*domain) + rm = self._get_conditional_role_manager(domain, store=True) + rm.add_link(name1, name2, domain) + + def delete_link(self, name1, name2, *domain): + domain = self._get_domain(*domain) + rm = self._get_conditional_role_manager(domain, store=True) + rm.delete_link(name1, name2, domain) + + def add_link_condition_func(self, user_name, role_name, fn): + for rm in self.rm_map.values(): + rm.add_link_condition_func(user_name, role_name, fn) + + def add_domain_link_condition_func(self, user_name, role_name, domain, fn): + for rm in self.rm_map.values(): + rm.add_domain_link_condition_func(user_name, role_name, domain, fn) + + def set_link_condition_func_params(self, user_name, role_name, *params): + for rm in self.rm_map.values(): + rm.set_link_condition_func_params(user_name, role_name, *params) + + def set_domain_link_condition_func_params(self, user_name, role_name, domain, *params): + for rm in self.rm_map.values(): + rm.set_domain_link_condition_func_params(user_name, role_name, domain, *params) diff --git a/casbin/rbac/role_manager.py b/casbin/rbac/role_manager.py index 09153e7a..4b96d26a 100644 --- a/casbin/rbac/role_manager.py +++ b/casbin/rbac/role_manager.py @@ -36,3 +36,29 @@ def get_users(self, name, *domain): def print_roles(self): pass + + +class ConditionalRoleManager(RoleManager): + """ + ConditionalRoleManager provides interface to define the operations for managing roles. + Link with conditions is supported + """ + + def add_link_condition_func(self, user_name, role_name, fn): + """add_link_condition_func add condition function fn for Link user_name->role_name, + when fn returns true, Link is valid, otherwise invalid""" + pass + + def set_link_condition_func_params(self, user_name, role_name, *params): + """set_link_condition_func_params Sets the parameters of the condition function fn for Link user_name->role_name""" + pass + + def add_domain_link_condition_func(self, user, role, domain, fn): + """add_domain_link_condition_func Add condition function fn for Link user_name-> {role_name, domain}, + when fn returns true, Link is valid, otherwise invalid""" + pass + + def set_domain_link_condition_func_params(self, user, role, domain, *params): + """set_domain_link_condition_func_params Sets the parameters of the condition function fn + for Link user_name->{role_name, domain}""" + pass diff --git a/casbin/synced_enforcer.py b/casbin/synced_enforcer.py index 5e457917..5a4377c1 100644 --- a/casbin/synced_enforcer.py +++ b/casbin/synced_enforcer.py @@ -559,11 +559,33 @@ def add_named_matching_func(self, ptype, fn): with self._wl: self._e.add_named_matching_func(ptype, fn) + def add_named_link_condition_func(self, ptype, user, role, fn): + """Add condition function fn for Link userName->roleName, + when fn returns true, Link is valid, otherwise invalid""" + with self._wl: + self._e.add_named_link_condition_func(ptype, user, role, fn) + def add_named_domain_matching_func(self, ptype, fn): """add_named_domain_matching_func add MatchingFunc by ptype to RoleManager""" with self._wl: self._e.add_named_domain_matching_func(ptype, fn) + def add_named_domain_link_condition_func(self, ptype, user, role, domain, fn): + """Add condition function fn for Link userName-> {roleName, domain}, + when fn returns true, Link is valid, otherwise invalid""" + with self._wl: + self._e.add_named_domain_link_condition_func(ptype, user, role, domain, fn) + + def set_named_domain_link_condition_func_params(self, ptype, user, role, domain, *params): + """Sets the parameters of the condition function fn for Link userName->{roleName, domain}""" + with self._wl: + self._e.set_named_domain_link_condition_func_params(ptype, user, role, domain, *params) + + def set_named_link_condition_func_params(self, ptype, user, role, *params): + """Sets the parameters of the condition function fn for Link userName->roleName""" + with self._wl: + self._e.set_named_link_condition_func_params(ptype, user, role, *params) + def is_filtered(self): """returns true if the loaded policy has been filtered.""" with self._rl: diff --git a/casbin/util/builtin_operators.py b/casbin/util/builtin_operators.py index 84965f45..c39c3366 100644 --- a/casbin/util/builtin_operators.py +++ b/casbin/util/builtin_operators.py @@ -14,6 +14,7 @@ import ipaddress import re +from datetime import datetime KEY_MATCH2_PATTERN = re.compile(r"(.*?):[^\/]+(.*?)") KEY_MATCH3_PATTERN = re.compile(r"(.*?){[^\/]+?}(.*?)") @@ -401,3 +402,46 @@ def f(*args): return rm.has_link(name1, name2, domain) return f + + +def generate_conditional_g_function(crm): + """the factory method of the g(_, _[, _]) function with conditions.""" + + def conditional_g_function(*args): + name1, name2 = args[0], args[1] + + if crm is None: + has_link = name1 == name2 + elif len(args) == 2: + has_link = crm.has_link(name1, name2) + else: + domain = str(args[2]) + has_link = crm.has_link(name1, name2, domain) + + return has_link + + return conditional_g_function + + +def time_match_func(*args): + """the wrapper for TimeMatch.""" + if len(args) != 2: + raise RuntimeError("time_match requires 2 arguments") + + start_time, end_time = args[0], args[1] + + try: + now = datetime.now() + if start_time != "_": + start = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S") + if not now > start: + return False + + if end_time != "_": + end = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S") + if not now < end: + return False + + return True + except Exception as e: + raise RuntimeError(e) diff --git a/examples/rbac_with_domain_temporal_roles_condition_policy.csv b/examples/rbac_with_domain_temporal_roles_condition_policy.csv new file mode 100644 index 00000000..24772122 --- /dev/null +++ b/examples/rbac_with_domain_temporal_roles_condition_policy.csv @@ -0,0 +1,15 @@ +p, alice, domain1, data1, read +p, alice, domain1, data1, write +p, data2_admin, domain2, data2, read +p, data2_admin, domain2, data2, write +p, data3_admin, domain3, data3, read +p, data3_admin, domain3, data3, write +p, data4_admin, domain4, data4, read +p, data4_admin, domain4, data4, write +p, data5_admin, domain5, data5, read +p, data5_admin, domain5, data5, write + +g, alice, data2_admin, domain2, _, _ +g, alice, data3_admin, domain3, _, _ +g, alice, data4_admin, domain4, _, _ +g, alice, data5_admin, domain5, _, _ \ No newline at end of file diff --git a/examples/rbac_with_domain_temporal_roles_model.conf b/examples/rbac_with_domain_temporal_roles_model.conf new file mode 100644 index 00000000..e1ab8a41 --- /dev/null +++ b/examples/rbac_with_domain_temporal_roles_model.conf @@ -0,0 +1,14 @@ +[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 \ No newline at end of file diff --git a/examples/rbac_with_domain_temporal_roles_policy.csv b/examples/rbac_with_domain_temporal_roles_policy.csv new file mode 100644 index 00000000..c1999e2d --- /dev/null +++ b/examples/rbac_with_domain_temporal_roles_policy.csv @@ -0,0 +1,24 @@ +p, alice, domain1, data1, read +p, alice, domain1, data1, write +p, data2_admin, domain2, data2, read +p, data2_admin, domain2, data2, write +p, data3_admin, domain3, data3, read +p, data3_admin, domain3, data3, write +p, data4_admin, domain4, data4, read +p, data4_admin, domain4, data4, write +p, data5_admin, domain5, data5, read +p, data5_admin, domain5, data5, write +p, data6_admin, domain6, data6, read +p, data6_admin, domain6, data6, write +p, data7_admin, domain7, data7, read +p, data7_admin, domain7, data7, write +p, data8_admin, domain8, data8, read +p, data8_admin, domain8, data8, write + +g, alice, data2_admin, domain2, 0001-01-01 00:00:00, 0001-01-02 00:00:00 +g, alice, data3_admin, domain3, 0001-01-01 00:00:00, 9999-12-30 00:00:00 +g, alice, data4_admin, domain4, _, _ +g, alice, data5_admin, domain5, _, 9999-12-30 00:00:00 +g, alice, data6_admin, domain6, _, 0001-01-02 00:00:00 +g, alice, data7_admin, domain7, 0001-01-01 00:00:00, _ +g, alice, data8_admin, domain8, 9999-12-30 00:00:00, _ \ No newline at end of file diff --git a/examples/rbac_with_temporal_roles_condition_policy.csv b/examples/rbac_with_temporal_roles_condition_policy.csv new file mode 100644 index 00000000..89b90d3d --- /dev/null +++ b/examples/rbac_with_temporal_roles_condition_policy.csv @@ -0,0 +1,15 @@ +p, alice, data1, read +p, alice, data1, write +p, data2_admin, data2, read +p, data2_admin, data2, write +p, data3_admin, data3, read +p, data3_admin, data3, write +p, data4_admin, data4, read +p, data4_admin, data4, write +p, data5_admin, data5, read +p, data5_admin, data5, write + +g, alice, data2_admin, _, _ +g, alice, data3_admin, _, _ +g, alice, data4_admin, _, _ +g, alice, data5_admin, _, _ \ No newline at end of file diff --git a/examples/rbac_with_temporal_roles_model.conf b/examples/rbac_with_temporal_roles_model.conf new file mode 100644 index 00000000..feeae160 --- /dev/null +++ b/examples/rbac_with_temporal_roles_model.conf @@ -0,0 +1,14 @@ +[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 \ No newline at end of file diff --git a/examples/rbac_with_temporal_roles_policy.csv b/examples/rbac_with_temporal_roles_policy.csv new file mode 100644 index 00000000..2089308b --- /dev/null +++ b/examples/rbac_with_temporal_roles_policy.csv @@ -0,0 +1,24 @@ +p, alice, data1, read +p, alice, data1, write +p, data2_admin, data2, read +p, data2_admin, data2, write +p, data3_admin, data3, read +p, data3_admin, data3, write +p, data4_admin, data4, read +p, data4_admin, data4, write +p, data5_admin, data5, read +p, data5_admin, data5, write +p, data6_admin, data6, read +p, data6_admin, data6, write +p, data7_admin, data7, read +p, data7_admin, data7, write +p, data8_admin, data8, read +p, data8_admin, data8, write + +g, alice, data2_admin, 0001-01-01 00:00:00, 0001-01-02 00:00:00 +g, alice, data3_admin, 0001-01-01 00:00:00, 9999-12-30 00:00:00 +g, alice, data4_admin, _, _ +g, alice, data5_admin, _, 9999-12-30 00:00:00 +g, alice, data6_admin, _, 0001-01-02 00:00:00 +g, alice, data7_admin, 0001-01-01 00:00:00, _ +g, alice, data8_admin, 9999-12-30 00:00:00, _ \ No newline at end of file diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 29e89ef5..8df34071 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -17,6 +17,7 @@ from unittest import TestCase, IsolatedAsyncioTestCase import casbin +from casbin import util def get_examples(path): @@ -240,7 +241,6 @@ def test_enforce_subpriority_with_domain(self): self.assertTrue(e.enforce("bob", "data2", "domain2", "write")) def test_multiple_policy_definitions(self): - e = self.get_enforcer( get_examples("multiple_policy_definitions_model.conf"), get_examples("multiple_policy_definitions_policy.csv"), @@ -431,6 +431,141 @@ def test_matcher_using_in_operator_bracket(self): self.assertTrue(e.enforce("alice", "data3", "scribble")) self.assertFalse(e.enforce("alice", "data4", "scribble")) + def test_temporal_roles_model(self): + e = self.get_enforcer( + get_examples("rbac_with_temporal_roles_model.conf"), + get_examples("rbac_with_temporal_roles_policy.csv"), + ) + + e.add_named_link_condition_func("g", "alice", "data2_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data3_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data4_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data5_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data6_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data7_admin", util.time_match_func) + e.add_named_link_condition_func("g", "alice", "data8_admin", util.time_match_func) + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertFalse(e.enforce("alice", "data2", "read")) + self.assertFalse(e.enforce("alice", "data2", "write")) + self.assertTrue(e.enforce("alice", "data3", "read")) + self.assertTrue(e.enforce("alice", "data3", "write")) + self.assertTrue(e.enforce("alice", "data4", "read")) + self.assertTrue(e.enforce("alice", "data4", "write")) + self.assertTrue(e.enforce("alice", "data5", "read")) + self.assertTrue(e.enforce("alice", "data5", "write")) + self.assertFalse(e.enforce("alice", "data6", "read")) + self.assertFalse(e.enforce("alice", "data6", "write")) + self.assertTrue(e.enforce("alice", "data7", "read")) + self.assertTrue(e.enforce("alice", "data7", "write")) + self.assertFalse(e.enforce("alice", "data8", "read")) + self.assertFalse(e.enforce("alice", "data8", "write")) + + def test_temporal_roles_model_with_domain(self): + e = self.get_enforcer( + get_examples("rbac_with_domain_temporal_roles_model.conf"), + get_examples("rbac_with_domain_temporal_roles_policy.csv"), + ) + + e.add_named_domain_link_condition_func("g", "alice", "data2_admin", "domain2", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data3_admin", "domain3", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data4_admin", "domain4", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data5_admin", "domain5", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data6_admin", "domain6", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data7_admin", "domain7", util.time_match_func) + e.add_named_domain_link_condition_func("g", "alice", "data8_admin", "domain8", util.time_match_func) + + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain2", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain2", "data2", "write")) + self.assertTrue(e.enforce("alice", "domain3", "data3", "read")) + self.assertTrue(e.enforce("alice", "domain3", "data3", "write")) + self.assertTrue(e.enforce("alice", "domain4", "data4", "read")) + self.assertTrue(e.enforce("alice", "domain4", "data4", "write")) + self.assertTrue(e.enforce("alice", "domain5", "data5", "read")) + self.assertTrue(e.enforce("alice", "domain5", "data5", "write")) + self.assertFalse(e.enforce("alice", "domain6", "data6", "read")) + self.assertFalse(e.enforce("alice", "domain6", "data6", "write")) + self.assertTrue(e.enforce("alice", "domain7", "data7", "read")) + self.assertTrue(e.enforce("alice", "domain7", "data7", "write")) + self.assertFalse(e.enforce("alice", "domain8", "data8", "read")) + self.assertFalse(e.enforce("alice", "domain8", "data8", "write")) + + self.assertFalse(e.enforce("alice", "domain_not_exist", "data1", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data1", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data2", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data2", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data3", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data3", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data4", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data4", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data5", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data5", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data6", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data6", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data7", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data7", "write")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data8", "read")) + self.assertFalse(e.enforce("alice", "domain_not_exist", "data8", "write")) + + def test_link_condition_function(self): + e = self.get_enforcer( + get_examples("rbac_with_temporal_roles_model.conf"), + get_examples("rbac_with_temporal_roles_condition_policy.csv"), + ) + + true_func = lambda *args: True if args[0] == "_" or args[0] == "true" else False + false_func = lambda *args: True if args[0] == "_" or args[0] == "false" else False + + e.add_named_link_condition_func("g", "alice", "data2_admin", true_func) + e.add_named_link_condition_func("g", "alice", "data3_admin", true_func) + e.add_named_link_condition_func("g", "alice", "data4_admin", false_func) + e.add_named_link_condition_func("g", "alice", "data5_admin", false_func) + + e.set_named_link_condition_func_params("g", "alice", "data2_admin", "true") + e.set_named_link_condition_func_params("g", "alice", "data3_admin", "not true") + e.set_named_link_condition_func_params("g", "alice", "data4_admin", "false") + e.set_named_link_condition_func_params("g", "alice", "data5_admin", "not false") + + self.assertTrue(e.enforce("alice", "data1", "read")) + self.assertTrue(e.enforce("alice", "data1", "write")) + self.assertTrue(e.enforce("alice", "data2", "read")) + self.assertTrue(e.enforce("alice", "data2", "write")) + self.assertFalse(e.enforce("alice", "data3", "read")) + self.assertFalse(e.enforce("alice", "data3", "write")) + self.assertTrue(e.enforce("alice", "data4", "read")) + self.assertTrue(e.enforce("alice", "data4", "write")) + self.assertFalse(e.enforce("alice", "data5", "read")) + self.assertFalse(e.enforce("alice", "data5", "write")) + + e = self.get_enforcer( + get_examples("rbac_with_domain_temporal_roles_model.conf"), + get_examples("rbac_with_domain_temporal_roles_condition_policy.csv"), + ) + + e.add_named_domain_link_condition_func("g", "alice", "data2_admin", "domain2", true_func) + e.add_named_domain_link_condition_func("g", "alice", "data3_admin", "domain3", true_func) + e.add_named_domain_link_condition_func("g", "alice", "data4_admin", "domain4", false_func) + e.add_named_domain_link_condition_func("g", "alice", "data5_admin", "domain5", false_func) + + e.set_named_domain_link_condition_func_params("g", "alice", "data2_admin", "domain2", "true") + e.set_named_domain_link_condition_func_params("g", "alice", "data3_admin", "domain3", "not true") + e.set_named_domain_link_condition_func_params("g", "alice", "data4_admin", "domain4", "false") + e.set_named_domain_link_condition_func_params("g", "alice", "data5_admin", "domain5", "not false") + + self.assertTrue(e.enforce("alice", "domain1", "data1", "read")) + self.assertTrue(e.enforce("alice", "domain1", "data1", "write")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "read")) + self.assertTrue(e.enforce("alice", "domain2", "data2", "write")) + self.assertFalse(e.enforce("alice", "domain3", "data3", "read")) + self.assertFalse(e.enforce("alice", "domain3", "data3", "write")) + self.assertTrue(e.enforce("alice", "domain4", "data4", "read")) + self.assertTrue(e.enforce("alice", "domain4", "data4", "write")) + self.assertFalse(e.enforce("alice", "domain5", "data5", "read")) + self.assertFalse(e.enforce("alice", "domain5", "data5", "write")) + class TestConfigSynced(TestConfig): def get_enforcer(self, model=None, adapter=None): @@ -690,7 +825,6 @@ async def test_enforce_subpriority_with_domain(self): self.assertTrue(e.enforce("bob", "data2", "domain2", "write")) async def test_multiple_policy_definitions(self): - e = self.get_enforcer( get_examples("multiple_policy_definitions_model.conf"), get_examples("multiple_policy_definitions_policy.csv"), diff --git a/tests/util/test_builtin_operators.py b/tests/util/test_builtin_operators.py index b0b47da6..c6fb57c4 100644 --- a/tests/util/test_builtin_operators.py +++ b/tests/util/test_builtin_operators.py @@ -320,3 +320,12 @@ def test_ip_match(self): self.assertTrue(util.ip_match_func("192.168.2.123", "192.168.2.123/32")) self.assertTrue(util.ip_match_func("10.0.0.11", "10.0.0.0/8")) self.assertFalse(util.ip_match_func("11.0.0.123", "10.0.0.0/8")) + + def test_time_match(self): + self.assertFalse(util.time_match_func("0001-01-01 00:00:00", "0001-01-02 00:00:00")) + self.assertTrue(util.time_match_func("0001-01-01 00:00:00", "9999-12-30 00:00:00")) + self.assertTrue(util.time_match_func("_", "_")) + self.assertTrue(util.time_match_func("_", "9999-12-30 00:00:00")) + self.assertFalse(util.time_match_func("_", "0001-01-02 00:00:00")) + self.assertTrue(util.time_match_func("0001-01-01 00:00:00", "_")) + self.assertFalse(util.time_match_func("9999-12-30 00:00:00", "_")) From 586dfc4ad731ecece2b77f2d62ee6c47396185e7 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 24 Sep 2023 02:01:08 +0000 Subject: [PATCH 307/349] chore(release): 1.31.0 [skip ci] # [1.31.0](https://github.com/casbin/pycasbin/compare/v1.30.0...v1.31.0) (2023-09-24) ### Features * add temporal role model ([#320](https://github.com/casbin/pycasbin/issues/320)) ([09ff119](https://github.com/casbin/pycasbin/commit/09ff1191856345b69026e5fa86a361dd5df8bab5)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f00eeda3..97b16797 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.31.0](https://github.com/casbin/pycasbin/compare/v1.30.0...v1.31.0) (2023-09-24) + + +### Features + +* add temporal role model ([#320](https://github.com/casbin/pycasbin/issues/320)) ([09ff119](https://github.com/casbin/pycasbin/commit/09ff1191856345b69026e5fa86a361dd5df8bab5)) + # [1.30.0](https://github.com/casbin/pycasbin/compare/v1.29.0...v1.30.0) (2023-09-23) diff --git a/setup.cfg b/setup.cfg index 9f1f1f8d..469bcb72 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.30.0 +version = 1.31.0 From 31153aea17102695a07003933c2c735dc5bdcea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Germain?= Date: Sun, 1 Oct 2023 01:54:08 +0200 Subject: [PATCH 308/349] fix: disabled all logger (#324) * fix: disabled all logger * fix: disabled all logger Co-authored-by: AurelienGAB <38694184+AurelienGAB@users.noreply.github.com> * tests: fix disabled logger --------- Co-authored-by: AurelienGAB <38694184+AurelienGAB@users.noreply.github.com> --- casbin/core_enforcer.py | 4 ++-- casbin/util/log.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 8b4d7b0c..79b3de69 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -21,7 +21,7 @@ from casbin.persist.adapters import FileAdapter from casbin.rbac import default_role_manager from casbin.util import generate_g_function, SimpleEval, util, generate_conditional_g_function -from casbin.util.log import configure_logging +from casbin.util.log import configure_logging, disabled_logging class EnforceContext: @@ -71,7 +71,7 @@ def __init__(self, model=None, adapter=None, enable_log=False): if enable_log: configure_logging() else: - logging.disable(logging.CRITICAL) + disabled_logging() def init_with_file(self, model_path, policy_path): """initializes an enforcer with a model file and a policy file.""" diff --git a/casbin/util/log.py b/casbin/util/log.py index 6fe692f7..cbf5fc49 100644 --- a/casbin/util/log.py +++ b/casbin/util/log.py @@ -48,3 +48,8 @@ def configure_logging(logging_config=None): logging.config.dictConfig(logging_config) else: logging.config.dictConfig(DEFAULT_LOGGING) + + +def disabled_logging(): + for logger_name in DEFAULT_LOGGING["loggers"].keys(): + logging.getLogger(logger_name).disabled = True From c8aaf9a7c4d3dbf5dc9a5f83dcf77c0be1bb8b1a Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 30 Sep 2023 23:57:43 +0000 Subject: [PATCH 309/349] chore(release): 1.31.1 [skip ci] ## [1.31.1](https://github.com/casbin/pycasbin/compare/v1.31.0...v1.31.1) (2023-09-30) ### Bug Fixes * disabled all logger ([#324](https://github.com/casbin/pycasbin/issues/324)) ([ae81a52](https://github.com/casbin/pycasbin/commit/ae81a5225b4a045c6eba4f3bae6dade68b92b78a)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97b16797..6bda5342 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.31.1](https://github.com/casbin/pycasbin/compare/v1.31.0...v1.31.1) (2023-09-30) + + +### Bug Fixes + +* disabled all logger ([#324](https://github.com/casbin/pycasbin/issues/324)) ([ae81a52](https://github.com/casbin/pycasbin/commit/ae81a5225b4a045c6eba4f3bae6dade68b92b78a)) + # [1.31.0](https://github.com/casbin/pycasbin/compare/v1.30.0...v1.31.0) (2023-09-24) diff --git a/setup.cfg b/setup.cfg index 469bcb72..ae767f3c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.31.0 +version = 1.31.1 From 097d84f6ba2a69378f5cf4f114a17f93b06ffb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20=C4=8Con=C4=8Dka?= Date: Fri, 6 Oct 2023 04:47:15 +0200 Subject: [PATCH 310/349] fix: use pre defined member (#325) --- casbin/util/expression.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/util/expression.py b/casbin/util/expression.py index 350f6f11..3be2015b 100644 --- a/casbin/util/expression.py +++ b/casbin/util/expression.py @@ -31,7 +31,7 @@ def __init__(self, expr, functions=None): super(SimpleEval, self).__init__(functions=functions) if expr != "": self.expr = expr - self.expr_parsed_value = ast.parse(expr.strip()).body[0].value + self.ast_parsed_value = ast.parse(expr.strip()).body[0].value def eval(self, names=None): """evaluate an expresssion, using the operators, functions and @@ -40,4 +40,4 @@ def eval(self, names=None): if names: self.names = names - return self._eval(self.expr_parsed_value) + return self._eval(self.ast_parsed_value) From 780fbbf749f956520985d212dca39cc8cdfe1ef9 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 6 Oct 2023 02:50:31 +0000 Subject: [PATCH 311/349] chore(release): 1.31.2 [skip ci] ## [1.31.2](https://github.com/casbin/pycasbin/compare/v1.31.1...v1.31.2) (2023-10-06) ### Bug Fixes * use pre defined member ([#325](https://github.com/casbin/pycasbin/issues/325)) ([4f191f0](https://github.com/casbin/pycasbin/commit/4f191f09a48a61b597fb73b62a841a69407dd292)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bda5342..244c8e5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.31.2](https://github.com/casbin/pycasbin/compare/v1.31.1...v1.31.2) (2023-10-06) + + +### Bug Fixes + +* use pre defined member ([#325](https://github.com/casbin/pycasbin/issues/325)) ([4f191f0](https://github.com/casbin/pycasbin/commit/4f191f09a48a61b597fb73b62a841a69407dd292)) + ## [1.31.1](https://github.com/casbin/pycasbin/compare/v1.31.0...v1.31.1) (2023-09-30) diff --git a/setup.cfg b/setup.cfg index ae767f3c..5a2a1bb9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.31.1 +version = 1.31.2 From ff8c6596a9444679399964aed04c28b755e92dba Mon Sep 17 00:00:00 2001 From: Nick Schwane Date: Wed, 18 Oct 2023 13:27:19 -0400 Subject: [PATCH 312/349] feat: fix integer priority sorting (#327) * fix: fix integer priority sorting * fix: fix formatting with black --- casbin/model/model.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/casbin/model/model.py b/casbin/model/model.py index e33fd715..ecef7753 100644 --- a/casbin/model/model.py +++ b/casbin/model/model.py @@ -124,7 +124,12 @@ def sort_policies_by_priority(self): if assertion.priority_index == -1: continue - assertion.policy = sorted(assertion.policy, key=lambda x: x[assertion.priority_index]) + assertion.policy = sorted( + assertion.policy, + key=lambda x: int(x[assertion.priority_index]) + if x[assertion.priority_index].isdigit() + else x[assertion.priority_index], + ) for i, policy in enumerate(assertion.policy): assertion.policy_map[",".join(policy)] = i From cad76e066e5b24ee80d5141f74da3235fec583c1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 18 Oct 2023 17:31:09 +0000 Subject: [PATCH 313/349] chore(release): 1.32.0 [skip ci] # [1.32.0](https://github.com/casbin/pycasbin/compare/v1.31.2...v1.32.0) (2023-10-18) ### Features * fix integer priority sorting ([#327](https://github.com/casbin/pycasbin/issues/327)) ([d5cd58a](https://github.com/casbin/pycasbin/commit/d5cd58a738ad8e8ad0a5010f9cff904736e1719a)) --- CHANGELOG.md | 7 +++++++ setup.cfg | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 244c8e5d..f7429a0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.32.0](https://github.com/casbin/pycasbin/compare/v1.31.2...v1.32.0) (2023-10-18) + + +### Features + +* fix integer priority sorting ([#327](https://github.com/casbin/pycasbin/issues/327)) ([d5cd58a](https://github.com/casbin/pycasbin/commit/d5cd58a738ad8e8ad0a5010f9cff904736e1719a)) + ## [1.31.2](https://github.com/casbin/pycasbin/compare/v1.31.1...v1.31.2) (2023-10-06) diff --git a/setup.cfg b/setup.cfg index 5a2a1bb9..6ab4cba6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,3 @@ [metadata] -version = 1.31.2 +version = 1.32.0 From d116b2a97346160c615ab4d828b1c3c013c11a9e Mon Sep 17 00:00:00 2001 From: Felix Lu Date: Wed, 25 Oct 2023 13:41:04 +0800 Subject: [PATCH 314/349] feat: change log level to warning (#329) --- casbin/core_enforcer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 79b3de69..b0dff852 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -512,9 +512,9 @@ def enforce_ex(self, *rvals): if result: self.logger.info(req_str) else: - # leaving this in error for now, if it's very noise this can be changed to info or debug, + # leaving this in warning for now, if it's very noise this can be changed to info or debug, # or change the log level - self.logger.error(req_str) + self.logger.warning(req_str) explain_rule = [] if explain_index != -1 and explain_index < policy_len: From afbd63d8b144bcd6a6b35359fcdf12043008d043 Mon Sep 17 00:00:00 2001 From: abichinger Date: Wed, 25 Oct 2023 15:36:52 +0200 Subject: [PATCH 315/349] chore: fix release workflow (#331) --- .github/workflows/build.yml | 9 ++---- .gitignore | 3 ++ package.json | 8 +++++ pyproject.toml | 45 ++++++++++++++++++++++++++ setup.cfg | 3 -- setup.py | 63 ------------------------------------- 6 files changed, 59 insertions(+), 72 deletions(-) create mode 100644 package.json delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4114fa08..9b101748 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -97,15 +97,12 @@ jobs: node-version: '18' - name: Setup - run: npm install -g semantic-release @semantic-release/github @semantic-release/changelog @semantic-release/commit-analyzer @semantic-release/git @semantic-release/release-notes-generator semantic-release-pypi + run: npm install - name: Set up python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.8 - - - name: Install setuptools - run: python -m pip install --upgrade setuptools wheel twine + python-version: 3.11 - name: Release env: diff --git a/.gitignore b/.gitignore index 47a276bb..0bf5f1c1 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ _trial_temp # vscode .vscode + +# node +node_modules \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..20ac176e --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "devDependencies": { + "@semantic-release/changelog": "^6.0.3", + "@semantic-release/git": "^10.0.1", + "semantic-release": "^22.0.5", + "semantic-release-pypi": "^3.0.0" + } +} diff --git a/pyproject.toml b/pyproject.toml index e34796ec..6222335d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,2 +1,47 @@ +[project] +name = "casbin" +version = "1.32.0" +authors = [ + {name = "TechLee", email = "techlee@qq.com"}, +] +description = "An authorization library that supports access control models like ACL, RBAC, ABAC in Python" +readme = "README.md" +keywords = [ + "casbin", + "acl", + "rbac", + "abac", + "auth", + "authz", + "authorization", + "access control", + "permission", +] +dynamic = ["dependencies"] +requires-python = ">=3.3" +license = {text = "Apache 2.0"} +classifiers = [ + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", +] + +[project.urls] +"Home-page" = "https://github.com/casbin/pycasbin" + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +exclude = ["tests", "tests.*"] + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} + [tool.black] line-length = 120 \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 6ab4cba6..00000000 --- a/setup.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[metadata] -version = 1.32.0 - diff --git a/setup.py b/setup.py deleted file mode 100644 index c81fcaac..00000000 --- a/setup.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2021 The casbin Authors. All Rights Reserved. -# -# 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. - -import setuptools -from os import path - -desc_file = "README.md" - -here = path.abspath(path.dirname(__file__)) - -with open(desc_file, "r", encoding="utf-8") as fh: - long_description = fh.read() - -# get the dependencies and installs -with open(path.join(here, "requirements.txt"), encoding="utf-8") as f: - all_reqs = f.read().split("\n") - -install_requires = [x.strip() for x in all_reqs if "git+" not in x] - -setuptools.setup( - name="casbin", - author="TechLee", - author_email="techlee@qq.com", - description="An authorization library that supports access control models like ACL, RBAC, ABAC in Python", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/casbin/pycasbin", - keywords=[ - "casbin", - "acl", - "rbac", - "abac", - "auth", - "authz", - "authorization", - "access control", - "permission", - ], - packages=setuptools.find_packages(exclude=("tests", "tests.*")), - install_requires=install_requires, - python_requires=">=3.3", - license="Apache 2.0", - classifiers=[ - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - ], -) From 8402a485a2c8f1a17ff27af923b9be02847f212d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 25 Oct 2023 13:41:18 +0000 Subject: [PATCH 316/349] chore(release): 1.33.0 [skip ci] # [1.33.0](https://github.com/casbin/pycasbin/compare/v1.32.0...v1.33.0) (2023-10-25) ### Features * change log level to warning ([#329](https://github.com/casbin/pycasbin/issues/329)) ([124d2c0](https://github.com/casbin/pycasbin/commit/124d2c05a69d61c17fe16c4ef0ad1423f62e0bbb)) --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7429a0a..f419518a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.33.0](https://github.com/casbin/pycasbin/compare/v1.32.0...v1.33.0) (2023-10-25) + + +### Features + +* change log level to warning ([#329](https://github.com/casbin/pycasbin/issues/329)) ([124d2c0](https://github.com/casbin/pycasbin/commit/124d2c05a69d61c17fe16c4ef0ad1423f62e0bbb)) + # [1.32.0](https://github.com/casbin/pycasbin/compare/v1.31.2...v1.32.0) (2023-10-18) From 9255185f264433e61992d6e10f8eec6dde08b625 Mon Sep 17 00:00:00 2001 From: abichinger Date: Wed, 25 Oct 2023 16:03:33 +0200 Subject: [PATCH 317/349] chore: semantic-release commits pyproject.toml (#332) --- .releaserc.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.releaserc.json b/.releaserc.json index 72f72984..f1c369e9 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -16,7 +16,7 @@ "@semantic-release/git", { "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}", - "assets": ["CHANGELOG.md", "setup.py", "setup.cfg"] + "assets": ["CHANGELOG.md", "pyproject.toml"] } ] ] diff --git a/pyproject.toml b/pyproject.toml index 6222335d..b9d07ebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.32.0" +version = "1.33.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From e5c67027bc987380a820c0bc9f862c7afbf4268e Mon Sep 17 00:00:00 2001 From: Gucheng <85475922+nomeguy@users.noreply.github.com> Date: Wed, 20 Dec 2023 01:54:25 +0800 Subject: [PATCH 318/349] Update README.md --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 294904ce..baede44e 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,20 @@ PyCasbin [![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN) +

+ Sponsored by +
+ + + + + + +
+ Build auth with fraud prevention, faster.
Try Stytch for API-first authentication, user & org management, multi-tenant SSO, MFA, device fingerprinting, and more.
+
+

+ 💖 [**Looking for an open-source identity and access management solution like Okta, Auth0, Keycloak ? Learn more about: Casdoor**](https://casdoor.org/) casdoor From 15cfb6185c01fe51f086de0295c0a705cd966f46 Mon Sep 17 00:00:00 2001 From: Elias Gabriel Date: Wed, 27 Dec 2023 23:17:24 -0500 Subject: [PATCH 319/349] feat: add interface stubs for async adapters (#335) * refactor!: change reorganize imports for sane asyncio ext * feat: async adapter interface stubs * chore: reflect pr comments * chore: revert black bump pending decision * refactor: move update adapter interface to persist module * ci: bump tooling and linting versions * ci: fix linting action --- .github/workflows/build.yml | 88 +++++++------- README.md | 4 +- casbin/async_internal_enforcer.py | 5 +- casbin/config/config.py | 1 - casbin/distributed_enforcer.py | 3 +- casbin/persist/__init__.py | 2 +- casbin/persist/adapters/__init__.py | 9 +- casbin/persist/adapters/adapter_filtered.py | 110 +---------------- casbin/persist/adapters/asyncio/__init__.py | 13 ++ casbin/persist/adapters/asyncio/adapter.py | 32 +++++ .../adapters/asyncio/adapter_filtered.py | 17 +++ .../persist/adapters/asyncio/batch_adapter.py | 15 +++ .../file_adapter.py} | 8 +- .../adapters/asyncio/update_adapter.py | 27 +++++ casbin/persist/adapters/file_adapter.py | 7 +- .../persist/adapters/filtered_file_adapter.py | 111 ++++++++++++++++++ casbin/persist/adapters/string_adapter.py | 7 +- casbin/persist/adapters/update_adapter.py | 38 +----- casbin/persist/update_adapter.py | 36 ++++++ requirements_dev.txt | 2 +- tests/__init__.py | 8 +- tests/test_enforcer.py | 38 +++--- tests/test_filter.py | 17 +-- 23 files changed, 360 insertions(+), 238 deletions(-) create mode 100644 casbin/persist/adapters/asyncio/__init__.py create mode 100644 casbin/persist/adapters/asyncio/adapter.py create mode 100644 casbin/persist/adapters/asyncio/adapter_filtered.py create mode 100644 casbin/persist/adapters/asyncio/batch_adapter.py rename casbin/persist/adapters/{async_file_adapter.py => asyncio/file_adapter.py} (93%) create mode 100644 casbin/persist/adapters/asyncio/update_adapter.py create mode 100644 casbin/persist/adapters/filtered_file_adapter.py create mode 100644 casbin/persist/update_adapter.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9b101748..db2a93fa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,55 +11,53 @@ jobs: strategy: fail-fast: false matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ["3.9", "3.10", "3.11"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - pip install -r requirements.txt - pip install -r requirements_dev.txt - pip install coveralls - pip install pytest - pip install pytest-benchmark + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install -r requirements_dev.txt + pip install coveralls - - name: Run tests - run: coverage run -m unittest discover -s tests -t tests + - name: Run tests + run: coverage run -m unittest discover -s tests -t tests - - name: Run benchmark - run: python3 -m pytest - --benchmark-verbose - --benchmark-columns=mean,stddev,iqr,ops,rounds - tests/benchmarks/benchmark_model.py - tests/benchmarks/benchmark_management_api.py - tests/benchmarks/benchmark_role_manager.py + - name: Run benchmark + run: python3 -m pytest + --benchmark-verbose + --benchmark-columns=mean,stddev,iqr,ops,rounds + tests/benchmarks/benchmark_model.py + tests/benchmarks/benchmark_management_api.py + tests/benchmarks/benchmark_role_manager.py - - name: Upload coverage data to coveralls.io - run: coveralls --service=github - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - COVERALLS_FLAG_NAME: ${{ matrix.os }} - ${{ matrix.python-version }} - COVERALLS_PARALLEL: true + - name: Upload coverage data to coveralls.io + run: coveralls --service=github + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERALLS_FLAG_NAME: ${{ matrix.os }} - ${{ matrix.python-version }} + COVERALLS_PARALLEL: true lint: name: Run Linters runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Super-Linter - uses: github/super-linter@v4.9.2 + uses: super-linter/super-linter@v5.7.2 env: VALIDATE_ALL_CODEBASE: false VALIDATE_PYTHON_BLACK: true @@ -74,36 +72,36 @@ jobs: runs-on: ubuntu-latest container: python:3-slim steps: - - name: Finished - run: | - pip3 install --upgrade coveralls - coveralls --finish - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Finished + run: | + pip3 install --upgrade coveralls + coveralls --finish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} release: name: Release runs-on: ubuntu-latest - needs: [ test, coveralls ] + needs: [test, coveralls] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 - + - name: Setup Node.js - uses: actions/setup-node@v2 + uses: actions/setup-node@v4 with: - node-version: '18' + node-version: "18" - name: Setup run: npm install - + - name: Set up python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: 3.11 - + - name: Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index baede44e..fccb6c52 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ https://casbin.org/docs/role-managers If your code use `async` / `await` and is heavily dependent on I/O operations, you can adopt Async Enforcer! -1. Create an async engine and new a Casbin AsyncEnforcer with a model file and an async Pycasbin adapter: +1. Create an async engine and new a Casbin AsyncEnforcer with a model file and an async Pycasbin adapter (AsyncAdapter subclass): ```python import asyncio @@ -266,6 +266,8 @@ async def get_enforcer(): Note: you can see all supported adapters in [Adapters | Casbin](https://casbin.org/docs/adapters). +Built-in async adapters are available in `casbin.persist.adapters.asyncio`. + 2. Add an enforcement hook into your code right before the access happens: ```python diff --git a/casbin/async_internal_enforcer.py b/casbin/async_internal_enforcer.py index e62a8bd2..f2bfc5f7 100644 --- a/casbin/async_internal_enforcer.py +++ b/casbin/async_internal_enforcer.py @@ -15,8 +15,7 @@ from casbin.core_enforcer import CoreEnforcer from casbin.model import Model, FunctionMap -from casbin.persist import Adapter -from casbin.persist.adapters.async_file_adapter import AsyncFileAdapter +from casbin.persist.adapters.asyncio import AsyncFileAdapter, AsyncAdapter class AsyncInternalEnforcer(CoreEnforcer): @@ -32,7 +31,7 @@ def init_with_file(self, model_path, policy_path): def init_with_model_and_adapter(self, m, adapter=None): """initializes an enforcer with a model and a database adapter.""" - if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, Adapter): + if not isinstance(m, Model) or adapter is not None and not isinstance(adapter, AsyncAdapter): raise RuntimeError("Invalid parameters for enforcer.") self.adapter = adapter diff --git a/casbin/config/config.py b/casbin/config/config.py index 56c6ba30..30299b48 100644 --- a/casbin/config/config.py +++ b/casbin/config/config.py @@ -96,7 +96,6 @@ def _parse_buffer(self, f): buf.append(p) def _write(self, section, line_num, b): - buf = "".join(b) if len(buf) <= 0: return diff --git a/casbin/distributed_enforcer.py b/casbin/distributed_enforcer.py index a5e3b7b9..63012bf8 100644 --- a/casbin/distributed_enforcer.py +++ b/casbin/distributed_enforcer.py @@ -13,8 +13,7 @@ # limitations under the License. from casbin.model.policy_op import PolicyOp -from casbin.persist import batch_adapter -from casbin.persist.adapters import update_adapter +from casbin.persist import batch_adapter, update_adapter from casbin.synced_enforcer import SyncedEnforcer diff --git a/casbin/persist/__init__.py b/casbin/persist/__init__.py index 78c13538..b3de4c12 100644 --- a/casbin/persist/__init__.py +++ b/casbin/persist/__init__.py @@ -14,5 +14,5 @@ from .adapter import * from .adapter_filtered import * -from .batch_adapter import * from .adapters import * +from .batch_adapter import * diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index 5bee4fcf..46f177e8 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -13,5 +13,10 @@ # limitations under the License. from .file_adapter import FileAdapter -from .adapter_filtered import FilteredAdapter -from .update_adapter import UpdateAdapter +from .filtered_file_adapter import FilteredFileAdapter +from ..update_adapter import UpdateAdapter + +# alias import for backwards compatibility +FilteredAdapter = FilteredFileAdapter + +__all__ = ["FileAdapter", "FilteredFileAdapter", "FilteredAdapter", "UpdateAdapter"] diff --git a/casbin/persist/adapters/adapter_filtered.py b/casbin/persist/adapters/adapter_filtered.py index 0fa85820..58a49820 100644 --- a/casbin/persist/adapters/adapter_filtered.py +++ b/casbin/persist/adapters/adapter_filtered.py @@ -1,107 +1,7 @@ -# Copyright 2021 The casbin Authors. All Rights Reserved. -# -# 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. +# NOTE: this file exists as a backwards compatible alias. please directly +# use FilteredFileAdapter from `casbin.persist.adapters.filtered_file_adapter` instead. -from casbin import persist -from .file_adapter import FileAdapter -import os +from .filtered_file_adapter import Filter +from .filtered_file_adapter import FilteredFileAdapter as FilteredAdapter - -class Filter: - # P,G are string [] - P = [] - G = [] - - -class FilteredAdapter(FileAdapter, persist.FilteredAdapter): - filtered = False - _file_path = "" - filter = Filter() - # new_filtered_adapte is the constructor for FilteredAdapter. - def __init__(self, file_path): - self.filtered = True - self._file_path = file_path - - def load_policy(self, model): - if not os.path.isfile(self._file_path): - raise RuntimeError("invalid file path, file path cannot be empty") - self.filtered = False - self._load_policy_file(model) - - # load_filtered_policy loads only policy rules that match the filter. - def load_filtered_policy(self, model, filter): - if filter == None: - return self.load_policy(model) - - if not os.path.isfile(self._file_path): - raise RuntimeError("invalid file path, file path cannot be empty") - - try: - filter_value = [filter.__dict__["P"]] + [filter.__dict__["G"]] - except: - raise RuntimeError("invalid filter type") - - self.load_filtered_policy_file(model, filter_value, persist.load_policy_line) - self.filtered = True - - def load_filtered_policy_file(self, model, filter, hanlder): - with open(self._file_path, "rb") as file: - while True: - line = file.readline() - line = line.decode().strip() - if line == "\n": - continue - if not line: - break - if filter_line(line, filter): - continue - - hanlder(line, model) - - # is_filtered returns true if the loaded policy has been filtered. - def is_filtered(self): - return self.filtered - - def save_policy(self, model): - if self.filtered: - raise RuntimeError("cannot save a filtered policy") - - self._save_policy_file(model) - - -def filter_line(line, filter): - if filter == None: - return False - - p = line.split(",") - if len(p) == 0: - return True - filter_slice = [] - - if p[0].strip() == "p": - filter_slice = filter[0] - elif p[0].strip() == "g": - filter_slice = filter[1] - return filter_words(p, filter_slice) - - -def filter_words(line, filter): - if len(line) < len(filter) + 1: - return True - skip_line = False - for i, v in enumerate(filter): - if len(v) > 0 and (v.strip() != line[i + 1].strip()): - skip_line = True - break - - return skip_line +__all__ = ["Filter", "FilteredAdapter"] diff --git a/casbin/persist/adapters/asyncio/__init__.py b/casbin/persist/adapters/asyncio/__init__.py new file mode 100644 index 00000000..1c3c1815 --- /dev/null +++ b/casbin/persist/adapters/asyncio/__init__.py @@ -0,0 +1,13 @@ +from .adapter import AsyncAdapter +from .adapter_filtered import AsyncFilteredAdapter +from .batch_adapter import AsyncBatchAdapter +from .file_adapter import AsyncFileAdapter +from .update_adapter import AsyncUpdateAdapter + +__all__ = [ + "AsyncAdapter", + "AsyncFilteredAdapter", + "AsyncBatchAdapter", + "AsyncFileAdapter", + "AsyncUpdateAdapter", +] diff --git a/casbin/persist/adapters/asyncio/adapter.py b/casbin/persist/adapters/asyncio/adapter.py new file mode 100644 index 00000000..9287c21d --- /dev/null +++ b/casbin/persist/adapters/asyncio/adapter.py @@ -0,0 +1,32 @@ +from abc import ABCMeta, abstractmethod + + +class AsyncAdapter(metaclass=ABCMeta): + """The interface for async Casbin adapters.""" + + @abstractmethod + async def load_policy(self, model): + """loads all policy rules from the storage.""" + pass + + @abstractmethod + async def save_policy(self, model): + """saves all policy rules to the storage.""" + pass + + @abstractmethod + async def add_policy(self, sec, ptype, rule): + """adds a policy rule to the storage.""" + pass + + @abstractmethod + async def remove_policy(self, sec, ptype, rule): + """removes a policy rule from the storage.""" + pass + + @abstractmethod + async def remove_filtered_policy(self, sec, ptype, field_index, *field_values): + """removes policy rules that match the filter from the storage. + This is part of the Auto-Save feature. + """ + pass diff --git a/casbin/persist/adapters/asyncio/adapter_filtered.py b/casbin/persist/adapters/asyncio/adapter_filtered.py new file mode 100644 index 00000000..d77ec3bc --- /dev/null +++ b/casbin/persist/adapters/asyncio/adapter_filtered.py @@ -0,0 +1,17 @@ +from abc import ABCMeta, abstractmethod + + +class AsyncFilteredAdapter(metaclass=ABCMeta): + """AsyncFilteredAdapter is the interface for async Casbin adapters supporting filtered policies.""" + + @abstractmethod + async def is_filtered(self): + """IsFiltered returns true if the loaded policy has been filtered + Marks if the loaded policy is filtered or not + """ + pass + + @abstractmethod + async def load_filtered_policy(self, model, filter): + """Loads policy rules that match the filter from the storage.""" + pass diff --git a/casbin/persist/adapters/asyncio/batch_adapter.py b/casbin/persist/adapters/asyncio/batch_adapter.py new file mode 100644 index 00000000..00abc7a8 --- /dev/null +++ b/casbin/persist/adapters/asyncio/batch_adapter.py @@ -0,0 +1,15 @@ +from abc import ABCMeta, abstractmethod + + +class AsyncBatchAdapter(metaclass=ABCMeta): + """AsyncBatchAdapter is the interface for async Casbin adapters with multiple add and remove policy functions.""" + + @abstractmethod + async def add_policies(self, sec, ptype, rules): + """AddPolicies adds policy rules to the storage.""" + pass + + @abstractmethod + async def remove_policies(self, sec, ptype, rules): + """RemovePolicies removes policy rules from the storage.""" + pass diff --git a/casbin/persist/adapters/async_file_adapter.py b/casbin/persist/adapters/asyncio/file_adapter.py similarity index 93% rename from casbin/persist/adapters/async_file_adapter.py rename to casbin/persist/adapters/asyncio/file_adapter.py index 27341310..274eeff2 100644 --- a/casbin/persist/adapters/async_file_adapter.py +++ b/casbin/persist/adapters/asyncio/file_adapter.py @@ -12,11 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -from casbin import persist import os +from ...adapter import load_policy_line +from .adapter import AsyncAdapter -class AsyncFileAdapter(persist.Adapter): + +class AsyncFileAdapter(AsyncAdapter): """the async file adapter for Casbin. It can load policy from file or save policy to file. """ @@ -42,7 +44,7 @@ def _load_policy_file(self, model): with open(self._file_path, "rb") as file: line = file.readline() while line: - persist.load_policy_line(line.decode().strip(), model) + load_policy_line(line.decode().strip(), model) line = file.readline() def _save_policy_file(self, model): diff --git a/casbin/persist/adapters/asyncio/update_adapter.py b/casbin/persist/adapters/asyncio/update_adapter.py new file mode 100644 index 00000000..bcce22de --- /dev/null +++ b/casbin/persist/adapters/asyncio/update_adapter.py @@ -0,0 +1,27 @@ +from abc import ABCMeta, abstractmethod + + +class AsyncUpdateAdapter(metaclass=ABCMeta): + """AsyncUpdateAdapter is the interface for async Casbin adapters with add update policy function.""" + + @abstractmethod + async def update_policy(self, sec, ptype, old_rule, new_policy): + """ + update_policy updates a policy rule from storage. + This is part of the Auto-Save feature. + """ + pass + + @abstractmethod + async def update_policies(self, sec, ptype, old_rules, new_rules): + """ + UpdatePolicies updates some policy rules to storage, like db, redis. + """ + pass + + @abstractmethod + async def update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): + """ + update_filtered_policies deletes old rules and adds new rules. + """ + pass diff --git a/casbin/persist/adapters/file_adapter.py b/casbin/persist/adapters/file_adapter.py index 0e807598..d5727a73 100644 --- a/casbin/persist/adapters/file_adapter.py +++ b/casbin/persist/adapters/file_adapter.py @@ -12,11 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from casbin import persist import os +from ..adapter import Adapter, load_policy_line -class FileAdapter(persist.Adapter): + +class FileAdapter(Adapter): """the file adapter for Casbin. It can load policy from file or save policy to file. """ @@ -42,7 +43,7 @@ def _load_policy_file(self, model): with open(self._file_path, "rb") as file: line = file.readline() while line: - persist.load_policy_line(line.decode().strip(), model) + load_policy_line(line.decode().strip(), model) line = file.readline() def _save_policy_file(self, model): diff --git a/casbin/persist/adapters/filtered_file_adapter.py b/casbin/persist/adapters/filtered_file_adapter.py new file mode 100644 index 00000000..eeb3a6cf --- /dev/null +++ b/casbin/persist/adapters/filtered_file_adapter.py @@ -0,0 +1,111 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + +import os + +from casbin import persist + +from .file_adapter import FileAdapter +from ..adapter_filtered import FilteredAdapter + + +class Filter: + # P,G are string [] + P = [] + G = [] + + +class FilteredFileAdapter(FileAdapter, FilteredAdapter): + filtered = False + _file_path = "" + filter = Filter() + + # new_filtered_adapter is the constructor for FilteredAdapter. + def __init__(self, file_path): + self.filtered = True + self._file_path = file_path + + def load_policy(self, model): + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + self.filtered = False + self._load_policy_file(model) + + # load_filtered_policy loads only policy rules that match the filter. + def load_filtered_policy(self, model, filter): + if filter == None: + return self.load_policy(model) + + if not os.path.isfile(self._file_path): + raise RuntimeError("invalid file path, file path cannot be empty") + + try: + filter_value = [filter.__dict__["P"]] + [filter.__dict__["G"]] + except: + raise RuntimeError("invalid filter type") + + self.load_filtered_policy_file(model, filter_value, persist.load_policy_line) + self.filtered = True + + def load_filtered_policy_file(self, model, filter, hanlder): + with open(self._file_path, "rb") as file: + while True: + line = file.readline() + line = line.decode().strip() + if line == "\n": + continue + if not line: + break + if filter_line(line, filter): + continue + + hanlder(line, model) + + # is_filtered returns true if the loaded policy has been filtered. + def is_filtered(self): + return self.filtered + + def save_policy(self, model): + if self.filtered: + raise RuntimeError("cannot save a filtered policy") + + self._save_policy_file(model) + + +def filter_line(line, filter): + if filter == None: + return False + + p = line.split(",") + if len(p) == 0: + return True + filter_slice = [] + + if p[0].strip() == "p": + filter_slice = filter[0] + elif p[0].strip() == "g": + filter_slice = filter[1] + return filter_words(p, filter_slice) + + +def filter_words(line, filter): + if len(line) < len(filter) + 1: + return True + skip_line = False + for i, v in enumerate(filter): + if len(v) > 0 and (v.strip() != line[i + 1].strip()): + skip_line = True + break + + return skip_line diff --git a/casbin/persist/adapters/string_adapter.py b/casbin/persist/adapters/string_adapter.py index 0076caee..b2e9e954 100644 --- a/casbin/persist/adapters/string_adapter.py +++ b/casbin/persist/adapters/string_adapter.py @@ -12,13 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from casbin import persist, load_policy_line -import os - from casbin.util import util +from ..adapter import Adapter, load_policy_line + -class StringAdapter(persist.Adapter): +class StringAdapter(Adapter): """the string adapter for Casbin. It can load policy from string or save policy to string. """ diff --git a/casbin/persist/adapters/update_adapter.py b/casbin/persist/adapters/update_adapter.py index 94a8c016..542be9a7 100644 --- a/casbin/persist/adapters/update_adapter.py +++ b/casbin/persist/adapters/update_adapter.py @@ -1,36 +1,6 @@ -# Copyright 2021 The casbin Authors. All Rights Reserved. -# -# 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. +# NOTE: this file exists as a backwards compatible alias. please directly +# use `casbin.persist.update_adapter` instead. +from ..update_adapter import UpdateAdapter -class UpdateAdapter: - """UpdateAdapter is the interface for Casbin adapters with add update policy function.""" - - def update_policy(self, sec, ptype, old_rule, new_policy): - """ - update_policy updates a policy rule from storage. - This is part of the Auto-Save feature. - """ - pass - - def update_policies(self, sec, ptype, old_rules, new_rules): - """ - UpdatePolicies updates some policy rules to storage, like db, redis. - """ - pass - - def update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): - """ - update_filtered_policies deletes old rules and adds new rules. - """ - pass +__all__ = ["UpdateAdapter"] diff --git a/casbin/persist/update_adapter.py b/casbin/persist/update_adapter.py new file mode 100644 index 00000000..94a8c016 --- /dev/null +++ b/casbin/persist/update_adapter.py @@ -0,0 +1,36 @@ +# Copyright 2021 The casbin Authors. All Rights Reserved. +# +# 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. + + +class UpdateAdapter: + """UpdateAdapter is the interface for Casbin adapters with add update policy function.""" + + def update_policy(self, sec, ptype, old_rule, new_policy): + """ + update_policy updates a policy rule from storage. + This is part of the Auto-Save feature. + """ + pass + + def update_policies(self, sec, ptype, old_rules, new_rules): + """ + UpdatePolicies updates some policy rules to storage, like db, redis. + """ + pass + + def update_filtered_policies(self, sec, ptype, new_rules, field_index, *field_values): + """ + update_filtered_policies deletes old rules and adds new rules. + """ + pass diff --git a/requirements_dev.txt b/requirements_dev.txt index 53cde028..af37e823 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,4 +1,4 @@ -r requirements.txt -black==21.6b0 +black==23.11.0 pytest==7.0.1 pytest-benchmark==3.4.1 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py index 6e64b186..e3d8d3c2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -12,15 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from . import benchmarks -from . import config -from . import model -from . import rbac -from . import util +from . import benchmarks, config, model, rbac, util from .test_distributed_api import TestDistributedApi from .test_enforcer import * from .test_fast_enforcer import TestFastEnforcer -from .test_filter import TestFilteredAdapter +from .test_filter import TestFilteredFileAdapter from .test_frontend import TestFrontend from .test_management_api import TestManagementApi, TestManagementApiSynced from .test_rbac_api import TestRbacApi, TestRbacApiSynced diff --git a/tests/test_enforcer.py b/tests/test_enforcer.py index 8df34071..11af3864 100644 --- a/tests/test_enforcer.py +++ b/tests/test_enforcer.py @@ -25,7 +25,7 @@ def get_examples(path): return os.path.abspath(examples_path + path) -class TestSub: +class MockSub: def __init__(self, name, age): self.name = name self.age = age @@ -249,8 +249,8 @@ def test_multiple_policy_definitions(self): enforce_context = e.new_enforce_context("2") enforce_context.etype = "e" - sub1 = TestSub("alice", 70) - sub2 = TestSub("bob", 30) + sub1 = MockSub("alice", 70) + sub2 = MockSub("bob", 30) self.assertTrue(e.enforce("alice", "data2", "read")) self.assertFalse(e.enforce(enforce_context, sub1, "/data1", "read")) @@ -370,9 +370,9 @@ def test_enforce_abac_log_enabled(self): def test_abac_with_sub_rule(self): e = self.get_enforcer(get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv")) - sub1 = TestSub("alice", 16) - sub2 = TestSub("bob", 20) - sub3 = TestSub("alice", 65) + sub1 = MockSub("alice", 16) + sub2 = MockSub("bob", 20) + sub3 = MockSub("alice", 65) self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) @@ -395,10 +395,10 @@ def test_abac_with_multiple_sub_rules(self): get_examples("abac_multiple_rules_policy.csv"), ) - sub1 = TestSub("alice", 16) - sub2 = TestSub("alice", 20) - sub3 = TestSub("bob", 65) - sub4 = TestSub("bob", 35) + sub1 = MockSub("alice", 16) + sub2 = MockSub("alice", 20) + sub3 = MockSub("bob", 65) + sub4 = MockSub("bob", 35) self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) @@ -834,8 +834,8 @@ async def test_multiple_policy_definitions(self): enforce_context = e.new_enforce_context("2") enforce_context.etype = "e" - sub1 = TestSub("alice", 70) - sub2 = TestSub("bob", 30) + sub1 = MockSub("alice", 70) + sub2 = MockSub("bob", 30) self.assertTrue(e.enforce("alice", "data2", "read")) self.assertFalse(e.enforce(enforce_context, sub1, "/data1", "read")) @@ -970,9 +970,9 @@ async def test_abac_with_sub_rule(self): e = self.get_enforcer(get_examples("abac_rule_model.conf"), get_examples("abac_rule_policy.csv")) await e.load_policy() - sub1 = TestSub("alice", 16) - sub2 = TestSub("bob", 20) - sub3 = TestSub("alice", 65) + sub1 = MockSub("alice", 16) + sub2 = MockSub("bob", 20) + sub3 = MockSub("alice", 65) self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) @@ -996,10 +996,10 @@ async def test_abac_with_multiple_sub_rules(self): ) await e.load_policy() - sub1 = TestSub("alice", 16) - sub2 = TestSub("alice", 20) - sub3 = TestSub("bob", 65) - sub4 = TestSub("bob", 35) + sub1 = MockSub("alice", 16) + sub2 = MockSub("alice", 20) + sub3 = MockSub("bob", 65) + sub4 = MockSub("bob", 35) self.assertFalse(e.enforce(sub1, "/data1", "read")) self.assertFalse(e.enforce(sub1, "/data2", "read")) diff --git a/tests/test_filter.py b/tests/test_filter.py index 75151609..536505f3 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -15,6 +15,7 @@ import casbin from unittest import TestCase from tests.test_enforcer import get_examples +from casbin.persist.adapters import FilteredFileAdapter class Filter: @@ -23,14 +24,14 @@ class Filter: G = [] -class TestFilteredAdapter(TestCase): +class TestFilteredFileAdapter(TestCase): def test_init_filtered_adapter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) self.assertFalse(e.has_policy(["admin", "domain1", "data1", "read"])) def test_load_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -59,7 +60,7 @@ def test_load_filtered_policy(self): e.get_adapter().save_policy(e.get_model()) def test_append_filtered_policy(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = Filter() filter.P = ["", "domain1"] @@ -92,7 +93,7 @@ def test_append_filtered_policy(self): self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) def test_filtered_policy_invalid_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) filter = ["", "domain1"] @@ -100,7 +101,7 @@ def test_filtered_policy_invalid_filter(self): e.load_filtered_policy(filter) def test_filtered_policy_empty_filter(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("rbac_with_domains_policy.csv")) + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) try: @@ -128,14 +129,14 @@ def test_unsupported_filtered_policy(self): e.load_filtered_policy(filter) def test_filtered_adapter_empty_filepath(self): - adapter = casbin.persist.adapters.FilteredAdapter("") + adapter = FilteredFileAdapter("") e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): e.load_filtered_policy(None) def test_filtered_adapter_invalid_filepath(self): - adapter = casbin.persist.adapters.FilteredAdapter(get_examples("does_not_exist_policy.csv")) + adapter = FilteredFileAdapter(get_examples("does_not_exist_policy.csv")) e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) with self.assertRaises(RuntimeError): From eb9293c4fc18765e3e0d1eca0a188cf36755ab80 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 28 Dec 2023 04:20:47 +0000 Subject: [PATCH 320/349] chore(release): 1.34.0 [skip ci] # [1.34.0](https://github.com/casbin/pycasbin/compare/v1.33.0...v1.34.0) (2023-12-28) ### Features * add interface stubs for async adapters ([#335](https://github.com/casbin/pycasbin/issues/335)) ([d557189](https://github.com/casbin/pycasbin/commit/d557189eecee788e81d20ceae3cb1dee16a84ae7)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f419518a..e35a87bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.34.0](https://github.com/casbin/pycasbin/compare/v1.33.0...v1.34.0) (2023-12-28) + + +### Features + +* add interface stubs for async adapters ([#335](https://github.com/casbin/pycasbin/issues/335)) ([d557189](https://github.com/casbin/pycasbin/commit/d557189eecee788e81d20ceae3cb1dee16a84ae7)) + # [1.33.0](https://github.com/casbin/pycasbin/compare/v1.32.0...v1.33.0) (2023-10-25) diff --git a/pyproject.toml b/pyproject.toml index b9d07ebe..f264ff9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.33.0" +version = "1.34.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 3494375f5856931dcaf5dce25bd668408d457c98 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Thu, 11 Jan 2024 22:09:24 +0800 Subject: [PATCH 321/349] feat: support up to Python 3.12 --- .github/workflows/build.yml | 2 +- pyproject.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db2a93fa..c3e63bad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] os: [ubuntu-latest, macOS-latest, windows-latest] steps: diff --git a/pyproject.toml b/pyproject.toml index f264ff9a..73713537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,8 @@ classifiers = [ "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", ] From 4cd113ca96b84c1611c0aae1e1da4134e49c8400 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 11 Jan 2024 14:13:25 +0000 Subject: [PATCH 322/349] chore(release): 1.35.0 [skip ci] # [1.35.0](https://github.com/casbin/pycasbin/compare/v1.34.0...v1.35.0) (2024-01-11) ### Features * support up to Python 3.12 ([6dea204](https://github.com/casbin/pycasbin/commit/6dea20476328b947cf1e4e837acf340e371655c6)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e35a87bc..d3c88ee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.35.0](https://github.com/casbin/pycasbin/compare/v1.34.0...v1.35.0) (2024-01-11) + + +### Features + +* support up to Python 3.12 ([6dea204](https://github.com/casbin/pycasbin/commit/6dea20476328b947cf1e4e837acf340e371655c6)) + # [1.34.0](https://github.com/casbin/pycasbin/compare/v1.33.0...v1.34.0) (2023-12-28) diff --git a/pyproject.toml b/pyproject.toml index 73713537..9f73d5ce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.34.0" +version = "1.35.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 25b7d39ace087739b363d92413ce061730818662 Mon Sep 17 00:00:00 2001 From: tanasecucliciu Date: Fri, 9 Feb 2024 03:43:44 +0200 Subject: [PATCH 323/349] feat: Added support for async watcher callbacks #340 (#341) --- casbin/async_internal_enforcer.py | 49 ++++++-- tests/test_watcher_ex.py | 178 ++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 12 deletions(-) diff --git a/casbin/async_internal_enforcer.py b/casbin/async_internal_enforcer.py index f2bfc5f7..c1d0547d 100644 --- a/casbin/async_internal_enforcer.py +++ b/casbin/async_internal_enforcer.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. import copy +import inspect from casbin.core_enforcer import CoreEnforcer from casbin.model import Model, FunctionMap @@ -105,8 +106,12 @@ async def save_policy(self): await self.adapter.save_policy(self.model) if self.watcher: - if callable(getattr(self.watcher, "update_for_save_policy", None)): - self.watcher.update_for_save_policy(self.model) + update_for_save_policy = getattr(self.watcher, "update_for_save_policy", None) + if callable(update_for_save_policy): + if inspect.iscoroutinefunction(update_for_save_policy): + await update_for_save_policy(self.model) + else: + update_for_save_policy(self.model) else: self.watcher.update() @@ -122,8 +127,12 @@ async def _add_policy(self, sec, ptype, rule): return False if self.watcher and self.auto_notify_watcher: - if callable(getattr(self.watcher, "update_for_add_policy", None)): - self.watcher.update_for_add_policy(sec, ptype, rule) + update_for_add_policy = getattr(self.watcher, "update_for_add_policy", None) + if callable(update_for_add_policy): + if inspect.iscoroutinefunction(update_for_add_policy): + await update_for_add_policy(sec, ptype, rule) + else: + update_for_add_policy(sec, ptype, rule) else: self.watcher.update() @@ -144,8 +153,12 @@ async def _add_policies(self, sec, ptype, rules): return False if self.watcher and self.auto_notify_watcher: - if callable(getattr(self.watcher, "update_for_add_policies", None)): - self.watcher.update_for_add_policies(sec, ptype, rules) + update_for_add_policies = getattr(self.watcher, "update_for_add_policies", None) + if callable(update_for_add_policies): + if inspect.iscoroutinefunction(update_for_add_policies): + await update_for_add_policies(sec, ptype, rules) + else: + update_for_add_policies(sec, ptype, rules) else: self.watcher.update() @@ -224,8 +237,12 @@ async def _remove_policy(self, sec, ptype, rule): return False if self.watcher and self.auto_notify_watcher: - if callable(getattr(self.watcher, "update_for_remove_policy", None)): - self.watcher.update_for_remove_policy(sec, ptype, rule) + update_for_remove_policy = getattr(self.watcher, "update_for_remove_policy", None) + if callable(update_for_remove_policy): + if inspect.iscoroutinefunction(update_for_remove_policy): + await update_for_remove_policy(sec, ptype, rule) + else: + update_for_remove_policy(sec, ptype, rule) else: self.watcher.update() @@ -246,8 +263,12 @@ async def _remove_policies(self, sec, ptype, rules): return False if self.watcher and self.auto_notify_watcher: - if callable(getattr(self.watcher, "update_for_remove_policies", None)): - self.watcher.update_for_remove_policies(sec, ptype, rules) + update_for_remove_policies = getattr(self.watcher, "update_for_remove_policies", None) + if callable(update_for_remove_policies): + if inspect.iscoroutinefunction(update_for_remove_policies): + await update_for_remove_policies(sec, ptype, rules) + else: + update_for_remove_policies(sec, ptype, rules) else: self.watcher.update() @@ -265,8 +286,12 @@ async def _remove_filtered_policy(self, sec, ptype, field_index, *field_values): return False if self.watcher and self.auto_notify_watcher: - if callable(getattr(self.watcher, "update_for_remove_filtered_policy", None)): - self.watcher.update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) + update_for_remove_filtered_policy = getattr(self.watcher, "update_for_remove_filtered_policy", None) + if callable(update_for_remove_filtered_policy): + if inspect.iscoroutinefunction(update_for_remove_filtered_policy): + await update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) + else: + update_for_remove_filtered_policy(sec, ptype, field_index, *field_values) else: self.watcher.update() diff --git a/tests/test_watcher_ex.py b/tests/test_watcher_ex.py index e98a12fb..36840877 100644 --- a/tests/test_watcher_ex.py +++ b/tests/test_watcher_ex.py @@ -14,6 +14,7 @@ import casbin from tests.test_enforcer import get_examples, TestCaseBase +from unittest import IsolatedAsyncioTestCase class SampleWatcher: @@ -113,6 +114,103 @@ def start_watch(self): pass +class AsyncSampleWatcher: + def __init__(self): + self.callback = None + self.notify_message = None + + async def close(self): + pass + + async def set_update_callback(self, callback): + """ + sets the callback function to be called when the policy is updated + :param callable callback: callback(event) + - event: event received from the rabbitmq + :return: + """ + self.callback = callback + + async def update(self, msg): + """ + update the policy + """ + self.notify_message = msg + return True + + async def update_for_add_policy(self, section, ptype, *params): + """ + update for add policy + :param section: section + :param ptype: policy type + :param params: other params + :return: True if updated + """ + message = "called add policy" + return await self.update(message) + + async def update_for_remove_policy(self, section, ptype, *params): + """ + update for remove policy + :param section: section + :param ptype: policy type + :param params: other params + :return: True if updated + """ + message = "called remove policy" + return await self.update(message) + + async def update_for_remove_filtered_policy(self, section, ptype, field_index, *params): + """ + update for remove filtered policy + :param section: section + :param ptype: policy type + :param field_index: field index + :param params: other params + :return: + """ + message = "called remove filtered policy" + return await self.update(message) + + async def update_for_save_policy(self, model: casbin.Model): + """ + update for save policy + :param model: casbin model + :return: + """ + message = "called save policy" + return await self.update(message) + + async def update_for_add_policies(self, section, ptype, *params): + """ + update for add policies + :param section: section + :param ptype: policy type + :param params: other params + :return: + """ + message = "called add policies" + return await self.update(message) + + async def update_for_remove_policies(self, section, ptype, *params): + """ + update for remove policies + :param section: section + :param ptype: policy type + :param params: other params + :return: + """ + message = "called remove policies" + return await self.update(message) + + async def start_watch(self): + """ + starts the watch thread + :return: + """ + pass + + class TestWatcherEx(TestCaseBase): def get_enforcer(self, model=None, adapter=None): return casbin.Enforcer( @@ -187,3 +285,83 @@ def test_auto_notify_disabled(self): e.remove_policies(rules) self.assertEqual(w.notify_message, None) + + +class TestAsyncWatcherEx(IsolatedAsyncioTestCase): + def get_enforcer(self, model=None, adapter=None): + return casbin.AsyncEnforcer( + model, + adapter, + ) + + async def test_auto_notify_enabled(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + + w = AsyncSampleWatcher() + e.set_watcher(w) + e.enable_auto_notify_watcher(True) + + await e.save_policy() + self.assertEqual(w.notify_message, "called save policy") + + await e.add_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, "called add policy") + + await e.remove_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, "called remove policy") + + await e.remove_filtered_policy(1, "data1") + self.assertEqual(w.notify_message, "called remove filtered policy") + + rules = [ + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ] + await e.add_policies(rules) + self.assertEqual(w.notify_message, "called add policies") + + await e.remove_policies(rules) + self.assertEqual(w.notify_message, "called remove policies") + + async def test_auto_notify_disabled(self): + e = self.get_enforcer( + get_examples("basic_model.conf"), + get_examples("basic_policy.csv"), + ) + await e.load_policy() + + w = SampleWatcher() + e.set_watcher(w) + e.enable_auto_notify_watcher(False) + + await e.save_policy() + self.assertEqual(w.notify_message, "called save policy") + + w.notify_message = None + + await e.add_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, None) + + await e.remove_policy("admin", "data1", "read") + self.assertEqual(w.notify_message, None) + + await e.remove_filtered_policy(1, "data1") + self.assertEqual(w.notify_message, None) + + rules = [ + ["jack", "data4", "read"], + ["katy", "data4", "write"], + ["leyo", "data4", "read"], + ["ham", "data4", "write"], + ] + await e.add_policies(rules) + self.assertEqual(w.notify_message, None) + + await e.remove_policies(rules) + self.assertEqual(w.notify_message, None) From ade8462445a23e787e1a21f0b16ddf5ad4214969 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 9 Feb 2024 01:47:39 +0000 Subject: [PATCH 324/349] chore(release): 1.36.0 [skip ci] # [1.36.0](https://github.com/casbin/pycasbin/compare/v1.35.0...v1.36.0) (2024-02-09) ### Features * Added support for async watcher callbacks [#340](https://github.com/casbin/pycasbin/issues/340) ([#341](https://github.com/casbin/pycasbin/issues/341)) ([c04d832](https://github.com/casbin/pycasbin/commit/c04d83237264178624780d70ab6dee89854ac87b)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3c88ee1..3beadbde 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.36.0](https://github.com/casbin/pycasbin/compare/v1.35.0...v1.36.0) (2024-02-09) + + +### Features + +* Added support for async watcher callbacks [#340](https://github.com/casbin/pycasbin/issues/340) ([#341](https://github.com/casbin/pycasbin/issues/341)) ([c04d832](https://github.com/casbin/pycasbin/commit/c04d83237264178624780d70ab6dee89854ac87b)) + # [1.35.0](https://github.com/casbin/pycasbin/compare/v1.34.0...v1.35.0) (2024-01-11) diff --git a/pyproject.toml b/pyproject.toml index 9f73d5ce..ed9667fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.35.0" +version = "1.36.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 5ca35df44f580505a8d71b6427365a306d59c5f0 Mon Sep 17 00:00:00 2001 From: mbierma Date: Mon, 20 May 2024 22:39:59 -0700 Subject: [PATCH 325/349] fix: FastEnforcer not fast (#344) Co-authored-by: mbierma <3448579-mbierma@users.noreply.gitlab.com> --- casbin/fast_enforcer.py | 2 +- tests/test_fast_enforcer.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/casbin/fast_enforcer.py b/casbin/fast_enforcer.py index 72e38ce1..fd000ddb 100644 --- a/casbin/fast_enforcer.py +++ b/casbin/fast_enforcer.py @@ -31,7 +31,7 @@ def enforce(self, *rvals): """decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act). """ - if FastEnforcer._cache_key_order is None: + if self._cache_key_order is None: result, _ = self.enforce_ex(*rvals) else: keys = [rvals[x] for x in self._cache_key_order] diff --git a/tests/test_fast_enforcer.py b/tests/test_fast_enforcer.py index b2a2eccb..ee55de54 100644 --- a/tests/test_fast_enforcer.py +++ b/tests/test_fast_enforcer.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import time from typing import Sequence from unittest import TestCase @@ -35,6 +36,24 @@ def get_enforcer(self, model=None, adapter=None, cache_key_order: Sequence[int] class TestFastEnforcer(TestCaseBase): + def test_performance(self) -> None: + e1 = self.get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + ) + e2 = self.get_enforcer( + get_examples("performance/rbac_with_pattern_large_scale_model.conf"), + get_examples("performance/rbac_with_pattern_large_scale_policy.csv"), + [2, 1], + ) + s_e1 = time.perf_counter() + e1.enforce("alice", "data1", "read") + t_e1 = time.perf_counter() - s_e1 + s_e2 = time.perf_counter() + e2.enforce("alice", "data1", "read") + t_e2 = time.perf_counter() - s_e2 + assert t_e1 > t_e2 * 5 + def test_creates_proper_policy(self) -> None: e = self.get_enforcer( get_examples("basic_model.conf"), From 9c11e33da24412b7f6bdf465efcbb3998a891dca Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 21 May 2024 10:07:38 +0000 Subject: [PATCH 326/349] chore(release): 1.36.1 [skip ci] ## [1.36.1](https://github.com/casbin/pycasbin/compare/v1.36.0...v1.36.1) (2024-05-21) ### Bug Fixes * FastEnforcer not fast ([#344](https://github.com/casbin/pycasbin/issues/344)) ([8aef43b](https://github.com/casbin/pycasbin/commit/8aef43bcd03943a860c2cb530f499e67eac38375)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3beadbde..c058964d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.36.1](https://github.com/casbin/pycasbin/compare/v1.36.0...v1.36.1) (2024-05-21) + + +### Bug Fixes + +* FastEnforcer not fast ([#344](https://github.com/casbin/pycasbin/issues/344)) ([8aef43b](https://github.com/casbin/pycasbin/commit/8aef43bcd03943a860c2cb530f499e67eac38375)) + # [1.36.0](https://github.com/casbin/pycasbin/compare/v1.35.0...v1.36.0) (2024-02-09) diff --git a/pyproject.toml b/pyproject.toml index ed9667fb..400dc865 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.36.0" +version = "1.36.1" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 0b6072ab0394781093a49a6a31be5c238717162e Mon Sep 17 00:00:00 2001 From: Truco <22969604+truc0@users.noreply.github.com> Date: Wed, 12 Jun 2024 00:24:53 +0800 Subject: [PATCH 327/349] fix: field_index is incorrect in RBAC with domains mode (#345) (#346) * fix: field_index is incorrect in RBAC with domains mode (#345) * chore: replace field name with constant in ManagementEnforcer --- casbin/constant/constants.py | 1 + casbin/management_enforcer.py | 10 +++++++--- tests/test_management_api.py | 12 ++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/casbin/constant/constants.py b/casbin/constant/constants.py index c5b89b3e..428dc136 100644 --- a/casbin/constant/constants.py +++ b/casbin/constant/constants.py @@ -13,6 +13,7 @@ # limitations under the License. # Index constants +ACTION_INDEX = "act" DOMAIN_INDEX = "dom" SUBJECT_INDEX = "sub" OBJECT_INDEX = "obj" diff --git a/casbin/management_enforcer.py b/casbin/management_enforcer.py index b374ce10..b1897fad 100644 --- a/casbin/management_enforcer.py +++ b/casbin/management_enforcer.py @@ -14,6 +14,7 @@ from casbin.internal_enforcer import InternalEnforcer from casbin.model.policy_op import PolicyOp +from casbin.constant.constants import ACTION_INDEX, SUBJECT_INDEX, OBJECT_INDEX class ManagementEnforcer(InternalEnforcer): @@ -27,7 +28,8 @@ def get_all_subjects(self): def get_all_named_subjects(self, ptype): """gets the list of subjects that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy("p", ptype, 0) + field_index = self.model.get_field_index(ptype, SUBJECT_INDEX) + return self.model.get_values_for_field_in_policy("p", ptype, field_index) def get_all_objects(self): """gets the list of objects that show up in the current policy.""" @@ -35,7 +37,8 @@ def get_all_objects(self): def get_all_named_objects(self, ptype): """gets the list of objects that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy("p", ptype, 1) + field_index = self.model.get_field_index(ptype, OBJECT_INDEX) + return self.model.get_values_for_field_in_policy("p", ptype, field_index) def get_all_actions(self): """gets the list of actions that show up in the current policy.""" @@ -43,7 +46,8 @@ def get_all_actions(self): def get_all_named_actions(self, ptype): """gets the list of actions that show up in the current named policy.""" - return self.model.get_values_for_field_in_policy("p", ptype, 2) + field_index = self.model.get_field_index(ptype, ACTION_INDEX) + return self.model.get_values_for_field_in_policy("p", ptype, field_index) def get_all_roles(self): """gets the list of roles that show up in the current named policy.""" diff --git a/tests/test_management_api.py b/tests/test_management_api.py index 84f55225..e924daa9 100644 --- a/tests/test_management_api.py +++ b/tests/test_management_api.py @@ -38,6 +38,18 @@ def test_get_list(self): self.assertEqual(e.get_all_actions(), ["read", "write"]) self.assertEqual(e.get_all_roles(), ["data2_admin"]) + def test_get_list_with_domains(self): + e = self.get_enforcer( + get_examples("rbac_with_domains_model.conf"), + get_examples("rbac_with_domains_policy.csv"), + # True, + ) + + self.assertEqual(e.get_all_subjects(), ["admin"]) + self.assertEqual(e.get_all_objects(), ["data1", "data2"]) + self.assertEqual(e.get_all_actions(), ["read", "write"]) + self.assertEqual(e.get_all_roles(), ["admin"]) + def test_get_policy_api(self): e = self.get_enforcer( get_examples("rbac_model.conf"), From 43d2a39f6699e5edd3ff87cf0c1eb23ff484945d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 11 Jun 2024 16:28:00 +0000 Subject: [PATCH 328/349] chore(release): 1.36.2 [skip ci] ## [1.36.2](https://github.com/casbin/pycasbin/compare/v1.36.1...v1.36.2) (2024-06-11) ### Bug Fixes * field_index is incorrect in RBAC with domains mode ([#345](https://github.com/casbin/pycasbin/issues/345)) ([#346](https://github.com/casbin/pycasbin/issues/346)) ([9f6a379](https://github.com/casbin/pycasbin/commit/9f6a379d27d69366c156c8536ec48904c8b321ab)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c058964d..b2d83fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.36.2](https://github.com/casbin/pycasbin/compare/v1.36.1...v1.36.2) (2024-06-11) + + +### Bug Fixes + +* field_index is incorrect in RBAC with domains mode ([#345](https://github.com/casbin/pycasbin/issues/345)) ([#346](https://github.com/casbin/pycasbin/issues/346)) ([9f6a379](https://github.com/casbin/pycasbin/commit/9f6a379d27d69366c156c8536ec48904c8b321ab)) + ## [1.36.1](https://github.com/casbin/pycasbin/compare/v1.36.0...v1.36.1) (2024-05-21) diff --git a/pyproject.toml b/pyproject.toml index 400dc865..2e39a303 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.36.1" +version = "1.36.2" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From b7432a4ed627c2a2b8b19f9b8f4b6695cf206cff Mon Sep 17 00:00:00 2001 From: Jon Lee Date: Tue, 25 Jun 2024 00:01:08 +0800 Subject: [PATCH 329/349] fix: KeyError: 'g' when build_role_links --- casbin/model/policy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/casbin/model/policy.py b/casbin/model/policy.py index 01a4d8a9..5daeea36 100644 --- a/casbin/model/policy.py +++ b/casbin/model/policy.py @@ -44,8 +44,9 @@ def build_role_links(self, rm_map): return for ptype, ast in self["g"].items(): - rm = rm_map[ptype] - ast.build_role_links(rm) + rm = rm_map.get(ptype) + if rm: + ast.build_role_links(rm) def build_incremental_role_links(self, rm, op, sec, ptype, rules): if sec == "g": From c0c6ae23cccbfbbde56062b35c8519062c3df995 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 24 Jun 2024 16:09:04 +0000 Subject: [PATCH 330/349] chore(release): 1.36.3 [skip ci] ## [1.36.3](https://github.com/casbin/pycasbin/compare/v1.36.2...v1.36.3) (2024-06-24) ### Bug Fixes * KeyError: 'g' when build_role_links ([bf9cb44](https://github.com/casbin/pycasbin/commit/bf9cb4403f290b1313a86a2dce1c81eb36a043b0)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d83fc0..0829d023 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +## [1.36.3](https://github.com/casbin/pycasbin/compare/v1.36.2...v1.36.3) (2024-06-24) + + +### Bug Fixes + +* KeyError: 'g' when build_role_links ([bf9cb44](https://github.com/casbin/pycasbin/commit/bf9cb4403f290b1313a86a2dce1c81eb36a043b0)) + ## [1.36.2](https://github.com/casbin/pycasbin/compare/v1.36.1...v1.36.2) (2024-06-11) diff --git a/pyproject.toml b/pyproject.toml index 2e39a303..8339eb06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.36.2" +version = "1.36.3" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From ebdda1ef92b4eeeec6cd8a6434cd56fe12089d64 Mon Sep 17 00:00:00 2001 From: Coki <92775570+HashCookie@users.noreply.github.com> Date: Mon, 25 Nov 2024 23:00:00 +0800 Subject: [PATCH 331/349] feat: enhance FilteredFileAdapter to handle flexible filtering for policies and roles (#360) * feat: optimize filtered file adapter policy loading * style: standardize whitespace and formatting in filtered_file_adapter.py * feat: add test * test: improve test_load_filtered_policy_with_comments in test_filter.py * test: update test description for mixed filter --- .../persist/adapters/filtered_file_adapter.py | 28 +-- tests/test_filter.py | 176 +++++++++++++++++- 2 files changed, 192 insertions(+), 12 deletions(-) diff --git a/casbin/persist/adapters/filtered_file_adapter.py b/casbin/persist/adapters/filtered_file_adapter.py index eeb3a6cf..b0e31cbf 100644 --- a/casbin/persist/adapters/filtered_file_adapter.py +++ b/casbin/persist/adapters/filtered_file_adapter.py @@ -52,25 +52,28 @@ def load_filtered_policy(self, model, filter): try: filter_value = [filter.__dict__["P"]] + [filter.__dict__["G"]] + is_empty_filter = all(not f for f in filter_value) or all( + all(not x.strip() for x in f) if f else True for f in filter_value + ) + if is_empty_filter: + return self.load_policy(model) except: raise RuntimeError("invalid filter type") self.load_filtered_policy_file(model, filter_value, persist.load_policy_line) self.filtered = True - def load_filtered_policy_file(self, model, filter, hanlder): + def load_filtered_policy_file(self, model, filter, handler): with open(self._file_path, "rb") as file: - while True: - line = file.readline() + for line in file: line = line.decode().strip() - if line == "\n": + if not line or line == "\n": continue - if not line: - break + if filter_line(line, filter): continue - hanlder(line, model) + handler(line, model) # is_filtered returns true if the loaded policy has been filtered. def is_filtered(self): @@ -92,10 +95,13 @@ def filter_line(line, filter): return True filter_slice = [] - if p[0].strip() == "p": - filter_slice = filter[0] - elif p[0].strip() == "g": + if p[0].strip() == "g": + if not filter[1] or all(not x.strip() for x in filter[1]): + return False filter_slice = filter[1] + elif p[0].strip() == "p": + filter_slice = filter[0] + return filter_words(p, filter_slice) @@ -104,7 +110,7 @@ def filter_words(line, filter): return True skip_line = False for i, v in enumerate(filter): - if len(v) > 0 and (v.strip() != line[i + 1].strip()): + if v and v.strip() and (v.strip() != line[i + 1].strip()): skip_line = True break diff --git a/tests/test_filter.py b/tests/test_filter.py index 536505f3..01603254 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -12,10 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -import casbin +import os from unittest import TestCase +import casbin from tests.test_enforcer import get_examples from casbin.persist.adapters import FilteredFileAdapter +from casbin.persist.adapters.filtered_file_adapter import filter_line, filter_words class Filter: @@ -141,3 +143,175 @@ def test_filtered_adapter_invalid_filepath(self): with self.assertRaises(RuntimeError): e.load_filtered_policy(None) + + def test_empty_filter_array(self): + """Test filter for empty array.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = [] + filter.G = [] + + e.load_filtered_policy(filter) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_empty_string_filter(self): + """Test the filter for all empty strings.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "", ""] + filter.G = ["", "", ""] + + e.load_filtered_policy(filter) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_mixed_empty_filter(self): + """Test the filter for mixed empty and non-empty strings.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "domain1", ""] + filter.G = ["", "", "domain1"] + + e.load_filtered_policy(filter) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertFalse(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_nonexistent_domain_filter(self): + """Testing the filter for a non-existent domain.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "domain3"] + filter.G = ["", "", "domain3"] + + e.load_filtered_policy(filter) + self.assertFalse(e.has_policy(["admin", "domain3", "data3", "read"])) + + def test_empty_filter_array(self): + """Test filter for empty array.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = [] + filter.G = [] + + try: + e.load_filtered_policy(filter) + except: + raise RuntimeError("unexpected error with empty filter arrays") + + self.assertFalse(e.is_filtered(), "Adapter should not be marked as filtered with empty filters") + + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_empty_string_filter(self): + """Test the filter for all empty strings.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "", ""] + filter.G = ["", "", ""] + + try: + e.load_filtered_policy(filter) + except: + raise RuntimeError("unexpected error with empty string filters") + + self.assertFalse(e.is_filtered(), "Adapter should not be marked as filtered with empty string filters") + + try: + e.save_policy() + except: + raise RuntimeError("unexpected error in SavePolicy with empty string filters") + + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_mixed_empty_filter(self): + """Test the filter for mixed empty and non-empty strings.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "domain1", ""] + filter.G = ["", "", "domain1"] + + try: + e.load_filtered_policy(filter) + except: + raise RuntimeError("unexpected error with mixed empty filters") + + self.assertTrue(e.is_filtered(), "Adapter should be marked as filtered") + + with self.assertRaises(RuntimeError): + e.save_policy() + + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertFalse(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_whitespace_filter(self): + """Test the filter for all blank characters.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = [" ", " ", "\t"] + filter.G = ["\n", " ", " "] + + e.load_filtered_policy(filter) + + self.assertFalse(e.is_filtered()) + self.assertTrue(e.has_policy(["admin", "domain1", "data1", "read"])) + self.assertTrue(e.has_policy(["admin", "domain2", "data2", "read"])) + + def test_filter_line_edge_cases(self): + """Test the boundary cases of the filter_line function.""" + adapter = FilteredFileAdapter(get_examples("rbac_with_domains_policy.csv")) + + self.assertFalse(filter_line("", [[""], [""]])) + + self.assertFalse(filter_line("invalid_line", [[""], [""]])) + + self.assertFalse(filter_line("p, admin, domain1, data1, read", None)) + + def test_filter_words_edge_cases(self): + """Test the boundary cases of the filter_words function.""" + self.assertTrue(filter_words(["p"], ["filter1", "filter2"])) + + self.assertFalse(filter_words(["p", "admin", "domain1"], [])) + + line = ["admin", "domain1", "data*", "read"] + filter = ["", "", "data1", ""] + self.assertTrue(filter_words(line, filter)) + + def test_load_filtered_policy_with_comments(self): + """Test loading filtering policies with comments.""" + import tempfile + import shutil + + with tempfile.NamedTemporaryFile(mode="w+", delete=False) as temp_file: + with open(get_examples("rbac_with_domains_policy.csv"), "r") as source: + shutil.copyfileobj(source, temp_file) + + temp_file.write("\n# This is a comment\np, admin, domain1, data3, read") + temp_file.flush() + + temp_path = temp_file.name + + try: + adapter = FilteredFileAdapter(temp_path) + e = casbin.Enforcer(get_examples("rbac_with_domains_model.conf"), adapter) + filter = Filter() + filter.P = ["", "domain1"] + filter.G = ["", "", "domain1"] + + e.load_filtered_policy(filter) + self.assertTrue(e.has_policy(["admin", "domain1", "data3", "read"])) + finally: + try: + os.unlink(temp_path) + except OSError: + pass From 7615b5ddb8f3dc42aa4ed98611eca95eaaafcb0d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 25 Nov 2024 15:03:05 +0000 Subject: [PATCH 332/349] chore(release): 1.37.0 [skip ci] # [1.37.0](https://github.com/casbin/pycasbin/compare/v1.36.3...v1.37.0) (2024-11-25) ### Features * enhance FilteredFileAdapter to handle flexible filtering for policies and roles ([#360](https://github.com/casbin/pycasbin/issues/360)) ([936d5f6](https://github.com/casbin/pycasbin/commit/936d5f68df1d60b233f411489cc69f8b095c899a)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0829d023..27a07502 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.37.0](https://github.com/casbin/pycasbin/compare/v1.36.3...v1.37.0) (2024-11-25) + + +### Features + +* enhance FilteredFileAdapter to handle flexible filtering for policies and roles ([#360](https://github.com/casbin/pycasbin/issues/360)) ([936d5f6](https://github.com/casbin/pycasbin/commit/936d5f68df1d60b233f411489cc69f8b095c899a)) + ## [1.36.3](https://github.com/casbin/pycasbin/compare/v1.36.2...v1.36.3) (2024-06-24) diff --git a/pyproject.toml b/pyproject.toml index 8339eb06..046fa903 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.36.3" +version = "1.37.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 35e5e90a970d61cf5a595cc590376d08d9b8b5d7 Mon Sep 17 00:00:00 2001 From: Jason <2653009137@qq.com> Date: Sun, 12 Jan 2025 22:16:04 +0800 Subject: [PATCH 333/349] feat: add StringAdapter's statement in init.py (#367) --- casbin/persist/adapters/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/casbin/persist/adapters/__init__.py b/casbin/persist/adapters/__init__.py index 46f177e8..c10546fc 100644 --- a/casbin/persist/adapters/__init__.py +++ b/casbin/persist/adapters/__init__.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .string_adapter import StringAdapter from .file_adapter import FileAdapter from .filtered_file_adapter import FilteredFileAdapter from ..update_adapter import UpdateAdapter @@ -19,4 +20,4 @@ # alias import for backwards compatibility FilteredAdapter = FilteredFileAdapter -__all__ = ["FileAdapter", "FilteredFileAdapter", "FilteredAdapter", "UpdateAdapter"] +__all__ = ["StringAdapter", "FileAdapter", "FilteredFileAdapter", "FilteredAdapter", "UpdateAdapter"] From dc908002ba3eb58dabe421f4862856b5b7b5de97 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sun, 12 Jan 2025 14:18:53 +0000 Subject: [PATCH 334/349] chore(release): 1.38.0 [skip ci] # [1.38.0](https://github.com/casbin/pycasbin/compare/v1.37.0...v1.38.0) (2025-01-12) ### Features * add StringAdapter's statement in init.py ([#367](https://github.com/casbin/pycasbin/issues/367)) ([bf84eed](https://github.com/casbin/pycasbin/commit/bf84eed7305d22338aae185a7d65e0d429927e89)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27a07502..8d0989d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.38.0](https://github.com/casbin/pycasbin/compare/v1.37.0...v1.38.0) (2025-01-12) + + +### Features + +* add StringAdapter's statement in init.py ([#367](https://github.com/casbin/pycasbin/issues/367)) ([bf84eed](https://github.com/casbin/pycasbin/commit/bf84eed7305d22338aae185a7d65e0d429927e89)) + # [1.37.0](https://github.com/casbin/pycasbin/compare/v1.36.3...v1.37.0) (2024-11-25) diff --git a/pyproject.toml b/pyproject.toml index 046fa903..20efb2b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.37.0" +version = "1.38.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 794a5f647fa7d694d9e700f4bb39f5b3a92467e7 Mon Sep 17 00:00:00 2001 From: LeTeddy <94374764+LeTeddy@users.noreply.github.com> Date: Tue, 11 Mar 2025 21:25:35 +0800 Subject: [PATCH 335/349] feat: Port get_allowed_object_conditions() from Golang to Python (#373) --- casbin/enforcer.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/casbin/enforcer.py b/casbin/enforcer.py index a1777f9c..7b7b3bea 100644 --- a/casbin/enforcer.py +++ b/casbin/enforcer.py @@ -335,3 +335,22 @@ def get_implicit_users_for_resource_by_domain(self, resource, domain): permissions = [list(t) for t in (list(key) for key in permissions.keys())] return permissions + + def get_allowed_object_conditions(self, user, action, prefix): + """ + GetAllowedObjectConditions returns a string array of object conditions that the user can access. + """ + Permissions = self.get_implicit_permissions_for_user(user) + + object_conditions = [] + + for policy in Permissions: + if policy[2] == action: + if not policy[1].startswith(prefix): + return None + object_conditions.append(policy[1].removeprefix(prefix)) + + if len(object_conditions) == 0: + return None + + return object_conditions From 3c9d2b70873d40a47f44bdbaa528a462925dbcfb Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 11 Mar 2025 13:28:29 +0000 Subject: [PATCH 336/349] chore(release): 1.39.0 [skip ci] # [1.39.0](https://github.com/casbin/pycasbin/compare/v1.38.0...v1.39.0) (2025-03-11) ### Features * Port get_allowed_object_conditions() from Golang to Python ([#373](https://github.com/casbin/pycasbin/issues/373)) ([c433a59](https://github.com/casbin/pycasbin/commit/c433a5996f5819f750f628625fd2a1f8f556e8db)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d0989d7..80b1d911 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.39.0](https://github.com/casbin/pycasbin/compare/v1.38.0...v1.39.0) (2025-03-11) + + +### Features + +* Port get_allowed_object_conditions() from Golang to Python ([#373](https://github.com/casbin/pycasbin/issues/373)) ([c433a59](https://github.com/casbin/pycasbin/commit/c433a5996f5819f750f628625fd2a1f8f556e8db)) + # [1.38.0](https://github.com/casbin/pycasbin/compare/v1.37.0...v1.38.0) (2025-01-12) diff --git a/pyproject.toml b/pyproject.toml index 20efb2b5..b27da16e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.38.0" +version = "1.39.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 7880b755d02a1fef469c4a8437dabd11dadd836f Mon Sep 17 00:00:00 2001 From: LeTeddy <94374764+LeTeddy@users.noreply.github.com> Date: Thu, 13 Mar 2025 22:10:54 +0800 Subject: [PATCH 337/349] feat: fix bug that "!=" in policies is replaced with "not=" (#375) --- casbin/core_enforcer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index b0dff852..21e97646 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -14,6 +14,7 @@ import copy import logging +import re from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap @@ -539,6 +540,6 @@ def configure_logging(logging_config=None): def _get_expression(expr, functions=None): expr = expr.replace("&&", "and") expr = expr.replace("||", "or") - expr = expr.replace("!", "not") + expr = re.sub(r"!(?!=)", "not ", expr) return SimpleEval(expr, functions) From 5c7853d48ce283ca34d92e6bd83d59a85bad09fe Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Thu, 13 Mar 2025 14:13:47 +0000 Subject: [PATCH 338/349] chore(release): 1.40.0 [skip ci] # [1.40.0](https://github.com/casbin/pycasbin/compare/v1.39.0...v1.40.0) (2025-03-13) ### Features * fix bug that "!=" in policies is replaced with "not=" ([#375](https://github.com/casbin/pycasbin/issues/375)) ([54208b6](https://github.com/casbin/pycasbin/commit/54208b66d3fd920adaeac2db37a78e74fa9d3b76)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b1d911..07f5426d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.40.0](https://github.com/casbin/pycasbin/compare/v1.39.0...v1.40.0) (2025-03-13) + + +### Features + +* fix bug that "!=" in policies is replaced with "not=" ([#375](https://github.com/casbin/pycasbin/issues/375)) ([54208b6](https://github.com/casbin/pycasbin/commit/54208b66d3fd920adaeac2db37a78e74fa9d3b76)) + # [1.39.0](https://github.com/casbin/pycasbin/compare/v1.38.0...v1.39.0) (2025-03-11) diff --git a/pyproject.toml b/pyproject.toml index b27da16e..950ddbde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.39.0" +version = "1.40.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From dad4e76e95ad81f4c212d3e6c460359c7d33b93d Mon Sep 17 00:00:00 2001 From: LeTeddy <94374764+LeTeddy@users.noreply.github.com> Date: Sat, 15 Mar 2025 23:32:12 +0800 Subject: [PATCH 339/349] feat: add "logging_config" for PyCasbin logging (#376) --- README.md | 5 +++++ casbin/core_enforcer.py | 4 ++-- casbin/fast_enforcer.py | 11 +++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fccb6c52..8645ea7c 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,7 @@ production-ready | production-ready | beta-test | production-ready - [Role manager](#role-manager) - [Async Enforcer](#async-enforcer) - [Benchmarks](#benchmarks) +- [Logging](#logging) - [Examples](#examples) - [Middlewares](#middlewares) - [Our adopters](#our-adopters) @@ -291,6 +292,10 @@ asyncio.run(main()) https://casbin.org/docs/benchmark +## Logging + +pycasbin leverages the default Python logging mechanism. The pycasbin package makes a call to `logging.getLogger()` to set the logger. No special logging configuration is needed other than initializing the logger in the parent application. If no logging is initialized within the parent application, you will not see any log messages from pycasbin. At the same time, When you enable log in pycasbin, you can specify the logging configuration through the parameter `logging_config`. If no configuration is specified, it will use the [default log configuration](https://github.com/casbin/pycasbin/blob/c33cabfa0ac65cd09cf812a65e71794d64cb5132/casbin/util/log.py#L6C1-L6C1). For other pycasbin extensions, you can refer to the [Django logging docs](https://docs.djangoproject.com/en/4.2/topics/logging/) if you are a Django user. For other Python users, you should refer to the [Python logging docs](https://docs.python.org/3/library/logging.config.html) to configure the logger. + ## Examples Model | Model file | Policy file diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 21e97646..0800fb22 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -55,7 +55,7 @@ class CoreEnforcer: auto_build_role_links = False auto_notify_watcher = False - def __init__(self, model=None, adapter=None, enable_log=False): + def __init__(self, model=None, adapter=None, enable_log=False, logging_config: dict = None): self.logger = logging.getLogger("casbin.enforcer") if isinstance(model, str): if isinstance(adapter, str): @@ -70,7 +70,7 @@ def __init__(self, model=None, adapter=None, enable_log=False): self.init_with_model_and_adapter(model, adapter) if enable_log: - configure_logging() + configure_logging(logging_config) else: disabled_logging() diff --git a/casbin/fast_enforcer.py b/casbin/fast_enforcer.py index fd000ddb..000fcaf6 100644 --- a/casbin/fast_enforcer.py +++ b/casbin/fast_enforcer.py @@ -10,9 +10,16 @@ class FastEnforcer(Enforcer): _cache_key_order: Sequence[int] = None - def __init__(self, model=None, adapter=None, enable_log=False, cache_key_order: Sequence[int] = None): + def __init__( + self, + model=None, + adapter=None, + enable_log=False, + logging_config: dict = None, + cache_key_order: Sequence[int] = None, + ): self._cache_key_order = cache_key_order - super().__init__(model, adapter, enable_log) + super().__init__(model, adapter, enable_log, logging_config) def new_model(self, path="", text=""): """creates a model.""" From 4d7226501784b39474a541541b4a1dbda6cc0cc8 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 15 Mar 2025 15:34:50 +0000 Subject: [PATCH 340/349] chore(release): 1.41.0 [skip ci] # [1.41.0](https://github.com/casbin/pycasbin/compare/v1.40.0...v1.41.0) (2025-03-15) ### Features * add "logging_config" for PyCasbin logging ([#376](https://github.com/casbin/pycasbin/issues/376)) ([f39ace2](https://github.com/casbin/pycasbin/commit/f39ace2f0a185bccd2a3722f0265c488f304f200)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f5426d..b4a44793 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.41.0](https://github.com/casbin/pycasbin/compare/v1.40.0...v1.41.0) (2025-03-15) + + +### Features + +* add "logging_config" for PyCasbin logging ([#376](https://github.com/casbin/pycasbin/issues/376)) ([f39ace2](https://github.com/casbin/pycasbin/commit/f39ace2f0a185bccd2a3722f0265c488f304f200)) + # [1.40.0](https://github.com/casbin/pycasbin/compare/v1.39.0...v1.40.0) (2025-03-13) diff --git a/pyproject.toml b/pyproject.toml index 950ddbde..863f826e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.40.0" +version = "1.41.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From 1f67fd65d5ac53f04205f7a7c0e64ee201bcd6a5 Mon Sep 17 00:00:00 2001 From: Konev Vladimir Date: Mon, 21 Apr 2025 21:29:58 +0300 Subject: [PATCH 341/349] feat: fix missing await in load_increment_filtered_policy() in async_internal_enforcer.py (#378) --- casbin/async_internal_enforcer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/casbin/async_internal_enforcer.py b/casbin/async_internal_enforcer.py index c1d0547d..62094a64 100644 --- a/casbin/async_internal_enforcer.py +++ b/casbin/async_internal_enforcer.py @@ -94,7 +94,7 @@ async def load_increment_filtered_policy(self, filter): if not hasattr(self.adapter, "is_filtered"): raise ValueError("filtered policies are not supported by this adapter") - self.adapter.load_filtered_policy(self.model, filter) + await self.adapter.load_filtered_policy(self.model, filter) self.model.print_policy() if self.auto_build_role_links: self.build_role_links() From 7d562555a4ff24464991b3dd8fb0494382eaf131 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Mon, 21 Apr 2025 18:32:37 +0000 Subject: [PATCH 342/349] chore(release): 1.42.0 [skip ci] # [1.42.0](https://github.com/casbin/pycasbin/compare/v1.41.0...v1.42.0) (2025-04-21) ### Features * fix missing await in load_increment_filtered_policy() in async_internal_enforcer.py ([#378](https://github.com/casbin/pycasbin/issues/378)) ([ca26402](https://github.com/casbin/pycasbin/commit/ca264023f60ab83c2f852578ccfb8e567b0d7e0c)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a44793..412aef35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.42.0](https://github.com/casbin/pycasbin/compare/v1.41.0...v1.42.0) (2025-04-21) + + +### Features + +* fix missing await in load_increment_filtered_policy() in async_internal_enforcer.py ([#378](https://github.com/casbin/pycasbin/issues/378)) ([ca26402](https://github.com/casbin/pycasbin/commit/ca264023f60ab83c2f852578ccfb8e567b0d7e0c)) + # [1.41.0](https://github.com/casbin/pycasbin/compare/v1.40.0...v1.41.0) (2025-03-15) diff --git a/pyproject.toml b/pyproject.toml index 863f826e..7958a267 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.41.0" +version = "1.42.0" authors = [ {name = "TechLee", email = "techlee@qq.com"}, ] From bf466dc08b7f13f6ff3a49e81db5becc378a9b61 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sat, 10 May 2025 14:53:30 +0800 Subject: [PATCH 343/349] feat: fix CI error --- package.json | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 20ac176e..16bdf0f8 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,6 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "semantic-release": "^22.0.5", - "semantic-release-pypi": "^3.0.0" + "semantic-release-pypi": "^5.1.0" } } diff --git a/pyproject.toml b/pyproject.toml index 7958a267..f28614ac 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ name = "casbin" version = "1.42.0" authors = [ - {name = "TechLee", email = "techlee@qq.com"}, + {name = "Casbin", email = "admin@casbin.org"}, ] description = "An authorization library that supports access control models like ACL, RBAC, ABAC in Python" readme = "README.md" From deb08623b850b723883412c78e580e3303c4268e Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 10 May 2025 06:57:14 +0000 Subject: [PATCH 344/349] chore(release): 1.43.0 [skip ci] # [1.43.0](https://github.com/casbin/pycasbin/compare/v1.42.0...v1.43.0) (2025-05-10) ### Features * fix CI error ([20719ab](https://github.com/casbin/pycasbin/commit/20719ab63528edcb6243f3d66e26729e5be42679)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 412aef35..8fd1501f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.43.0](https://github.com/casbin/pycasbin/compare/v1.42.0...v1.43.0) (2025-05-10) + + +### Features + +* fix CI error ([20719ab](https://github.com/casbin/pycasbin/commit/20719ab63528edcb6243f3d66e26729e5be42679)) + # [1.42.0](https://github.com/casbin/pycasbin/compare/v1.41.0...v1.42.0) (2025-04-21) diff --git a/pyproject.toml b/pyproject.toml index f28614ac..4149f902 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "casbin" -version = "1.42.0" +version = "1.43.0" authors = [ {name = "Casbin", email = "admin@casbin.org"}, ] From eb9489164e790bbf3ebeea70740355490932314b Mon Sep 17 00:00:00 2001 From: Gucheng Wang Date: Sat, 7 Jun 2025 18:57:12 +0800 Subject: [PATCH 345/349] feat: update info --- .github/workflows/build.yml | 2 +- README.md | 56 ++++++++++++++++++------------------- pyproject.toml | 3 +- 3 files changed, 31 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c3e63bad..d9c7ba3e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,5 +105,5 @@ jobs: - name: Release env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN_CASBIN }} run: npx semantic-release diff --git a/README.md b/README.md index 8645ea7c..7ba2b40a 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@ PyCasbin [![GitHub Action](https://github.com/casbin/pycasbin/workflows/build/badge.svg?branch=master)](https://github.com/casbin/pycasbin/actions) [![Coverage Status](https://coveralls.io/repos/github/casbin/pycasbin/badge.svg)](https://coveralls.io/github/casbin/pycasbin) -[![Version](https://img.shields.io/pypi/v/casbin.svg)](https://pypi.org/project/casbin/) -[![PyPI - Wheel](https://img.shields.io/pypi/wheel/casbin.svg)](https://pypi.org/project/casbin/) -[![Pyversions](https://img.shields.io/pypi/pyversions/casbin.svg)](https://pypi.org/project/casbin/) -[![Download](https://img.shields.io/pypi/dm/casbin.svg)](https://pypi.org/project/casbin/) +[![Version](https://img.shields.io/pypi/v/pycasbin.svg)](https://pypi.org/project/pycasbin/) +[![PyPI - Wheel](https://img.shields.io/pypi/wheel/pycasbin.svg)](https://pypi.org/project/pycasbin/) +[![Pyversions](https://img.shields.io/pypi/pyversions/pycasbin.svg)](https://pypi.org/project/pycasbin/) +[![Download](https://img.shields.io/pypi/dm/pycasbin.svg)](https://pypi.org/project/pycasbin/) [![Discord](https://img.shields.io/discord/1022748306096537660?logo=discord&label=discord&color=5865F2)](https://discord.gg/S5UjpzGZjN)

@@ -37,15 +37,15 @@ Casbin is a powerful and efficient open-source access control library for Python ## 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 +| [![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-net/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-net/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 +| [![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-net/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-net/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 | ## Table of contents @@ -157,7 +157,7 @@ What Casbin does NOT do: ## Installation ``` -pip install casbin +pip install pycasbin ``` ## Documentation @@ -298,19 +298,19 @@ pycasbin leverages the default Python logging mechanism. The pycasbin package ma ## Examples -Model | Model file | Policy file -----|------|---- -ACL | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) -ACL with superuser | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) -ACL without users | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv) -ACL without resources | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv) -RBAC | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv) -RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) -RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv) -ABAC | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf) | N/A -RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv) -Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv) -Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv) +| Model | Model file | Policy file | +|---------------------------|----------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------| +| ACL | [basic_model.conf](https://github.com/casbin/casbin/blob/master/examples/basic_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) | +| ACL with superuser | [basic_model_with_root.conf](https://github.com/casbin/casbin/blob/master/examples/basic_with_root_model.conf) | [basic_policy.csv](https://github.com/casbin/casbin/blob/master/examples/basic_policy.csv) | +| ACL without users | [basic_model_without_users.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_model.conf) | [basic_policy_without_users.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_users_policy.csv) | +| ACL without resources | [basic_model_without_resources.conf](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_model.conf) | [basic_policy_without_resources.csv](https://github.com/casbin/casbin/blob/master/examples/basic_without_resources_policy.csv) | +| RBAC | [rbac_model.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_model.conf) | [rbac_policy.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_policy.csv) | +| RBAC with resource roles | [rbac_model_with_resource_roles.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_model.conf) | [rbac_policy_with_resource_roles.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_resource_roles_policy.csv) | +| RBAC with domains/tenants | [rbac_model_with_domains.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_model.conf) | [rbac_policy_with_domains.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_domains_policy.csv) | +| ABAC | [abac_model.conf](https://github.com/casbin/casbin/blob/master/examples/abac_model.conf) | N/A | +| RESTful | [keymatch_model.conf](https://github.com/casbin/casbin/blob/master/examples/keymatch_model.conf) | [keymatch_policy.csv](https://github.com/casbin/casbin/blob/master/examples/keymatch_policy.csv) | +| Deny-override | [rbac_model_with_deny.conf](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_model.conf) | [rbac_policy_with_deny.csv](https://github.com/casbin/casbin/blob/master/examples/rbac_with_deny_policy.csv) | +| Priority | [priority_model.conf](https://github.com/casbin/casbin/blob/master/examples/priority_model.conf) | [priority_policy.csv](https://github.com/casbin/casbin/blob/master/examples/priority_policy.csv) | ## Middlewares @@ -353,6 +353,6 @@ 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/casbin/pycasbin/issues -- techlee@qq.com -- Tencent QQ group: [546057381](//shang.qq.com/wpa/qunwpa?idkey=8ac8b91fc97ace3d383d0035f7aa06f7d670fd8e8d4837347354a31c18fac885) +- https://discord.gg/S5UjpzGZjN diff --git a/pyproject.toml b/pyproject.toml index 4149f902..3fe8643c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "casbin" +name = "pycasbin" version = "1.43.0" authors = [ {name = "Casbin", email = "admin@casbin.org"}, @@ -8,6 +8,7 @@ description = "An authorization library that supports access control models like readme = "README.md" keywords = [ "casbin", + "pycasbin", "acl", "rbac", "abac", From 5abe5316221ffcc20e7f07a053412e1d6c06a14d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 7 Jun 2025 11:01:39 +0000 Subject: [PATCH 346/349] chore(release): 1.44.0 [skip ci] # [1.44.0](https://github.com/casbin/pycasbin/compare/v1.43.0...v1.44.0) (2025-06-07) ### Features * update info ([ed77f0e](https://github.com/casbin/pycasbin/commit/ed77f0e153357e32ca5c50319ea82cdb34da3338)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd1501f..635bd740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.44.0](https://github.com/casbin/pycasbin/compare/v1.43.0...v1.44.0) (2025-06-07) + + +### Features + +* update info ([ed77f0e](https://github.com/casbin/pycasbin/commit/ed77f0e153357e32ca5c50319ea82cdb34da3338)) + # [1.43.0](https://github.com/casbin/pycasbin/compare/v1.42.0...v1.43.0) (2025-05-10) diff --git a/pyproject.toml b/pyproject.toml index 3fe8643c..ea0b4bb3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pycasbin" -version = "1.43.0" +version = "1.44.0" authors = [ {name = "Casbin", email = "admin@casbin.org"}, ] From c9feef4160c11de94c826497d5061f5e69e26da7 Mon Sep 17 00:00:00 2001 From: Huon Wilson Date: Sat, 7 Jun 2025 23:03:11 +1000 Subject: [PATCH 347/349] feat: Optimize `load_policy_line` to avoid quadratic individual-character loop (#355) --- .github/workflows/build.yml | 1 + casbin/persist/adapter.py | 54 +++++++++++++++++++-------- tests/benchmarks/benchmark_adapter.py | 30 +++++++++++++++ tests/persist/test_adapter.py | 53 ++++++++++++++++++++++++++ 4 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 tests/benchmarks/benchmark_adapter.py create mode 100644 tests/persist/test_adapter.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9c7ba3e..fe0edced 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,6 +39,7 @@ jobs: tests/benchmarks/benchmark_model.py tests/benchmarks/benchmark_management_api.py tests/benchmarks/benchmark_role_manager.py + tests/benchmarks/benchmark_adapter.py - name: Upload coverage data to coveralls.io run: coveralls --service=github diff --git a/casbin/persist/adapter.py b/casbin/persist/adapter.py index 2c9b9a38..b5c457d5 100644 --- a/casbin/persist/adapter.py +++ b/casbin/persist/adapter.py @@ -12,34 +12,56 @@ # See the License for the specific language governing permissions and # limitations under the License. +import re -def load_policy_line(line, model): - """loads a text line as a policy rule to model.""" +_INTERESTING_TOKENS_RE = re.compile(r"[,\[\]\(\)]") + + +def _extract_tokens(line): + """Return the list of 'tokens' from the line, or None if this line has none""" if line == "": - return + return None if line[:1] == "#": - return + return None stack = [] tokens = [] - for c in line: + + # The tokens are separated by commas, but we support nesting so a naive `line.split(",")` is + # wrong. E.g. `abc(def, ghi), jkl` is two tokens: `abc(def, ghi)` and `jkl`. We do this by + # iterating over the locations of any tokens of interest, and either: + # + # - [](): adjust the nesting depth + # - ,: slice the line to save the token, if the , is at the top-level, outside all []() + # + # `start_idx` represents the start of the current token, that we haven't seen a `,` for yet. + start_idx = 0 + for match in _INTERESTING_TOKENS_RE.finditer(line): + c = match.group() if c == "[" or c == "(": stack.append(c) - tokens[-1] += c elif c == "]" or c == ")": stack.pop() - tokens[-1] += c - elif c == "," and len(stack) == 0: - tokens.append("") - else: - if len(tokens) == 0: - tokens.append(c) - else: - tokens[-1] += c - - tokens = [x.strip() for x in tokens] + elif not stack: + # must be a comma outside of any nesting: we've found the end of a top level token so + # save that and start a new one + tokens.append(line[start_idx : match.start()].strip()) + start_idx = match.end() + + # trailing token after the last , + tokens.append(line[start_idx:].strip()) + + return tokens + + +def load_policy_line(line, model): + """loads a text line as a policy rule to model.""" + + tokens = _extract_tokens(line) + if tokens is None: + return key = tokens[0] sec = key[0] diff --git a/tests/benchmarks/benchmark_adapter.py b/tests/benchmarks/benchmark_adapter.py new file mode 100644 index 00000000..3a094662 --- /dev/null +++ b/tests/benchmarks/benchmark_adapter.py @@ -0,0 +1,30 @@ +from casbin.persist.adapter import _extract_tokens + + +def _benchmark_extract_tokens(benchmark, line): + @benchmark + def run_benchmark(): + _extract_tokens(line) + + +def test_benchmark_extract_tokens_short_simple(benchmark): + _benchmark_extract_tokens(benchmark, "abc,def,ghi") + + +def test_benchmark_extract_tokens_long_simple(benchmark): + # fixed UUIDs for length and to be similar to "real world" usage of UUIDs + _benchmark_extract_tokens( + benchmark, + "00000000-0000-0000-0000-000000000000,00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002", + ) + + +def test_benchmark_extract_tokens_short_nested(benchmark): + _benchmark_extract_tokens(benchmark, "abc(def,ghi),jkl(mno,pqr)") + + +def test_benchmark_extract_tokens_long_nested(benchmark): + _benchmark_extract_tokens( + benchmark, + "00000000-0000-0000-0000-000000000000(00000000-0000-0000-0000-000000000001,00000000-0000-0000-0000-000000000002),00000000-0000-0000-0000-000000000003(00000000-0000-0000-0000-000000000004,00000000-0000-0000-0000-000000000005)", + ) diff --git a/tests/persist/test_adapter.py b/tests/persist/test_adapter.py new file mode 100644 index 00000000..c6028544 --- /dev/null +++ b/tests/persist/test_adapter.py @@ -0,0 +1,53 @@ +from casbin.persist.adapter import _extract_tokens +from tests import TestCaseBase + + +class TestExtractTokens(TestCaseBase): + def test_ignore_lines(self): + self.assertIsNone(_extract_tokens("")) # empty + self.assertIsNone(_extract_tokens("# comment")) + + def test_simple_lines(self): + # split on top-level commas, strip whitespace from start and end + self.assertEqual(_extract_tokens("one"), ["one"]) + self.assertEqual(_extract_tokens("one,two"), ["one", "two"]) + self.assertEqual(_extract_tokens(" ignore \t,\t external, spaces "), ["ignore", "external", "spaces"]) + + self.assertEqual(_extract_tokens("internal spaces preserved"), ["internal spaces preserved"]) + + def test_nested_lines(self): + # basic nesting within a single token + self.assertEqual( + _extract_tokens("outside1()"), + ["outside1()"], + ) + self.assertEqual( + _extract_tokens("outside1(inside1())"), + ["outside1(inside1())"], + ) + + # split on top-level commas, but not on internal ones + self.assertEqual( + _extract_tokens("outside1(inside1(), inside2())"), + ["outside1(inside1(), inside2())"], + ) + self.assertEqual( + _extract_tokens("outside1(inside1(), inside2(inside3(), inside4()))"), + ["outside1(inside1(), inside2(inside3(), inside4()))"], + ) + self.assertEqual( + _extract_tokens("outside1(inside1(), inside2()), outside2(inside3(), inside4())"), + ["outside1(inside1(), inside2())", "outside2(inside3(), inside4())"], + ) + + # different delimiters + self.assertEqual( + _extract_tokens( + "all_square[inside1[], inside2[]],square_and_parens[inside1(), inside2()],parens_and_square(inside1[], inside2[])" + ), + [ + "all_square[inside1[], inside2[]]", + "square_and_parens[inside1(), inside2()]", + "parens_and_square(inside1[], inside2[])", + ], + ) From a9e38f3331d55c27f83acfdda65c4edff8d875bc Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Sat, 7 Jun 2025 13:06:07 +0000 Subject: [PATCH 348/349] chore(release): 1.45.0 [skip ci] # [1.45.0](https://github.com/casbin/pycasbin/compare/v1.44.0...v1.45.0) (2025-06-07) ### Features * Optimize `load_policy_line` to avoid quadratic individual-character loop ([#355](https://github.com/casbin/pycasbin/issues/355)) ([d3149e8](https://github.com/casbin/pycasbin/commit/d3149e81c36d29e96ebdf3484897461ab86d8afb)) --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 635bd740..c1b7ba6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Semantic Versioning Changelog +# [1.45.0](https://github.com/casbin/pycasbin/compare/v1.44.0...v1.45.0) (2025-06-07) + + +### Features + +* Optimize `load_policy_line` to avoid quadratic individual-character loop ([#355](https://github.com/casbin/pycasbin/issues/355)) ([d3149e8](https://github.com/casbin/pycasbin/commit/d3149e81c36d29e96ebdf3484897461ab86d8afb)) + # [1.44.0](https://github.com/casbin/pycasbin/compare/v1.43.0...v1.44.0) (2025-06-07) diff --git a/pyproject.toml b/pyproject.toml index ea0b4bb3..c2e88459 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "pycasbin" -version = "1.44.0" +version = "1.45.0" authors = [ {name = "Casbin", email = "admin@casbin.org"}, ] From 28219c63fd5e16a42b690e0ffea7ab81fce8d496 Mon Sep 17 00:00:00 2001 From: Theo Date: Thu, 17 Jul 2025 14:05:35 +0800 Subject: [PATCH 349/349] support both sync and async func in add_funciton --- casbin/async_enforcer.py | 10 ++++++++++ casbin/core_enforcer.py | 3 +++ 2 files changed, 13 insertions(+) diff --git a/casbin/async_enforcer.py b/casbin/async_enforcer.py index f61322f7..ba36aa2d 100644 --- a/casbin/async_enforcer.py +++ b/casbin/async_enforcer.py @@ -13,6 +13,7 @@ # limitations under the License. from functools import partial +import asyncio from casbin.async_management_enforcer import AsyncManagementEnforcer from casbin.util import join_slice, array_remove_duplicates, set_subtract @@ -22,6 +23,15 @@ class AsyncEnforcer(AsyncManagementEnforcer): """ AsyncEnforcer = AsyncManagementEnforcer + RBAC_API + RBAC_WITH_DOMAIN_API """ + async def async_enforce(self, *rvals): + loop = asyncio.get_running_loop() + result = await loop.run_in_executor(None, super().enforce, *rvals) + return result + + async def async_batch_enforce(self, rvals): + """batch_enforce enforce in batches""" + tasks = [self.async_enforce(*request) for request in rvals] + return await asyncio.gather(*tasks) async def get_roles_for_user(self, name): """gets the roles that a user has.""" diff --git a/casbin/core_enforcer.py b/casbin/core_enforcer.py index 0800fb22..67b5122f 100644 --- a/casbin/core_enforcer.py +++ b/casbin/core_enforcer.py @@ -15,6 +15,7 @@ import copy import logging import re +import asyncio from casbin.effect import Effector, get_effector, effect_to_bool from casbin.model import Model, FunctionMap @@ -457,6 +458,8 @@ def enforce_ex(self, *rvals): expression = self._get_expression(exp_with_rule, functions) result = expression.eval(parameters) + if asyncio.iscoroutine(result): + result = asyncio.run(result) if isinstance(result, bool): if not result: